From c50ae4d875e12c1b72d8d87301931500b753d84c Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 20 May 2022 22:56:43 +0800 Subject: [PATCH] feat: integrate mxGraph editor --- .eslintignore | 7 - .prettierignore | 4 +- packages/client/.eslintignore | 6 +- packages/client/public/diagram.js | 134092 +++++++++++++++ packages/client/public/diagram.min.js | 2 + .../client/public/diagram/images/button.gif | Bin 0 -> 137 bytes .../public/diagram/images/checkmark.gif | Bin 0 -> 1253 bytes .../client/public/diagram/images/clear.gif | Bin 0 -> 1114 bytes .../client/public/diagram/images/close.gif | Bin 0 -> 70 bytes .../client/public/diagram/images/close.png | Bin 0 -> 118 bytes .../public/diagram/images/collapsed.gif | Bin 0 -> 877 bytes .../client/public/diagram/images/dropdown.gif | Bin 0 -> 1110 bytes .../client/public/diagram/images/dropdown.png | Bin 0 -> 206 bytes .../client/public/diagram/images/edit.gif | Bin 0 -> 66 bytes .../client/public/diagram/images/error.gif | Bin 0 -> 907 bytes .../client/public/diagram/images/expanded.gif | Bin 0 -> 878 bytes .../client/public/diagram/images/grid.gif | Bin 0 -> 56 bytes .../public/diagram/images/handle-fixed.png | Bin 0 -> 1293 bytes .../public/diagram/images/handle-main.png | Bin 0 -> 379 bytes .../public/diagram/images/handle-rotate.png | Bin 0 -> 298 bytes .../diagram/images/handle-secondary.png | Bin 0 -> 1270 bytes .../public/diagram/images/handle-terminal.png | Bin 0 -> 1286 bytes .../client/public/diagram/images/help.png | Bin 0 -> 338 bytes .../client/public/diagram/images/locked.png | Bin 0 -> 1020 bytes .../client/public/diagram/images/logo.png | Bin 0 -> 8999 bytes .../client/public/diagram/images/maximize.gif | Bin 0 -> 843 bytes .../client/public/diagram/images/minimize.gif | Bin 0 -> 64 bytes .../client/public/diagram/images/move.png | Bin 0 -> 300 bytes .../client/public/diagram/images/nocolor.png | Bin 0 -> 948 bytes .../public/diagram/images/normalize.gif | Bin 0 -> 845 bytes .../client/public/diagram/images/point.gif | Bin 0 -> 55 bytes .../client/public/diagram/images/refresh.png | Bin 0 -> 522 bytes .../client/public/diagram/images/resize.gif | Bin 0 -> 74 bytes .../public/diagram/images/round-drop.png | Bin 0 -> 1216 bytes .../client/public/diagram/images/search.png | Bin 0 -> 404 bytes .../public/diagram/images/separator.gif | Bin 0 -> 146 bytes .../client/public/diagram/images/submenu.gif | Bin 0 -> 56 bytes .../public/diagram/images/transparent.gif | Bin 0 -> 90 bytes .../public/diagram/images/triangle-down.png | Bin 0 -> 1104 bytes .../public/diagram/images/triangle-left.png | Bin 0 -> 1123 bytes .../public/diagram/images/triangle-right.png | Bin 0 -> 1103 bytes .../public/diagram/images/triangle-up.png | Bin 0 -> 1086 bytes .../client/public/diagram/images/unlocked.png | Bin 0 -> 1024 bytes .../client/public/diagram/images/warning.gif | Bin 0 -> 276 bytes .../client/public/diagram/images/warning.png | Bin 0 -> 425 bytes .../public/diagram/images/window-title.gif | Bin 0 -> 275 bytes .../client/public/diagram/images/window.gif | Bin 0 -> 75 bytes .../public/diagram/resources/editor.txt | 5 + .../client/public/diagram/resources/graph.txt | 1198 + .../client/public/diagram/stencils/arrows.xml | 849 + .../client/public/diagram/stencils/basic.xml | 895 + .../client/public/diagram/stencils/bpmn.xml | 1162 + .../stencils/clipart/Credit_Card_128x128.png | Bin 0 -> 15493 bytes .../stencils/clipart/Database_128x128.png | Bin 0 -> 10455 bytes .../stencils/clipart/Doctor1_128x128.png | Bin 0 -> 9441 bytes .../stencils/clipart/Earth_globe_128x128.png | Bin 0 -> 14646 bytes .../stencils/clipart/Email_128x128.png | Bin 0 -> 7410 bytes .../stencils/clipart/Empty_Folder_128x128.png | Bin 0 -> 8093 bytes .../stencils/clipart/Firewall_02_128x128.png | Bin 0 -> 8428 bytes .../stencils/clipart/Full_Folder_128x128.png | Bin 0 -> 9568 bytes .../diagram/stencils/clipart/Gear_128x128.png | Bin 0 -> 9193 bytes .../stencils/clipart/Graph_128x128.png | Bin 0 -> 8045 bytes .../stencils/clipart/Laptop_128x128.png | Bin 0 -> 12593 bytes .../diagram/stencils/clipart/Lock_128x128.png | Bin 0 -> 13233 bytes .../stencils/clipart/MacBook_128x128.png | Bin 0 -> 12713 bytes .../clipart/Monitor_Tower_128x128.png | Bin 0 -> 10301 bytes .../stencils/clipart/Piggy_Bank_128x128.png | Bin 0 -> 12780 bytes .../stencils/clipart/Pilot1_128x128.png | Bin 0 -> 10147 bytes .../stencils/clipart/Printer_128x128.png | Bin 0 -> 12104 bytes .../stencils/clipart/Router_Icon_128x128.png | Bin 0 -> 5670 bytes .../diagram/stencils/clipart/Safe_128x128.png | Bin 0 -> 8876 bytes .../stencils/clipart/Security1_128x128.png | Bin 0 -> 10400 bytes .../stencils/clipart/Server_Tower_128x128.png | Bin 0 -> 13352 bytes .../clipart/Shopping_Cart_128x128.png | Bin 0 -> 13803 bytes .../stencils/clipart/Software_128x128.png | Bin 0 -> 11020 bytes .../stencils/clipart/Soldier1_128x128.png | Bin 0 -> 13540 bytes .../stencils/clipart/Suit1_128x128.png | Bin 0 -> 9128 bytes .../stencils/clipart/Suit2_128x128.png | Bin 0 -> 8925 bytes .../stencils/clipart/Suit3_128x128.png | Bin 0 -> 8326 bytes .../stencils/clipart/Tech1_128x128.png | Bin 0 -> 7146 bytes .../stencils/clipart/Telesales1_128x128.png | Bin 0 -> 8257 bytes .../clipart/Virtual_Machine_128x128.png | Bin 0 -> 16557 bytes .../stencils/clipart/Virus_128x128.png | Bin 0 -> 11358 bytes .../clipart/Wireless_Router_N_128x128.png | Bin 0 -> 7468 bytes .../stencils/clipart/Worker1_128x128.png | Bin 0 -> 9519 bytes .../stencils/clipart/Workstation_128x128.png | Bin 0 -> 13635 bytes .../diagram/stencils/clipart/iMac_128x128.png | Bin 0 -> 13149 bytes .../diagram/stencils/clipart/iPad_128x128.png | Bin 0 -> 11727 bytes .../public/diagram/stencils/flowchart.xml | 920 + packages/client/src/diagram/index.js | 65 + packages/client/src/pages/_app.tsx | 2 - packages/client/src/pages/index.tsx | 2 +- .../client/src/tiptap/core/styles/flow.scss | 1996 +- .../src/tiptap/core/wrappers/flow/index.tsx | 98 +- .../src/tiptap/editor/menus/flow/modal.tsx | 84 +- packages/client/tsconfig.json | 3 +- 96 files changed, 141277 insertions(+), 113 deletions(-) delete mode 100644 .eslintignore create mode 100644 packages/client/public/diagram.js create mode 100644 packages/client/public/diagram.min.js create mode 100644 packages/client/public/diagram/images/button.gif create mode 100644 packages/client/public/diagram/images/checkmark.gif create mode 100644 packages/client/public/diagram/images/clear.gif create mode 100644 packages/client/public/diagram/images/close.gif create mode 100644 packages/client/public/diagram/images/close.png create mode 100644 packages/client/public/diagram/images/collapsed.gif create mode 100644 packages/client/public/diagram/images/dropdown.gif create mode 100644 packages/client/public/diagram/images/dropdown.png create mode 100644 packages/client/public/diagram/images/edit.gif create mode 100644 packages/client/public/diagram/images/error.gif create mode 100644 packages/client/public/diagram/images/expanded.gif create mode 100644 packages/client/public/diagram/images/grid.gif create mode 100644 packages/client/public/diagram/images/handle-fixed.png create mode 100644 packages/client/public/diagram/images/handle-main.png create mode 100644 packages/client/public/diagram/images/handle-rotate.png create mode 100644 packages/client/public/diagram/images/handle-secondary.png create mode 100644 packages/client/public/diagram/images/handle-terminal.png create mode 100644 packages/client/public/diagram/images/help.png create mode 100644 packages/client/public/diagram/images/locked.png create mode 100644 packages/client/public/diagram/images/logo.png create mode 100644 packages/client/public/diagram/images/maximize.gif create mode 100644 packages/client/public/diagram/images/minimize.gif create mode 100644 packages/client/public/diagram/images/move.png create mode 100644 packages/client/public/diagram/images/nocolor.png create mode 100644 packages/client/public/diagram/images/normalize.gif create mode 100644 packages/client/public/diagram/images/point.gif create mode 100644 packages/client/public/diagram/images/refresh.png create mode 100644 packages/client/public/diagram/images/resize.gif create mode 100644 packages/client/public/diagram/images/round-drop.png create mode 100644 packages/client/public/diagram/images/search.png create mode 100644 packages/client/public/diagram/images/separator.gif create mode 100644 packages/client/public/diagram/images/submenu.gif create mode 100644 packages/client/public/diagram/images/transparent.gif create mode 100644 packages/client/public/diagram/images/triangle-down.png create mode 100644 packages/client/public/diagram/images/triangle-left.png create mode 100644 packages/client/public/diagram/images/triangle-right.png create mode 100644 packages/client/public/diagram/images/triangle-up.png create mode 100644 packages/client/public/diagram/images/unlocked.png create mode 100644 packages/client/public/diagram/images/warning.gif create mode 100644 packages/client/public/diagram/images/warning.png create mode 100644 packages/client/public/diagram/images/window-title.gif create mode 100644 packages/client/public/diagram/images/window.gif create mode 100644 packages/client/public/diagram/resources/editor.txt create mode 100644 packages/client/public/diagram/resources/graph.txt create mode 100644 packages/client/public/diagram/stencils/arrows.xml create mode 100644 packages/client/public/diagram/stencils/basic.xml create mode 100644 packages/client/public/diagram/stencils/bpmn.xml create mode 100644 packages/client/public/diagram/stencils/clipart/Credit_Card_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Database_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Doctor1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Earth_globe_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Email_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Empty_Folder_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Firewall_02_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Full_Folder_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Gear_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Graph_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Laptop_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Lock_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/MacBook_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Monitor_Tower_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Piggy_Bank_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Pilot1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Printer_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Router_Icon_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Safe_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Security1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Server_Tower_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Shopping_Cart_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Software_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Soldier1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Suit1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Suit2_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Suit3_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Tech1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Telesales1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Virtual_Machine_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Virus_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Wireless_Router_N_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Worker1_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/Workstation_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/iMac_128x128.png create mode 100644 packages/client/public/diagram/stencils/clipart/iPad_128x128.png create mode 100644 packages/client/public/diagram/stencils/flowchart.xml create mode 100644 packages/client/src/diagram/index.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 70095245..00000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -**/.next/** -**/_next/** -**/dist/** -.eslintrc.js -./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js -./packages/client/public/viewer.min.js diff --git a/.prettierignore b/.prettierignore index 5a29e474..fa6ec791 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,6 @@ node_modules .next dist -lib \ No newline at end of file +lib +./packages/client/public/diagram.js +./packages/client/public/diagram.min.js diff --git a/packages/client/.eslintignore b/packages/client/.eslintignore index 70095245..5284f72d 100644 --- a/packages/client/.eslintignore +++ b/packages/client/.eslintignore @@ -3,5 +3,7 @@ node_modules **/_next/** **/dist/** .eslintrc.js -./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js -./packages/client/public/viewer.min.js + +./src/tiptap/core/wrappers/mind/mind-elixir/iconfont/iconfont.js +./public/diagram.js +./public/diagram.min.js diff --git a/packages/client/public/diagram.js b/packages/client/public/diagram.js new file mode 100644 index 00000000..f16451e0 --- /dev/null +++ b/packages/client/public/diagram.js @@ -0,0 +1,134092 @@ +/* eslint-disable */ +// urlParams is null when used for embedding +window.urlParams = window.urlParams || {}; + +// Public global variables +window.MAX_REQUEST_SIZE = window.MAX_REQUEST_SIZE || 10485760; +window.MAX_AREA = window.MAX_AREA || 15000 * 15000; + +// URLs for save and export +// window.EXPORT_URL = window.EXPORT_URL || '/export'; +// window.SAVE_URL = window.SAVE_URL || '/save'; +// window.OPEN_URL = window.OPEN_URL || '/open'; +window.mxBasePath = '/diagram'; +window.RESOURCES_PATH = '/diagram/resources'; +// window.RESOURCE_BASE = window.RESOURCE_BASE || window.RESOURCES_PATH + '/grapheditor'; +window.STENCIL_PATH = '/diagram/stencils'; +window.IMAGE_PATH = '/diagram/images'; +// window.STYLE_PATH = window.STYLE_PATH || 'styles'; +// window.CSS_PATH = window.CSS_PATH || 'styles'; +// window.OPEN_FORM = window.OPEN_FORM || 'open.html'; + +var HoverIcons = function () {}; +var mxCellEditorGetInitialValue = function () {}; +var mxCellEditorGetCurrentValue = function () {}; +var mxGraphHandlerIsValidDropTarget = function () {}; +var Format = function () {}; +var BaseFormatPanel = function () {}; +var ArrangePanel = function () {}; +var TextFormatPanel = function () {}; +var StyleFormatPanel = function () {}; +var DiagramStylePanel = function () {}; +var DiagramFormatPanel = function () {}; +var Menus = function () {}; + +/** + * jscolor, JavaScript Color Picker + * + * @version 1.3.13 + * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html + * @author Jan Odvarko, http://odvarko.cz + * @created 2008-06-15 + * @updated 2012-01-19 + * @link http://jscolor.com + */ + +var mxJSColor = { + dir: '', // location of jscolor directory (leave empty to autodetect) + bindClass: 'color', // class name + binding: true, // automatic binding via + preloading: true, // use image preloading? + + install: function () { + //mxJSColor.addEvent(window, 'load', mxJSColor.init); + }, + + init: function () { + if (mxJSColor.preloading) { + mxJSColor.preload(); + } + }, + + getDir: function () { + if (!mxJSColor.dir) { + var detected = mxJSColor.detectDir(); + mxJSColor.dir = detected !== false ? detected : 'jscolor/'; + } + return mxJSColor.dir; + }, + + detectDir: function () { + var base = location.href; + + var e = document.getElementsByTagName('base'); + for (var i = 0; i < e.length; i += 1) { + if (e[i].href) { + base = e[i].href; + } + } + + var e = document.getElementsByTagName('script'); + for (var i = 0; i < e.length; i += 1) { + if (e[i].src && /(^|\/)jscolor\.js([?#].*)?$/i.test(e[i].src)) { + var src = new mxJSColor.URI(e[i].src); + var srcAbs = src.toAbsolute(base); + srcAbs.path = srcAbs.path.replace(/[^\/]+$/, ''); // remove filename + srcAbs.query = null; + srcAbs.fragment = null; + return srcAbs.toString(); + } + } + return false; + }, + + preload: function () { + for (var fn in mxJSColor.imgRequire) { + if (mxJSColor.imgRequire.hasOwnProperty(fn)) { + mxJSColor.loadImage(fn); + } + } + }, + + images: { + pad: [181, 101], + sld: [16, 101], + cross: [15, 15], + arrow: [7, 11], + }, + + imgRequire: {}, + imgLoaded: {}, + + requireImage: function (filename) { + mxJSColor.imgRequire[filename] = true; + }, + + loadImage: function (filename) { + if (!mxJSColor.imgLoaded[filename]) { + mxJSColor.imgLoaded[filename] = new Image(); + mxJSColor.imgLoaded[filename].src = mxJSColor.getDir() + filename; + } + }, + + fetchElement: function (mixed) { + return typeof mixed === 'string' ? document.getElementById(mixed) : mixed; + }, + + addEvent: function (el, evnt, func) { + if (el.addEventListener) { + el.addEventListener(evnt, func, false); + } else if (el.attachEvent) { + el.attachEvent('on' + evnt, func); + } + }, + + fireEvent: function (el, evnt) { + if (!el) { + return; + } + if (document.createEvent) { + var ev = document.createEvent('HTMLEvents'); + ev.initEvent(evnt, true, true); + el.dispatchEvent(ev); + } else if (document.createEventObject) { + var ev = document.createEventObject(); + el.fireEvent('on' + evnt, ev); + } else if (el['on' + evnt]) { + // alternatively use the traditional event model (IE5) + el['on' + evnt](); + } + }, + + getElementPos: function (e) { + var e1 = e, + e2 = e; + var x = 0, + y = 0; + if (e1.offsetParent) { + do { + x += e1.offsetLeft; + y += e1.offsetTop; + } while ((e1 = e1.offsetParent)); + } + while ((e2 = e2.parentNode) && e2.nodeName.toUpperCase() !== 'BODY') { + x -= e2.scrollLeft; + y -= e2.scrollTop; + } + return [x, y]; + }, + + getElementSize: function (e) { + return [e.offsetWidth, e.offsetHeight]; + }, + + getRelMousePos: function (e) { + var x = 0, + y = 0; + if (!e) { + e = window.event; + } + if (typeof e.offsetX === 'number') { + x = e.offsetX; + y = e.offsetY; + } else if (typeof e.layerX === 'number') { + x = e.layerX; + y = e.layerY; + } + return { x: x, y: y }; + }, + + getViewPos: function () { + if (typeof window.pageYOffset === 'number') { + return [window.pageXOffset, window.pageYOffset]; + } else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) { + return [document.body.scrollLeft, document.body.scrollTop]; + } else if ( + document.documentElement && + (document.documentElement.scrollLeft || document.documentElement.scrollTop) + ) { + return [document.documentElement.scrollLeft, document.documentElement.scrollTop]; + } else { + return [0, 0]; + } + }, + + getViewSize: function () { + if (typeof window.innerWidth === 'number') { + return [window.innerWidth, window.innerHeight]; + } else if (document.body && (document.body.clientWidth || document.body.clientHeight)) { + return [document.body.clientWidth, document.body.clientHeight]; + } else if ( + document.documentElement && + (document.documentElement.clientWidth || document.documentElement.clientHeight) + ) { + return [document.documentElement.clientWidth, document.documentElement.clientHeight]; + } else { + return [0, 0]; + } + }, + + URI: function (uri) { + // See RFC3986 + + this.scheme = null; + this.authority = null; + this.path = ''; + this.query = null; + this.fragment = null; + + this.parse = function (uri) { + var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/); + this.scheme = m[3] ? m[2] : null; + this.authority = m[5] ? m[6] : null; + this.path = m[7]; + this.query = m[9] ? m[10] : null; + this.fragment = m[12] ? m[13] : null; + return this; + }; + + this.toString = function () { + var result = ''; + if (this.scheme !== null) { + result = result + this.scheme + ':'; + } + if (this.authority !== null) { + result = result + '//' + this.authority; + } + if (this.path !== null) { + result = result + this.path; + } + if (this.query !== null) { + result = result + '?' + this.query; + } + if (this.fragment !== null) { + result = result + '#' + this.fragment; + } + return result; + }; + + this.toAbsolute = function (base) { + var base = new mxJSColor.URI(base); + var r = this; + var t = new mxJSColor.URI(); + + if (base.scheme === null) { + return false; + } + + if (r.scheme !== null && r.scheme.toLowerCase() === base.scheme.toLowerCase()) { + r.scheme = null; + } + + if (r.scheme !== null) { + t.scheme = r.scheme; + t.authority = r.authority; + t.path = removeDotSegments(r.path); + t.query = r.query; + } else { + if (r.authority !== null) { + t.authority = r.authority; + t.path = removeDotSegments(r.path); + t.query = r.query; + } else { + if (r.path === '') { + // TODO: == or === ? + t.path = base.path; + if (r.query !== null) { + t.query = r.query; + } else { + t.query = base.query; + } + } else { + if (r.path.substr(0, 1) === '/') { + t.path = removeDotSegments(r.path); + } else { + if (base.authority !== null && base.path === '') { + // TODO: == or === ? + t.path = '/' + r.path; + } else { + t.path = base.path.replace(/[^\/]+$/, '') + r.path; + } + t.path = removeDotSegments(t.path); + } + t.query = r.query; + } + t.authority = base.authority; + } + t.scheme = base.scheme; + } + t.fragment = r.fragment; + + return t; + }; + + function removeDotSegments(path) { + var out = ''; + while (path) { + if (path.substr(0, 3) === '../' || path.substr(0, 2) === './') { + path = path.replace(/^\.+/, '').substr(1); + } else if (path.substr(0, 3) === '/./' || path === '/.') { + path = '/' + path.substr(3); + } else if (path.substr(0, 4) === '/../' || path === '/..') { + path = '/' + path.substr(4); + out = out.replace(/\/?[^\/]*$/, ''); + } else if (path === '.' || path === '..') { + path = ''; + } else { + var rm = path.match(/^\/?[^\/]*/)[0]; + path = path.substr(rm.length); + out = out + rm; + } + } + return out; + } + + if (uri) { + this.parse(uri); + } + }, + + /* + * Usage example: + * var myColor = new mxJSColor.color(myInputElement) + */ + + color: function (target, prop) { + this.required = true; // refuse empty values? + this.adjust = true; // adjust value to uniform notation? + this.hash = false; // prefix color with # symbol? + this.caps = true; // uppercase? + this.slider = true; // show the value/saturation slider? + this.valueElement = target; // value holder + this.styleElement = target; // where to reflect current color + this.onImmediateChange = null; // onchange callback (can be either string or function) + this.hsv = [0, 0, 1]; // read-only 0-6, 0-1, 0-1 + this.rgb = [1, 1, 1]; // read-only 0-1, 0-1, 0-1 + + this.pickerOnfocus = true; // display picker on focus? + this.pickerMode = 'HSV'; // HSV | HVS + this.pickerPosition = 'bottom'; // left | right | top | bottom + this.pickerSmartPosition = true; // automatically adjust picker position when necessary + this.pickerButtonHeight = 20; // px + this.pickerClosable = false; + this.pickerCloseText = 'Close'; + this.pickerButtonColor = 'ButtonText'; // px + this.pickerFace = 0; // px + this.pickerFaceColor = 'ThreeDFace'; // CSS color + this.pickerBorder = 1; // px + this.pickerBorderColor = 'ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight'; // CSS color + this.pickerInset = 1; // px + this.pickerInsetColor = 'ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow'; // CSS color + this.pickerZIndex = 10000; + + for (var p in prop) { + if (prop.hasOwnProperty(p)) { + this[p] = prop[p]; + } + } + + this.hidePicker = function () { + if (isPickerOwner()) { + removePicker(); + } + }; + + this.showPicker = function () { + if (!isPickerOwner()) { + var tp = mxJSColor.getElementPos(target); // target pos + var ts = mxJSColor.getElementSize(target); // target size + var vp = mxJSColor.getViewPos(); // view pos + var vs = mxJSColor.getViewSize(); // view size + var ps = getPickerDims(this); // picker size + var a, b, c; + switch (this.pickerPosition.toLowerCase()) { + case 'left': + a = 1; + b = 0; + c = -1; + break; + case 'right': + a = 1; + b = 0; + c = 1; + break; + case 'top': + a = 0; + b = 1; + c = -1; + break; + default: + a = 0; + b = 1; + c = 1; + break; + } + var l = (ts[b] + ps[b]) / 2; + + // picker pos + if (!this.pickerSmartPosition) { + var pp = [tp[a], tp[b] + ts[b] - l + l * c]; + } else { + var pp = [ + -vp[a] + tp[a] + ps[a] > vs[a] + ? -vp[a] + tp[a] + ts[a] / 2 > vs[a] / 2 && tp[a] + ts[a] - ps[a] >= 0 + ? tp[a] + ts[a] - ps[a] + : tp[a] + : tp[a], + -vp[b] + tp[b] + ts[b] + ps[b] - l + l * c > vs[b] + ? -vp[b] + tp[b] + ts[b] / 2 > vs[b] / 2 && tp[b] + ts[b] - l - l * c >= 0 + ? tp[b] + ts[b] - l - l * c + : tp[b] + ts[b] - l + l * c + : tp[b] + ts[b] - l + l * c >= 0 + ? tp[b] + ts[b] - l + l * c + : tp[b] + ts[b] - l - l * c, + ]; + } + drawPicker(0, 0); + } + }; + + this.importColor = function () { + if (!valueElement) { + this.exportColor(); + } else { + if (!this.adjust) { + if (!this.fromString(valueElement.value, leaveValue)) { + styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; + styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; + styleElement.style.color = styleElement.jscStyle.color; + this.exportColor(leaveValue | leaveStyle); + } + } else if (!this.required && /^\s*$/.test(valueElement.value)) { + valueElement.value = ''; + styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; + styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; + styleElement.style.color = styleElement.jscStyle.color; + this.exportColor(leaveValue | leaveStyle); + } else if (this.fromString(valueElement.value)) { + // OK + } else { + this.exportColor(); + } + } + }; + + this.exportColor = function (flags) { + if (!(flags & leaveValue) && valueElement) { + var value = this.toString(); + if (this.caps) { + value = value.toUpperCase(); + } + if (this.hash) { + value = '#' + value; + } + valueElement.value = value; + } + if (!(flags & leaveStyle) && styleElement) { + styleElement.style.backgroundImage = 'none'; + styleElement.style.backgroundColor = '#' + this.toString(); + styleElement.style.color = + 0.213 * this.rgb[0] + 0.715 * this.rgb[1] + 0.072 * this.rgb[2] < 0.5 ? '#FFF' : '#000'; + } + if (!(flags & leavePad) && isPickerOwner()) { + redrawPad(); + } + if (!(flags & leaveSld) && isPickerOwner()) { + redrawSld(); + } + }; + + this.fromHSV = function (h, s, v, flags) { + // null = don't change + (h < 0 && (h = 0)) || (h > 6 && (h = 6)); + (s < 0 && (s = 0)) || (s > 1 && (s = 1)); + (v < 0 && (v = 0)) || (v > 1 && (v = 1)); + this.rgb = HSV_RGB( + h === null ? this.hsv[0] : (this.hsv[0] = h), + s === null ? this.hsv[1] : (this.hsv[1] = s), + v === null ? this.hsv[2] : (this.hsv[2] = v) + ); + this.exportColor(flags); + }; + + this.fromRGB = function (r, g, b, flags) { + // null = don't change + (r < 0 && (r = 0)) || (r > 1 && (r = 1)); + (g < 0 && (g = 0)) || (g > 1 && (g = 1)); + (b < 0 && (b = 0)) || (b > 1 && (b = 1)); + var hsv = RGB_HSV( + r === null ? this.rgb[0] : (this.rgb[0] = r), + g === null ? this.rgb[1] : (this.rgb[1] = g), + b === null ? this.rgb[2] : (this.rgb[2] = b) + ); + if (hsv[0] !== null) { + this.hsv[0] = hsv[0]; + } + if (hsv[2] !== 0) { + this.hsv[1] = hsv[1]; + } + this.hsv[2] = hsv[2]; + this.exportColor(flags); + }; + + this.fromString = function (hex, flags) { + var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i); + if (!m) { + return false; + } else { + if (m[1].length === 6) { + // 6-char notation + this.fromRGB( + parseInt(m[1].substr(0, 2), 16) / 255, + parseInt(m[1].substr(2, 2), 16) / 255, + parseInt(m[1].substr(4, 2), 16) / 255, + flags + ); + } else { + // 3-char notation + this.fromRGB( + parseInt(m[1].charAt(0) + m[1].charAt(0), 16) / 255, + parseInt(m[1].charAt(1) + m[1].charAt(1), 16) / 255, + parseInt(m[1].charAt(2) + m[1].charAt(2), 16) / 255, + flags + ); + } + return true; + } + }; + + this.toString = function () { + return ( + (0x100 | Math.round(255 * this.rgb[0])).toString(16).substr(1) + + (0x100 | Math.round(255 * this.rgb[1])).toString(16).substr(1) + + (0x100 | Math.round(255 * this.rgb[2])).toString(16).substr(1) + ); + }; + + function RGB_HSV(r, g, b) { + var n = Math.min(Math.min(r, g), b); + var v = Math.max(Math.max(r, g), b); + var m = v - n; + if (m === 0) { + return [null, 0, v]; + } + var h = r === n ? 3 + (b - g) / m : g === n ? 5 + (r - b) / m : 1 + (g - r) / m; + return [h === 6 ? 0 : h, m / v, v]; + } + + function HSV_RGB(h, s, v) { + if (h === null) { + return [v, v, v]; + } + var i = Math.floor(h); + var f = i % 2 ? h - i : 1 - (h - i); + var m = v * (1 - s); + var n = v * (1 - s * f); + switch (i) { + case 6: + case 0: + return [v, n, m]; + case 1: + return [n, v, m]; + case 2: + return [m, v, n]; + case 3: + return [m, n, v]; + case 4: + return [n, m, v]; + case 5: + return [v, m, n]; + } + } + + function removePicker() { + delete mxJSColor.picker.owner; + document.getElementsByTagName('body')[0].removeChild(mxJSColor.picker.boxB); + } + + function drawPicker(x, y) { + if (!mxJSColor.picker) { + mxJSColor.picker = { + box: document.createElement('div'), + boxB: document.createElement('div'), + pad: document.createElement('div'), + padB: document.createElement('div'), + padM: document.createElement('div'), + sld: document.createElement('div'), + sldB: document.createElement('div'), + sldM: document.createElement('div'), + btn: document.createElement('div'), + btnS: document.createElement('span'), + btnT: document.createTextNode(THIS.pickerCloseText), + }; + for (var i = 0, segSize = 4; i < mxJSColor.images.sld[1]; i += segSize) { + var seg = document.createElement('div'); + seg.style.height = segSize + 'px'; + seg.style.fontSize = '1px'; + seg.style.lineHeight = '0'; + mxJSColor.picker.sld.appendChild(seg); + } + mxJSColor.picker.sldB.appendChild(mxJSColor.picker.sld); + mxJSColor.picker.box.appendChild(mxJSColor.picker.sldB); + mxJSColor.picker.box.appendChild(mxJSColor.picker.sldM); + mxJSColor.picker.padB.appendChild(mxJSColor.picker.pad); + mxJSColor.picker.box.appendChild(mxJSColor.picker.padB); + mxJSColor.picker.box.appendChild(mxJSColor.picker.padM); + mxJSColor.picker.btnS.appendChild(mxJSColor.picker.btnT); + mxJSColor.picker.btn.appendChild(mxJSColor.picker.btnS); + mxJSColor.picker.box.appendChild(mxJSColor.picker.btn); + mxJSColor.picker.boxB.appendChild(mxJSColor.picker.box); + } + + var p = mxJSColor.picker; + + // controls interaction + p.box.onmouseup = p.box.onmouseout = function () { + if (!mxClient.IS_TOUCH) { + target.focus(); + } + }; + p.box.onmousedown = function () { + abortBlur = true; + }; + p.box.onmousemove = function (e) { + if (holdPad || holdSld) { + holdPad && setPad(e); + holdSld && setSld(e); + if (document.selection) { + document.selection.empty(); + } else if (window.getSelection) { + window.getSelection().removeAllRanges(); + } + dispatchImmediateChange(); + } + }; + p.padM.onmouseup = p.padM.onmouseout = function () { + if (holdPad) { + holdPad = false; + mxJSColor.fireEvent(valueElement, 'change'); + } + }; + p.padM.onmousedown = function (e) { + // if the slider is at the bottom, move it up + switch (modeID) { + case 0: + if (THIS.hsv[2] === 0) { + THIS.fromHSV(null, null, 1.0); + } + break; + case 1: + if (THIS.hsv[1] === 0) { + THIS.fromHSV(null, 1.0, null); + } + break; + } + holdPad = true; + setPad(e); + dispatchImmediateChange(); + }; + p.sldM.onmouseup = p.sldM.onmouseout = function () { + if (holdSld) { + holdSld = false; + mxJSColor.fireEvent(valueElement, 'change'); + } + }; + p.sldM.onmousedown = function (e) { + holdSld = true; + setSld(e); + dispatchImmediateChange(); + }; + + // picker + var dims = getPickerDims(THIS); + p.box.style.width = dims[0] + 'px'; + p.box.style.height = dims[1] + 'px'; + + // picker border + p.boxB.style.position = 'absolute'; + p.boxB.style.clear = 'both'; + p.boxB.style.left = x + 'px'; + p.boxB.style.top = y + 'px'; + p.boxB.style.zIndex = THIS.pickerZIndex; + p.boxB.style.border = THIS.pickerBorder + 'px solid'; + p.boxB.style.borderColor = THIS.pickerBorderColor; + p.boxB.style.background = THIS.pickerFaceColor; + + // pad image + p.pad.style.width = mxJSColor.images.pad[0] + 'px'; + p.pad.style.height = mxJSColor.images.pad[1] + 'px'; + + // pad border + p.padB.style.position = 'absolute'; + p.padB.style.left = THIS.pickerFace + 'px'; + p.padB.style.top = THIS.pickerFace + 'px'; + p.padB.style.border = THIS.pickerInset + 'px solid'; + p.padB.style.borderColor = THIS.pickerInsetColor; + + // pad mouse area + p.padM.style.position = 'absolute'; + p.padM.style.left = '0'; + p.padM.style.top = '0'; + p.padM.style.width = + THIS.pickerFace + 2 * THIS.pickerInset + mxJSColor.images.pad[0] + mxJSColor.images.arrow[0] + 'px'; + p.padM.style.height = p.box.style.height; + p.padM.style.cursor = 'crosshair'; + + // slider image + p.sld.style.overflow = 'hidden'; + p.sld.style.width = mxJSColor.images.sld[0] + 'px'; + p.sld.style.height = mxJSColor.images.sld[1] + 'px'; + + // slider border + p.sldB.style.display = THIS.slider ? 'block' : 'none'; + p.sldB.style.position = 'absolute'; + p.sldB.style.right = THIS.pickerFace + 'px'; + p.sldB.style.top = THIS.pickerFace + 'px'; + p.sldB.style.border = THIS.pickerInset + 'px solid'; + p.sldB.style.borderColor = THIS.pickerInsetColor; + + // slider mouse area + p.sldM.style.display = THIS.slider ? 'block' : 'none'; + p.sldM.style.position = 'absolute'; + p.sldM.style.right = '0'; + p.sldM.style.top = '0'; + p.sldM.style.width = + mxJSColor.images.sld[0] + mxJSColor.images.arrow[0] + THIS.pickerFace + 2 * THIS.pickerInset + 'px'; + p.sldM.style.height = p.box.style.height; + try { + p.sldM.style.cursor = 'pointer'; + } catch (eOldIE) { + p.sldM.style.cursor = 'hand'; + } + + // "close" button + function setBtnBorder() { + var insetColors = THIS.pickerInsetColor.split(/\s+/); + var pickerOutsetColor = + insetColors.length < 2 + ? insetColors[0] + : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; + p.btn.style.borderColor = pickerOutsetColor; + } + p.btn.style.display = THIS.pickerClosable ? 'block' : 'none'; + p.btn.style.position = 'absolute'; + p.btn.style.left = THIS.pickerFace + 'px'; + p.btn.style.bottom = THIS.pickerFace + 'px'; + p.btn.style.padding = '0 15px'; + p.btn.style.height = '18px'; + p.btn.style.border = THIS.pickerInset + 'px solid'; + setBtnBorder(); + p.btn.style.color = THIS.pickerButtonColor; + p.btn.style.font = '12px sans-serif'; + p.btn.style.textAlign = 'center'; + try { + p.btn.style.cursor = 'pointer'; + } catch (eOldIE) { + p.btn.style.cursor = 'hand'; + } + p.btn.onmousedown = function () { + THIS.hidePicker(); + }; + p.btnS.style.lineHeight = p.btn.style.height; + + // load images in optimal order + switch (modeID) { + case 0: + var padImg = 'hs'; + break; + case 1: + var padImg = 'hv'; + break; + } + + const bgs = { + arrow: + "url('data:image/gif;base64,R0lGODlhBwALAKECAAAAAP///6g8eKg8eCH5BAEKAAIALAAAAAAHAAsAAAITTIQYcLnsgGxvijrxqdQq6DRJAQA7')", + cross: + "url('data:image/gif;base64,R0lGODlhDwAPAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAPAA8AAAIklB8Qx53b4otSUWcvyiz4/4AeQJbmKY4p1HHapBlwPL/uVRsFADs=')", + hs: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALUAAABlCAIAAACEDzXRAAAKQ0lEQVR42u2d23IjKwxFBeRh5v8/9uQlzXlI2gGELoCEm6pxubp6PI69WoV3C20uIQPAH4A/AH/p41/pDcTxP4BPGDtq3vYJGPpv8craeRdo/fyTI8ZhHnodOGgcV/3rnx8QI0SABO2xfAbiCfVR8Wj+CD8jenbRJNyGOxLoAaH1iLu4EUEHAv1+MsQ4zLE4AiIOcpibv4u9OET6+cP4ASnJ1JH4/G60A9km+GbBxLlpKAkADoR2JnaBrvWD/x1GCZn9EYLEHqVfI6Efx0D7E9tDF/rBSJ54l8FqTUse0J+EvxPrdK0fD4aOLXQi7zyGxMaRvvWjjH2U8o9I44OKnf+wyCpHoR/Phk6d/COq8481YrNI3/qR2B+kMl1Fl6J8o/JrU/GEA6HdiB2ha/0YBVx7rFwNHAj9JuIl6EI/qKyJug4Yvg7xA0QEpB8nQe8itoQu9EOT8M3mTsqUSUzykH6cBO1PbA9d91+ShDzV9xrtcgW21IT6L0yfcS90FKCdw+wS6Q9ISWjVfLTVVT2mdiPGGRduFKWm7dBStWlLmI0j/QExdgp7gT5qCu2BLKiLqtc94hI7PB8auQP4KuyIvSJ960fUqXWcd2F48Khr1bHSj2dDp/afmg7tMrFxpG/9mHMFuqrH3spFw0s0Bgr9OAzamdgFutaPpMuqo6WVEXWJdSL14xhof2J76MKfS2yXWGOVB1WXXGOVdxFSx587CXoLsTE0689RH7VW4RMLeKI9wPpzz4XeS2wDzdbXR52AWStjwiSAA6HdiB2h1f6cqT0whzzuzz0LejuxATTy90Xt40v7CzdGRvKS4O/zQ0AiXaRW5x/dv4vS4A/a308LYR5pFquRRvlHIrKWoCj5woCVId4eR/KPNDjURg2tdEC7rSQN5B9DYR70X5YivVxft7AyrOvrD4V2JnaBJurr+qpemLEywlRhT6qvPx16C7ExdK0fSjcj0maAzsqgBt7rfIxGP46B9iT2gi70I842bNj0U4wd/WAG/L5VP3p0W4iNoYv6Oh6eoJmSMW5laIzF8iT1TtgeV9RZoZ7QvdEUzsQu0CP+HOMKDFoZvDFg6s89CNqf2B5a4e8Hts84W0pgeohBtspBXYp8H3Tk/H23MBtHuvbn5mp7dlU9TUkP+XNJMabzHdCIbnuYDSItzZ+bdgUWrAwNPhwInRxw1/wXuYGj+4vo7flboRpjEQ6EjgojYK9/K04NFfNTauEBypXQWQLAfqQoHl9CfvpQ6C3ExtC9/q2YW2u6X1JWzfsYFPKX3L99LrQ/sT10XR+jKh+UN2AxlDOyXfLQaxxf/fkvT4d2JnaBZv258M6h4IFu2Em7/sezoLcQG0Mjfy6xrqLnVBLGWHw1i69WP5LCCn0TNEL3DLNXpCV/n5oo5jAVLY5Em6i8Pwm6uSNuCrNxpNX+nDira20qqyZriv384xhoZ2IX6Lr/wg8eiwpXYGQqPEgjsBrX+er3X46B9ie2h6bX/4i6IfcLS2mIY++7TfoCuLj6hwgN89CwBJ3Gx8rOVsn0kQ48tDS/4X1L8SzMb3BbkGdtwsD05AbP9YOU8xui+uljZei/P/3qx2HQbsSO0Ar/lveHLZYCVK4GeN1P5N+eAb2R2Aya9ueUY+8dlhLtdhWvgfEfz4AeGf9hR2wcaeTPpacvRdzLTx8J/Qq1Ij995PrJ3/mp0p8LO5YyF0s215g/9yxoZ2IXaLo+FiwtgTlvIAznp4+G3khsBk3Pf5mYvqOwMqZnNnw3i9cRDoT2JPaCZuvreskDuXkHxfQMvfbBgdBbiI2hC/3QLCg6lESZZkrfJ7nSj3gWNEMcRkYJsf5L0BXENBl1WV8/aquMLM9/eSJ0UtxZmFGGg4MigZ75ovHnvsOcUX09zRbzjCrU/K/Rev0gT2i2vu4cZrNI1+sH2e49Yb3lRC6e9fSS9ETohjj7hdkx0rQ/57/rxIrhBQdCv494Hlrtz1lo30Qjz1z+cRh02ndTNIMm1j8NxtmePs+76i+/esJHrH/6dGh/YntoRf9l2RtQWgJXzY4bdtbqx0OhnYldoHv7I79jf4+rOGGaRWr14yToLcTG0Ky/v32rjAsdX9S5vgI4ENqT2Asa7Z/9vq0yStiGNNVHkOe/RGmqwBQ0jEDHFpoJ85BLN+jPaRoHGWl2/2yx3msxVRHjZ0Td4GfV/Bf9bCM1dBiBfrHeJ1HR6dKEedDf5yPdBLilp/efC3Ribb1VxlUHGQe8iTb0598+Hdqf2B6a3b9SOerezsoIBWaJ3IBn1fp0j4PeQmwMzc5/idLK3UZWRkmKX2zA4Vc/DoPeSGwGrdvfY24RpKmVjrLuCQdCuxE7Qkv64TZ3Z44dVPrxUOh3EK9C1/lHGpz7ZzpVsYTF7K8Hyj+Ogd5FbAk9uL9H9LIyYg821sjo13gYtD+xPbRU/9Ds/b1sZTTIr5OStMGHA6GdiV2gUf2UmQcfJdUbXOoe6HoN1A0b2uQD4EBosXJqQTwKnXvXAEX+oZlfGaS9ewetDGCLeV1exH4e9Mr+2TDpv2iggYFW+LdBsTbngv/yUroGH0jleEX7MOgtxMbQ7PzKoMuajKyM8pYIiB194HnQzsQu0Gp/LvpulVHylsgK/TgG2p/YHrqXf6T3bJUBhfxFWUDPg95CbAytm/+ycasM9WOxWu0PnTniLWE2iLRi/4Z3bJXRvY76s0+Arl93I3aMNOHPBce2PdSkL7Kpnwe9l9gGutCPoFgkUtytXAGbex0vKCznSOs00o+ToLcQG0N/QErCrG6jrTIynVUDwo896vCzssPrNnAYtD+xPfR9fwmKeu/CVhm5OAn1STPkvhxBW1KXjeP60Y/DoJ2JXaAL/RCPy+PXc80L9d7OV294dVPbC/fb4EDoLcTG0IV+TCDrrIxMHIFg512BWj9OgvYk9oK+9SMoZG55q/JMiGS40/sLjacu28T3MVfd0Aha+u3QJXr+0Q//MBtHutaPQOzh3H0RBoZS5OJ/MjqJ9x2vQb7qgSuvX0StH0+FLptzrvTDh9gl0oV+iAbi2lAsqNmbK3yBAyLNdbRzW8Y6Btqf2B76A2IURE2zZY16KGfZlwqofJSL4feABka+xCNX+ekx0LuILaFr/Zit7s4ZA7y5Qg2sDr/t4zDodxCvQt/6occctDKGriMPzH85D9qN2BG61g/QwcLSVDSGl78C6LePY6A3EptBf0BKcjWWH/GqyJqoxKnMufEVBLJxAJwJ7U9sDH3fX0CxnIB4orMyuilTyRh6g+0bfDgQ2p/YHrrQD+jV6qb/SdRrMit8TRWwyEabxgFwJrQnsQt0rR+LR3VqFNiGHRBv73ge9EZiM+haPyicuRcVmIxzAMTMl9wfi3sGtBuxF3ShH92C29yJTteU+ohbRrZk3Qe9hdgY+tYPnOqanA+mTN33ZGKE06HQbsQu0LV+MF8+94rU5dK8B8/KyJaI+6D9ie2hC/3Q12QnSnYLn5p9P/6d0D7EltAh5wz/Hv8exON/LUjHOuz5CksAAAAASUVORK5CYII=')", + hv: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALUAAABlCAIAAACEDzXRAAAK+ElEQVR42u2d7ZLkrA2FD55N5d2q3P+tZv8A+fFu92JAXyD1mCRdXS6Pd7bnsQqrhY4EqQL4C/gL+Ekff0q/QBz/DfyC7aj5tV8YoX82V/bOp0D757844tHMpuvgoEe76q//+pExe9X7scyO3TtPjtPL3bv74Dr8cd2r/X/tu9zPp/T5ds7gtp8xfnyH0BmTJQZhh6mlR5NLNi4zm5Th709M3oyPytq5DoOjStRZRi6EUabWrtxAPgY6gBgIg/5RMBvpDH6RnsaGk/cZhX4Ix+ewcp7jMGhvYiAMevh+qQP+lL1I7q95FMsMv8689Qg+emvWVXtDlyjoHeLcEwNhlr77D9DIhcAfzNteoZALwVtodnUI4gqdY6HXiEd7IszSPzIzgKs6UhoN/noUM7gHkr8JdrSfBx1GDIRBE/6DH+TqG1qm236dB70zbhAGPZvfUuELRVrJsZ3ZSSJ1E1ix/HnQfsRAGPQwf9FETWKQd38UNQHfRrR3HrQ3MRAGPeQ/9LOuyqaa7lOBLPGuzhYXoMs6dHGADjAzEGbp2fyWYhftPORuihQyMeBSZmwZuq5DVzdoVzMDYZZ+fb/U2ZHP7VE2b1J5Y1aP+QAmq0ek28+DdiEeJYvZNR9o2n9AQc3PyjP3j0UBDrP/OAZ6h3i8Ad2EdgW6iU+rTiFQagOvR3FNEpi6PCn+OAM6gBgIg777DyZ2KrrYOs8fxawLqYuz/vJQaG9iIAx6yI9Vi1Q+Hd65l7oy++saqbxqUwnHQLsSA2HQRP0HFDk8USHIshggpvo8kpCPhvYgBsKgfxS9MGDXCbxkgA395UHQYcRAGDTtP7BKbZS6vAWN86C3iZX63Ao0HX8wcy/G66n1/SoVJmBRygAtZVTVFFcZ6hU2Q70XNPFmzoK+nx0tTcxfNLm9QiSZFPVBVRIGYNNfxG9zZpRYoKtOzVBXnq6Z2Rh/bFnaor98Nr/upL88DjqAeDO/zuovhREG6lJuj01V61N6dVF/OQPalVhZn7wC/fIfVToWs45BtQqIHybisGrGo6FjiKn+Bgdou/+w+D69y/sm/5Fl6Pws/zGlQxg0EX+IuZvxJE9OqH+pdMPRdEKg1l+gG9FsQUVRNEdp6mx0+osHMRAGzfa/8NqAn9TFSAJ2/eUAaG9ivT5nhib6X/iSSGVuL2tTepraSEv+4wDoHeIs6PvZEZqtX7dm9XKfx5tdMyT2XPOnz4XeSZ4O+txaCpWvX68B2oCuFe3j+ssedNmCrgHQUn5s19Ib+q1CWxSbFD+u3+5BV39o5WjgJVyEWZqt/wDbkye6vy+bvyusmqGWMs6AdiUGwqDZ+rGqm+KO1F/CVFEzud2Yvzwd2ptY7H9Zh1bXnxZ2Vl5n1F/azMcoDDjVnz4ROoAYCIPerl+v9NjOqlUpvqN+/fuhXYmV63/s1K/rGzOqQjj6uj2KeSO/7tT/8hRoF+KGtfMf/LpYK9D2/jlGfu68Xp4n1wv9SWH9c4+D3iG2+I9daCL+qIrEjRhbf6n0ObGla7v/9lnQAcRAGPQwfxG1AbGE7JpMBcTiMVESMPbvPxramxgIg57l103JPCpwurhUQp2x79X8Kutm96D5JBNs0HozV4EYCLM0W7+un6TvSV1PWoonbyXXY6C3+2831w/ybcZoxna2/I8w/eUp0GHEQBi0Zf1C5WqA1+tdBFVR8xe21y98NLQTsajfrkNvrH9adl21UnV2Xf/0KdA7xPk1LHT1H1vQrusnv8FfwzsTqYQnrZ/8DdCO6ydff75fotdP1i8MLmZtLoPUJbYaOa2//hToAGKlPrcCTccfenmg2kK96qm8nA3tRAyEQRP9L2stO2/e11HcdWJtbSydeZ8IHUMMhEET/S/wcX/7/g6qB/I8aFdiIAz6Hn8ou3Y0UZP0VV43lueUusiXWJH++I8C1SqoS9BKMxddOE3EH5mW+DXpsmqc3xbjrOtva6c+qrZOvALmt2K1bxJaSSL39yhGfe5lZihq7fkqQ7LEUJ0f216KJ69m8vzS6h9cPyiSuMt/FLn/ZR1anV9XJnvT7Z1jNp7wHA08dPaE9t3co4VGmKUV+3tsCF7ftLnHqdDLoydOTlTs76EhTeRX+ae89XnQfsRKfW4Feml/jwJcd/xr5vvKSslK2P4eD4L2JgbCoO37e1x3/HFsJ9Wj6KRmnAcdQAyEQbPrw4CmvljefHsU14QBv/rkJ0K7Emvyp6v7e2Rr4f2b+prdQbrdhDLT+5H9PR4BHUMMhEHT/kPP3iE3Y9u6rbp9ZSm92SlcC3SxSF3VwG0ycx6ObP/LlqXp9aVE3/fm7cAbdjFPrXR5VRV/gLXzG3pqbR20fv1TKf6oG2YeB4pifctFS1v29+iMPBq8s7a6zqYE7u/xRGhv4kBoYn0HTUl1Gqg79iSvmqYUizz0l2dBuxJr1qdbhCb6G0TY8WLHDnnVtKprz9heauW50E7EgdBL+3tMYWdv93r7jfWlvh86jBgIg5b6o9bwFaM6skXqPOg94kBoqb8SLO+I/37dv8qzsfEPW/rLGdB+xEAYtKI/e6TueMsdvLmD5f09XPWXDegSAr1JjJ440NKW9R066vcJBvDXiWlL+LD9PTagsz+0C/GAHmVpdn0YKneTBuT2R/SjWtxVsUguz74+/9OhTcRpdgPQQtcdaMX+HhT1FPmOXzaEAWzt7/FQ6GVicMTQzVxWoNX6y9vZdXcAckiLqmJVLMy5tz7dc6FdiQOhJf2Fj50w4N9toNl1Qi8creovz4IOINbkTxehjft7tMgttfQoWmcD8Nzf43HQ3sSB0Pb9PTr3Bxq8GpJ5wft7oOEuwkcVabWEj+zvYSEGwqDt+3uoCt4nUdNCes81FUlxJw7ac6sMN+INS394f4/xVu7X3feb8Njf49ugHYmHRz4KmvYfplF9za+LiwB6P43nQXsQB0LT8Ueazb3QqM6F9tP3R7EqFoksPvt7HAPtSgyEQQ/6S6IDawx3MI1P6+8lB1C5VnRNS4Z6/pIGm8dAF2l5N/X8xWTmOpu/NMRAmKXv+ktqTjpTd1X3bflsC95SX+QGAlS9/dL+HiN0u9vwB6HV+Q+rmTEIRXdiIMzSs++XdEcGbfDW2mnAr6oNocWuHV3+9DBoV2IgDLrZ3yMRRxD4vDDQPIoLvGr95TDoGGIgDJr2H9NUMO7UU/y/kdPvR5HZXUKT9TX6j2Ogd4jfbuP68yFKVvv+6vP4Aw1+uiNjoL6avOrVPBQXuWlzYS/CVv9xGPQ+cTtQUu8/nKFn/S+JjrDe7Bhg093azaNYFdn0vfqxw6C9iYEwaKL+tJ0A1iETk5oKfAy1ke9RnQSPptmvxlJ/ehi0HzEQBq2oX+ez/lRtdf1t6r0k+rIEcxj0HjEQBv3qfzHdStK+TYx2/eUw6DBiq/H2+18qi8zfBCamhg4Tu/1zwdCmO/EjnkJjPj6cLU3UJ6cBvxJG7tjRN2nwKWS+TFcRnJ4KbSVOAnEU9FA/Np17TUdyndXb3++AArSeqPWXM6C9iYEw6Hv9aWKRp+LBPUzqqEGk+Hd+pPNMZ0DHEEdB0+snd7lfPq9NpH/55K3+aAnpToJ2InbBnUOnf3Vad5r9uHyRSB5vXhwE+pOgY4ijoGf9L2nwgxoXOWtVrAT78okiBDkD2pXYkbUHSf+kipO8zof43OX8aOgY4hDo9A8ef+9KVcz41678F0AnzytR0OnSxFHJ6Xd0oZvTavfnQfsRf9bS/3/9z77+A2DdeCv3ceV0AAAAAElFTkSuQmCC')", + }; + + p.padM.style.backgroundImage = bgs['cross']; + p.padM.style.backgroundRepeat = 'no-repeat'; + p.sldM.style.backgroundImage = bgs['arrow']; + p.sldM.style.backgroundRepeat = 'no-repeat'; + p.pad.style.backgroundImage = bgs[padImg]; + p.pad.style.backgroundRepeat = 'no-repeat'; + p.pad.style.backgroundPosition = '0 0'; + + // place pointers + redrawPad(); + redrawSld(); + + mxJSColor.picker.owner = THIS; + document.getElementsByTagName('body')[0].appendChild(p.boxB); + } + + function getPickerDims(o) { + var dims = [ + 2 * o.pickerInset + + 2 * o.pickerFace + + mxJSColor.images.pad[0] + + (o.slider ? 2 * o.pickerInset + 2 * mxJSColor.images.arrow[0] + mxJSColor.images.sld[0] : 0), + o.pickerClosable + ? 4 * o.pickerInset + 3 * o.pickerFace + mxJSColor.images.pad[1] + o.pickerButtonHeight + : 2 * o.pickerInset + 2 * o.pickerFace + mxJSColor.images.pad[1], + ]; + return dims; + } + + function redrawPad() { + // redraw the pad pointer + switch (modeID) { + case 0: + var yComponent = 1; + break; + case 1: + var yComponent = 2; + break; + } + var x = Math.round((THIS.hsv[0] / 6) * (mxJSColor.images.pad[0] - 1)); + var y = Math.round((1 - THIS.hsv[yComponent]) * (mxJSColor.images.pad[1] - 1)); + mxJSColor.picker.padM.style.backgroundPosition = + THIS.pickerFace + + THIS.pickerInset + + x - + Math.floor(mxJSColor.images.cross[0] / 2) + + 'px ' + + (THIS.pickerFace + THIS.pickerInset + y - Math.floor(mxJSColor.images.cross[1] / 2)) + + 'px'; + + // redraw the slider image + var seg = mxJSColor.picker.sld.childNodes; + + switch (modeID) { + case 0: + var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 1); + for (var i = 0; i < seg.length; i += 1) { + seg[i].style.backgroundColor = + 'rgb(' + + rgb[0] * (1 - i / seg.length) * 100 + + '%,' + + rgb[1] * (1 - i / seg.length) * 100 + + '%,' + + rgb[2] * (1 - i / seg.length) * 100 + + '%)'; + } + break; + case 1: + var rgb, + s, + c = [THIS.hsv[2], 0, 0]; + var i = Math.floor(THIS.hsv[0]); + var f = i % 2 ? THIS.hsv[0] - i : 1 - (THIS.hsv[0] - i); + switch (i) { + case 6: + case 0: + rgb = [0, 1, 2]; + break; + case 1: + rgb = [1, 0, 2]; + break; + case 2: + rgb = [2, 0, 1]; + break; + case 3: + rgb = [2, 1, 0]; + break; + case 4: + rgb = [1, 2, 0]; + break; + case 5: + rgb = [0, 2, 1]; + break; + } + for (var i = 0; i < seg.length; i += 1) { + s = 1 - (1 / (seg.length - 1)) * i; + c[1] = c[0] * (1 - s * f); + c[2] = c[0] * (1 - s); + seg[i].style.backgroundColor = + 'rgb(' + c[rgb[0]] * 100 + '%,' + c[rgb[1]] * 100 + '%,' + c[rgb[2]] * 100 + '%)'; + } + break; + } + } + + function redrawSld() { + // redraw the slider pointer + switch (modeID) { + case 0: + var yComponent = 2; + break; + case 1: + var yComponent = 1; + break; + } + var y = Math.round((1 - THIS.hsv[yComponent]) * (mxJSColor.images.sld[1] - 1)); + mxJSColor.picker.sldM.style.backgroundPosition = + '0 ' + (THIS.pickerFace + THIS.pickerInset + y - Math.floor(mxJSColor.images.arrow[1] / 2)) + 'px'; + } + + function isPickerOwner() { + return mxJSColor.picker && mxJSColor.picker.owner === THIS; + } + + function blurTarget() { + if (valueElement === target) { + THIS.importColor(); + } + if (THIS.pickerOnfocus) { + THIS.hidePicker(); + } + } + + function blurValue() { + if (valueElement !== target) { + THIS.importColor(); + } + } + + function setPad(e) { + var mpos = mxJSColor.getRelMousePos(e); + var x = mpos.x - THIS.pickerFace - THIS.pickerInset; + var y = mpos.y - THIS.pickerFace - THIS.pickerInset; + switch (modeID) { + case 0: + THIS.fromHSV(x * (6 / (mxJSColor.images.pad[0] - 1)), 1 - y / (mxJSColor.images.pad[1] - 1), null, leaveSld); + break; + case 1: + THIS.fromHSV(x * (6 / (mxJSColor.images.pad[0] - 1)), null, 1 - y / (mxJSColor.images.pad[1] - 1), leaveSld); + break; + } + } + + function setSld(e) { + var mpos = mxJSColor.getRelMousePos(e); + var y = mpos.y - THIS.pickerFace - THIS.pickerInset; + switch (modeID) { + case 0: + THIS.fromHSV(null, null, 1 - y / (mxJSColor.images.sld[1] - 1), leavePad); + break; + case 1: + THIS.fromHSV(null, 1 - y / (mxJSColor.images.sld[1] - 1), null, leavePad); + break; + } + } + + function dispatchImmediateChange() { + if (THIS.onImmediateChange) { + if (typeof THIS.onImmediateChange === 'string') { + eval(THIS.onImmediateChange); + } else { + THIS.onImmediateChange(THIS); + } + } + } + + var THIS = this; + var modeID = this.pickerMode.toLowerCase() === 'hvs' ? 1 : 0; + var abortBlur = false; + var valueElement = mxJSColor.fetchElement(this.valueElement), + styleElement = mxJSColor.fetchElement(this.styleElement); + var holdPad = false, + holdSld = false; + var leaveValue = 1 << 0, + leaveStyle = 1 << 1, + leavePad = 1 << 2, + leaveSld = 1 << 3; + + // target + /*mxJSColor.addEvent(target, 'focus', function() { + if(THIS.pickerOnfocus) { THIS.showPicker(); } + }); + mxJSColor.addEvent(target, 'blur', function() { + if(!abortBlur) { + window.setTimeout(function(){ abortBlur || blurTarget(); abortBlur=false; }, 0); + } else { + abortBlur = false; + } + });*/ + + // valueElement + if (valueElement) { + var updateField = function () { + THIS.fromString(valueElement.value, leaveValue); + dispatchImmediateChange(); + }; + mxJSColor.addEvent(valueElement, 'keyup', updateField); + mxJSColor.addEvent(valueElement, 'input', updateField); + mxJSColor.addEvent(valueElement, 'blur', blurValue); + valueElement.setAttribute('autocomplete', 'off'); + } + + // styleElement + if (styleElement) { + styleElement.jscStyle = { + backgroundImage: styleElement.style.backgroundImage, + backgroundColor: styleElement.style.backgroundColor, + color: styleElement.style.color, + }; + } + + // require images + switch (modeID) { + case 0: + mxJSColor.requireImage('hs.png'); + break; + case 1: + mxJSColor.requireImage('hv.png'); + break; + } + mxJSColor.requireImage('cross.gif'); + mxJSColor.requireImage('arrow.gif'); + + this.importColor(); + }, +}; + +mxJSColor.install(); + +// Sets the base path, the UI language via URL param and configures the +// supported languages to avoid 404s. The loading of all core language +// resources is disabled as all required resources are in grapheditor. +// properties. Note that in this example the loading of two resource +// files (the special bundle and the default bundle) is disabled to +// save a GET request. This requires that all resources be present in +// each properties file since only one file is loaded. +window.mxBasePath = window.mxBasePath || '/'; +window.mxLanguage = window.mxLanguage || urlParams['lang']; +window.mxLanguages = window.mxLanguages || ['de', 'se']; +!(function (t) { + if ('object' == typeof exports && 'undefined' != typeof module) module.exports = t(); + else if ('function' == typeof define && define.amd) define([], t); + else { + ('undefined' != typeof window + ? window + : 'undefined' != typeof global + ? global + : 'undefined' != typeof self + ? self + : this + ).pako = t(); + } +})(function () { + return (function r(s, o, l) { + function h(e, t) { + if (!o[e]) { + if (!s[e]) { + var a = 'function' == typeof require && require; + if (!t && a) return a(e, !0); + if (d) return d(e, !0); + var i = new Error("Cannot find module '" + e + "'"); + throw ((i.code = 'MODULE_NOT_FOUND'), i); + } + var n = (o[e] = { exports: {} }); + s[e][0].call( + n.exports, + function (t) { + return h(s[e][1][t] || t); + }, + n, + n.exports, + r, + s, + o, + l + ); + } + return o[e].exports; + } + for (var d = 'function' == typeof require && require, t = 0; t < l.length; t++) h(l[t]); + return h; + })( + { + '1': [ + function (t, e, a) { + 'use strict'; + var s = t('./zlib/deflate'), + o = t('./utils/common'), + l = t('./utils/strings'), + n = t('./zlib/messages'), + r = t('./zlib/zstream'), + h = Object.prototype.toString, + d = 0, + f = -1, + _ = 0, + u = 8; + function c(t) { + if (!(this instanceof c)) return new c(t); + this.options = o.assign( + { level: f, method: u, chunkSize: 16384, windowBits: 15, memLevel: 8, strategy: _, to: '' }, + t || {} + ); + var e = this.options; + e.raw && 0 < e.windowBits + ? (e.windowBits = -e.windowBits) + : e.gzip && 0 < e.windowBits && e.windowBits < 16 && (e.windowBits += 16), + (this.err = 0), + (this.msg = ''), + (this.ended = !1), + (this.chunks = []), + (this.strm = new r()), + (this.strm.avail_out = 0); + var a = s.deflateInit2(this.strm, e.level, e.method, e.windowBits, e.memLevel, e.strategy); + if (a !== d) throw new Error(n[a]); + if ((e.header && s.deflateSetHeader(this.strm, e.header), e.dictionary)) { + var i; + if ( + ((i = + 'string' == typeof e.dictionary + ? l.string2buf(e.dictionary) + : '[object ArrayBuffer]' === h.call(e.dictionary) + ? new Uint8Array(e.dictionary) + : e.dictionary), + (a = s.deflateSetDictionary(this.strm, i)) !== d) + ) + throw new Error(n[a]); + this._dict_set = !0; + } + } + function i(t, e) { + var a = new c(e); + if ((a.push(t, !0), a.err)) throw a.msg || n[a.err]; + return a.result; + } + (c.prototype.push = function (t, e) { + var a, + i, + n = this.strm, + r = this.options.chunkSize; + if (this.ended) return !1; + (i = e === ~~e ? e : !0 === e ? 4 : 0), + 'string' == typeof t + ? (n.input = l.string2buf(t)) + : '[object ArrayBuffer]' === h.call(t) + ? (n.input = new Uint8Array(t)) + : (n.input = t), + (n.next_in = 0), + (n.avail_in = n.input.length); + do { + if ( + (0 === n.avail_out && ((n.output = new o.Buf8(r)), (n.next_out = 0), (n.avail_out = r)), + 1 !== (a = s.deflate(n, i)) && a !== d) + ) + return this.onEnd(a), !(this.ended = !0); + (0 !== n.avail_out && (0 !== n.avail_in || (4 !== i && 2 !== i))) || + ('string' === this.options.to + ? this.onData(l.buf2binstring(o.shrinkBuf(n.output, n.next_out))) + : this.onData(o.shrinkBuf(n.output, n.next_out))); + } while ((0 < n.avail_in || 0 === n.avail_out) && 1 !== a); + return 4 === i + ? ((a = s.deflateEnd(this.strm)), this.onEnd(a), (this.ended = !0), a === d) + : 2 !== i || (this.onEnd(d), !(n.avail_out = 0)); + }), + (c.prototype.onData = function (t) { + this.chunks.push(t); + }), + (c.prototype.onEnd = function (t) { + t === d && + ('string' === this.options.to + ? (this.result = this.chunks.join('')) + : (this.result = o.flattenChunks(this.chunks))), + (this.chunks = []), + (this.err = t), + (this.msg = this.strm.msg); + }), + (a.Deflate = c), + (a.deflate = i), + (a.deflateRaw = function (t, e) { + return ((e = e || {}).raw = !0), i(t, e); + }), + (a.gzip = function (t, e) { + return ((e = e || {}).gzip = !0), i(t, e); + }); + }, + { './utils/common': 3, './utils/strings': 4, './zlib/deflate': 8, './zlib/messages': 13, './zlib/zstream': 15 }, + ], + '2': [ + function (t, e, a) { + 'use strict'; + var f = t('./zlib/inflate'), + _ = t('./utils/common'), + u = t('./utils/strings'), + c = t('./zlib/constants'), + i = t('./zlib/messages'), + n = t('./zlib/zstream'), + r = t('./zlib/gzheader'), + b = Object.prototype.toString; + function s(t) { + if (!(this instanceof s)) return new s(t); + this.options = _.assign({ chunkSize: 16384, windowBits: 0, to: '' }, t || {}); + var e = this.options; + e.raw && + 0 <= e.windowBits && + e.windowBits < 16 && + ((e.windowBits = -e.windowBits), 0 === e.windowBits && (e.windowBits = -15)), + !(0 <= e.windowBits && e.windowBits < 16) || (t && t.windowBits) || (e.windowBits += 32), + 15 < e.windowBits && e.windowBits < 48 && 0 == (15 & e.windowBits) && (e.windowBits |= 15), + (this.err = 0), + (this.msg = ''), + (this.ended = !1), + (this.chunks = []), + (this.strm = new n()), + (this.strm.avail_out = 0); + var a = f.inflateInit2(this.strm, e.windowBits); + if (a !== c.Z_OK) throw new Error(i[a]); + if ( + ((this.header = new r()), + f.inflateGetHeader(this.strm, this.header), + e.dictionary && + ('string' == typeof e.dictionary + ? (e.dictionary = u.string2buf(e.dictionary)) + : '[object ArrayBuffer]' === b.call(e.dictionary) && (e.dictionary = new Uint8Array(e.dictionary)), + e.raw && (a = f.inflateSetDictionary(this.strm, e.dictionary)) !== c.Z_OK)) + ) + throw new Error(i[a]); + } + function o(t, e) { + var a = new s(e); + if ((a.push(t, !0), a.err)) throw a.msg || i[a.err]; + return a.result; + } + (s.prototype.push = function (t, e) { + var a, + i, + n, + r, + s, + o = this.strm, + l = this.options.chunkSize, + h = this.options.dictionary, + d = !1; + if (this.ended) return !1; + (i = e === ~~e ? e : !0 === e ? c.Z_FINISH : c.Z_NO_FLUSH), + 'string' == typeof t + ? (o.input = u.binstring2buf(t)) + : '[object ArrayBuffer]' === b.call(t) + ? (o.input = new Uint8Array(t)) + : (o.input = t), + (o.next_in = 0), + (o.avail_in = o.input.length); + do { + if ( + (0 === o.avail_out && ((o.output = new _.Buf8(l)), (o.next_out = 0), (o.avail_out = l)), + (a = f.inflate(o, c.Z_NO_FLUSH)) === c.Z_NEED_DICT && h && (a = f.inflateSetDictionary(this.strm, h)), + a === c.Z_BUF_ERROR && !0 === d && ((a = c.Z_OK), (d = !1)), + a !== c.Z_STREAM_END && a !== c.Z_OK) + ) + return this.onEnd(a), !(this.ended = !0); + o.next_out && + ((0 !== o.avail_out && + a !== c.Z_STREAM_END && + (0 !== o.avail_in || (i !== c.Z_FINISH && i !== c.Z_SYNC_FLUSH))) || + ('string' === this.options.to + ? ((n = u.utf8border(o.output, o.next_out)), + (r = o.next_out - n), + (s = u.buf2string(o.output, n)), + (o.next_out = r), + (o.avail_out = l - r), + r && _.arraySet(o.output, o.output, n, r, 0), + this.onData(s)) + : this.onData(_.shrinkBuf(o.output, o.next_out)))), + 0 === o.avail_in && 0 === o.avail_out && (d = !0); + } while ((0 < o.avail_in || 0 === o.avail_out) && a !== c.Z_STREAM_END); + return ( + a === c.Z_STREAM_END && (i = c.Z_FINISH), + i === c.Z_FINISH + ? ((a = f.inflateEnd(this.strm)), this.onEnd(a), (this.ended = !0), a === c.Z_OK) + : i !== c.Z_SYNC_FLUSH || (this.onEnd(c.Z_OK), !(o.avail_out = 0)) + ); + }), + (s.prototype.onData = function (t) { + this.chunks.push(t); + }), + (s.prototype.onEnd = function (t) { + t === c.Z_OK && + ('string' === this.options.to + ? (this.result = this.chunks.join('')) + : (this.result = _.flattenChunks(this.chunks))), + (this.chunks = []), + (this.err = t), + (this.msg = this.strm.msg); + }), + (a.Inflate = s), + (a.inflate = o), + (a.inflateRaw = function (t, e) { + return ((e = e || {}).raw = !0), o(t, e); + }), + (a.ungzip = o); + }, + { + './utils/common': 3, + './utils/strings': 4, + './zlib/constants': 6, + './zlib/gzheader': 9, + './zlib/inflate': 11, + './zlib/messages': 13, + './zlib/zstream': 15, + }, + ], + '3': [ + function (t, e, a) { + 'use strict'; + var i = + 'undefined' != typeof Uint8Array && 'undefined' != typeof Uint16Array && 'undefined' != typeof Int32Array; + (a.assign = function (t) { + for (var e, a, i = Array.prototype.slice.call(arguments, 1); i.length; ) { + var n = i.shift(); + if (n) { + if ('object' != typeof n) throw new TypeError(n + 'must be non-object'); + for (var r in n) (e = n), (a = r), Object.prototype.hasOwnProperty.call(e, a) && (t[r] = n[r]); + } + } + return t; + }), + (a.shrinkBuf = function (t, e) { + return t.length === e ? t : t.subarray ? t.subarray(0, e) : ((t.length = e), t); + }); + var n = { + arraySet: function (t, e, a, i, n) { + if (e.subarray && t.subarray) t.set(e.subarray(a, a + i), n); + else for (var r = 0; r < i; r++) t[n + r] = e[a + r]; + }, + flattenChunks: function (t) { + var e, a, i, n, r, s; + for (e = i = 0, a = t.length; e < a; e++) i += t[e].length; + for (s = new Uint8Array(i), e = n = 0, a = t.length; e < a; e++) + (r = t[e]), s.set(r, n), (n += r.length); + return s; + }, + }, + r = { + arraySet: function (t, e, a, i, n) { + for (var r = 0; r < i; r++) t[n + r] = e[a + r]; + }, + flattenChunks: function (t) { + return [].concat.apply([], t); + }, + }; + (a.setTyped = function (t) { + t + ? ((a.Buf8 = Uint8Array), (a.Buf16 = Uint16Array), (a.Buf32 = Int32Array), a.assign(a, n)) + : ((a.Buf8 = Array), (a.Buf16 = Array), (a.Buf32 = Array), a.assign(a, r)); + }), + a.setTyped(i); + }, + {}, + ], + '4': [ + function (t, e, a) { + 'use strict'; + var l = t('./common'), + n = !0, + r = !0; + try { + String.fromCharCode.apply(null, [0]); + } catch (t) { + n = !1; + } + try { + String.fromCharCode.apply(null, new Uint8Array(1)); + } catch (t) { + r = !1; + } + for (var h = new l.Buf8(256), i = 0; i < 256; i++) + h[i] = 252 <= i ? 6 : 248 <= i ? 5 : 240 <= i ? 4 : 224 <= i ? 3 : 192 <= i ? 2 : 1; + function d(t, e) { + if (e < 65534 && ((t.subarray && r) || (!t.subarray && n))) + return String.fromCharCode.apply(null, l.shrinkBuf(t, e)); + for (var a = '', i = 0; i < e; i++) a += String.fromCharCode(t[i]); + return a; + } + (h[254] = h[254] = 1), + (a.string2buf = function (t) { + var e, + a, + i, + n, + r, + s = t.length, + o = 0; + for (n = 0; n < s; n++) + 55296 == (64512 & (a = t.charCodeAt(n))) && + n + 1 < s && + 56320 == (64512 & (i = t.charCodeAt(n + 1))) && + ((a = 65536 + ((a - 55296) << 10) + (i - 56320)), n++), + (o += a < 128 ? 1 : a < 2048 ? 2 : a < 65536 ? 3 : 4); + for (e = new l.Buf8(o), n = r = 0; r < o; n++) + 55296 == (64512 & (a = t.charCodeAt(n))) && + n + 1 < s && + 56320 == (64512 & (i = t.charCodeAt(n + 1))) && + ((a = 65536 + ((a - 55296) << 10) + (i - 56320)), n++), + a < 128 + ? (e[r++] = a) + : (a < 2048 + ? (e[r++] = 192 | (a >>> 6)) + : (a < 65536 + ? (e[r++] = 224 | (a >>> 12)) + : ((e[r++] = 240 | (a >>> 18)), (e[r++] = 128 | ((a >>> 12) & 63))), + (e[r++] = 128 | ((a >>> 6) & 63))), + (e[r++] = 128 | (63 & a))); + return e; + }), + (a.buf2binstring = function (t) { + return d(t, t.length); + }), + (a.binstring2buf = function (t) { + for (var e = new l.Buf8(t.length), a = 0, i = e.length; a < i; a++) e[a] = t.charCodeAt(a); + return e; + }), + (a.buf2string = function (t, e) { + var a, + i, + n, + r, + s = e || t.length, + o = new Array(2 * s); + for (a = i = 0; a < s; ) + if ((n = t[a++]) < 128) o[i++] = n; + else if (4 < (r = h[n])) (o[i++] = 65533), (a += r - 1); + else { + for (n &= 2 === r ? 31 : 3 === r ? 15 : 7; 1 < r && a < s; ) (n = (n << 6) | (63 & t[a++])), r--; + 1 < r + ? (o[i++] = 65533) + : n < 65536 + ? (o[i++] = n) + : ((n -= 65536), (o[i++] = 55296 | ((n >> 10) & 1023)), (o[i++] = 56320 | (1023 & n))); + } + return d(o, i); + }), + (a.utf8border = function (t, e) { + var a; + for ((e = e || t.length) > t.length && (e = t.length), a = e - 1; 0 <= a && 128 == (192 & t[a]); ) a--; + return a < 0 ? e : 0 === a ? e : a + h[t[a]] > e ? a : e; + }); + }, + { './common': 3 }, + ], + '5': [ + function (t, e, a) { + 'use strict'; + e.exports = function (t, e, a, i) { + for (var n = (65535 & t) | 0, r = ((t >>> 16) & 65535) | 0, s = 0; 0 !== a; ) { + for (a -= s = 2e3 < a ? 2e3 : a; (r = (r + (n = (n + e[i++]) | 0)) | 0), --s; ); + (n %= 65521), (r %= 65521); + } + return n | (r << 16) | 0; + }; + }, + {}, + ], + '6': [ + function (t, e, a) { + 'use strict'; + e.exports = { + 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, + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_BUF_ERROR: -5, + 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, + Z_BINARY: 0, + Z_TEXT: 1, + Z_UNKNOWN: 2, + Z_DEFLATED: 8, + }; + }, + {}, + ], + '7': [ + function (t, e, a) { + 'use strict'; + var o = (function () { + for (var t, e = [], a = 0; a < 256; a++) { + t = a; + for (var i = 0; i < 8; i++) t = 1 & t ? 3988292384 ^ (t >>> 1) : t >>> 1; + e[a] = t; + } + return e; + })(); + e.exports = function (t, e, a, i) { + var n = o, + r = i + a; + t ^= -1; + for (var s = i; s < r; s++) t = (t >>> 8) ^ n[255 & (t ^ e[s])]; + return -1 ^ t; + }; + }, + {}, + ], + '8': [ + function (t, e, a) { + 'use strict'; + var l, + _ = t('../utils/common'), + h = t('./trees'), + u = t('./adler32'), + c = t('./crc32'), + i = t('./messages'), + d = 0, + f = 4, + b = 0, + g = -2, + m = -1, + w = 4, + n = 2, + p = 8, + v = 9, + r = 286, + s = 30, + o = 19, + k = 2 * r + 1, + y = 15, + x = 3, + z = 258, + B = z + x + 1, + S = 42, + E = 113, + A = 1, + Z = 2, + R = 3, + C = 4; + function N(t, e) { + return (t.msg = i[e]), e; + } + function O(t) { + return (t << 1) - (4 < t ? 9 : 0); + } + function D(t) { + for (var e = t.length; 0 <= --e; ) t[e] = 0; + } + function I(t) { + var e = t.state, + a = e.pending; + a > t.avail_out && (a = t.avail_out), + 0 !== a && + (_.arraySet(t.output, e.pending_buf, e.pending_out, a, t.next_out), + (t.next_out += a), + (e.pending_out += a), + (t.total_out += a), + (t.avail_out -= a), + (e.pending -= a), + 0 === e.pending && (e.pending_out = 0)); + } + function U(t, e) { + h._tr_flush_block(t, 0 <= t.block_start ? t.block_start : -1, t.strstart - t.block_start, e), + (t.block_start = t.strstart), + I(t.strm); + } + function T(t, e) { + t.pending_buf[t.pending++] = e; + } + function F(t, e) { + (t.pending_buf[t.pending++] = (e >>> 8) & 255), (t.pending_buf[t.pending++] = 255 & e); + } + function L(t, e) { + var a, + i, + n = t.max_chain_length, + r = t.strstart, + s = t.prev_length, + o = t.nice_match, + l = t.strstart > t.w_size - B ? t.strstart - (t.w_size - B) : 0, + h = t.window, + d = t.w_mask, + f = t.prev, + _ = t.strstart + z, + u = h[r + s - 1], + c = h[r + s]; + t.prev_length >= t.good_match && (n >>= 2), o > t.lookahead && (o = t.lookahead); + do { + if (h[(a = e) + s] === c && h[a + s - 1] === u && h[a] === h[r] && h[++a] === h[r + 1]) { + (r += 2), a++; + do {} while ( + h[++r] === h[++a] && + h[++r] === h[++a] && + h[++r] === h[++a] && + h[++r] === h[++a] && + h[++r] === h[++a] && + h[++r] === h[++a] && + h[++r] === h[++a] && + h[++r] === h[++a] && + r < _ + ); + if (((i = z - (_ - r)), (r = _ - z), s < i)) { + if (((t.match_start = e), o <= (s = i))) break; + (u = h[r + s - 1]), (c = h[r + s]); + } + } + } while ((e = f[e & d]) > l && 0 != --n); + return s <= t.lookahead ? s : t.lookahead; + } + function H(t) { + var e, + a, + i, + n, + r, + s, + o, + l, + h, + d, + f = t.w_size; + do { + if (((n = t.window_size - t.lookahead - t.strstart), t.strstart >= f + (f - B))) { + for ( + _.arraySet(t.window, t.window, f, f, 0), + t.match_start -= f, + t.strstart -= f, + t.block_start -= f, + e = a = t.hash_size; + (i = t.head[--e]), (t.head[e] = f <= i ? i - f : 0), --a; + + ); + for (e = a = f; (i = t.prev[--e]), (t.prev[e] = f <= i ? i - f : 0), --a; ); + n += f; + } + if (0 === t.strm.avail_in) break; + if ( + ((s = t.strm), + (o = t.window), + (l = t.strstart + t.lookahead), + (h = n), + (d = void 0), + (d = s.avail_in), + h < d && (d = h), + (a = + 0 === d + ? 0 + : ((s.avail_in -= d), + _.arraySet(o, s.input, s.next_in, d, l), + 1 === s.state.wrap + ? (s.adler = u(s.adler, o, d, l)) + : 2 === s.state.wrap && (s.adler = c(s.adler, o, d, l)), + (s.next_in += d), + (s.total_in += d), + d)), + (t.lookahead += a), + t.lookahead + t.insert >= x) + ) + for ( + r = t.strstart - t.insert, + t.ins_h = t.window[r], + t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[r + 1]) & t.hash_mask; + t.insert && + ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[r + x - 1]) & t.hash_mask), + (t.prev[r & t.w_mask] = t.head[t.ins_h]), + (t.head[t.ins_h] = r), + r++, + t.insert--, + !(t.lookahead + t.insert < x)); + + ); + } while (t.lookahead < B && 0 !== t.strm.avail_in); + } + function j(t, e) { + for (var a, i; ; ) { + if (t.lookahead < B) { + if ((H(t), t.lookahead < B && e === d)) return A; + if (0 === t.lookahead) break; + } + if ( + ((a = 0), + t.lookahead >= x && + ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask), + (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart)), + 0 !== a && t.strstart - a <= t.w_size - B && (t.match_length = L(t, a)), + t.match_length >= x) + ) + if ( + ((i = h._tr_tally(t, t.strstart - t.match_start, t.match_length - x)), + (t.lookahead -= t.match_length), + t.match_length <= t.max_lazy_match && t.lookahead >= x) + ) { + for ( + t.match_length--; + t.strstart++, + (t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask), + (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart), + 0 != --t.match_length; + + ); + t.strstart++; + } else + (t.strstart += t.match_length), + (t.match_length = 0), + (t.ins_h = t.window[t.strstart]), + (t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + 1]) & t.hash_mask); + else (i = h._tr_tally(t, 0, t.window[t.strstart])), t.lookahead--, t.strstart++; + if (i && (U(t, !1), 0 === t.strm.avail_out)) return A; + } + return ( + (t.insert = t.strstart < x - 1 ? t.strstart : x - 1), + e === f + ? (U(t, !0), 0 === t.strm.avail_out ? R : C) + : t.last_lit && (U(t, !1), 0 === t.strm.avail_out) + ? A + : Z + ); + } + function K(t, e) { + for (var a, i, n; ; ) { + if (t.lookahead < B) { + if ((H(t), t.lookahead < B && e === d)) return A; + if (0 === t.lookahead) break; + } + if ( + ((a = 0), + t.lookahead >= x && + ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask), + (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart)), + (t.prev_length = t.match_length), + (t.prev_match = t.match_start), + (t.match_length = x - 1), + 0 !== a && + t.prev_length < t.max_lazy_match && + t.strstart - a <= t.w_size - B && + ((t.match_length = L(t, a)), + t.match_length <= 5 && + (1 === t.strategy || (t.match_length === x && 4096 < t.strstart - t.match_start)) && + (t.match_length = x - 1)), + t.prev_length >= x && t.match_length <= t.prev_length) + ) { + for ( + n = t.strstart + t.lookahead - x, + i = h._tr_tally(t, t.strstart - 1 - t.prev_match, t.prev_length - x), + t.lookahead -= t.prev_length - 1, + t.prev_length -= 2; + ++t.strstart <= n && + ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask), + (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart)), + 0 != --t.prev_length; + + ); + if ( + ((t.match_available = 0), + (t.match_length = x - 1), + t.strstart++, + i && (U(t, !1), 0 === t.strm.avail_out)) + ) + return A; + } else if (t.match_available) { + if ( + ((i = h._tr_tally(t, 0, t.window[t.strstart - 1])) && U(t, !1), + t.strstart++, + t.lookahead--, + 0 === t.strm.avail_out) + ) + return A; + } else (t.match_available = 1), t.strstart++, t.lookahead--; + } + return ( + t.match_available && ((i = h._tr_tally(t, 0, t.window[t.strstart - 1])), (t.match_available = 0)), + (t.insert = t.strstart < x - 1 ? t.strstart : x - 1), + e === f + ? (U(t, !0), 0 === t.strm.avail_out ? R : C) + : t.last_lit && (U(t, !1), 0 === t.strm.avail_out) + ? A + : Z + ); + } + function M(t, e, a, i, n) { + (this.good_length = t), (this.max_lazy = e), (this.nice_length = a), (this.max_chain = i), (this.func = n); + } + function P() { + (this.strm = null), + (this.status = 0), + (this.pending_buf = null), + (this.pending_buf_size = 0), + (this.pending_out = 0), + (this.pending = 0), + (this.wrap = 0), + (this.gzhead = null), + (this.gzindex = 0), + (this.method = p), + (this.last_flush = -1), + (this.w_size = 0), + (this.w_bits = 0), + (this.w_mask = 0), + (this.window = null), + (this.window_size = 0), + (this.prev = null), + (this.head = null), + (this.ins_h = 0), + (this.hash_size = 0), + (this.hash_bits = 0), + (this.hash_mask = 0), + (this.hash_shift = 0), + (this.block_start = 0), + (this.match_length = 0), + (this.prev_match = 0), + (this.match_available = 0), + (this.strstart = 0), + (this.match_start = 0), + (this.lookahead = 0), + (this.prev_length = 0), + (this.max_chain_length = 0), + (this.max_lazy_match = 0), + (this.level = 0), + (this.strategy = 0), + (this.good_match = 0), + (this.nice_match = 0), + (this.dyn_ltree = new _.Buf16(2 * k)), + (this.dyn_dtree = new _.Buf16(2 * (2 * s + 1))), + (this.bl_tree = new _.Buf16(2 * (2 * o + 1))), + D(this.dyn_ltree), + D(this.dyn_dtree), + D(this.bl_tree), + (this.l_desc = null), + (this.d_desc = null), + (this.bl_desc = null), + (this.bl_count = new _.Buf16(y + 1)), + (this.heap = new _.Buf16(2 * r + 1)), + D(this.heap), + (this.heap_len = 0), + (this.heap_max = 0), + (this.depth = new _.Buf16(2 * r + 1)), + D(this.depth), + (this.l_buf = 0), + (this.lit_bufsize = 0), + (this.last_lit = 0), + (this.d_buf = 0), + (this.opt_len = 0), + (this.static_len = 0), + (this.matches = 0), + (this.insert = 0), + (this.bi_buf = 0), + (this.bi_valid = 0); + } + function Y(t) { + var e; + return t && t.state + ? ((t.total_in = t.total_out = 0), + (t.data_type = n), + ((e = t.state).pending = 0), + (e.pending_out = 0), + e.wrap < 0 && (e.wrap = -e.wrap), + (e.status = e.wrap ? S : E), + (t.adler = 2 === e.wrap ? 0 : 1), + (e.last_flush = d), + h._tr_init(e), + b) + : N(t, g); + } + function q(t) { + var e, + a = Y(t); + return ( + a === b && + (((e = t.state).window_size = 2 * e.w_size), + D(e.head), + (e.max_lazy_match = l[e.level].max_lazy), + (e.good_match = l[e.level].good_length), + (e.nice_match = l[e.level].nice_length), + (e.max_chain_length = l[e.level].max_chain), + (e.strstart = 0), + (e.block_start = 0), + (e.lookahead = 0), + (e.insert = 0), + (e.match_length = e.prev_length = x - 1), + (e.match_available = 0), + (e.ins_h = 0)), + a + ); + } + function G(t, e, a, i, n, r) { + if (!t) return g; + var s = 1; + if ( + (e === m && (e = 6), + i < 0 ? ((s = 0), (i = -i)) : 15 < i && ((s = 2), (i -= 16)), + n < 1 || v < n || a !== p || i < 8 || 15 < i || e < 0 || 9 < e || r < 0 || w < r) + ) + return N(t, g); + 8 === i && (i = 9); + var o = new P(); + return ( + ((t.state = o).strm = t), + (o.wrap = s), + (o.gzhead = null), + (o.w_bits = i), + (o.w_size = 1 << o.w_bits), + (o.w_mask = o.w_size - 1), + (o.hash_bits = n + 7), + (o.hash_size = 1 << o.hash_bits), + (o.hash_mask = o.hash_size - 1), + (o.hash_shift = ~~((o.hash_bits + x - 1) / x)), + (o.window = new _.Buf8(2 * o.w_size)), + (o.head = new _.Buf16(o.hash_size)), + (o.prev = new _.Buf16(o.w_size)), + (o.lit_bufsize = 1 << (n + 6)), + (o.pending_buf_size = 4 * o.lit_bufsize), + (o.pending_buf = new _.Buf8(o.pending_buf_size)), + (o.d_buf = 1 * o.lit_bufsize), + (o.l_buf = 3 * o.lit_bufsize), + (o.level = e), + (o.strategy = r), + (o.method = a), + q(t) + ); + } + (l = [ + new M(0, 0, 0, 0, function (t, e) { + var a = 65535; + for (a > t.pending_buf_size - 5 && (a = t.pending_buf_size - 5); ; ) { + if (t.lookahead <= 1) { + if ((H(t), 0 === t.lookahead && e === d)) return A; + if (0 === t.lookahead) break; + } + (t.strstart += t.lookahead), (t.lookahead = 0); + var i = t.block_start + a; + if ( + (0 === t.strstart || t.strstart >= i) && + ((t.lookahead = t.strstart - i), (t.strstart = i), U(t, !1), 0 === t.strm.avail_out) + ) + return A; + if (t.strstart - t.block_start >= t.w_size - B && (U(t, !1), 0 === t.strm.avail_out)) return A; + } + return ( + (t.insert = 0), + e === f + ? (U(t, !0), 0 === t.strm.avail_out ? R : C) + : (t.strstart > t.block_start && (U(t, !1), t.strm.avail_out), A) + ); + }), + new M(4, 4, 8, 4, j), + new M(4, 5, 16, 8, j), + new M(4, 6, 32, 32, j), + new M(4, 4, 16, 16, K), + new M(8, 16, 32, 32, K), + new M(8, 16, 128, 128, K), + new M(8, 32, 128, 256, K), + new M(32, 128, 258, 1024, K), + new M(32, 258, 258, 4096, K), + ]), + (a.deflateInit = function (t, e) { + return G(t, e, p, 15, 8, 0); + }), + (a.deflateInit2 = G), + (a.deflateReset = q), + (a.deflateResetKeep = Y), + (a.deflateSetHeader = function (t, e) { + return t && t.state ? (2 !== t.state.wrap ? g : ((t.state.gzhead = e), b)) : g; + }), + (a.deflate = function (t, e) { + var a, i, n, r; + if (!t || !t.state || 5 < e || e < 0) return t ? N(t, g) : g; + if (((i = t.state), !t.output || (!t.input && 0 !== t.avail_in) || (666 === i.status && e !== f))) + return N(t, 0 === t.avail_out ? -5 : g); + if (((i.strm = t), (a = i.last_flush), (i.last_flush = e), i.status === S)) + if (2 === i.wrap) + (t.adler = 0), + T(i, 31), + T(i, 139), + T(i, 8), + i.gzhead + ? (T( + i, + (i.gzhead.text ? 1 : 0) + + (i.gzhead.hcrc ? 2 : 0) + + (i.gzhead.extra ? 4 : 0) + + (i.gzhead.name ? 8 : 0) + + (i.gzhead.comment ? 16 : 0) + ), + T(i, 255 & i.gzhead.time), + T(i, (i.gzhead.time >> 8) & 255), + T(i, (i.gzhead.time >> 16) & 255), + T(i, (i.gzhead.time >> 24) & 255), + T(i, 9 === i.level ? 2 : 2 <= i.strategy || i.level < 2 ? 4 : 0), + T(i, 255 & i.gzhead.os), + i.gzhead.extra && + i.gzhead.extra.length && + (T(i, 255 & i.gzhead.extra.length), T(i, (i.gzhead.extra.length >> 8) & 255)), + i.gzhead.hcrc && (t.adler = c(t.adler, i.pending_buf, i.pending, 0)), + (i.gzindex = 0), + (i.status = 69)) + : (T(i, 0), + T(i, 0), + T(i, 0), + T(i, 0), + T(i, 0), + T(i, 9 === i.level ? 2 : 2 <= i.strategy || i.level < 2 ? 4 : 0), + T(i, 3), + (i.status = E)); + else { + var s = (p + ((i.w_bits - 8) << 4)) << 8; + (s |= (2 <= i.strategy || i.level < 2 ? 0 : i.level < 6 ? 1 : 6 === i.level ? 2 : 3) << 6), + 0 !== i.strstart && (s |= 32), + (s += 31 - (s % 31)), + (i.status = E), + F(i, s), + 0 !== i.strstart && (F(i, t.adler >>> 16), F(i, 65535 & t.adler)), + (t.adler = 1); + } + if (69 === i.status) + if (i.gzhead.extra) { + for ( + n = i.pending; + i.gzindex < (65535 & i.gzhead.extra.length) && + (i.pending !== i.pending_buf_size || + (i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)), + I(t), + (n = i.pending), + i.pending !== i.pending_buf_size)); + + ) + T(i, 255 & i.gzhead.extra[i.gzindex]), i.gzindex++; + i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)), + i.gzindex === i.gzhead.extra.length && ((i.gzindex = 0), (i.status = 73)); + } else i.status = 73; + if (73 === i.status) + if (i.gzhead.name) { + n = i.pending; + do { + if ( + i.pending === i.pending_buf_size && + (i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)), + I(t), + (n = i.pending), + i.pending === i.pending_buf_size) + ) { + r = 1; + break; + } + T(i, (r = i.gzindex < i.gzhead.name.length ? 255 & i.gzhead.name.charCodeAt(i.gzindex++) : 0)); + } while (0 !== r); + i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)), + 0 === r && ((i.gzindex = 0), (i.status = 91)); + } else i.status = 91; + if (91 === i.status) + if (i.gzhead.comment) { + n = i.pending; + do { + if ( + i.pending === i.pending_buf_size && + (i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)), + I(t), + (n = i.pending), + i.pending === i.pending_buf_size) + ) { + r = 1; + break; + } + T( + i, + (r = i.gzindex < i.gzhead.comment.length ? 255 & i.gzhead.comment.charCodeAt(i.gzindex++) : 0) + ); + } while (0 !== r); + i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)), + 0 === r && (i.status = 103); + } else i.status = 103; + if ( + (103 === i.status && + (i.gzhead.hcrc + ? (i.pending + 2 > i.pending_buf_size && I(t), + i.pending + 2 <= i.pending_buf_size && + (T(i, 255 & t.adler), T(i, (t.adler >> 8) & 255), (t.adler = 0), (i.status = E))) + : (i.status = E)), + 0 !== i.pending) + ) { + if ((I(t), 0 === t.avail_out)) return (i.last_flush = -1), b; + } else if (0 === t.avail_in && O(e) <= O(a) && e !== f) return N(t, -5); + if (666 === i.status && 0 !== t.avail_in) return N(t, -5); + if (0 !== t.avail_in || 0 !== i.lookahead || (e !== d && 666 !== i.status)) { + var o = + 2 === i.strategy + ? (function (t, e) { + for (var a; ; ) { + if (0 === t.lookahead && (H(t), 0 === t.lookahead)) { + if (e === d) return A; + break; + } + if ( + ((t.match_length = 0), + (a = h._tr_tally(t, 0, t.window[t.strstart])), + t.lookahead--, + t.strstart++, + a && (U(t, !1), 0 === t.strm.avail_out)) + ) + return A; + } + return ( + (t.insert = 0), + e === f + ? (U(t, !0), 0 === t.strm.avail_out ? R : C) + : t.last_lit && (U(t, !1), 0 === t.strm.avail_out) + ? A + : Z + ); + })(i, e) + : 3 === i.strategy + ? (function (t, e) { + for (var a, i, n, r, s = t.window; ; ) { + if (t.lookahead <= z) { + if ((H(t), t.lookahead <= z && e === d)) return A; + if (0 === t.lookahead) break; + } + if ( + ((t.match_length = 0), + t.lookahead >= x && + 0 < t.strstart && + (i = s[(n = t.strstart - 1)]) === s[++n] && + i === s[++n] && + i === s[++n]) + ) { + r = t.strstart + z; + do {} while ( + i === s[++n] && + i === s[++n] && + i === s[++n] && + i === s[++n] && + i === s[++n] && + i === s[++n] && + i === s[++n] && + i === s[++n] && + n < r + ); + (t.match_length = z - (r - n)), + t.match_length > t.lookahead && (t.match_length = t.lookahead); + } + if ( + (t.match_length >= x + ? ((a = h._tr_tally(t, 1, t.match_length - x)), + (t.lookahead -= t.match_length), + (t.strstart += t.match_length), + (t.match_length = 0)) + : ((a = h._tr_tally(t, 0, t.window[t.strstart])), t.lookahead--, t.strstart++), + a && (U(t, !1), 0 === t.strm.avail_out)) + ) + return A; + } + return ( + (t.insert = 0), + e === f + ? (U(t, !0), 0 === t.strm.avail_out ? R : C) + : t.last_lit && (U(t, !1), 0 === t.strm.avail_out) + ? A + : Z + ); + })(i, e) + : l[i.level].func(i, e); + if (((o !== R && o !== C) || (i.status = 666), o === A || o === R)) + return 0 === t.avail_out && (i.last_flush = -1), b; + if ( + o === Z && + (1 === e + ? h._tr_align(i) + : 5 !== e && + (h._tr_stored_block(i, 0, 0, !1), + 3 === e && + (D(i.head), 0 === i.lookahead && ((i.strstart = 0), (i.block_start = 0), (i.insert = 0)))), + I(t), + 0 === t.avail_out) + ) + return (i.last_flush = -1), b; + } + return e !== f + ? b + : i.wrap <= 0 + ? 1 + : (2 === i.wrap + ? (T(i, 255 & t.adler), + T(i, (t.adler >> 8) & 255), + T(i, (t.adler >> 16) & 255), + T(i, (t.adler >> 24) & 255), + T(i, 255 & t.total_in), + T(i, (t.total_in >> 8) & 255), + T(i, (t.total_in >> 16) & 255), + T(i, (t.total_in >> 24) & 255)) + : (F(i, t.adler >>> 16), F(i, 65535 & t.adler)), + I(t), + 0 < i.wrap && (i.wrap = -i.wrap), + 0 !== i.pending ? b : 1); + }), + (a.deflateEnd = function (t) { + var e; + return t && t.state + ? (e = t.state.status) !== S && 69 !== e && 73 !== e && 91 !== e && 103 !== e && e !== E && 666 !== e + ? N(t, g) + : ((t.state = null), e === E ? N(t, -3) : b) + : g; + }), + (a.deflateSetDictionary = function (t, e) { + var a, + i, + n, + r, + s, + o, + l, + h, + d = e.length; + if (!t || !t.state) return g; + if (2 === (r = (a = t.state).wrap) || (1 === r && a.status !== S) || a.lookahead) return g; + for ( + 1 === r && (t.adler = u(t.adler, e, d, 0)), + a.wrap = 0, + d >= a.w_size && + (0 === r && (D(a.head), (a.strstart = 0), (a.block_start = 0), (a.insert = 0)), + (h = new _.Buf8(a.w_size)), + _.arraySet(h, e, d - a.w_size, a.w_size, 0), + (e = h), + (d = a.w_size)), + s = t.avail_in, + o = t.next_in, + l = t.input, + t.avail_in = d, + t.next_in = 0, + t.input = e, + H(a); + a.lookahead >= x; + + ) { + for ( + i = a.strstart, n = a.lookahead - (x - 1); + (a.ins_h = ((a.ins_h << a.hash_shift) ^ a.window[i + x - 1]) & a.hash_mask), + (a.prev[i & a.w_mask] = a.head[a.ins_h]), + (a.head[a.ins_h] = i), + i++, + --n; + + ); + (a.strstart = i), (a.lookahead = x - 1), H(a); + } + return ( + (a.strstart += a.lookahead), + (a.block_start = a.strstart), + (a.insert = a.lookahead), + (a.lookahead = 0), + (a.match_length = a.prev_length = x - 1), + (a.match_available = 0), + (t.next_in = o), + (t.input = l), + (t.avail_in = s), + (a.wrap = r), + b + ); + }), + (a.deflateInfo = 'pako deflate (from Nodeca project)'); + }, + { '../utils/common': 3, './adler32': 5, './crc32': 7, './messages': 13, './trees': 14 }, + ], + '9': [ + function (t, e, a) { + 'use strict'; + e.exports = function () { + (this.text = 0), + (this.time = 0), + (this.xflags = 0), + (this.os = 0), + (this.extra = null), + (this.extra_len = 0), + (this.name = ''), + (this.comment = ''), + (this.hcrc = 0), + (this.done = !1); + }; + }, + {}, + ], + '10': [ + function (t, e, a) { + 'use strict'; + e.exports = function (t, e) { + var a, i, n, r, s, o, l, h, d, f, _, u, c, b, g, m, w, p, v, k, y, x, z, B, S; + (a = t.state), + (i = t.next_in), + (B = t.input), + (n = i + (t.avail_in - 5)), + (r = t.next_out), + (S = t.output), + (s = r - (e - t.avail_out)), + (o = r + (t.avail_out - 257)), + (l = a.dmax), + (h = a.wsize), + (d = a.whave), + (f = a.wnext), + (_ = a.window), + (u = a.hold), + (c = a.bits), + (b = a.lencode), + (g = a.distcode), + (m = (1 << a.lenbits) - 1), + (w = (1 << a.distbits) - 1); + t: do { + c < 15 && ((u += B[i++] << c), (c += 8), (u += B[i++] << c), (c += 8)), (p = b[u & m]); + e: for (;;) { + if (((u >>>= v = p >>> 24), (c -= v), 0 === (v = (p >>> 16) & 255))) S[r++] = 65535 & p; + else { + if (!(16 & v)) { + if (0 == (64 & v)) { + p = b[(65535 & p) + (u & ((1 << v) - 1))]; + continue e; + } + if (32 & v) { + a.mode = 12; + break t; + } + (t.msg = 'invalid literal/length code'), (a.mode = 30); + break t; + } + (k = 65535 & p), + (v &= 15) && + (c < v && ((u += B[i++] << c), (c += 8)), (k += u & ((1 << v) - 1)), (u >>>= v), (c -= v)), + c < 15 && ((u += B[i++] << c), (c += 8), (u += B[i++] << c), (c += 8)), + (p = g[u & w]); + a: for (;;) { + if (((u >>>= v = p >>> 24), (c -= v), !(16 & (v = (p >>> 16) & 255)))) { + if (0 == (64 & v)) { + p = g[(65535 & p) + (u & ((1 << v) - 1))]; + continue a; + } + (t.msg = 'invalid distance code'), (a.mode = 30); + break t; + } + if ( + ((y = 65535 & p), + c < (v &= 15) && ((u += B[i++] << c), (c += 8) < v && ((u += B[i++] << c), (c += 8))), + l < (y += u & ((1 << v) - 1))) + ) { + (t.msg = 'invalid distance too far back'), (a.mode = 30); + break t; + } + if (((u >>>= v), (c -= v), (v = r - s) < y)) { + if (d < (v = y - v) && a.sane) { + (t.msg = 'invalid distance too far back'), (a.mode = 30); + break t; + } + if (((z = _), (x = 0) === f)) { + if (((x += h - v), v < k)) { + for (k -= v; (S[r++] = _[x++]), --v; ); + (x = r - y), (z = S); + } + } else if (f < v) { + if (((x += h + f - v), (v -= f) < k)) { + for (k -= v; (S[r++] = _[x++]), --v; ); + if (((x = 0), f < k)) { + for (k -= v = f; (S[r++] = _[x++]), --v; ); + (x = r - y), (z = S); + } + } + } else if (((x += f - v), v < k)) { + for (k -= v; (S[r++] = _[x++]), --v; ); + (x = r - y), (z = S); + } + for (; 2 < k; ) (S[r++] = z[x++]), (S[r++] = z[x++]), (S[r++] = z[x++]), (k -= 3); + k && ((S[r++] = z[x++]), 1 < k && (S[r++] = z[x++])); + } else { + for (x = r - y; (S[r++] = S[x++]), (S[r++] = S[x++]), (S[r++] = S[x++]), 2 < (k -= 3); ); + k && ((S[r++] = S[x++]), 1 < k && (S[r++] = S[x++])); + } + break; + } + } + break; + } + } while (i < n && r < o); + (i -= k = c >> 3), + (u &= (1 << (c -= k << 3)) - 1), + (t.next_in = i), + (t.next_out = r), + (t.avail_in = i < n ? n - i + 5 : 5 - (i - n)), + (t.avail_out = r < o ? o - r + 257 : 257 - (r - o)), + (a.hold = u), + (a.bits = c); + }; + }, + {}, + ], + '11': [ + function (t, e, a) { + 'use strict'; + var Z = t('../utils/common'), + R = t('./adler32'), + C = t('./crc32'), + N = t('./inffast'), + O = t('./inftrees'), + D = 1, + I = 2, + U = 0, + T = -2, + F = 1, + i = 852, + n = 592; + function L(t) { + return ((t >>> 24) & 255) + ((t >>> 8) & 65280) + ((65280 & t) << 8) + ((255 & t) << 24); + } + function r() { + (this.mode = 0), + (this.last = !1), + (this.wrap = 0), + (this.havedict = !1), + (this.flags = 0), + (this.dmax = 0), + (this.check = 0), + (this.total = 0), + (this.head = null), + (this.wbits = 0), + (this.wsize = 0), + (this.whave = 0), + (this.wnext = 0), + (this.window = null), + (this.hold = 0), + (this.bits = 0), + (this.length = 0), + (this.offset = 0), + (this.extra = 0), + (this.lencode = null), + (this.distcode = null), + (this.lenbits = 0), + (this.distbits = 0), + (this.ncode = 0), + (this.nlen = 0), + (this.ndist = 0), + (this.have = 0), + (this.next = null), + (this.lens = new Z.Buf16(320)), + (this.work = new Z.Buf16(288)), + (this.lendyn = null), + (this.distdyn = null), + (this.sane = 0), + (this.back = 0), + (this.was = 0); + } + function s(t) { + var e; + return t && t.state + ? ((e = t.state), + (t.total_in = t.total_out = e.total = 0), + (t.msg = ''), + e.wrap && (t.adler = 1 & e.wrap), + (e.mode = F), + (e.last = 0), + (e.havedict = 0), + (e.dmax = 32768), + (e.head = null), + (e.hold = 0), + (e.bits = 0), + (e.lencode = e.lendyn = new Z.Buf32(i)), + (e.distcode = e.distdyn = new Z.Buf32(n)), + (e.sane = 1), + (e.back = -1), + U) + : T; + } + function o(t) { + var e; + return t && t.state ? (((e = t.state).wsize = 0), (e.whave = 0), (e.wnext = 0), s(t)) : T; + } + function l(t, e) { + var a, i; + return t && t.state + ? ((i = t.state), + e < 0 ? ((a = 0), (e = -e)) : ((a = 1 + (e >> 4)), e < 48 && (e &= 15)), + e && (e < 8 || 15 < e) + ? T + : (null !== i.window && i.wbits !== e && (i.window = null), (i.wrap = a), (i.wbits = e), o(t))) + : T; + } + function h(t, e) { + var a, i; + return t ? ((i = new r()), ((t.state = i).window = null), (a = l(t, e)) !== U && (t.state = null), a) : T; + } + var d, + f, + _ = !0; + function H(t) { + if (_) { + var e; + for (d = new Z.Buf32(512), f = new Z.Buf32(32), e = 0; e < 144; ) t.lens[e++] = 8; + for (; e < 256; ) t.lens[e++] = 9; + for (; e < 280; ) t.lens[e++] = 7; + for (; e < 288; ) t.lens[e++] = 8; + for (O(D, t.lens, 0, 288, d, 0, t.work, { bits: 9 }), e = 0; e < 32; ) t.lens[e++] = 5; + O(I, t.lens, 0, 32, f, 0, t.work, { bits: 5 }), (_ = !1); + } + (t.lencode = d), (t.lenbits = 9), (t.distcode = f), (t.distbits = 5); + } + function j(t, e, a, i) { + var n, + r = t.state; + return ( + null === r.window && + ((r.wsize = 1 << r.wbits), (r.wnext = 0), (r.whave = 0), (r.window = new Z.Buf8(r.wsize))), + i >= r.wsize + ? (Z.arraySet(r.window, e, a - r.wsize, r.wsize, 0), (r.wnext = 0), (r.whave = r.wsize)) + : (i < (n = r.wsize - r.wnext) && (n = i), + Z.arraySet(r.window, e, a - i, n, r.wnext), + (i -= n) + ? (Z.arraySet(r.window, e, a - i, i, 0), (r.wnext = i), (r.whave = r.wsize)) + : ((r.wnext += n), r.wnext === r.wsize && (r.wnext = 0), r.whave < r.wsize && (r.whave += n))), + 0 + ); + } + (a.inflateReset = o), + (a.inflateReset2 = l), + (a.inflateResetKeep = s), + (a.inflateInit = function (t) { + return h(t, 15); + }), + (a.inflateInit2 = h), + (a.inflate = function (t, e) { + var a, + i, + n, + r, + s, + o, + l, + h, + d, + f, + _, + u, + c, + b, + g, + m, + w, + p, + v, + k, + y, + x, + z, + B, + S = 0, + E = new Z.Buf8(4), + A = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + if (!t || !t.state || !t.output || (!t.input && 0 !== t.avail_in)) return T; + 12 === (a = t.state).mode && (a.mode = 13), + (s = t.next_out), + (n = t.output), + (l = t.avail_out), + (r = t.next_in), + (i = t.input), + (o = t.avail_in), + (h = a.hold), + (d = a.bits), + (f = o), + (_ = l), + (x = U); + t: for (;;) + switch (a.mode) { + case F: + if (0 === a.wrap) { + a.mode = 13; + break; + } + for (; d < 16; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (2 & a.wrap && 35615 === h) { + (E[(a.check = 0)] = 255 & h), + (E[1] = (h >>> 8) & 255), + (a.check = C(a.check, E, 2, 0)), + (d = h = 0), + (a.mode = 2); + break; + } + if ( + ((a.flags = 0), a.head && (a.head.done = !1), !(1 & a.wrap) || (((255 & h) << 8) + (h >> 8)) % 31) + ) { + (t.msg = 'incorrect header check'), (a.mode = 30); + break; + } + if (8 != (15 & h)) { + (t.msg = 'unknown compression method'), (a.mode = 30); + break; + } + if (((d -= 4), (y = 8 + (15 & (h >>>= 4))), 0 === a.wbits)) a.wbits = y; + else if (y > a.wbits) { + (t.msg = 'invalid window size'), (a.mode = 30); + break; + } + (a.dmax = 1 << y), (t.adler = a.check = 1), (a.mode = 512 & h ? 10 : 12), (d = h = 0); + break; + case 2: + for (; d < 16; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (((a.flags = h), 8 != (255 & a.flags))) { + (t.msg = 'unknown compression method'), (a.mode = 30); + break; + } + if (57344 & a.flags) { + (t.msg = 'unknown header flags set'), (a.mode = 30); + break; + } + a.head && (a.head.text = (h >> 8) & 1), + 512 & a.flags && ((E[0] = 255 & h), (E[1] = (h >>> 8) & 255), (a.check = C(a.check, E, 2, 0))), + (d = h = 0), + (a.mode = 3); + case 3: + for (; d < 32; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + a.head && (a.head.time = h), + 512 & a.flags && + ((E[0] = 255 & h), + (E[1] = (h >>> 8) & 255), + (E[2] = (h >>> 16) & 255), + (E[3] = (h >>> 24) & 255), + (a.check = C(a.check, E, 4, 0))), + (d = h = 0), + (a.mode = 4); + case 4: + for (; d < 16; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + a.head && ((a.head.xflags = 255 & h), (a.head.os = h >> 8)), + 512 & a.flags && ((E[0] = 255 & h), (E[1] = (h >>> 8) & 255), (a.check = C(a.check, E, 2, 0))), + (d = h = 0), + (a.mode = 5); + case 5: + if (1024 & a.flags) { + for (; d < 16; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (a.length = h), + a.head && (a.head.extra_len = h), + 512 & a.flags && ((E[0] = 255 & h), (E[1] = (h >>> 8) & 255), (a.check = C(a.check, E, 2, 0))), + (d = h = 0); + } else a.head && (a.head.extra = null); + a.mode = 6; + case 6: + if ( + 1024 & a.flags && + (o < (u = a.length) && (u = o), + u && + (a.head && + ((y = a.head.extra_len - a.length), + a.head.extra || (a.head.extra = new Array(a.head.extra_len)), + Z.arraySet(a.head.extra, i, r, u, y)), + 512 & a.flags && (a.check = C(a.check, i, u, r)), + (o -= u), + (r += u), + (a.length -= u)), + a.length) + ) + break t; + (a.length = 0), (a.mode = 7); + case 7: + if (2048 & a.flags) { + if (0 === o) break t; + for ( + u = 0; + (y = i[r + u++]), + a.head && y && a.length < 65536 && (a.head.name += String.fromCharCode(y)), + y && u < o; + + ); + if ((512 & a.flags && (a.check = C(a.check, i, u, r)), (o -= u), (r += u), y)) break t; + } else a.head && (a.head.name = null); + (a.length = 0), (a.mode = 8); + case 8: + if (4096 & a.flags) { + if (0 === o) break t; + for ( + u = 0; + (y = i[r + u++]), + a.head && y && a.length < 65536 && (a.head.comment += String.fromCharCode(y)), + y && u < o; + + ); + if ((512 & a.flags && (a.check = C(a.check, i, u, r)), (o -= u), (r += u), y)) break t; + } else a.head && (a.head.comment = null); + a.mode = 9; + case 9: + if (512 & a.flags) { + for (; d < 16; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (h !== (65535 & a.check)) { + (t.msg = 'header crc mismatch'), (a.mode = 30); + break; + } + d = h = 0; + } + a.head && ((a.head.hcrc = (a.flags >> 9) & 1), (a.head.done = !0)), + (t.adler = a.check = 0), + (a.mode = 12); + break; + case 10: + for (; d < 32; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (t.adler = a.check = L(h)), (d = h = 0), (a.mode = 11); + case 11: + if (0 === a.havedict) + return ( + (t.next_out = s), + (t.avail_out = l), + (t.next_in = r), + (t.avail_in = o), + (a.hold = h), + (a.bits = d), + 2 + ); + (t.adler = a.check = 1), (a.mode = 12); + case 12: + if (5 === e || 6 === e) break t; + case 13: + if (a.last) { + (h >>>= 7 & d), (d -= 7 & d), (a.mode = 27); + break; + } + for (; d < 3; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + switch (((a.last = 1 & h), (d -= 1), 3 & (h >>>= 1))) { + case 0: + a.mode = 14; + break; + case 1: + if ((H(a), (a.mode = 20), 6 !== e)) break; + (h >>>= 2), (d -= 2); + break t; + case 2: + a.mode = 17; + break; + case 3: + (t.msg = 'invalid block type'), (a.mode = 30); + } + (h >>>= 2), (d -= 2); + break; + case 14: + for (h >>>= 7 & d, d -= 7 & d; d < 32; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if ((65535 & h) != ((h >>> 16) ^ 65535)) { + (t.msg = 'invalid stored block lengths'), (a.mode = 30); + break; + } + if (((a.length = 65535 & h), (d = h = 0), (a.mode = 15), 6 === e)) break t; + case 15: + a.mode = 16; + case 16: + if ((u = a.length)) { + if ((o < u && (u = o), l < u && (u = l), 0 === u)) break t; + Z.arraySet(n, i, r, u, s), (o -= u), (r += u), (l -= u), (s += u), (a.length -= u); + break; + } + a.mode = 12; + break; + case 17: + for (; d < 14; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if ( + ((a.nlen = 257 + (31 & h)), + (h >>>= 5), + (d -= 5), + (a.ndist = 1 + (31 & h)), + (h >>>= 5), + (d -= 5), + (a.ncode = 4 + (15 & h)), + (h >>>= 4), + (d -= 4), + 286 < a.nlen || 30 < a.ndist) + ) { + (t.msg = 'too many length or distance symbols'), (a.mode = 30); + break; + } + (a.have = 0), (a.mode = 18); + case 18: + for (; a.have < a.ncode; ) { + for (; d < 3; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (a.lens[A[a.have++]] = 7 & h), (h >>>= 3), (d -= 3); + } + for (; a.have < 19; ) a.lens[A[a.have++]] = 0; + if ( + ((a.lencode = a.lendyn), + (a.lenbits = 7), + (z = { bits: a.lenbits }), + (x = O(0, a.lens, 0, 19, a.lencode, 0, a.work, z)), + (a.lenbits = z.bits), + x) + ) { + (t.msg = 'invalid code lengths set'), (a.mode = 30); + break; + } + (a.have = 0), (a.mode = 19); + case 19: + for (; a.have < a.nlen + a.ndist; ) { + for ( + ; + (m = ((S = a.lencode[h & ((1 << a.lenbits) - 1)]) >>> 16) & 255), + (w = 65535 & S), + !((g = S >>> 24) <= d); + + ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (w < 16) (h >>>= g), (d -= g), (a.lens[a.have++] = w); + else { + if (16 === w) { + for (B = g + 2; d < B; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (((h >>>= g), (d -= g), 0 === a.have)) { + (t.msg = 'invalid bit length repeat'), (a.mode = 30); + break; + } + (y = a.lens[a.have - 1]), (u = 3 + (3 & h)), (h >>>= 2), (d -= 2); + } else if (17 === w) { + for (B = g + 3; d < B; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (d -= g), (y = 0), (u = 3 + (7 & (h >>>= g))), (h >>>= 3), (d -= 3); + } else { + for (B = g + 7; d < B; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (d -= g), (y = 0), (u = 11 + (127 & (h >>>= g))), (h >>>= 7), (d -= 7); + } + if (a.have + u > a.nlen + a.ndist) { + (t.msg = 'invalid bit length repeat'), (a.mode = 30); + break; + } + for (; u--; ) a.lens[a.have++] = y; + } + } + if (30 === a.mode) break; + if (0 === a.lens[256]) { + (t.msg = 'invalid code -- missing end-of-block'), (a.mode = 30); + break; + } + if ( + ((a.lenbits = 9), + (z = { bits: a.lenbits }), + (x = O(D, a.lens, 0, a.nlen, a.lencode, 0, a.work, z)), + (a.lenbits = z.bits), + x) + ) { + (t.msg = 'invalid literal/lengths set'), (a.mode = 30); + break; + } + if ( + ((a.distbits = 6), + (a.distcode = a.distdyn), + (z = { bits: a.distbits }), + (x = O(I, a.lens, a.nlen, a.ndist, a.distcode, 0, a.work, z)), + (a.distbits = z.bits), + x) + ) { + (t.msg = 'invalid distances set'), (a.mode = 30); + break; + } + if (((a.mode = 20), 6 === e)) break t; + case 20: + a.mode = 21; + case 21: + if (6 <= o && 258 <= l) { + (t.next_out = s), + (t.avail_out = l), + (t.next_in = r), + (t.avail_in = o), + (a.hold = h), + (a.bits = d), + N(t, _), + (s = t.next_out), + (n = t.output), + (l = t.avail_out), + (r = t.next_in), + (i = t.input), + (o = t.avail_in), + (h = a.hold), + (d = a.bits), + 12 === a.mode && (a.back = -1); + break; + } + for ( + a.back = 0; + (m = ((S = a.lencode[h & ((1 << a.lenbits) - 1)]) >>> 16) & 255), + (w = 65535 & S), + !((g = S >>> 24) <= d); + + ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (m && 0 == (240 & m)) { + for ( + p = g, v = m, k = w; + (m = ((S = a.lencode[k + ((h & ((1 << (p + v)) - 1)) >> p)]) >>> 16) & 255), + (w = 65535 & S), + !(p + (g = S >>> 24) <= d); + + ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (h >>>= p), (d -= p), (a.back += p); + } + if (((h >>>= g), (d -= g), (a.back += g), (a.length = w), 0 === m)) { + a.mode = 26; + break; + } + if (32 & m) { + (a.back = -1), (a.mode = 12); + break; + } + if (64 & m) { + (t.msg = 'invalid literal/length code'), (a.mode = 30); + break; + } + (a.extra = 15 & m), (a.mode = 22); + case 22: + if (a.extra) { + for (B = a.extra; d < B; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (a.length += h & ((1 << a.extra) - 1)), (h >>>= a.extra), (d -= a.extra), (a.back += a.extra); + } + (a.was = a.length), (a.mode = 23); + case 23: + for ( + ; + (m = ((S = a.distcode[h & ((1 << a.distbits) - 1)]) >>> 16) & 255), + (w = 65535 & S), + !((g = S >>> 24) <= d); + + ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (0 == (240 & m)) { + for ( + p = g, v = m, k = w; + (m = ((S = a.distcode[k + ((h & ((1 << (p + v)) - 1)) >> p)]) >>> 16) & 255), + (w = 65535 & S), + !(p + (g = S >>> 24) <= d); + + ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (h >>>= p), (d -= p), (a.back += p); + } + if (((h >>>= g), (d -= g), (a.back += g), 64 & m)) { + (t.msg = 'invalid distance code'), (a.mode = 30); + break; + } + (a.offset = w), (a.extra = 15 & m), (a.mode = 24); + case 24: + if (a.extra) { + for (B = a.extra; d < B; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + (a.offset += h & ((1 << a.extra) - 1)), (h >>>= a.extra), (d -= a.extra), (a.back += a.extra); + } + if (a.offset > a.dmax) { + (t.msg = 'invalid distance too far back'), (a.mode = 30); + break; + } + a.mode = 25; + case 25: + if (0 === l) break t; + if (((u = _ - l), a.offset > u)) { + if ((u = a.offset - u) > a.whave && a.sane) { + (t.msg = 'invalid distance too far back'), (a.mode = 30); + break; + } + u > a.wnext ? ((u -= a.wnext), (c = a.wsize - u)) : (c = a.wnext - u), + u > a.length && (u = a.length), + (b = a.window); + } else (b = n), (c = s - a.offset), (u = a.length); + for (l < u && (u = l), l -= u, a.length -= u; (n[s++] = b[c++]), --u; ); + 0 === a.length && (a.mode = 21); + break; + case 26: + if (0 === l) break t; + (n[s++] = a.length), l--, (a.mode = 21); + break; + case 27: + if (a.wrap) { + for (; d < 32; ) { + if (0 === o) break t; + o--, (h |= i[r++] << d), (d += 8); + } + if ( + ((_ -= l), + (t.total_out += _), + (a.total += _), + _ && (t.adler = a.check = a.flags ? C(a.check, n, _, s - _) : R(a.check, n, _, s - _)), + (_ = l), + (a.flags ? h : L(h)) !== a.check) + ) { + (t.msg = 'incorrect data check'), (a.mode = 30); + break; + } + d = h = 0; + } + a.mode = 28; + case 28: + if (a.wrap && a.flags) { + for (; d < 32; ) { + if (0 === o) break t; + o--, (h += i[r++] << d), (d += 8); + } + if (h !== (4294967295 & a.total)) { + (t.msg = 'incorrect length check'), (a.mode = 30); + break; + } + d = h = 0; + } + a.mode = 29; + case 29: + x = 1; + break t; + case 30: + x = -3; + break t; + case 31: + return -4; + case 32: + default: + return T; + } + return ( + (t.next_out = s), + (t.avail_out = l), + (t.next_in = r), + (t.avail_in = o), + (a.hold = h), + (a.bits = d), + (a.wsize || (_ !== t.avail_out && a.mode < 30 && (a.mode < 27 || 4 !== e))) && + j(t, t.output, t.next_out, _ - t.avail_out) + ? ((a.mode = 31), -4) + : ((f -= t.avail_in), + (_ -= t.avail_out), + (t.total_in += f), + (t.total_out += _), + (a.total += _), + a.wrap && + _ && + (t.adler = a.check = + a.flags ? C(a.check, n, _, t.next_out - _) : R(a.check, n, _, t.next_out - _)), + (t.data_type = + a.bits + + (a.last ? 64 : 0) + + (12 === a.mode ? 128 : 0) + + (20 === a.mode || 15 === a.mode ? 256 : 0)), + ((0 === f && 0 === _) || 4 === e) && x === U && (x = -5), + x) + ); + }), + (a.inflateEnd = function (t) { + if (!t || !t.state) return T; + var e = t.state; + return e.window && (e.window = null), (t.state = null), U; + }), + (a.inflateGetHeader = function (t, e) { + var a; + return t && t.state ? (0 == (2 & (a = t.state).wrap) ? T : (((a.head = e).done = !1), U)) : T; + }), + (a.inflateSetDictionary = function (t, e) { + var a, + i = e.length; + return t && t.state + ? 0 !== (a = t.state).wrap && 11 !== a.mode + ? T + : 11 === a.mode && R(1, e, i, 0) !== a.check + ? -3 + : j(t, e, i, i) + ? ((a.mode = 31), -4) + : ((a.havedict = 1), U) + : T; + }), + (a.inflateInfo = 'pako inflate (from Nodeca project)'); + }, + { '../utils/common': 3, './adler32': 5, './crc32': 7, './inffast': 10, './inftrees': 12 }, + ], + '12': [ + function (t, e, a) { + 'use strict'; + var D = t('../utils/common'), + I = [ + 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, + ], + U = [ + 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, + ], + T = [ + 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, + ], + F = [ + 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, + ]; + e.exports = function (t, e, a, i, n, r, s, o) { + var l, + h, + d, + f, + _, + u, + c, + b, + g, + m = o.bits, + w = 0, + p = 0, + v = 0, + k = 0, + y = 0, + x = 0, + z = 0, + B = 0, + S = 0, + E = 0, + A = null, + Z = 0, + R = new D.Buf16(16), + C = new D.Buf16(16), + N = null, + O = 0; + for (w = 0; w <= 15; w++) R[w] = 0; + for (p = 0; p < i; p++) R[e[a + p]]++; + for (y = m, k = 15; 1 <= k && 0 === R[k]; k--); + if ((k < y && (y = k), 0 === k)) return (n[r++] = 20971520), (n[r++] = 20971520), (o.bits = 1), 0; + for (v = 1; v < k && 0 === R[v]; v++); + for (y < v && (y = v), w = B = 1; w <= 15; w++) if (((B <<= 1), (B -= R[w]) < 0)) return -1; + if (0 < B && (0 === t || 1 !== k)) return -1; + for (C[1] = 0, w = 1; w < 15; w++) C[w + 1] = C[w] + R[w]; + for (p = 0; p < i; p++) 0 !== e[a + p] && (s[C[e[a + p]]++] = p); + if ( + (0 === t + ? ((A = N = s), (u = 19)) + : 1 === t + ? ((A = I), (Z -= 257), (N = U), (O -= 257), (u = 256)) + : ((A = T), (N = F), (u = -1)), + (w = v), + (_ = r), + (z = p = E = 0), + (d = -1), + (f = (S = 1 << (x = y)) - 1), + (1 === t && 852 < S) || (2 === t && 592 < S)) + ) + return 1; + for (;;) { + for ( + c = w - z, + s[p] < u + ? ((b = 0), (g = s[p])) + : s[p] > u + ? ((b = N[O + s[p]]), (g = A[Z + s[p]])) + : ((b = 96), (g = 0)), + l = 1 << (w - z), + v = h = 1 << x; + (n[_ + (E >> z) + (h -= l)] = (c << 24) | (b << 16) | g | 0), 0 !== h; + + ); + for (l = 1 << (w - 1); E & l; ) l >>= 1; + if ((0 !== l ? ((E &= l - 1), (E += l)) : (E = 0), p++, 0 == --R[w])) { + if (w === k) break; + w = e[a + s[p]]; + } + if (y < w && (E & f) !== d) { + for (0 === z && (z = y), _ += v, B = 1 << (x = w - z); x + z < k && !((B -= R[x + z]) <= 0); ) + x++, (B <<= 1); + if (((S += 1 << x), (1 === t && 852 < S) || (2 === t && 592 < S))) return 1; + n[(d = E & f)] = (y << 24) | (x << 16) | (_ - r) | 0; + } + } + return 0 !== E && (n[_ + E] = ((w - z) << 24) | (64 << 16) | 0), (o.bits = y), 0; + }; + }, + { '../utils/common': 3 }, + ], + '13': [ + function (t, e, a) { + 'use strict'; + e.exports = { + '2': 'need dictionary', + '1': 'stream end', + '0': '', + '-1': 'file error', + '-2': 'stream error', + '-3': 'data error', + '-4': 'insufficient memory', + '-5': 'buffer error', + '-6': 'incompatible version', + }; + }, + {}, + ], + '14': [ + function (t, e, a) { + 'use strict'; + var l = t('../utils/common'), + o = 0, + h = 1; + function i(t) { + for (var e = t.length; 0 <= --e; ) t[e] = 0; + } + var d = 0, + s = 29, + f = 256, + _ = f + 1 + s, + u = 30, + c = 19, + g = 2 * _ + 1, + m = 15, + n = 16, + b = 7, + w = 256, + p = 16, + v = 17, + k = 18, + y = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0], + x = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13], + z = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7], + B = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], + S = new Array(2 * (_ + 2)); + i(S); + var E = new Array(2 * u); + i(E); + var A = new Array(512); + i(A); + var Z = new Array(256); + i(Z); + var R = new Array(s); + i(R); + var C, + N, + O, + D = new Array(u); + function I(t, e, a, i, n) { + (this.static_tree = t), + (this.extra_bits = e), + (this.extra_base = a), + (this.elems = i), + (this.max_length = n), + (this.has_stree = t && t.length); + } + function r(t, e) { + (this.dyn_tree = t), (this.max_code = 0), (this.stat_desc = e); + } + function U(t) { + return t < 256 ? A[t] : A[256 + (t >>> 7)]; + } + function T(t, e) { + (t.pending_buf[t.pending++] = 255 & e), (t.pending_buf[t.pending++] = (e >>> 8) & 255); + } + function F(t, e, a) { + t.bi_valid > n - a + ? ((t.bi_buf |= (e << t.bi_valid) & 65535), + T(t, t.bi_buf), + (t.bi_buf = e >> (n - t.bi_valid)), + (t.bi_valid += a - n)) + : ((t.bi_buf |= (e << t.bi_valid) & 65535), (t.bi_valid += a)); + } + function L(t, e, a) { + F(t, a[2 * e], a[2 * e + 1]); + } + function H(t, e) { + for (var a = 0; (a |= 1 & t), (t >>>= 1), (a <<= 1), 0 < --e; ); + return a >>> 1; + } + function j(t, e, a) { + var i, + n, + r = new Array(m + 1), + s = 0; + for (i = 1; i <= m; i++) r[i] = s = (s + a[i - 1]) << 1; + for (n = 0; n <= e; n++) { + var o = t[2 * n + 1]; + 0 !== o && (t[2 * n] = H(r[o]++, o)); + } + } + function K(t) { + var e; + for (e = 0; e < _; e++) t.dyn_ltree[2 * e] = 0; + for (e = 0; e < u; e++) t.dyn_dtree[2 * e] = 0; + for (e = 0; e < c; e++) t.bl_tree[2 * e] = 0; + (t.dyn_ltree[2 * w] = 1), (t.opt_len = t.static_len = 0), (t.last_lit = t.matches = 0); + } + function M(t) { + 8 < t.bi_valid ? T(t, t.bi_buf) : 0 < t.bi_valid && (t.pending_buf[t.pending++] = t.bi_buf), + (t.bi_buf = 0), + (t.bi_valid = 0); + } + function P(t, e, a, i) { + var n = 2 * e, + r = 2 * a; + return t[n] < t[r] || (t[n] === t[r] && i[e] <= i[a]); + } + function Y(t, e, a) { + for ( + var i = t.heap[a], n = a << 1; + n <= t.heap_len && + (n < t.heap_len && P(e, t.heap[n + 1], t.heap[n], t.depth) && n++, !P(e, i, t.heap[n], t.depth)); + + ) + (t.heap[a] = t.heap[n]), (a = n), (n <<= 1); + t.heap[a] = i; + } + function q(t, e, a) { + var i, + n, + r, + s, + o = 0; + if (0 !== t.last_lit) + for ( + ; + (i = (t.pending_buf[t.d_buf + 2 * o] << 8) | t.pending_buf[t.d_buf + 2 * o + 1]), + (n = t.pending_buf[t.l_buf + o]), + o++, + 0 === i + ? L(t, n, e) + : (L(t, (r = Z[n]) + f + 1, e), + 0 !== (s = y[r]) && F(t, (n -= R[r]), s), + L(t, (r = U(--i)), a), + 0 !== (s = x[r]) && F(t, (i -= D[r]), s)), + o < t.last_lit; + + ); + L(t, w, e); + } + function G(t, e) { + var a, + i, + n, + r = e.dyn_tree, + s = e.stat_desc.static_tree, + o = e.stat_desc.has_stree, + l = e.stat_desc.elems, + h = -1; + for (t.heap_len = 0, t.heap_max = g, a = 0; a < l; a++) + 0 !== r[2 * a] ? ((t.heap[++t.heap_len] = h = a), (t.depth[a] = 0)) : (r[2 * a + 1] = 0); + for (; t.heap_len < 2; ) + (r[2 * (n = t.heap[++t.heap_len] = h < 2 ? ++h : 0)] = 1), + (t.depth[n] = 0), + t.opt_len--, + o && (t.static_len -= s[2 * n + 1]); + for (e.max_code = h, a = t.heap_len >> 1; 1 <= a; a--) Y(t, r, a); + for ( + n = l; + (a = t.heap[1]), + (t.heap[1] = t.heap[t.heap_len--]), + Y(t, r, 1), + (i = t.heap[1]), + (t.heap[--t.heap_max] = a), + (t.heap[--t.heap_max] = i), + (r[2 * n] = r[2 * a] + r[2 * i]), + (t.depth[n] = (t.depth[a] >= t.depth[i] ? t.depth[a] : t.depth[i]) + 1), + (r[2 * a + 1] = r[2 * i + 1] = n), + (t.heap[1] = n++), + Y(t, r, 1), + 2 <= t.heap_len; + + ); + (t.heap[--t.heap_max] = t.heap[1]), + (function (t, e) { + var a, + i, + n, + r, + s, + o, + l = e.dyn_tree, + h = e.max_code, + d = e.stat_desc.static_tree, + f = e.stat_desc.has_stree, + _ = e.stat_desc.extra_bits, + u = e.stat_desc.extra_base, + c = e.stat_desc.max_length, + b = 0; + for (r = 0; r <= m; r++) t.bl_count[r] = 0; + for (l[2 * t.heap[t.heap_max] + 1] = 0, a = t.heap_max + 1; a < g; a++) + c < (r = l[2 * l[2 * (i = t.heap[a]) + 1] + 1] + 1) && ((r = c), b++), + (l[2 * i + 1] = r), + h < i || + (t.bl_count[r]++, + (s = 0), + u <= i && (s = _[i - u]), + (o = l[2 * i]), + (t.opt_len += o * (r + s)), + f && (t.static_len += o * (d[2 * i + 1] + s))); + if (0 !== b) { + do { + for (r = c - 1; 0 === t.bl_count[r]; ) r--; + t.bl_count[r]--, (t.bl_count[r + 1] += 2), t.bl_count[c]--, (b -= 2); + } while (0 < b); + for (r = c; 0 !== r; r--) + for (i = t.bl_count[r]; 0 !== i; ) + h < (n = t.heap[--a]) || + (l[2 * n + 1] !== r && ((t.opt_len += (r - l[2 * n + 1]) * l[2 * n]), (l[2 * n + 1] = r)), i--); + } + })(t, e), + j(r, h, t.bl_count); + } + function X(t, e, a) { + var i, + n, + r = -1, + s = e[1], + o = 0, + l = 7, + h = 4; + for (0 === s && ((l = 138), (h = 3)), e[2 * (a + 1) + 1] = 65535, i = 0; i <= a; i++) + (n = s), + (s = e[2 * (i + 1) + 1]), + (++o < l && n === s) || + (o < h + ? (t.bl_tree[2 * n] += o) + : 0 !== n + ? (n !== r && t.bl_tree[2 * n]++, t.bl_tree[2 * p]++) + : o <= 10 + ? t.bl_tree[2 * v]++ + : t.bl_tree[2 * k]++, + (r = n), + (o = 0) === s ? ((l = 138), (h = 3)) : n === s ? ((l = 6), (h = 3)) : ((l = 7), (h = 4))); + } + function W(t, e, a) { + var i, + n, + r = -1, + s = e[1], + o = 0, + l = 7, + h = 4; + for (0 === s && ((l = 138), (h = 3)), i = 0; i <= a; i++) + if (((n = s), (s = e[2 * (i + 1) + 1]), !(++o < l && n === s))) { + if (o < h) for (; L(t, n, t.bl_tree), 0 != --o; ); + else + 0 !== n + ? (n !== r && (L(t, n, t.bl_tree), o--), L(t, p, t.bl_tree), F(t, o - 3, 2)) + : o <= 10 + ? (L(t, v, t.bl_tree), F(t, o - 3, 3)) + : (L(t, k, t.bl_tree), F(t, o - 11, 7)); + (r = n), (o = 0) === s ? ((l = 138), (h = 3)) : n === s ? ((l = 6), (h = 3)) : ((l = 7), (h = 4)); + } + } + i(D); + var J = !1; + function Q(t, e, a, i) { + var n, r, s, o; + F(t, (d << 1) + (i ? 1 : 0), 3), + (r = e), + (s = a), + (o = !0), + M((n = t)), + o && (T(n, s), T(n, ~s)), + l.arraySet(n.pending_buf, n.window, r, s, n.pending), + (n.pending += s); + } + (a._tr_init = function (t) { + J || + ((function () { + var t, + e, + a, + i, + n, + r = new Array(m + 1); + for (i = a = 0; i < s - 1; i++) for (R[i] = a, t = 0; t < 1 << y[i]; t++) Z[a++] = i; + for (Z[a - 1] = i, i = n = 0; i < 16; i++) for (D[i] = n, t = 0; t < 1 << x[i]; t++) A[n++] = i; + for (n >>= 7; i < u; i++) for (D[i] = n << 7, t = 0; t < 1 << (x[i] - 7); t++) A[256 + n++] = i; + for (e = 0; e <= m; e++) r[e] = 0; + for (t = 0; t <= 143; ) (S[2 * t + 1] = 8), t++, r[8]++; + for (; t <= 255; ) (S[2 * t + 1] = 9), t++, r[9]++; + for (; t <= 279; ) (S[2 * t + 1] = 7), t++, r[7]++; + for (; t <= 287; ) (S[2 * t + 1] = 8), t++, r[8]++; + for (j(S, _ + 1, r), t = 0; t < u; t++) (E[2 * t + 1] = 5), (E[2 * t] = H(t, 5)); + (C = new I(S, y, f + 1, _, m)), (N = new I(E, x, 0, u, m)), (O = new I(new Array(0), z, 0, c, b)); + })(), + (J = !0)), + (t.l_desc = new r(t.dyn_ltree, C)), + (t.d_desc = new r(t.dyn_dtree, N)), + (t.bl_desc = new r(t.bl_tree, O)), + (t.bi_buf = 0), + (t.bi_valid = 0), + K(t); + }), + (a._tr_stored_block = Q), + (a._tr_flush_block = function (t, e, a, i) { + var n, + r, + s = 0; + 0 < t.level + ? (2 === t.strm.data_type && + (t.strm.data_type = (function (t) { + var e, + a = 4093624447; + for (e = 0; e <= 31; e++, a >>>= 1) if (1 & a && 0 !== t.dyn_ltree[2 * e]) return o; + if (0 !== t.dyn_ltree[18] || 0 !== t.dyn_ltree[20] || 0 !== t.dyn_ltree[26]) return h; + for (e = 32; e < f; e++) if (0 !== t.dyn_ltree[2 * e]) return h; + return o; + })(t)), + G(t, t.l_desc), + G(t, t.d_desc), + (s = (function (t) { + var e; + for ( + X(t, t.dyn_ltree, t.l_desc.max_code), + X(t, t.dyn_dtree, t.d_desc.max_code), + G(t, t.bl_desc), + e = c - 1; + 3 <= e && 0 === t.bl_tree[2 * B[e] + 1]; + e-- + ); + return (t.opt_len += 3 * (e + 1) + 5 + 5 + 4), e; + })(t)), + (n = (t.opt_len + 3 + 7) >>> 3), + (r = (t.static_len + 3 + 7) >>> 3) <= n && (n = r)) + : (n = r = a + 5), + a + 4 <= n && -1 !== e + ? Q(t, e, a, i) + : 4 === t.strategy || r === n + ? (F(t, 2 + (i ? 1 : 0), 3), q(t, S, E)) + : (F(t, 4 + (i ? 1 : 0), 3), + (function (t, e, a, i) { + var n; + for (F(t, e - 257, 5), F(t, a - 1, 5), F(t, i - 4, 4), n = 0; n < i; n++) + F(t, t.bl_tree[2 * B[n] + 1], 3); + W(t, t.dyn_ltree, e - 1), W(t, t.dyn_dtree, a - 1); + })(t, t.l_desc.max_code + 1, t.d_desc.max_code + 1, s + 1), + q(t, t.dyn_ltree, t.dyn_dtree)), + K(t), + i && M(t); + }), + (a._tr_tally = function (t, e, a) { + return ( + (t.pending_buf[t.d_buf + 2 * t.last_lit] = (e >>> 8) & 255), + (t.pending_buf[t.d_buf + 2 * t.last_lit + 1] = 255 & e), + (t.pending_buf[t.l_buf + t.last_lit] = 255 & a), + t.last_lit++, + 0 === e + ? t.dyn_ltree[2 * a]++ + : (t.matches++, e--, t.dyn_ltree[2 * (Z[a] + f + 1)]++, t.dyn_dtree[2 * U(e)]++), + t.last_lit === t.lit_bufsize - 1 + ); + }), + (a._tr_align = function (t) { + var e; + F(t, 2, 3), + L(t, w, S), + 16 === (e = t).bi_valid + ? (T(e, e.bi_buf), (e.bi_buf = 0), (e.bi_valid = 0)) + : 8 <= e.bi_valid && + ((e.pending_buf[e.pending++] = 255 & e.bi_buf), (e.bi_buf >>= 8), (e.bi_valid -= 8)); + }); + }, + { '../utils/common': 3 }, + ], + '15': [ + function (t, e, a) { + 'use strict'; + e.exports = function () { + (this.input = null), + (this.next_in = 0), + (this.avail_in = 0), + (this.total_in = 0), + (this.output = null), + (this.next_out = 0), + (this.avail_out = 0), + (this.total_out = 0), + (this.msg = ''), + (this.state = null), + (this.data_type = 2), + (this.adler = 0); + }; + }, + {}, + ], + '/': [ + function (t, e, a) { + 'use strict'; + var i = {}; + (0, t('./lib/utils/common').assign)(i, t('./lib/deflate'), t('./lib/inflate'), t('./lib/zlib/constants')), + (e.exports = i); + }, + { './lib/deflate': 1, './lib/inflate': 2, './lib/utils/common': 3, './lib/zlib/constants': 6 }, + ], + }, + {}, + [] + )('/'); +}); +/** + * + * Base64 encode / decode + * http://www.webtoolkit.info/ + * + **/ + +var Base64 = { + // private property + _keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', + + // public method for encoding + encode: function (input, binary) { + binary = binary != null ? binary : false; + var output = ''; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + + if (!binary) { + input = Base64._utf8_encode(input); + } + + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = + output + + this._keyStr.charAt(enc1) + + this._keyStr.charAt(enc2) + + this._keyStr.charAt(enc3) + + this._keyStr.charAt(enc4); + } + + return output; + }, + + // public method for decoding + decode: function (input, binary) { + binary = binary != null ? binary : false; + var output = ''; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + + while (i < input.length) { + enc1 = this._keyStr.indexOf(input.charAt(i++)); + enc2 = this._keyStr.indexOf(input.charAt(i++)); + enc3 = this._keyStr.indexOf(input.charAt(i++)); + enc4 = this._keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } + + if (!binary) { + output = Base64._utf8_decode(output); + } + + return output; + }, + + // private method for UTF-8 encoding + _utf8_encode: function (string) { + string = string.replace(/\r\n/g, '\n'); + var utftext = ''; + + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } else if (c > 127 && c < 2048) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + + return utftext; + }, + + // private method for UTF-8 decoding + _utf8_decode: function (utftext) { + var string = ''; + var i = 0; + var c = (c1 = c2 = 0); + + while (i < utftext.length) { + c = utftext.charCodeAt(i); + + if (c < 128) { + string += String.fromCharCode(c); + i++; + } else if (c > 191 && c < 224) { + c2 = utftext.charCodeAt(i + 1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = utftext.charCodeAt(i + 1); + c3 = utftext.charCodeAt(i + 2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return string; + }, +}; // NOTE: Modified to support data URIs for images, ie. data:image/* +// Modified to allow "word-break: break-word" in styles. It is done by adding "break-word" which is at index 55 of J array J[55] to "cssLitGroup" of "word-break" +(function () { + var c = void 0, + n = !0, + s = null, + C = !1, + J = [ + 'aliceblue,antiquewhite,aqua,aquamarine,azure,beige,bisque,black,blanchedalmond,blue,blueviolet,brown,burlywood,cadetblue,chartreuse,chocolate,coral,cornflowerblue,cornsilk,crimson,cyan,darkblue,darkcyan,darkgoldenrod,darkgray,darkgreen,darkkhaki,darkmagenta,darkolivegreen,darkorange,darkorchid,darkred,darksalmon,darkseagreen,darkslateblue,darkslategray,darkturquoise,darkviolet,deeppink,deepskyblue,dimgray,dodgerblue,firebrick,floralwhite,forestgreen,fuchsia,gainsboro,ghostwhite,gold,goldenrod,gray,green,greenyellow,honeydew,hotpink,indianred,indigo,ivory,khaki,lavender,lavenderblush,lawngreen,lemonchiffon,lightblue,lightcoral,lightcyan,lightgoldenrodyellow,lightgreen,lightgrey,lightpink,lightsalmon,lightseagreen,lightskyblue,lightslategray,lightsteelblue,lightyellow,lime,limegreen,linen,magenta,maroon,mediumaquamarine,mediumblue,mediumorchid,mediumpurple,mediumseagreen,mediumslateblue,mediumspringgreen,mediumturquoise,mediumvioletred,midnightblue,mintcream,mistyrose,moccasin,navajowhite,navy,oldlace,olive,olivedrab,orange,orangered,orchid,palegoldenrod,palegreen,paleturquoise,palevioletred,papayawhip,peachpuff,peru,pink,plum,powderblue,purple,red,rosybrown,royalblue,saddlebrown,salmon,sandybrown,seagreen,seashell,sienna,silver,skyblue,slateblue,slategray,snow,springgreen,steelblue,tan,teal,thistle,tomato,transparent,turquoise,violet,wheat,white,whitesmoke,yellow,yellowgreen'.split( + ',' + ), + 'all-scroll,col-resize,crosshair,default,e-resize,hand,help,move,n-resize,ne-resize,no-drop,not-allowed,nw-resize,pointer,progress,row-resize,s-resize,se-resize,sw-resize,text,vertical-text,w-resize,wait'.split( + ',' + ), + 'armenian,decimal,decimal-leading-zero,disc,georgian,lower-alpha,lower-greek,lower-latin,lower-roman,square,upper-alpha,upper-latin,upper-roman'.split( + ',' + ), + '100,200,300,400,500,600,700,800,900,bold,bolder,lighter'.split(','), + 'block-level,inline-level,table-caption,table-cell,table-column,table-column-group,table-footer-group,table-header-group,table-row,table-row-group'.split( + ',' + ), + 'condensed,expanded,extra-condensed,extra-expanded,narrower,semi-condensed,semi-expanded,ultra-condensed,ultra-expanded,wider'.split( + ',' + ), + 'inherit,inline,inline-block,inline-box,inline-flex,inline-grid,inline-list-item,inline-stack,inline-table,run-in'.split( + ',' + ), + 'behind,center-left,center-right,far-left,far-right,left-side,leftwards,right-side,rightwards'.split(','), + 'large,larger,small,smaller,x-large,x-small,xx-large,xx-small'.split(','), + 'dashed,dotted,double,groove,outset,ridge,solid'.split(','), + 'ease,ease-in,ease-in-out,ease-out,linear,step-end,step-start'.split(','), + 'at,closest-corner,closest-side,ellipse,farthest-corner,farthest-side'.split(','), + 'baseline,middle,sub,super,text-bottom,text-top'.split(','), + 'caption,icon,menu,message-box,small-caption,status-bar'.split(','), + 'fast,faster,slow,slower,x-fast,x-slow'.split(','), + ['above', 'below', 'higher', 'level', 'lower'], + ['cursive', 'fantasy', 'monospace', 'sans-serif', 'serif'], + ['loud', 'silent', 'soft', 'x-loud', 'x-soft'], + ['no-repeat', 'repeat-x', 'repeat-y', 'round', 'space'], + ['blink', 'line-through', 'overline', 'underline'], + ['block', 'flex', 'grid', 'table'], + ['high', 'low', 'x-high', 'x-low'], + ['nowrap', 'pre', 'pre-line', 'pre-wrap'], + ['absolute', 'relative', 'static'], + ['alternate', 'alternate-reverse', 'reverse'], + ['border-box', 'content-box', 'padding-box'], + ['capitalize', 'lowercase', 'uppercase'], + ['child', 'female', 'male'], + ['=', 'opacity'], + ['backwards', 'forwards'], + ['bidi-override', 'embed'], + ['bottom', 'top'], + ['break-all', 'keep-all'], + ['clip', 'ellipsis'], + ['contain', 'cover'], + ['continuous', 'digits'], + ['end', 'start'], + ['flat', 'preserve-3d'], + ['hide', 'show'], + ['horizontal', 'vertical'], + ['inside', 'outside'], + ['italic', 'oblique'], + ['left', 'right'], + ['ltr', 'rtl'], + ['no-content', 'no-display'], + ['paused', 'running'], + ['suppress', 'unrestricted'], + ['thick', 'thin'], + [','], + ['/'], + ['all'], + ['always'], + ['auto'], + ['avoid'], + ['both'], + ['break-word'], + ['center'], + ['circle'], + ['code'], + ['collapse'], + ['contents'], + ['fixed'], + ['hidden'], + ['infinite'], + ['inset'], + ['invert'], + ['justify'], + ['list-item'], + ['local'], + ['medium'], + ['mix'], + ['none'], + ['normal'], + ['once'], + ['repeat'], + ['scroll'], + ['separate'], + ['small-caps'], + ['spell-out'], + ['to'], + ['visible'], + ], + L = { + 'animation': { + cssPropBits: 517, + cssLitGroup: [J[10], J[24], J[29], J[45], J[48], J[54], J[63], J[71], J[72]], + cssFns: ['cubic-bezier()', 'steps()'], + }, + 'animation-delay': { cssPropBits: 5, cssLitGroup: [J[48]], cssFns: [] }, + 'animation-direction': { cssPropBits: 0, cssLitGroup: [J[24], J[48], J[72]], cssFns: [] }, + 'animation-duration': 'animation-delay', + 'animation-fill-mode': { cssPropBits: 0, cssLitGroup: [J[29], J[48], J[54], J[71]], cssFns: [] }, + 'animation-iteration-count': { cssPropBits: 5, cssLitGroup: [J[48], J[63]], cssFns: [] }, + 'animation-name': { cssPropBits: 512, cssLitGroup: [J[48], J[71]], cssFns: [] }, + 'animation-play-state': { cssPropBits: 0, cssLitGroup: [J[45], J[48]], cssFns: [] }, + 'animation-timing-function': { + cssPropBits: 0, + cssLitGroup: [J[10], J[48]], + cssFns: ['cubic-bezier()', 'steps()'], + }, + 'appearance': { cssPropBits: 0, cssLitGroup: [J[71]], cssFns: [] }, + 'azimuth': { cssPropBits: 5, cssLitGroup: [J[7], J[42], J[56]], cssFns: [] }, + 'backface-visibility': { cssPropBits: 0, cssLitGroup: [J[59], J[62], J[80]], cssFns: [] }, + 'background': { + cssPropBits: 23, + cssLitGroup: [ + J[0], + J[18], + J[25], + J[31], + J[34], + J[42], + J[48], + J[49], + J[52], + J[56], + J[61], + J[68], + J[71], + J[74], + J[75], + ], + cssFns: + 'image(),linear-gradient(),radial-gradient(),repeating-linear-gradient(),repeating-radial-gradient(),rgb(),rgba()'.split( + ',' + ), + }, + 'background-attachment': { cssPropBits: 0, cssLitGroup: [J[48], J[61], J[68], J[75]], cssFns: [] }, + 'background-color': { cssPropBits: 2, cssLitGroup: [J[0]], cssFns: ['rgb()', 'rgba()'] }, + 'background-image': { + cssPropBits: 16, + cssLitGroup: [J[48], J[71]], + cssFns: [ + 'image()', + 'linear-gradient()', + 'radial-gradient()', + 'repeating-linear-gradient()', + 'repeating-radial-gradient()', + ], + }, + 'background-position': { cssPropBits: 5, cssLitGroup: [J[31], J[42], J[48], J[56]], cssFns: [] }, + 'background-repeat': { cssPropBits: 0, cssLitGroup: [J[18], J[48], J[74]], cssFns: [] }, + 'background-size': { cssPropBits: 5, cssLitGroup: [J[34], J[48], J[52]], cssFns: [] }, + 'border': { + cssPropBits: 7, + cssLitGroup: [J[0], J[9], J[47], J[62], J[64], J[69], J[71]], + cssFns: ['rgb()', 'rgba()'], + }, + 'border-bottom': 'border', + 'border-bottom-color': 'background-color', + 'border-bottom-left-radius': { cssPropBits: 5, cssFns: [] }, + 'border-bottom-right-radius': 'border-bottom-left-radius', + 'border-bottom-style': { cssPropBits: 0, cssLitGroup: [J[9], J[62], J[64], J[71]], cssFns: [] }, + 'border-bottom-width': { cssPropBits: 5, cssLitGroup: [J[47], J[69]], cssFns: [] }, + 'border-collapse': { cssPropBits: 0, cssLitGroup: [J[59], J[76]], cssFns: [] }, + 'border-color': 'background-color', + 'border-left': 'border', + 'border-left-color': 'background-color', + 'border-left-style': 'border-bottom-style', + 'border-left-width': 'border-bottom-width', + 'border-radius': { cssPropBits: 5, cssLitGroup: [J[49]], cssFns: [] }, + 'border-right': 'border', + 'border-right-color': 'background-color', + 'border-right-style': 'border-bottom-style', + 'border-right-width': 'border-bottom-width', + 'border-spacing': 'border-bottom-left-radius', + 'border-style': 'border-bottom-style', + 'border-top': 'border', + 'border-top-color': 'background-color', + 'border-top-left-radius': 'border-bottom-left-radius', + 'border-top-right-radius': 'border-bottom-left-radius', + 'border-top-style': 'border-bottom-style', + 'border-top-width': 'border-bottom-width', + 'border-width': 'border-bottom-width', + 'bottom': { cssPropBits: 5, cssLitGroup: [J[52]], cssFns: [] }, + 'box': { cssPropBits: 0, cssLitGroup: [J[60], J[71], J[72]], cssFns: [] }, + 'box-shadow': { cssPropBits: 7, cssLitGroup: [J[0], J[48], J[64], J[71]], cssFns: ['rgb()', 'rgba()'] }, + 'box-sizing': { cssPropBits: 0, cssLitGroup: [J[25]], cssFns: [] }, + 'caption-side': { cssPropBits: 0, cssLitGroup: [J[31]], cssFns: [] }, + 'clear': { cssPropBits: 0, cssLitGroup: [J[42], J[54], J[71]], cssFns: [] }, + 'clip': { cssPropBits: 0, cssLitGroup: [J[52]], cssFns: ['rect()'] }, + 'color': 'background-color', + 'content': { cssPropBits: 8, cssLitGroup: [J[71], J[72]], cssFns: [] }, + 'cue': { cssPropBits: 16, cssLitGroup: [J[71]], cssFns: [] }, + 'cue-after': 'cue', + 'cue-before': 'cue', + 'cursor': { cssPropBits: 16, cssLitGroup: [J[1], J[48], J[52]], cssFns: [] }, + 'direction': { cssPropBits: 0, cssLitGroup: [J[43]], cssFns: [] }, + 'display': { cssPropBits: 0, cssLitGroup: [J[4], J[6], J[20], J[52], J[67], J[71]], cssFns: [] }, + 'display-extras': { cssPropBits: 0, cssLitGroup: [J[67], J[71]], cssFns: [] }, + 'display-inside': { cssPropBits: 0, cssLitGroup: [J[20], J[52]], cssFns: [] }, + 'display-outside': { cssPropBits: 0, cssLitGroup: [J[4], J[71]], cssFns: [] }, + 'elevation': { cssPropBits: 5, cssLitGroup: [J[15]], cssFns: [] }, + 'empty-cells': { cssPropBits: 0, cssLitGroup: [J[38]], cssFns: [] }, + 'filter': { cssPropBits: 0, cssFns: ['alpha()'] }, + 'float': { cssPropBits: 0, cssLitGroup: [J[42], J[71]], cssFns: [] }, + 'font': { + cssPropBits: 73, + cssLitGroup: [J[3], J[8], J[13], J[16], J[41], J[48], J[49], J[69], J[72], J[77]], + cssFns: [], + }, + 'font-family': { cssPropBits: 72, cssLitGroup: [J[16], J[48]], cssFns: [] }, + 'font-size': { cssPropBits: 1, cssLitGroup: [J[8], J[69]], cssFns: [] }, + 'font-stretch': { cssPropBits: 0, cssLitGroup: [J[5], J[72]], cssFns: [] }, + 'font-style': { cssPropBits: 0, cssLitGroup: [J[41], J[72]], cssFns: [] }, + 'font-variant': { cssPropBits: 0, cssLitGroup: [J[72], J[77]], cssFns: [] }, + 'font-weight': { cssPropBits: 0, cssLitGroup: [J[3], J[72]], cssFns: [] }, + 'height': 'bottom', + 'left': 'bottom', + 'letter-spacing': { cssPropBits: 5, cssLitGroup: [J[72]], cssFns: [] }, + 'line-height': { cssPropBits: 1, cssLitGroup: [J[72]], cssFns: [] }, + 'list-style': { + cssPropBits: 16, + cssLitGroup: [J[2], J[40], J[57], J[71]], + cssFns: [ + 'image()', + 'linear-gradient()', + 'radial-gradient()', + 'repeating-linear-gradient()', + 'repeating-radial-gradient()', + ], + }, + 'list-style-image': { + cssPropBits: 16, + cssLitGroup: [J[71]], + cssFns: [ + 'image()', + 'linear-gradient()', + 'radial-gradient()', + 'repeating-linear-gradient()', + 'repeating-radial-gradient()', + ], + }, + 'list-style-position': { cssPropBits: 0, cssLitGroup: [J[40]], cssFns: [] }, + 'list-style-type': { cssPropBits: 0, cssLitGroup: [J[2], J[57], J[71]], cssFns: [] }, + 'margin': 'bottom', + 'margin-bottom': 'bottom', + 'margin-left': 'bottom', + 'margin-right': 'bottom', + 'margin-top': 'bottom', + 'max-height': { cssPropBits: 1, cssLitGroup: [J[52], J[71]], cssFns: [] }, + 'max-width': 'max-height', + 'min-height': { cssPropBits: 1, cssLitGroup: [J[52]], cssFns: [] }, + 'min-width': 'min-height', + 'opacity': { cssPropBits: 1, cssFns: [] }, + 'outline': { + cssPropBits: 7, + cssLitGroup: [J[0], J[9], J[47], J[62], J[64], J[65], J[69], J[71]], + cssFns: ['rgb()', 'rgba()'], + }, + 'outline-color': { cssPropBits: 2, cssLitGroup: [J[0], J[65]], cssFns: ['rgb()', 'rgba()'] }, + 'outline-style': 'border-bottom-style', + 'outline-width': 'border-bottom-width', + 'overflow': { cssPropBits: 0, cssLitGroup: [J[52], J[62], J[75], J[80]], cssFns: [] }, + 'overflow-wrap': { cssPropBits: 0, cssLitGroup: [J[55], J[72]], cssFns: [] }, + 'overflow-x': { cssPropBits: 0, cssLitGroup: [J[44], J[52], J[62], J[75], J[80]], cssFns: [] }, + 'overflow-y': 'overflow-x', + 'padding': 'opacity', + 'padding-bottom': 'opacity', + 'padding-left': 'opacity', + 'padding-right': 'opacity', + 'padding-top': 'opacity', + 'page-break-after': { cssPropBits: 0, cssLitGroup: [J[42], J[51], J[52], J[53]], cssFns: [] }, + 'page-break-before': 'page-break-after', + 'page-break-inside': { cssPropBits: 0, cssLitGroup: [J[52], J[53]], cssFns: [] }, + 'pause': 'border-bottom-left-radius', + 'pause-after': 'border-bottom-left-radius', + 'pause-before': 'border-bottom-left-radius', + 'perspective': { cssPropBits: 5, cssLitGroup: [J[71]], cssFns: [] }, + 'perspective-origin': { cssPropBits: 5, cssLitGroup: [J[31], J[42], J[56]], cssFns: [] }, + 'pitch': { cssPropBits: 5, cssLitGroup: [J[21], J[69]], cssFns: [] }, + 'pitch-range': 'border-bottom-left-radius', + 'play-during': { cssPropBits: 16, cssLitGroup: [J[52], J[70], J[71], J[74]], cssFns: [] }, + 'position': { cssPropBits: 0, cssLitGroup: [J[23]], cssFns: [] }, + 'quotes': { cssPropBits: 8, cssLitGroup: [J[71]], cssFns: [] }, + 'resize': { cssPropBits: 0, cssLitGroup: [J[39], J[54], J[71]], cssFns: [] }, + 'richness': 'border-bottom-left-radius', + 'right': 'bottom', + 'speak': { cssPropBits: 0, cssLitGroup: [J[71], J[72], J[78]], cssFns: [] }, + 'speak-header': { cssPropBits: 0, cssLitGroup: [J[51], J[73]], cssFns: [] }, + 'speak-numeral': { cssPropBits: 0, cssLitGroup: [J[35]], cssFns: [] }, + 'speak-punctuation': { cssPropBits: 0, cssLitGroup: [J[58], J[71]], cssFns: [] }, + 'speech-rate': { cssPropBits: 5, cssLitGroup: [J[14], J[69]], cssFns: [] }, + 'stress': 'border-bottom-left-radius', + 'table-layout': { cssPropBits: 0, cssLitGroup: [J[52], J[61]], cssFns: [] }, + 'text-align': { cssPropBits: 0, cssLitGroup: [J[42], J[56], J[66]], cssFns: [] }, + 'text-decoration': { cssPropBits: 0, cssLitGroup: [J[19], J[71]], cssFns: [] }, + 'text-indent': 'border-bottom-left-radius', + 'text-overflow': { cssPropBits: 8, cssLitGroup: [J[33]], cssFns: [] }, + 'text-shadow': 'box-shadow', + 'text-transform': { cssPropBits: 0, cssLitGroup: [J[26], J[71]], cssFns: [] }, + 'text-wrap': { cssPropBits: 0, cssLitGroup: [J[46], J[71], J[72]], cssFns: [] }, + 'top': 'bottom', + 'transform': { + cssPropBits: 0, + cssLitGroup: [J[71]], + cssFns: + 'matrix(),perspective(),rotate(),rotate3d(),rotatex(),rotatey(),rotatez(),scale(),scale3d(),scalex(),scaley(),scalez(),skew(),skewx(),skewy(),translate(),translate3d(),translatex(),translatey(),translatez()'.split( + ',' + ), + }, + 'transform-origin': 'perspective-origin', + 'transform-style': { cssPropBits: 0, cssLitGroup: [J[37]], cssFns: [] }, + 'transition': { + cssPropBits: 1029, + cssLitGroup: [J[10], J[48], J[50], J[71]], + cssFns: ['cubic-bezier()', 'steps()'], + }, + 'transition-delay': 'animation-delay', + 'transition-duration': 'animation-delay', + 'transition-property': { cssPropBits: 1024, cssLitGroup: [J[48], J[50]], cssFns: [] }, + 'transition-timing-function': 'animation-timing-function', + 'unicode-bidi': { cssPropBits: 0, cssLitGroup: [J[30], J[72]], cssFns: [] }, + 'vertical-align': { cssPropBits: 5, cssLitGroup: [J[12], J[31]], cssFns: [] }, + 'visibility': 'backface-visibility', + 'voice-family': { cssPropBits: 8, cssLitGroup: [J[27], J[48]], cssFns: [] }, + 'volume': { cssPropBits: 1, cssLitGroup: [J[17], J[69]], cssFns: [] }, + 'white-space': { cssPropBits: 0, cssLitGroup: [J[22], J[72]], cssFns: [] }, + 'width': 'min-height', + 'word-break': { cssPropBits: 0, cssLitGroup: [J[32], J[72], J[55]], cssFns: [] }, + 'word-spacing': 'letter-spacing', + 'word-wrap': 'overflow-wrap', + 'z-index': 'bottom', + 'zoom': 'line-height', + 'cubic-bezier()': 'animation-delay', + 'steps()': { cssPropBits: 5, cssLitGroup: [J[36], J[48]], cssFns: [] }, + 'image()': { cssPropBits: 18, cssLitGroup: [J[0], J[48]], cssFns: ['rgb()', 'rgba()'] }, + 'linear-gradient()': { + cssPropBits: 7, + cssLitGroup: [J[0], J[31], J[42], J[48], J[79]], + cssFns: ['rgb()', 'rgba()'], + }, + 'radial-gradient()': { + cssPropBits: 7, + cssLitGroup: [J[0], J[11], J[31], J[42], J[48], J[56], J[57]], + cssFns: ['rgb()', 'rgba()'], + }, + 'repeating-linear-gradient()': 'linear-gradient()', + 'repeating-radial-gradient()': 'radial-gradient()', + 'rgb()': { cssPropBits: 1, cssLitGroup: [J[48]], cssFns: [] }, + 'rgba()': 'rgb()', + 'rect()': { cssPropBits: 5, cssLitGroup: [J[48], J[52]], cssFns: [] }, + 'alpha()': { cssPropBits: 1, cssLitGroup: [J[28]], cssFns: [] }, + 'matrix()': 'animation-delay', + 'perspective()': 'border-bottom-left-radius', + 'rotate()': 'border-bottom-left-radius', + 'rotate3d()': 'animation-delay', + 'rotatex()': 'border-bottom-left-radius', + 'rotatey()': 'border-bottom-left-radius', + 'rotatez()': 'border-bottom-left-radius', + 'scale()': 'animation-delay', + 'scale3d()': 'animation-delay', + 'scalex()': 'border-bottom-left-radius', + 'scaley()': 'border-bottom-left-radius', + 'scalez()': 'border-bottom-left-radius', + 'skew()': 'animation-delay', + 'skewx()': 'border-bottom-left-radius', + 'skewy()': 'border-bottom-left-radius', + 'translate()': 'animation-delay', + 'translate3d()': 'animation-delay', + 'translatex()': 'border-bottom-left-radius', + 'translatey()': 'border-bottom-left-radius', + 'translatez()': 'border-bottom-left-radius', + }, + O; + for (O in L) 'string' === typeof L[O] && Object.hasOwnProperty.call(L, O) && (L[O] = L[L[O]]); + 'undefined' !== typeof window && (window.cssSchema = L); + var U, X; + (function () { + function g(a) { + var f = parseInt(a.substring(1), 16); + return 65535 < f + ? ((f -= 65536), String.fromCharCode(55296 + (f >> 10), 56320 + (f & 1023))) + : f == f + ? String.fromCharCode(f) + : ' ' > a[1] + ? '' + : a[1]; + } + function w(a, f) { + return '"' + a.replace(/[\u0000-\u001f\\\"<>]/g, f) + '"'; + } + function M(a) { + return E[a] || (E[a] = '\\' + a.charCodeAt(0).toString(16) + ' '); + } + function x(a) { + return e[a] || (e[a] = ('\u0010' > a ? '%0' : '%') + a.charCodeAt(0).toString(16)); + } + var E = { '\\': '\\\\' }, + e = { '\\': '%5c' }, + v = RegExp( + '\\uFEFF|U[+][0-9A-F?]{1,6}(?:-[0-9A-F]{1,6})?|url[(][\\t\\n\\f ]*(?:"(?:\'|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*"|\'(?:"|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*\'|(?:[\\t\\x21\\x23-\\x26\\x28-\\x5b\\x5d-\\x7e]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*)[\\t\\n\\f ]*[)]|(?!url[(])-?(?:[a-zA-Z_]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))(?:[a-zA-Z0-9_-]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*[(]|(?:@?-?(?:[a-zA-Z_]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))|#)(?:[a-zA-Z0-9_-]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*|"(?:\'|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*"|\'(?:"|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*\'|[-+]?(?:[0-9]+(?:[.][0-9]+)?|[.][0-9]+)(?:%|-?(?:[a-zA-Z_]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))(?:[a-zA-Z0-9_-]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*)?||[\\t\\n\\f ]+|/(?:[*][^*]*[*]+(?:[^/][^*]*[*]+)*/|/[^\\n\\f]*)|[~|^$*]=|[^"\'\\\\/]|/(?![/*])', + 'gi' + ), + b = RegExp( + '\\\\(?:(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff])|[\\n\\f])', + 'g' + ), + a = RegExp('^url\\([\\t\\n\\f ]*["\']?|["\']?[\\t\\n\\f ]*\\)$', 'gi'); + X = function (a) { + return a.replace(b, g); + }; + U = function (b) { + for (var b = ('' + b).replace(/\r\n?/g, '\n').match(v) || [], f = 0, h = ' ', d = 0, y = b.length; d < y; ++d) { + var l = X(b[d]), + V = l.length, + g = l.charCodeAt(0), + l = + 34 == g || 39 == g + ? w(l.substring(1, V - 1), M) + : (47 == g && 1 < V) || '\\' == l || '-->' == l || '' + newline); + } + } else if (node.nodeType == mxConstants.NODETYPE_TEXT) { + var value = mxUtils.trim(mxUtils.getTextContent(node)); + + if (value.length > 0) { + result.push(indent + mxUtils.htmlEntities(value, false) + newline); + } + } else if (node.nodeType == mxConstants.NODETYPE_CDATA) { + var value = mxUtils.getTextContent(node); + + if (value.length > 0) { + result.push(indent + '' + newline); + + while (tmp != null) { + result.push(mxUtils.getPrettyXml(tmp, tab, indent + tab, newline, ns)); + tmp = tmp.nextSibling; + } + + result.push(indent + '' + newline); + } else { + result.push(' />' + newline); + } + } + } + + return result.join(''); + }, + + /** + * Function: extractTextWithWhitespace + * + * Returns the text content of the specified node. + * + * Parameters: + * + * elems - DOM nodes to return the text for. + */ + extractTextWithWhitespace: function (elems) { + // Known block elements for handling linefeeds (list is not complete) + var blocks = ['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'P', 'PRE', 'TABLE', 'UL']; + var ret = []; + + function doExtract(elts) { + // Single break should be ignored + if (elts.length == 1 && (elts[0].nodeName == 'BR' || elts[0].innerHTML == '\n')) { + return; + } + + for (var i = 0; i < elts.length; i++) { + var elem = elts[i]; + + // DIV with a br or linefeed forces a linefeed + if ( + elem.nodeName == 'BR' || + elem.innerHTML == '\n' || + ((elts.length == 1 || i == 0) && elem.nodeName == 'DIV' && elem.innerHTML.toLowerCase() == '
') + ) { + ret.push('\n'); + } else { + if (elem.nodeType === 3 || elem.nodeType === 4) { + if (elem.nodeValue.length > 0) { + ret.push(elem.nodeValue); + } + } else if (elem.nodeType !== 8 && elem.childNodes.length > 0) { + doExtract(elem.childNodes); + } + + if (i < elts.length - 1 && mxUtils.indexOf(blocks, elts[i + 1].nodeName) >= 0) { + ret.push('\n'); + } + } + } + } + + doExtract(elems); + + return ret.join(''); + }, + + /** + * Function: replaceTrailingNewlines + * + * Replaces each trailing newline with the given pattern. + */ + replaceTrailingNewlines: function (str, pattern) { + // LATER: Check is this can be done with a regular expression + var postfix = ''; + + while (str.length > 0 && str.charAt(str.length - 1) == '\n') { + str = str.substring(0, str.length - 1); + postfix += pattern; + } + + return str + postfix; + }, + + /** + * Function: getTextContent + * + * Returns the text content of the specified node. + * + * Parameters: + * + * node - DOM node to return the text content for. + */ + getTextContent: function (node) { + // Only IE10- + if (mxClient.IS_IE && node.innerText !== undefined) { + return node.innerText; + } else { + return node != null ? node[node.textContent === undefined ? 'text' : 'textContent'] : ''; + } + }, + + /** + * Function: setTextContent + * + * Sets the text content of the specified node. + * + * Parameters: + * + * node - DOM node to set the text content for. + * text - String that represents the text content. + */ + setTextContent: function (node, text) { + if (node.innerText !== undefined) { + node.innerText = text; + } else { + node[node.textContent === undefined ? 'text' : 'textContent'] = text; + } + }, + + /** + * Function: getInnerHtml + * + * Returns the inner HTML for the given node as a string or an empty string + * if no node was specified. The inner HTML is the text representing all + * children of the node, but not the node itself. + * + * Parameters: + * + * node - DOM node to return the inner HTML for. + */ + getInnerHtml: (function () { + if (mxClient.IS_IE) { + return function (node) { + if (node != null) { + return node.innerHTML; + } + + return ''; + }; + } else { + return function (node) { + if (node != null) { + var serializer = new XMLSerializer(); + return serializer.serializeToString(node); + } + + return ''; + }; + } + })(), + + /** + * Function: getOuterHtml + * + * Returns the outer HTML for the given node as a string or an empty + * string if no node was specified. The outer HTML is the text representing + * all children of the node including the node itself. + * + * Parameters: + * + * node - DOM node to return the outer HTML for. + */ + getOuterHtml: (function () { + if (mxClient.IS_IE) { + return function (node) { + if (node != null) { + if (node.outerHTML != null) { + return node.outerHTML; + } else { + var tmp = []; + tmp.push('<' + node.nodeName); + + var attrs = node.attributes; + + if (attrs != null) { + for (var i = 0; i < attrs.length; i++) { + var value = attrs[i].value; + + if (value != null && value.length > 0) { + tmp.push(' '); + tmp.push(attrs[i].nodeName); + tmp.push('="'); + tmp.push(value); + tmp.push('"'); + } + } + } + + if (node.innerHTML.length == 0) { + tmp.push('/>'); + } else { + tmp.push('>'); + tmp.push(node.innerHTML); + tmp.push(''); + } + + return tmp.join(''); + } + } + + return ''; + }; + } else { + return function (node) { + if (node != null) { + var serializer = new XMLSerializer(); + return serializer.serializeToString(node); + } + + return ''; + }; + } + })(), + + /** + * Function: write + * + * Creates a text node for the given string and appends it to the given + * parent. Returns the text node. + * + * Parameters: + * + * parent - DOM node to append the text node to. + * text - String representing the text to be added. + */ + write: function (parent, text) { + var doc = parent.ownerDocument; + var node = doc.createTextNode(text); + + if (parent != null) { + parent.appendChild(node); + } + + return node; + }, + + /** + * Function: writeln + * + * Creates a text node for the given string and appends it to the given + * parent with an additional linefeed. Returns the text node. + * + * Parameters: + * + * parent - DOM node to append the text node to. + * text - String representing the text to be added. + */ + writeln: function (parent, text) { + var doc = parent.ownerDocument; + var node = doc.createTextNode(text); + + if (parent != null) { + parent.appendChild(node); + parent.appendChild(document.createElement('br')); + } + + return node; + }, + + /** + * Function: br + * + * Appends a linebreak to the given parent and returns the linebreak. + * + * Parameters: + * + * parent - DOM node to append the linebreak to. + */ + br: function (parent, count) { + count = count || 1; + var br = null; + + for (var i = 0; i < count; i++) { + if (parent != null) { + br = parent.ownerDocument.createElement('br'); + parent.appendChild(br); + } + } + + return br; + }, + + /** + * Function: button + * + * Returns a new button with the given level and function as an onclick + * event handler. + * + * (code) + * document.body.appendChild(mxUtils.button('Test', function(evt) + * { + * alert('Hello, World!'); + * })); + * (end) + * + * Parameters: + * + * label - String that represents the label of the button. + * funct - Function to be called if the button is pressed. + * doc - Optional document to be used for creating the button. Default is the + * current document. + */ + button: function (label, funct, doc) { + doc = doc != null ? doc : document; + + var button = doc.createElement('button'); + mxUtils.write(button, label); + + mxEvent.addListener(button, 'click', function (evt) { + funct(evt); + }); + + return button; + }, + + /** + * Function: para + * + * Appends a new paragraph with the given text to the specified parent and + * returns the paragraph. + * + * Parameters: + * + * parent - DOM node to append the text node to. + * text - String representing the text for the new paragraph. + */ + para: function (parent, text) { + var p = document.createElement('p'); + mxUtils.write(p, text); + + if (parent != null) { + parent.appendChild(p); + } + + return p; + }, + + /** + * Function: addTransparentBackgroundFilter + * + * Adds a transparent background to the filter of the given node. This + * background can be used in IE8 standards mode (native IE8 only) to pass + * events through the node. + */ + addTransparentBackgroundFilter: function (node) { + node.style.filter += + "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + + mxClient.imageBasePath + + "/transparent.gif', sizingMethod='scale')"; + }, + + /** + * Function: linkAction + * + * Adds a hyperlink to the specified parent that invokes action on the + * specified editor. + * + * Parameters: + * + * parent - DOM node to contain the new link. + * text - String that is used as the link label. + * editor - that will execute the action. + * action - String that defines the name of the action to be executed. + * pad - Optional left-padding for the link. Default is 0. + */ + linkAction: function (parent, text, editor, action, pad) { + return mxUtils.link( + parent, + text, + function () { + editor.execute(action); + }, + pad + ); + }, + + /** + * Function: linkInvoke + * + * Adds a hyperlink to the specified parent that invokes the specified + * function on the editor passing along the specified argument. The + * function name is the name of a function of the editor instance, + * not an action name. + * + * Parameters: + * + * parent - DOM node to contain the new link. + * text - String that is used as the link label. + * editor - instance to execute the function on. + * functName - String that represents the name of the function. + * arg - Object that represents the argument to the function. + * pad - Optional left-padding for the link. Default is 0. + */ + linkInvoke: function (parent, text, editor, functName, arg, pad) { + return mxUtils.link( + parent, + text, + function () { + editor[functName](arg); + }, + pad + ); + }, + + /** + * Function: link + * + * Adds a hyperlink to the specified parent and invokes the given function + * when the link is clicked. + * + * Parameters: + * + * parent - DOM node to contain the new link. + * text - String that is used as the link label. + * funct - Function to execute when the link is clicked. + * pad - Optional left-padding for the link. Default is 0. + */ + link: function (parent, text, funct, pad) { + var a = document.createElement('span'); + + a.style.color = 'blue'; + a.style.textDecoration = 'underline'; + a.style.cursor = 'pointer'; + + if (pad != null) { + a.style.paddingLeft = pad + 'px'; + } + + mxEvent.addListener(a, 'click', funct); + mxUtils.write(a, text); + + if (parent != null) { + parent.appendChild(a); + } + + return a; + }, + + /** + * Function: getDocumentSize + * + * Returns the client size for the current document as an . + */ + getDocumentSize: function () { + var b = document.body; + var d = document.documentElement; + + try { + return new mxRectangle(0, 0, b.clientWidth || d.clientWidth, Math.max(b.clientHeight || 0, d.clientHeight)); + } catch (e) { + return new mxRectangle(); + } + }, + + /** + * Function: fit + * + * Makes sure the given node is inside the visible area of the window. This + * is done by setting the left and top in the style. + */ + fit: function (node) { + var ds = mxUtils.getDocumentSize(); + var left = parseInt(node.offsetLeft); + var width = parseInt(node.offsetWidth); + + var offset = mxUtils.getDocumentScrollOrigin(node.ownerDocument); + var sl = offset.x; + var st = offset.y; + + var b = document.body; + var d = document.documentElement; + var right = sl + ds.width; + + if (left + width > right) { + node.style.left = Math.max(sl, right - width) + 'px'; + } + + var top = parseInt(node.offsetTop); + var height = parseInt(node.offsetHeight); + + var bottom = st + ds.height; + + if (top + height > bottom) { + node.style.top = Math.max(st, bottom - height) + 'px'; + } + }, + + /** + * Function: load + * + * Loads the specified URL *synchronously* and returns the . + * Throws an exception if the file cannot be loaded. See for + * an asynchronous implementation. + * + * Example: + * + * (code) + * try + * { + * var req = mxUtils.load(filename); + * var root = req.getDocumentElement(); + * // Process XML DOM... + * } + * catch (ex) + * { + * mxUtils.alert('Cannot load '+filename+': '+ex); + * } + * (end) + * + * Parameters: + * + * url - URL to get the data from. + */ + load: function (url) { + var req = new mxXmlRequest(url, null, 'GET', false); + req.send(); + + return req; + }, + + /** + * Function: get + * + * Loads the specified URL *asynchronously* and invokes the given functions + * depending on the request status. Returns the in use. Both + * functions take the as the only parameter. See + * for a synchronous implementation. + * + * Example: + * + * (code) + * mxUtils.get(url, function(req) + * { + * var node = req.getDocumentElement(); + * // Process XML DOM... + * }); + * (end) + * + * So for example, to load a diagram into an existing graph model, the + * following code is used. + * + * (code) + * mxUtils.get(url, function(req) + * { + * var node = req.getDocumentElement(); + * var dec = new mxCodec(node.ownerDocument); + * dec.decode(node, graph.getModel()); + * }); + * (end) + * + * Parameters: + * + * url - URL to get the data from. + * onload - Optional function to execute for a successful response. + * onerror - Optional function to execute on error. + * binary - Optional boolean parameter that specifies if the request is + * binary. + * timeout - Optional timeout in ms before calling ontimeout. + * ontimeout - Optional function to execute on timeout. + * headers - Optional with headers, eg. {'Authorization': 'token xyz'} + */ + get: function (url, onload, onerror, binary, timeout, ontimeout, headers) { + var req = new mxXmlRequest(url, null, 'GET'); + var setRequestHeaders = req.setRequestHeaders; + + if (headers) { + req.setRequestHeaders = function (request, params) { + setRequestHeaders.apply(this, arguments); + + for (var key in headers) { + request.setRequestHeader(key, headers[key]); + } + }; + } + + if (binary != null) { + req.setBinary(binary); + } + + req.send(onload, onerror, timeout, ontimeout); + + return req; + }, + + /** + * Function: getAll + * + * Loads the URLs in the given array *asynchronously* and invokes the given function + * if all requests returned with a valid 2xx status. The error handler is invoked + * once on the first error or invalid response. + * + * Parameters: + * + * urls - Array of URLs to be loaded. + * onload - Callback with array of . + * onerror - Optional function to execute on error. + */ + getAll: function (urls, onload, onerror) { + var remain = urls.length; + var result = []; + var errors = 0; + var err = function () { + if (errors == 0 && onerror != null) { + onerror(); + } + + errors++; + }; + + for (var i = 0; i < urls.length; i++) { + (function (url, index) { + mxUtils.get( + url, + function (req) { + var status = req.getStatus(); + + if (status < 200 || status > 299) { + err(); + } else { + result[index] = req; + remain--; + + if (remain == 0) { + onload(result); + } + } + }, + err + ); + })(urls[i], i); + } + + if (remain == 0) { + onload(result); + } + }, + + /** + * Function: post + * + * Posts the specified params to the given URL *asynchronously* and invokes + * the given functions depending on the request status. Returns the + * in use. Both functions take the as the + * only parameter. Make sure to use encodeURIComponent for the parameter + * values. + * + * Example: + * + * (code) + * mxUtils.post(url, 'key=value', function(req) + * { + * mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus()); + * // Process req.getDocumentElement() using DOM API if OK... + * }); + * (end) + * + * Parameters: + * + * url - URL to get the data from. + * params - Parameters for the post request. + * onload - Optional function to execute for a successful response. + * onerror - Optional function to execute on error. + */ + post: function (url, params, onload, onerror) { + return new mxXmlRequest(url, params).send(onload, onerror); + }, + + /** + * Function: submit + * + * Submits the given parameters to the specified URL using + * and returns the . + * Make sure to use encodeURIComponent for the parameter + * values. + * + * Parameters: + * + * url - URL to get the data from. + * params - Parameters for the form. + * doc - Document to create the form in. + * target - Target to send the form result to. + */ + submit: function (url, params, doc, target) { + return new mxXmlRequest(url, params).simulate(doc, target); + }, + + /** + * Function: loadInto + * + * Loads the specified URL *asynchronously* into the specified document, + * invoking onload after the document has been loaded. This implementation + * does not use , but the document.load method. + * + * Parameters: + * + * url - URL to get the data from. + * doc - The document to load the URL into. + * onload - Function to execute when the URL has been loaded. + */ + loadInto: function (url, doc, onload) { + if (mxClient.IS_IE) { + doc.onreadystatechange = function () { + if (doc.readyState == 4) { + onload(); + } + }; + } else { + doc.addEventListener('load', onload, false); + } + + doc.load(url); + }, + + /** + * Function: getValue + * + * Returns the value for the given key in the given associative array or + * the given default value if the value is null. + * + * Parameters: + * + * array - Associative array that contains the value for the key. + * key - Key whose value should be returned. + * defaultValue - Value to be returned if the value for the given + * key is null. + */ + getValue: function (array, key, defaultValue) { + var value = array != null ? array[key] : null; + + if (value == null) { + value = defaultValue; + } + + return value; + }, + + /** + * Function: getNumber + * + * Returns the numeric value for the given key in the given associative + * array or the given default value (or 0) if the value is null. The value + * is converted to a numeric value using the Number function. + * + * Parameters: + * + * array - Associative array that contains the value for the key. + * key - Key whose value should be returned. + * defaultValue - Value to be returned if the value for the given + * key is null. Default is 0. + */ + getNumber: function (array, key, defaultValue) { + var value = array != null ? array[key] : null; + + if (value == null) { + value = defaultValue || 0; + } + + return Number(value); + }, + + /** + * Function: getColor + * + * Returns the color value for the given key in the given associative + * array or the given default value if the value is null. If the value + * is then null is returned. + * + * Parameters: + * + * array - Associative array that contains the value for the key. + * key - Key whose value should be returned. + * defaultValue - Value to be returned if the value for the given + * key is null. Default is null. + */ + getColor: function (array, key, defaultValue) { + var value = array != null ? array[key] : null; + + if (value == null) { + value = defaultValue; + } else if (value == mxConstants.NONE) { + value = null; + } + + return value; + }, + + /** + * Function: clone + * + * Recursively clones the specified object ignoring all fieldnames in the + * given array of transient fields. is always + * ignored by this function. + * + * Parameters: + * + * obj - Object to be cloned. + * transients - Optional array of strings representing the fieldname to be + * ignored. + * shallow - Optional boolean argument to specify if a shallow clone should + * be created, that is, one where all object references are not cloned or, + * in other words, one where only atomic (strings, numbers) values are + * cloned. Default is false. + */ + clone: function (obj, transients, shallow) { + shallow = shallow != null ? shallow : false; + var clone = null; + + if (obj != null && typeof obj.constructor == 'function') { + clone = new obj.constructor(); + + for (var i in obj) { + if (i != mxObjectIdentity.FIELD_NAME && (transients == null || mxUtils.indexOf(transients, i) < 0)) { + if (!shallow && typeof obj[i] == 'object') { + clone[i] = mxUtils.clone(obj[i]); + } else { + clone[i] = obj[i]; + } + } + } + } + + return clone; + }, + + /** + * Function: equalPoints + * + * Compares all mxPoints in the given lists. + * + * Parameters: + * + * a - Array of to be compared. + * b - Array of to be compared. + */ + equalPoints: function (a, b) { + if ((a == null && b != null) || (a != null && b == null) || (a != null && b != null && a.length != b.length)) { + return false; + } else if (a != null && b != null) { + for (var i = 0; i < a.length; i++) { + if ( + (a[i] != null && b[i] == null) || + (a[i] == null && b[i] != null) || + (a[i] != null && b[i] != null && (a[i].x != b[i].x || a[i].y != b[i].y)) + ) { + return false; + } + } + } + + return true; + }, + + /** + * Function: equalEntries + * + * Returns true if all properties of the given objects are equal. Values + * with NaN are equal to NaN and unequal to any other value. + * + * Parameters: + * + * a - First object to be compared. + * b - Second object to be compared. + */ + equalEntries: function (a, b) { + // Counts keys in b to check if all values have been compared + var count = 0; + + if ((a == null && b != null) || (a != null && b == null) || (a != null && b != null && a.length != b.length)) { + return false; + } else if (a != null && b != null) { + for (var key in b) { + count++; + } + + for (var key in a) { + count--; + + if ((!mxUtils.isNaN(a[key]) || !mxUtils.isNaN(b[key])) && a[key] != b[key]) { + return false; + } + } + } + + return count == 0; + }, + + /** + * Function: removeDuplicates + * + * Removes all duplicates from the given array. + */ + removeDuplicates: function (arr) { + var dict = new mxDictionary(); + var result = []; + + for (var i = 0; i < arr.length; i++) { + if (!dict.get(arr[i])) { + result.push(arr[i]); + dict.put(arr[i], true); + } + } + + return result; + }, + + /** + * Function: isNaN + * + * Returns true if the given value is of type number and isNaN returns true. + */ + isNaN: function (value) { + return typeof value == 'number' && isNaN(value); + }, + + /** + * Function: extend + * + * Assigns a copy of the superclass prototype to the subclass prototype. + * Note that this does not call the constructor of the superclass at this + * point, the superclass constructor should be called explicitely in the + * subclass constructor. Below is an example. + * + * (code) + * MyGraph = function(container, model, renderHint, stylesheet) + * { + * mxGraph.call(this, container, model, renderHint, stylesheet); + * } + * + * mxUtils.extend(MyGraph, mxGraph); + * (end) + * + * Parameters: + * + * ctor - Constructor of the subclass. + * superCtor - Constructor of the superclass. + */ + extend: function (ctor, superCtor) { + var f = function () {}; + f.prototype = superCtor.prototype; + + ctor.prototype = new f(); + ctor.prototype.constructor = ctor; + }, + + /** + * Function: toString + * + * Returns a textual representation of the specified object. + * + * Parameters: + * + * obj - Object to return the string representation for. + */ + toString: function (obj) { + var output = ''; + + for (var i in obj) { + try { + if (obj[i] == null) { + output += i + ' = [null]\n'; + } else if (typeof obj[i] == 'function') { + output += i + ' => [Function]\n'; + } else if (typeof obj[i] == 'object') { + var ctor = mxUtils.getFunctionName(obj[i].constructor); + output += i + ' => [' + ctor + ']\n'; + } else { + output += i + ' = ' + obj[i] + '\n'; + } + } catch (e) { + output += i + '=' + e.message; + } + } + + return output; + }, + + /** + * Function: toRadians + * + * Converts the given degree to radians. + */ + toRadians: function (deg) { + return (Math.PI * deg) / 180; + }, + + /** + * Function: toDegree + * + * Converts the given radians to degree. + */ + toDegree: function (rad) { + return (rad * 180) / Math.PI; + }, + + /** + * Function: arcToCurves + * + * Converts the given arc to a series of curves. + */ + arcToCurves: function (x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y) { + x -= x0; + y -= y0; + + if (r1 === 0 || r2 === 0) { + return result; + } + + var fS = sweepFlag; + var psai = angle; + r1 = Math.abs(r1); + r2 = Math.abs(r2); + var ctx = -x / 2; + var cty = -y / 2; + var cpsi = Math.cos((psai * Math.PI) / 180); + var spsi = Math.sin((psai * Math.PI) / 180); + var rxd = cpsi * ctx + spsi * cty; + var ryd = -1 * spsi * ctx + cpsi * cty; + var rxdd = rxd * rxd; + var rydd = ryd * ryd; + var r1x = r1 * r1; + var r2y = r2 * r2; + var lamda = rxdd / r1x + rydd / r2y; + var sds; + + if (lamda > 1) { + r1 = Math.sqrt(lamda) * r1; + r2 = Math.sqrt(lamda) * r2; + sds = 0; + } else { + var seif = 1; + + if (largeArcFlag === fS) { + seif = -1; + } + + sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd)); + } + + var txd = (sds * r1 * ryd) / r2; + var tyd = (-1 * sds * r2 * rxd) / r1; + var tx = cpsi * txd - spsi * tyd + x / 2; + var ty = spsi * txd + cpsi * tyd + y / 2; + var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1); + var s1 = rad >= 0 ? rad : 2 * Math.PI + rad; + rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1); + var dr = rad >= 0 ? rad : 2 * Math.PI + rad; + + if (fS == 0 && dr > 0) { + dr -= 2 * Math.PI; + } else if (fS != 0 && dr < 0) { + dr += 2 * Math.PI; + } + + var sse = (dr * 2) / Math.PI; + var seg = Math.ceil(sse < 0 ? -1 * sse : sse); + var segr = dr / seg; + var t = ((8 / 3) * Math.sin(segr / 4) * Math.sin(segr / 4)) / Math.sin(segr / 2); + var cpsir1 = cpsi * r1; + var cpsir2 = cpsi * r2; + var spsir1 = spsi * r1; + var spsir2 = spsi * r2; + var mc = Math.cos(s1); + var ms = Math.sin(s1); + var x2 = -t * (cpsir1 * ms + spsir2 * mc); + var y2 = -t * (spsir1 * ms - cpsir2 * mc); + var x3 = 0; + var y3 = 0; + + var result = []; + + for (var n = 0; n < seg; ++n) { + s1 += segr; + mc = Math.cos(s1); + ms = Math.sin(s1); + + x3 = cpsir1 * mc - spsir2 * ms + tx; + y3 = spsir1 * mc + cpsir2 * ms + ty; + var dx = -t * (cpsir1 * ms + spsir2 * mc); + var dy = -t * (spsir1 * ms - cpsir2 * mc); + + // CurveTo updates x0, y0 so need to restore it + var index = n * 6; + result[index] = Number(x2 + x0); + result[index + 1] = Number(y2 + y0); + result[index + 2] = Number(x3 - dx + x0); + result[index + 3] = Number(y3 - dy + y0); + result[index + 4] = Number(x3 + x0); + result[index + 5] = Number(y3 + y0); + + x2 = x3 + dx; + y2 = y3 + dy; + } + + return result; + }, + + /** + * Function: getBoundingBox + * + * Returns the bounding box for the rotated rectangle. + * + * Parameters: + * + * rect - to be rotated. + * angle - Number that represents the angle (in degrees). + * cx - Optional that represents the rotation center. If no + * rotation center is given then the center of rect is used. + */ + getBoundingBox: function (rect, rotation, cx) { + var result = null; + + if (rect != null && rotation != null && rotation != 0) { + var rad = mxUtils.toRadians(rotation); + var cos = Math.cos(rad); + var sin = Math.sin(rad); + + cx = cx != null ? cx : new mxPoint(rect.x + rect.width / 2, rect.y + rect.height / 2); + + var p1 = new mxPoint(rect.x, rect.y); + var p2 = new mxPoint(rect.x + rect.width, rect.y); + var p3 = new mxPoint(p2.x, rect.y + rect.height); + var p4 = new mxPoint(rect.x, p3.y); + + p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx); + p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx); + p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx); + p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx); + + result = new mxRectangle(p1.x, p1.y, 0, 0); + result.add(new mxRectangle(p2.x, p2.y, 0, 0)); + result.add(new mxRectangle(p3.x, p3.y, 0, 0)); + result.add(new mxRectangle(p4.x, p4.y, 0, 0)); + } + + return result; + }, + + /** + * Function: getRotatedPoint + * + * Rotates the given point by the given cos and sin. + */ + getRotatedPoint: function (pt, cos, sin, c) { + c = c != null ? c : new mxPoint(); + var x = pt.x - c.x; + var y = pt.y - c.y; + + var x1 = x * cos - y * sin; + var y1 = y * cos + x * sin; + + return new mxPoint(x1 + c.x, y1 + c.y); + }, + + /** + * Returns an integer mask of the port constraints of the given map + * @param dict the style map to determine the port constraints for + * @param defaultValue Default value to return if the key is undefined. + * @return the mask of port constraint directions + * + * Parameters: + * + * terminal - that represents the terminal. + * edge - that represents the edge. + * source - Boolean that specifies if the terminal is the source terminal. + * defaultValue - Default value to be returned. + */ + getPortConstraints: function (terminal, edge, source, defaultValue) { + var value = mxUtils.getValue( + terminal.style, + mxConstants.STYLE_PORT_CONSTRAINT, + mxUtils.getValue( + edge.style, + source ? mxConstants.STYLE_SOURCE_PORT_CONSTRAINT : mxConstants.STYLE_TARGET_PORT_CONSTRAINT, + null + ) + ); + + if (value == null) { + return defaultValue; + } else { + var directions = value.toString(); + var returnValue = mxConstants.DIRECTION_MASK_NONE; + var constraintRotationEnabled = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT_ROTATION, 0); + var rotation = 0; + + if (constraintRotationEnabled == 1) { + rotation = mxUtils.getValue(terminal.style, mxConstants.STYLE_ROTATION, 0); + } + + var quad = 0; + + if (rotation > 45) { + quad = 1; + + if (rotation >= 135) { + quad = 2; + } + } else if (rotation < -45) { + quad = 3; + + if (rotation <= -135) { + quad = 2; + } + } + + if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0) { + switch (quad) { + case 0: + returnValue |= mxConstants.DIRECTION_MASK_NORTH; + break; + case 1: + returnValue |= mxConstants.DIRECTION_MASK_EAST; + break; + case 2: + returnValue |= mxConstants.DIRECTION_MASK_SOUTH; + break; + case 3: + returnValue |= mxConstants.DIRECTION_MASK_WEST; + break; + } + } + if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0) { + switch (quad) { + case 0: + returnValue |= mxConstants.DIRECTION_MASK_WEST; + break; + case 1: + returnValue |= mxConstants.DIRECTION_MASK_NORTH; + break; + case 2: + returnValue |= mxConstants.DIRECTION_MASK_EAST; + break; + case 3: + returnValue |= mxConstants.DIRECTION_MASK_SOUTH; + break; + } + } + if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0) { + switch (quad) { + case 0: + returnValue |= mxConstants.DIRECTION_MASK_SOUTH; + break; + case 1: + returnValue |= mxConstants.DIRECTION_MASK_WEST; + break; + case 2: + returnValue |= mxConstants.DIRECTION_MASK_NORTH; + break; + case 3: + returnValue |= mxConstants.DIRECTION_MASK_EAST; + break; + } + } + if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0) { + switch (quad) { + case 0: + returnValue |= mxConstants.DIRECTION_MASK_EAST; + break; + case 1: + returnValue |= mxConstants.DIRECTION_MASK_SOUTH; + break; + case 2: + returnValue |= mxConstants.DIRECTION_MASK_WEST; + break; + case 3: + returnValue |= mxConstants.DIRECTION_MASK_NORTH; + break; + } + } + + return returnValue; + } + }, + + /** + * Function: reversePortConstraints + * + * Reverse the port constraint bitmask. For example, north | east + * becomes south | west + */ + reversePortConstraints: function (constraint) { + var result = 0; + + result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3; + result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1; + result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1; + result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3; + + return result; + }, + + /** + * Function: findNearestSegment + * + * Finds the index of the nearest segment on the given cell state for + * the specified coordinate pair. + */ + findNearestSegment: function (state, x, y) { + var index = -1; + + if (state.absolutePoints.length > 0) { + var last = state.absolutePoints[0]; + var min = null; + + for (var i = 1; i < state.absolutePoints.length; i++) { + var current = state.absolutePoints[i]; + var dist = mxUtils.ptSegDistSq(last.x, last.y, current.x, current.y, x, y); + + if (min == null || dist < min) { + min = dist; + index = i - 1; + } + + last = current; + } + } + + return index; + }, + + /** + * Function: getDirectedBounds + * + * Adds the given margins to the given rectangle and rotates and flips the + * rectangle according to the respective styles in style. + */ + getDirectedBounds: function (rect, m, style, flipH, flipV) { + var d = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST); + flipH = flipH != null ? flipH : mxUtils.getValue(style, mxConstants.STYLE_FLIPH, false); + flipV = flipV != null ? flipV : mxUtils.getValue(style, mxConstants.STYLE_FLIPV, false); + + m.x = Math.round(Math.max(0, Math.min(rect.width, m.x))); + m.y = Math.round(Math.max(0, Math.min(rect.height, m.y))); + m.width = Math.round(Math.max(0, Math.min(rect.width, m.width))); + m.height = Math.round(Math.max(0, Math.min(rect.height, m.height))); + + if ( + (flipV && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) || + (flipH && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)) + ) { + var tmp = m.x; + m.x = m.width; + m.width = tmp; + } + + if ( + (flipH && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) || + (flipV && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)) + ) { + var tmp = m.y; + m.y = m.height; + m.height = tmp; + } + + var m2 = mxRectangle.fromRectangle(m); + + if (d == mxConstants.DIRECTION_SOUTH) { + m2.y = m.x; + m2.x = m.height; + m2.width = m.y; + m2.height = m.width; + } else if (d == mxConstants.DIRECTION_WEST) { + m2.y = m.height; + m2.x = m.width; + m2.width = m.x; + m2.height = m.y; + } else if (d == mxConstants.DIRECTION_NORTH) { + m2.y = m.width; + m2.x = m.y; + m2.width = m.height; + m2.height = m.x; + } + + return new mxRectangle(rect.x + m2.x, rect.y + m2.y, rect.width - m2.width - m2.x, rect.height - m2.height - m2.y); + }, + + /** + * Function: getPerimeterPoint + * + * Returns the intersection between the polygon defined by the array of + * points and the line between center and point. + */ + getPerimeterPoint: function (pts, center, point) { + var min = null; + + for (var i = 0; i < pts.length - 1; i++) { + var pt = mxUtils.intersection( + pts[i].x, + pts[i].y, + pts[i + 1].x, + pts[i + 1].y, + center.x, + center.y, + point.x, + point.y + ); + + if (pt != null) { + var dx = point.x - pt.x; + var dy = point.y - pt.y; + var ip = { p: pt, distSq: dy * dy + dx * dx }; + + if (ip != null && (min == null || min.distSq > ip.distSq)) { + min = ip; + } + } + } + + return min != null ? min.p : null; + }, + + /** + * Function: rectangleIntersectsSegment + * + * Returns true if the given rectangle intersects the given segment. + * + * Parameters: + * + * bounds - that represents the rectangle. + * p1 - that represents the first point of the segment. + * p2 - that represents the second point of the segment. + */ + rectangleIntersectsSegment: function (bounds, p1, p2) { + var top = bounds.y; + var left = bounds.x; + var bottom = top + bounds.height; + var right = left + bounds.width; + + // Find min and max X for the segment + var minX = p1.x; + var maxX = p2.x; + + if (p1.x > p2.x) { + minX = p2.x; + maxX = p1.x; + } + + // Find the intersection of the segment's and rectangle's x-projections + if (maxX > right) { + maxX = right; + } + + if (minX < left) { + minX = left; + } + + if (minX > maxX) { + // If their projections do not intersect return false + return false; + } + + // Find corresponding min and max Y for min and max X we found before + var minY = p1.y; + var maxY = p2.y; + var dx = p2.x - p1.x; + + if (Math.abs(dx) > 0.0000001) { + var a = (p2.y - p1.y) / dx; + var b = p1.y - a * p1.x; + minY = a * minX + b; + maxY = a * maxX + b; + } + + if (minY > maxY) { + var tmp = maxY; + maxY = minY; + minY = tmp; + } + + // Find the intersection of the segment's and rectangle's y-projections + if (maxY > bottom) { + maxY = bottom; + } + + if (minY < top) { + minY = top; + } + + if (minY > maxY) { + // If Y-projections do not intersect return false + return false; + } + + return true; + }, + + /** + * Function: contains + * + * Returns true if the specified point (x, y) is contained in the given rectangle. + * + * Parameters: + * + * bounds - that represents the area. + * x - X-coordinate of the point. + * y - Y-coordinate of the point. + */ + contains: function (bounds, x, y) { + return bounds.x <= x && bounds.x + bounds.width >= x && bounds.y <= y && bounds.y + bounds.height >= y; + }, + + /** + * Function: intersects + * + * Returns true if the two rectangles intersect. + * + * Parameters: + * + * a - to be checked for intersection. + * b - to be checked for intersection. + */ + intersects: function (a, b) { + var tw = a.width; + var th = a.height; + var rw = b.width; + var rh = b.height; + + if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) { + return false; + } + + var tx = a.x; + var ty = a.y; + var rx = b.x; + var ry = b.y; + + rw += rx; + rh += ry; + tw += tx; + th += ty; + + return (rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry); + }, + + /** + * Function: intersectsHotspot + * + * Returns true if the state and the hotspot intersect. + * + * Parameters: + * + * state - + * x - X-coordinate. + * y - Y-coordinate. + * hotspot - Optional size of the hostpot. + * min - Optional min size of the hostpot. + * max - Optional max size of the hostpot. + */ + intersectsHotspot: function (state, x, y, hotspot, min, max) { + hotspot = hotspot != null ? hotspot : 1; + min = min != null ? min : 0; + max = max != null ? max : 0; + + if (hotspot > 0) { + var cx = state.getCenterX(); + var cy = state.getCenterY(); + var w = state.width; + var h = state.height; + + var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale; + + if (start > 0) { + if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true)) { + cy = state.y + start / 2; + h = start; + } else { + cx = state.x + start / 2; + w = start; + } + } + + w = Math.max(min, w * hotspot); + h = Math.max(min, h * hotspot); + + if (max > 0) { + w = Math.min(w, max); + h = Math.min(h, max); + } + + var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h); + var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0); + + if (alpha != 0) { + var cos = Math.cos(-alpha); + var sin = Math.sin(-alpha); + var cx = new mxPoint(state.getCenterX(), state.getCenterY()); + var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx); + x = pt.x; + y = pt.y; + } + + return mxUtils.contains(rect, x, y); + } + + return true; + }, + + /** + * Function: getOffset + * + * Returns the offset for the specified container as an . The + * offset is the distance from the top left corner of the container to the + * top left corner of the document. + * + * Parameters: + * + * container - DOM node to return the offset for. + * scollOffset - Optional boolean to add the scroll offset of the document. + * Default is false. + */ + getOffset: function (container, scrollOffset, withOffsetParent = false) { + var offsetLeft = 0; + var offsetTop = 0; + + if (withOffsetParent) { + offsetLeft = container.offsetLeft; + offsetTop = container.offsetTop; + } else { + // Ignores document scroll origin for fixed elements + var fixed = false; + var node = container; + var b = document.body; + var d = document.documentElement; + + while (node != null && node != b && node != d && !fixed) { + var style = mxUtils.getCurrentStyle(node); + + if (style != null) { + fixed = fixed || style.position == 'fixed'; + } + + node = node.parentNode; + } + + if (!scrollOffset && !fixed) { + var offset = mxUtils.getDocumentScrollOrigin(container.ownerDocument); + offsetLeft += offset.x; + offsetTop += offset.y; + } + + var r = container.getBoundingClientRect(); + + if (r != null) { + offsetLeft += r.left; + offsetTop += r.top; + } + } + + return new mxPoint(offsetLeft, offsetTop); + }, + + /** + * Function: getDocumentScrollOrigin + * + * Returns the scroll origin of the given document or the current document + * if no document is given. + */ + getDocumentScrollOrigin: function (doc) { + if (mxClient.IS_QUIRKS) { + return new mxPoint(doc.body.scrollLeft, doc.body.scrollTop); + } else { + var wnd = doc.defaultView || doc.parentWindow; + + var x = + wnd != null && window.pageXOffset !== undefined + ? window.pageXOffset + : (document.documentElement || document.body.parentNode || document.body).scrollLeft; + var y = + wnd != null && window.pageYOffset !== undefined + ? window.pageYOffset + : (document.documentElement || document.body.parentNode || document.body).scrollTop; + + return new mxPoint(x, y); + } + }, + + /** + * Function: getScrollOrigin + * + * Returns the top, left corner of the viewrect as an . + * + * Parameters: + * + * node - DOM node whose scroll origin should be returned. + * includeAncestors - Whether the scroll origin of the ancestors should be + * included. Default is false. + * includeDocument - Whether the scroll origin of the document should be + * included. Default is true. + */ + getScrollOrigin: function (node, includeAncestors, includeDocument) { + includeAncestors = includeAncestors != null ? includeAncestors : false; + includeDocument = includeDocument != null ? includeDocument : true; + + var doc = node != null ? node.ownerDocument : document; + var b = doc.body; + var d = doc.documentElement; + var result = new mxPoint(); + var fixed = false; + + while (node != null && node != b && node != d) { + if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop)) { + result.x += node.scrollLeft; + result.y += node.scrollTop; + } + + var style = mxUtils.getCurrentStyle(node); + + if (style != null) { + fixed = fixed || style.position == 'fixed'; + } + + node = includeAncestors ? node.parentNode : null; + } + + if (!fixed && includeDocument) { + var origin = mxUtils.getDocumentScrollOrigin(doc); + + result.x += origin.x; + result.y += origin.y; + } + + return result; + }, + + /** + * Function: convertPoint + * + * Converts the specified point (x, y) using the offset of the specified + * container and returns a new with the result. + * + * (code) + * var pt = mxUtils.convertPoint(graph.container, + * mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + * (end) + * + * Parameters: + * + * container - DOM node to use for the offset. + * x - X-coordinate of the point to be converted. + * y - Y-coordinate of the point to be converted. + */ + convertPoint: function (container, x, y) { + var origin = mxUtils.getScrollOrigin(container, false); + var offset = mxUtils.getOffset(container); + + offset.x -= origin.x; + offset.y -= origin.y; + + return new mxPoint(x - offset.x, y - offset.y); + }, + + /** + * Function: ltrim + * + * Strips all whitespaces from the beginning of the string. Without the + * second parameter, this will trim these characters: + * + * - " " (ASCII 32 (0x20)), an ordinary space + * - "\t" (ASCII 9 (0x09)), a tab + * - "\n" (ASCII 10 (0x0A)), a new line (line feed) + * - "\r" (ASCII 13 (0x0D)), a carriage return + * - "\0" (ASCII 0 (0x00)), the NUL-byte + * - "\x0B" (ASCII 11 (0x0B)), a vertical tab + */ + ltrim: function (str, chars) { + chars = chars || '\\s'; + + return str != null ? str.replace(new RegExp('^[' + chars + ']+', 'g'), '') : null; + }, + + /** + * Function: rtrim + * + * Strips all whitespaces from the end of the string. Without the second + * parameter, this will trim these characters: + * + * - " " (ASCII 32 (0x20)), an ordinary space + * - "\t" (ASCII 9 (0x09)), a tab + * - "\n" (ASCII 10 (0x0A)), a new line (line feed) + * - "\r" (ASCII 13 (0x0D)), a carriage return + * - "\0" (ASCII 0 (0x00)), the NUL-byte + * - "\x0B" (ASCII 11 (0x0B)), a vertical tab + */ + rtrim: function (str, chars) { + chars = chars || '\\s'; + + return str != null ? str.replace(new RegExp('[' + chars + ']+$', 'g'), '') : null; + }, + + /** + * Function: trim + * + * Strips all whitespaces from both end of the string. + * Without the second parameter, Javascript function will trim these + * characters: + * + * - " " (ASCII 32 (0x20)), an ordinary space + * - "\t" (ASCII 9 (0x09)), a tab + * - "\n" (ASCII 10 (0x0A)), a new line (line feed) + * - "\r" (ASCII 13 (0x0D)), a carriage return + * - "\0" (ASCII 0 (0x00)), the NUL-byte + * - "\x0B" (ASCII 11 (0x0B)), a vertical tab + */ + trim: function (str, chars) { + return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars); + }, + + /** + * Function: isNumeric + * + * Returns true if the specified value is numeric, that is, if it is not + * null, not an empty string, not a HEX number and isNaN returns false. + * + * Parameters: + * + * n - String representing the possibly numeric value. + */ + isNumeric: function (n) { + return !isNaN(parseFloat(n)) && isFinite(n) && (typeof n != 'string' || n.toLowerCase().indexOf('0x') < 0); + }, + + /** + * Function: isInteger + * + * Returns true if the given value is an valid integer number. + * + * Parameters: + * + * n - String representing the possibly numeric value. + */ + isInteger: function (n) { + return String(parseInt(n)) === String(n); + }, + + /** + * Function: mod + * + * Returns the remainder of division of n by m. You should use this instead + * of the built-in operation as the built-in operation does not properly + * handle negative numbers. + */ + mod: function (n, m) { + return ((n % m) + m) % m; + }, + + /** + * Function: intersection + * + * Returns the intersection of two lines as an . + * + * Parameters: + * + * x0 - X-coordinate of the first line's startpoint. + * y0 - X-coordinate of the first line's startpoint. + * x1 - X-coordinate of the first line's endpoint. + * y1 - Y-coordinate of the first line's endpoint. + * x2 - X-coordinate of the second line's startpoint. + * y2 - Y-coordinate of the second line's startpoint. + * x3 - X-coordinate of the second line's endpoint. + * y3 - Y-coordinate of the second line's endpoint. + */ + intersection: function (x0, y0, x1, y1, x2, y2, x3, y3) { + var denom = (y3 - y2) * (x1 - x0) - (x3 - x2) * (y1 - y0); + var nume_a = (x3 - x2) * (y0 - y2) - (y3 - y2) * (x0 - x2); + var nume_b = (x1 - x0) * (y0 - y2) - (y1 - y0) * (x0 - x2); + + var ua = nume_a / denom; + var ub = nume_b / denom; + + if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) { + // Get the intersection point + var x = x0 + ua * (x1 - x0); + var y = y0 + ua * (y1 - y0); + + return new mxPoint(x, y); + } + + // No intersection + return null; + }, + + /** + * Function: ptSegDistSq + * + * Returns the square distance between a segment and a point. To get the + * distance between a point and a line (with infinite length) use + * . + * + * Parameters: + * + * x1 - X-coordinate of the startpoint of the segment. + * y1 - Y-coordinate of the startpoint of the segment. + * x2 - X-coordinate of the endpoint of the segment. + * y2 - Y-coordinate of the endpoint of the segment. + * px - X-coordinate of the point. + * py - Y-coordinate of the point. + */ + ptSegDistSq: function (x1, y1, x2, y2, px, py) { + x2 -= x1; + y2 -= y1; + + px -= x1; + py -= y1; + + var dotprod = px * x2 + py * y2; + var projlenSq; + + if (dotprod <= 0.0) { + projlenSq = 0.0; + } else { + px = x2 - px; + py = y2 - py; + dotprod = px * x2 + py * y2; + + if (dotprod <= 0.0) { + projlenSq = 0.0; + } else { + projlenSq = (dotprod * dotprod) / (x2 * x2 + y2 * y2); + } + } + + var lenSq = px * px + py * py - projlenSq; + + if (lenSq < 0) { + lenSq = 0; + } + + return lenSq; + }, + + /** + * Function: ptLineDist + * + * Returns the distance between a line defined by two points and a point. + * To get the distance between a point and a segment (with a specific + * length) use . + * + * Parameters: + * + * x1 - X-coordinate of point 1 of the line. + * y1 - Y-coordinate of point 1 of the line. + * x2 - X-coordinate of point 1 of the line. + * y2 - Y-coordinate of point 1 of the line. + * px - X-coordinate of the point. + * py - Y-coordinate of the point. + */ + ptLineDist: function (x1, y1, x2, y2, px, py) { + return ( + Math.abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) / + Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1)) + ); + }, + + /** + * Function: relativeCcw + * + * Returns 1 if the given point on the right side of the segment, 0 if its + * on the segment, and -1 if the point is on the left side of the segment. + * + * Parameters: + * + * x1 - X-coordinate of the startpoint of the segment. + * y1 - Y-coordinate of the startpoint of the segment. + * x2 - X-coordinate of the endpoint of the segment. + * y2 - Y-coordinate of the endpoint of the segment. + * px - X-coordinate of the point. + * py - Y-coordinate of the point. + */ + relativeCcw: function (x1, y1, x2, y2, px, py) { + x2 -= x1; + y2 -= y1; + px -= x1; + py -= y1; + var ccw = px * y2 - py * x2; + + if (ccw == 0.0) { + ccw = px * x2 + py * y2; + + if (ccw > 0.0) { + px -= x2; + py -= y2; + ccw = px * x2 + py * y2; + + if (ccw < 0.0) { + ccw = 0.0; + } + } + } + + return ccw < 0.0 ? -1 : ccw > 0.0 ? 1 : 0; + }, + + /** + * Function: animateChanges + * + * See . This is for backwards compatibility and + * will be removed later. + */ + animateChanges: function (graph, changes) { + // LATER: Deprecated, remove this function + mxEffects.animateChanges.apply(this, arguments); + }, + + /** + * Function: cascadeOpacity + * + * See . This is for backwards compatibility and + * will be removed later. + */ + cascadeOpacity: function (graph, cell, opacity) { + mxEffects.cascadeOpacity.apply(this, arguments); + }, + + /** + * Function: fadeOut + * + * See . This is for backwards compatibility and + * will be removed later. + */ + fadeOut: function (node, from, remove, step, delay, isEnabled) { + mxEffects.fadeOut.apply(this, arguments); + }, + + /** + * Function: setOpacity + * + * Sets the opacity of the specified DOM node to the given value in %. + * + * Parameters: + * + * node - DOM node to set the opacity for. + * value - Opacity in %. Possible values are between 0 and 100. + */ + setOpacity: function (node, value) { + if (mxUtils.isVml(node)) { + if (value >= 100) { + node.style.filter = ''; + } else { + // TODO: Why is the division by 5 needed in VML? + node.style.filter = 'alpha(opacity=' + value / 5 + ')'; + } + } else if (mxClient.IS_IE && (typeof document.documentMode === 'undefined' || document.documentMode < 9)) { + if (value >= 100) { + node.style.filter = ''; + } else { + node.style.filter = 'alpha(opacity=' + value + ')'; + } + } else { + node.style.opacity = value / 100; + } + }, + + /** + * Function: createImage + * + * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in + * quirks mode. + * + * Parameters: + * + * src - URL that points to the image to be displayed. + */ + createImage: function (src) { + var imageNode = null; + + if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat') { + imageNode = document.createElement(mxClient.VML_PREFIX + ':image'); + imageNode.setAttribute('src', src); + imageNode.style.borderStyle = 'none'; + } else { + imageNode = document.createElement('img'); + imageNode.setAttribute('src', src); + imageNode.setAttribute('border', '0'); + } + + return imageNode; + }, + + /** + * Function: sortCells + * + * Sorts the given cells according to the order in the cell hierarchy. + * Ascending is optional and defaults to true. + */ + sortCells: function (cells, ascending) { + ascending = ascending != null ? ascending : true; + var lookup = new mxDictionary(); + cells.sort(function (o1, o2) { + var p1 = lookup.get(o1); + + if (p1 == null) { + p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR); + lookup.put(o1, p1); + } + + var p2 = lookup.get(o2); + + if (p2 == null) { + p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR); + lookup.put(o2, p2); + } + + var comp = mxCellPath.compare(p1, p2); + + return comp == 0 ? 0 : comp > 0 == ascending ? 1 : -1; + }); + + return cells; + }, + + /** + * Function: getStylename + * + * Returns the stylename in a style of the form [(stylename|key=value);] or + * an empty string if the given style does not contain a stylename. + * + * Parameters: + * + * style - String of the form [(stylename|key=value);]. + */ + getStylename: function (style) { + if (style != null) { + var pairs = style.split(';'); + var stylename = pairs[0]; + + if (stylename.indexOf('=') < 0) { + return stylename; + } + } + + return ''; + }, + + /** + * Function: getStylenames + * + * Returns the stylenames in a style of the form [(stylename|key=value);] + * or an empty array if the given style does not contain any stylenames. + * + * Parameters: + * + * style - String of the form [(stylename|key=value);]. + */ + getStylenames: function (style) { + var result = []; + + if (style != null) { + var pairs = style.split(';'); + + for (var i = 0; i < pairs.length; i++) { + if (pairs[i].indexOf('=') < 0) { + result.push(pairs[i]); + } + } + } + + return result; + }, + + /** + * Function: indexOfStylename + * + * Returns the index of the given stylename in the given style. This + * returns -1 if the given stylename does not occur (as a stylename) in the + * given style, otherwise it returns the index of the first character. + */ + indexOfStylename: function (style, stylename) { + if (style != null && stylename != null) { + var tokens = style.split(';'); + var pos = 0; + + for (var i = 0; i < tokens.length; i++) { + if (tokens[i] == stylename) { + return pos; + } + + pos += tokens[i].length + 1; + } + } + + return -1; + }, + + /** + * Function: addStylename + * + * Adds the specified stylename to the given style if it does not already + * contain the stylename. + */ + addStylename: function (style, stylename) { + if (mxUtils.indexOfStylename(style, stylename) < 0) { + if (style == null) { + style = ''; + } else if (style.length > 0 && style.charAt(style.length - 1) != ';') { + style += ';'; + } + + style += stylename; + } + + return style; + }, + + /** + * Function: removeStylename + * + * Removes all occurrences of the specified stylename in the given style + * and returns the updated style. Trailing semicolons are not preserved. + */ + removeStylename: function (style, stylename) { + var result = []; + + if (style != null) { + var tokens = style.split(';'); + + for (var i = 0; i < tokens.length; i++) { + if (tokens[i] != stylename) { + result.push(tokens[i]); + } + } + } + + return result.join(';'); + }, + + /** + * Function: removeAllStylenames + * + * Removes all stylenames from the given style and returns the updated + * style. + */ + removeAllStylenames: function (style) { + var result = []; + + if (style != null) { + var tokens = style.split(';'); + + for (var i = 0; i < tokens.length; i++) { + // Keeps the key, value assignments + if (tokens[i].indexOf('=') >= 0) { + result.push(tokens[i]); + } + } + } + + return result.join(';'); + }, + + /** + * Function: setCellStyles + * + * Assigns the value for the given key in the styles of the given cells, or + * removes the key from the styles if the value is null. + * + * Parameters: + * + * model - to execute the transaction in. + * cells - Array of to be updated. + * key - Key of the style to be changed. + * value - New value for the given key. + */ + setCellStyles: function (model, cells, key, value) { + if (cells != null && cells.length > 0) { + model.beginUpdate(); + try { + for (var i = 0; i < cells.length; i++) { + if (cells[i] != null) { + var style = mxUtils.setStyle(model.getStyle(cells[i]), key, value); + model.setStyle(cells[i], style); + } + } + } finally { + model.endUpdate(); + } + } + }, + + /** + * Function: setStyle + * + * Adds or removes the given key, value pair to the style and returns the + * new style. If value is null or zero length then the key is removed from + * the style. This is for cell styles, not for CSS styles. + * + * Parameters: + * + * style - String of the form [(stylename|key=value);]. + * key - Key of the style to be changed. + * value - New value for the given key. + */ + setStyle: function (style, key, value) { + var isValue = value != null && (typeof value.length == 'undefined' || value.length > 0); + + if (style == null || style.length == 0) { + if (isValue) { + style = key + '=' + value + ';'; + } + } else { + if (style.substring(0, key.length + 1) == key + '=') { + var next = style.indexOf(';'); + + if (isValue) { + style = key + '=' + value + (next < 0 ? ';' : style.substring(next)); + } else { + style = next < 0 || next == style.length - 1 ? '' : style.substring(next + 1); + } + } else { + var index = style.indexOf(';' + key + '='); + + if (index < 0) { + if (isValue) { + var sep = style.charAt(style.length - 1) == ';' ? '' : ';'; + style = style + sep + key + '=' + value + ';'; + } + } else { + var next = style.indexOf(';', index + 1); + + if (isValue) { + style = style.substring(0, index + 1) + key + '=' + value + (next < 0 ? ';' : style.substring(next)); + } else { + style = style.substring(0, index) + (next < 0 ? ';' : style.substring(next)); + } + } + } + } + + return style; + }, + + /** + * Function: setCellStyleFlags + * + * Sets or toggles the flag bit for the given key in the cell's styles. + * If value is null then the flag is toggled. + * + * Example: + * + * (code) + * var cells = graph.getSelectionCells(); + * mxUtils.setCellStyleFlags(graph.model, + * cells, + * mxConstants.STYLE_FONTSTYLE, + * mxConstants.FONT_BOLD); + * (end) + * + * Toggles the bold font style. + * + * Parameters: + * + * model - that contains the cells. + * cells - Array of to change the style for. + * key - Key of the style to be changed. + * flag - Integer for the bit to be changed. + * value - Optional boolean value for the flag. + */ + setCellStyleFlags: function (model, cells, key, flag, value) { + if (cells != null && cells.length > 0) { + model.beginUpdate(); + try { + for (var i = 0; i < cells.length; i++) { + if (cells[i] != null) { + var style = mxUtils.setStyleFlag(model.getStyle(cells[i]), key, flag, value); + model.setStyle(cells[i], style); + } + } + } finally { + model.endUpdate(); + } + } + }, + + /** + * Function: setStyleFlag + * + * Sets or removes the given key from the specified style and returns the + * new style. If value is null then the flag is toggled. + * + * Parameters: + * + * style - String of the form [(stylename|key=value);]. + * key - Key of the style to be changed. + * flag - Integer for the bit to be changed. + * value - Optional boolean value for the given flag. + */ + setStyleFlag: function (style, key, flag, value) { + if (style == null || style.length == 0) { + if (value || value == null) { + style = key + '=' + flag; + } else { + style = key + '=0'; + } + } else { + var index = style.indexOf(key + '='); + + if (index < 0) { + var sep = style.charAt(style.length - 1) == ';' ? '' : ';'; + + if (value || value == null) { + style = style + sep + key + '=' + flag; + } else { + style = style + sep + key + '=0'; + } + } else { + var cont = style.indexOf(';', index); + var tmp = ''; + + if (cont < 0) { + tmp = style.substring(index + key.length + 1); + } else { + tmp = style.substring(index + key.length + 1, cont); + } + + if (value == null) { + tmp = parseInt(tmp) ^ flag; + } else if (value) { + tmp = parseInt(tmp) | flag; + } else { + tmp = parseInt(tmp) & ~flag; + } + + style = style.substring(0, index) + key + '=' + tmp + (cont >= 0 ? style.substring(cont) : ''); + } + } + + return style; + }, + + /** + * Function: getAlignmentAsPoint + * + * Returns an that represents the horizontal and vertical alignment + * for numeric computations. X is -0.5 for center, -1 for right and 0 for + * left alignment. Y is -0.5 for middle, -1 for bottom and 0 for top + * alignment. Default values for missing arguments is top, left. + */ + getAlignmentAsPoint: function (align, valign) { + var dx = -0.5; + var dy = -0.5; + + // Horizontal alignment + if (align == mxConstants.ALIGN_LEFT) { + dx = 0; + } else if (align == mxConstants.ALIGN_RIGHT) { + dx = -1; + } + + // Vertical alignment + if (valign == mxConstants.ALIGN_TOP) { + dy = 0; + } else if (valign == mxConstants.ALIGN_BOTTOM) { + dy = -1; + } + + return new mxPoint(dx, dy); + }, + + /** + * Function: getSizeForString + * + * Returns an with the size (width and height in pixels) of + * the given string. The string may contain HTML markup. Newlines should be + * converted to
before calling this method. The caller is responsible + * for sanitizing the HTML markup. + * + * Example: + * + * (code) + * var label = graph.getLabel(cell).replace(/\n/g, "
"); + * var size = graph.getSizeForString(label); + * (end) + * + * Parameters: + * + * text - String whose size should be returned. + * fontSize - Integer that specifies the font size in pixels. Default is + * . + * fontFamily - String that specifies the name of the font family. Default + * is . + * textWidth - Optional width for text wrapping. + * fontStyle - Optional font style. + */ + getSizeForString: function (text, fontSize, fontFamily, textWidth, fontStyle) { + fontSize = fontSize != null ? fontSize : mxConstants.DEFAULT_FONTSIZE; + fontFamily = fontFamily != null ? fontFamily : mxConstants.DEFAULT_FONTFAMILY; + var div = document.createElement('div'); + + // Sets the font size and family + div.style.fontFamily = fontFamily; + div.style.fontSize = Math.round(fontSize) + 'px'; + div.style.lineHeight = Math.round(fontSize * mxConstants.LINE_HEIGHT) + 'px'; + + // Sets the font style + if (fontStyle != null) { + if ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + div.style.fontWeight = 'bold'; + } + + if ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + div.style.fontStyle = 'italic'; + } + + var txtDecor = []; + + if ((fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + txtDecor.push('underline'); + } + + if ((fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { + txtDecor.push('line-through'); + } + + if (txtDecor.length > 0) { + div.style.textDecoration = txtDecor.join(' '); + } + } + + // Disables block layout and outside wrapping and hides the div + div.style.position = 'absolute'; + div.style.visibility = 'hidden'; + div.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block'; + div.style.zoom = '1'; + + if (textWidth != null) { + div.style.width = textWidth + 'px'; + div.style.whiteSpace = 'normal'; + } else { + div.style.whiteSpace = 'nowrap'; + } + + // Adds the text and inserts into DOM for updating of size + div.innerHTML = text; + document.body.appendChild(div); + + // Gets the size and removes from DOM + var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight); + document.body.removeChild(div); + + return size; + }, + + /** + * Function: getViewXml + */ + getViewXml: function (graph, scale, cells, x0, y0) { + x0 = x0 != null ? x0 : 0; + y0 = y0 != null ? y0 : 0; + scale = scale != null ? scale : 1; + + if (cells == null) { + var model = graph.getModel(); + cells = [model.getRoot()]; + } + + var view = graph.getView(); + var result = null; + + // Disables events on the view + var eventsEnabled = view.isEventsEnabled(); + view.setEventsEnabled(false); + + // Workaround for label bounds not taken into account for image export. + // Creates a temporary draw pane which is used for rendering the text. + // Text rendering is required for finding the bounds of the labels. + var drawPane = view.drawPane; + var overlayPane = view.overlayPane; + + if (graph.dialect == mxConstants.DIALECT_SVG) { + view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g'); + view.canvas.appendChild(view.drawPane); + + // Redirects cell overlays into temporary container + view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g'); + view.canvas.appendChild(view.overlayPane); + } else { + view.drawPane = view.drawPane.cloneNode(false); + view.canvas.appendChild(view.drawPane); + + // Redirects cell overlays into temporary container + view.overlayPane = view.overlayPane.cloneNode(false); + view.canvas.appendChild(view.overlayPane); + } + + // Resets the translation + var translate = view.getTranslate(); + view.translate = new mxPoint(x0, y0); + + // Creates the temporary cell states in the view + var temp = new mxTemporaryCellStates(graph.getView(), scale, cells); + + try { + var enc = new mxCodec(); + result = enc.encode(graph.getView()); + } finally { + temp.destroy(); + view.translate = translate; + view.canvas.removeChild(view.drawPane); + view.canvas.removeChild(view.overlayPane); + view.drawPane = drawPane; + view.overlayPane = overlayPane; + view.setEventsEnabled(eventsEnabled); + } + + return result; + }, + + /** + * Function: getScaleForPageCount + * + * Returns the scale to be used for printing the graph with the given + * bounds across the specifies number of pages with the given format. The + * scale is always computed such that it given the given amount or fewer + * pages in the print output. See for an example. + * + * Parameters: + * + * pageCount - Specifies the number of pages in the print output. + * graph - that should be printed. + * pageFormat - Optional that specifies the page format. + * Default is . + * border - The border along each side of every page. + */ + getScaleForPageCount: function (pageCount, graph, pageFormat, border) { + if (pageCount < 1) { + // We can't work with less than 1 page, return no scale + // change + return 1; + } + + pageFormat = pageFormat != null ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT; + border = border != null ? border : 0; + + var availablePageWidth = pageFormat.width - border * 2; + var availablePageHeight = pageFormat.height - border * 2; + + // Work out the number of pages required if the + // graph is not scaled. + var graphBounds = graph.getGraphBounds().clone(); + var sc = graph.getView().getScale(); + graphBounds.width /= sc; + graphBounds.height /= sc; + var graphWidth = graphBounds.width; + var graphHeight = graphBounds.height; + + var scale = 1; + + // The ratio of the width/height for each printer page + var pageFormatAspectRatio = availablePageWidth / availablePageHeight; + // The ratio of the width/height for the graph to be printer + var graphAspectRatio = graphWidth / graphHeight; + + // The ratio of horizontal pages / vertical pages for this + // graph to maintain its aspect ratio on this page format + var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio; + + // Factor the square root of the page count up and down + // by the pages aspect ratio to obtain a horizontal and + // vertical page count that adds up to the page count + // and has the correct aspect ratio + var pageRoot = Math.sqrt(pageCount); + var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio); + var numRowPages = pageRoot * pagesAspectRatioSqrt; + var numColumnPages = pageRoot / pagesAspectRatioSqrt; + + // These value are rarely more than 2 rounding downs away from + // a total that meets the page count. In cases of one being less + // than 1 page, the other value can be too high and take more iterations + // In this case, just change that value to be the page count, since + // we know the other value is 1 + if (numRowPages < 1 && numColumnPages > pageCount) { + var scaleChange = numColumnPages / pageCount; + numColumnPages = pageCount; + numRowPages /= scaleChange; + } + + if (numColumnPages < 1 && numRowPages > pageCount) { + var scaleChange = numRowPages / pageCount; + numRowPages = pageCount; + numColumnPages /= scaleChange; + } + + var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages); + + var numLoops = 0; + + // Iterate through while the rounded up number of pages comes to + // a total greater than the required number + while (currentTotalPages > pageCount) { + // Round down the page count (rows or columns) that is + // closest to its next integer down in percentage terms. + // i.e. Reduce the page total by reducing the total + // page area by the least possible amount + + var roundRowDownProportion = Math.floor(numRowPages) / numRowPages; + var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages; + + // If the round down proportion is, work out the proportion to + // round down to 1 page less + if (roundRowDownProportion == 1) { + roundRowDownProportion = Math.floor(numRowPages - 1) / numRowPages; + } + if (roundColumnDownProportion == 1) { + roundColumnDownProportion = Math.floor(numColumnPages - 1) / numColumnPages; + } + + // Check which rounding down is smaller, but in the case of very small roundings + // try the other dimension instead + var scaleChange = 1; + + // Use the higher of the two values + if (roundRowDownProportion > roundColumnDownProportion) { + scaleChange = roundRowDownProportion; + } else { + scaleChange = roundColumnDownProportion; + } + + numRowPages = numRowPages * scaleChange; + numColumnPages = numColumnPages * scaleChange; + currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages); + + numLoops++; + + if (numLoops > 10) { + break; + } + } + + // Work out the scale from the number of row pages required + // The column pages will give the same value + var posterWidth = availablePageWidth * numRowPages; + scale = posterWidth / graphWidth; + + // Allow for rounding errors + return scale * 0.99999; + }, + + /** + * Function: show + * + * Copies the styles and the markup from the graph's container into the + * given document and removes all cursor styles. The document is returned. + * + * This function should be called from within the document with the graph. + * If you experience problems with missing stylesheets in IE then try adding + * the domain to the trusted sites. + * + * Parameters: + * + * graph - to be copied. + * doc - Document where the new graph is created. + * x0 - X-coordinate of the graph view origin. Default is 0. + * y0 - Y-coordinate of the graph view origin. Default is 0. + * w - Optional width of the graph view. + * h - Optional height of the graph view. + */ + show: function (graph, doc, x0, y0, w, h) { + x0 = x0 != null ? x0 : 0; + y0 = y0 != null ? y0 : 0; + + if (doc == null) { + var wnd = window.open(); + doc = wnd.document; + } else { + doc.open(); + } + + // Workaround for missing print output in IE9 standards + if (document.documentMode == 9) { + doc.writeln(''); + } + + var bounds = graph.getGraphBounds(); + var dx = Math.ceil(x0 - bounds.x); + var dy = Math.ceil(y0 - bounds.y); + + if (w == null) { + w = Math.ceil(bounds.width + x0) + Math.ceil(Math.ceil(bounds.x) - bounds.x); + } + + if (h == null) { + h = Math.ceil(bounds.height + y0) + Math.ceil(Math.ceil(bounds.y) - bounds.y); + } + + // Needs a special way of creating the page so that no click is required + // to refresh the contents after the external CSS styles have been loaded. + // To avoid a click or programmatic refresh, the styleSheets[].cssText + // property is copied over from the original document. + if (mxClient.IS_IE || document.documentMode == 11) { + var html = ''; + + var base = document.getElementsByTagName('base'); + + for (var i = 0; i < base.length; i++) { + html += base[i].outerHTML; + } + + html += ''; + + // Copies the contents of the graph container + html += + '
'; + html += graph.container.innerHTML; + html += '
'; + + doc.writeln(html); + doc.close(); + } else { + doc.writeln(''); + + var base = document.getElementsByTagName('base'); + + for (var i = 0; i < base.length; i++) { + doc.writeln(mxUtils.getOuterHtml(base[i])); + } + + var links = document.getElementsByTagName('link'); + + for (var i = 0; i < links.length; i++) { + doc.writeln(mxUtils.getOuterHtml(links[i])); + } + + var styles = document.getElementsByTagName('style'); + + for (var i = 0; i < styles.length; i++) { + doc.writeln(mxUtils.getOuterHtml(styles[i])); + } + + doc.writeln(''); + doc.close(); + + var outer = doc.createElement('div'); + outer.position = 'absolute'; + outer.overflow = 'hidden'; + outer.style.width = w + 'px'; + outer.style.height = h + 'px'; + + // Required for HTML labels if foreignObjects are disabled + var div = doc.createElement('div'); + div.style.position = 'absolute'; + div.style.left = dx + 'px'; + div.style.top = dy + 'px'; + + var node = graph.container.firstChild; + var svg = null; + + while (node != null) { + var clone = node.cloneNode(true); + + if (node == graph.view.drawPane.ownerSVGElement) { + outer.appendChild(clone); + svg = clone; + } else { + div.appendChild(clone); + } + + node = node.nextSibling; + } + + doc.body.appendChild(outer); + + if (div.firstChild != null) { + doc.body.appendChild(div); + } + + if (svg != null) { + svg.style.minWidth = ''; + svg.style.minHeight = ''; + svg.firstChild.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); + } + } + + mxUtils.removeCursors(doc.body); + + return doc; + }, + + /** + * Function: printScreen + * + * Prints the specified graph using a new window and the built-in print + * dialog. + * + * This function should be called from within the document with the graph. + * + * Parameters: + * + * graph - to be printed. + */ + printScreen: function (graph) { + var wnd = window.open(); + var bounds = graph.getGraphBounds(); + mxUtils.show(graph, wnd.document); + + var print = function () { + wnd.focus(); + wnd.print(); + wnd.close(); + }; + + // Workaround for Google Chrome which needs a bit of a + // delay in order to render the SVG contents + if (mxClient.IS_GC) { + wnd.setTimeout(print, 500); + } else { + print(); + } + }, + + /** + * Function: popup + * + * Shows the specified text content in a new or a new browser + * window if isInternalWindow is false. + * + * Parameters: + * + * content - String that specifies the text to be displayed. + * isInternalWindow - Optional boolean indicating if an mxWindow should be + * used instead of a new browser window. Default is false. + */ + popup: function (content, isInternalWindow) { + if (isInternalWindow) { + var div = document.createElement('div'); + + div.style.overflow = 'scroll'; + div.style.width = '636px'; + div.style.height = '460px'; + + var pre = document.createElement('pre'); + pre.innerHTML = mxUtils.htmlEntities(content, false).replace(/\n/g, '
').replace(/ /g, ' '); + + div.appendChild(pre); + + var w = document.body.clientWidth; + var h = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight); + var wnd = new mxWindow('Popup Window', div, w / 2 - 320, h / 2 - 240, 640, 480, false, true); + + wnd.setClosable(true); + wnd.setVisible(true); + } else { + // Wraps up the XML content in a textarea + if (mxClient.IS_NS) { + var wnd = window.open(); + wnd.document.writeln('
' + mxUtils.htmlEntities(content) + '').replace(/ /g, ' ');
+        wnd.document.body.appendChild(pre);
+      }
+    }
+  },
+
+  /**
+   * Function: alert
+   *
+   * Displayss the given alert in a new dialog. This implementation uses the
+   * built-in alert function. This is used to display validation errors when
+   * connections cannot be changed or created.
+   *
+   * Parameters:
+   *
+   * message - String specifying the message to be displayed.
+   */
+  alert: function (message) {
+    alert(message);
+  },
+
+  /**
+   * Function: prompt
+   *
+   * Displays the given message in a prompt dialog. This implementation uses
+   * the built-in prompt function.
+   *
+   * Parameters:
+   *
+   * message - String specifying the message to be displayed.
+   * defaultValue - Optional string specifying the default value.
+   */
+  prompt: function (message, defaultValue) {
+    return prompt(message, defaultValue != null ? defaultValue : '');
+  },
+
+  /**
+   * Function: confirm
+   *
+   * Displays the given message in a confirm dialog. This implementation uses
+   * the built-in confirm function.
+   *
+   * Parameters:
+   *
+   * message - String specifying the message to be displayed.
+   */
+  confirm: function (message) {
+    return confirm(message);
+  },
+
+  /**
+   * Function: error
+   *
+   * Displays the given error message in a new  of the given width.
+   * If close is true then an additional close button is added to the window.
+   * The optional icon specifies the icon to be used for the window. Default
+   * is .
+   *
+   * Parameters:
+   *
+   * message - String specifying the message to be displayed.
+   * width - Integer specifying the width of the window.
+   * close - Optional boolean indicating whether to add a close button.
+   * icon - Optional icon for the window decoration.
+   */
+  error: function (message, width, close, icon) {
+    var div = document.createElement('div');
+    div.style.padding = '20px';
+
+    var img = document.createElement('img');
+    img.setAttribute('src', icon || mxUtils.errorImage);
+    img.setAttribute('valign', 'bottom');
+    img.style.verticalAlign = 'middle';
+    div.appendChild(img);
+
+    div.appendChild(document.createTextNode('\u00a0')); //  
+    div.appendChild(document.createTextNode('\u00a0')); //  
+    div.appendChild(document.createTextNode('\u00a0')); //  
+    mxUtils.write(div, message);
+
+    var w = document.body.clientWidth;
+    var h = document.body.clientHeight || document.documentElement.clientHeight;
+    var warn = new mxWindow(
+      mxResources.get(mxUtils.errorResource) || mxUtils.errorResource,
+      div,
+      (w - width) / 2,
+      h / 4,
+      width,
+      null,
+      false,
+      true
+    );
+
+    if (close) {
+      mxUtils.br(div);
+
+      var tmp = document.createElement('p');
+      var button = document.createElement('button');
+
+      if (mxClient.IS_IE) {
+        button.style.cssText = 'float:right';
+      } else {
+        button.setAttribute('style', 'float:right');
+      }
+
+      mxEvent.addListener(button, 'click', function (evt) {
+        warn.destroy();
+      });
+
+      mxUtils.write(button, mxResources.get(mxUtils.closeResource) || mxUtils.closeResource);
+
+      tmp.appendChild(button);
+      div.appendChild(tmp);
+
+      mxUtils.br(div);
+
+      warn.setClosable(true);
+    }
+
+    warn.setVisible(true);
+
+    return warn;
+  },
+
+  /**
+   * Function: makeDraggable
+   *
+   * Configures the given DOM element to act as a drag source for the
+   * specified graph. Returns a a new . If
+   *  is enabled then the x and y arguments must
+   * be used in funct to match the preview location.
+   *
+   * Example:
+   *
+   * (code)
+   * var funct = function(graph, evt, cell, x, y)
+   * {
+   *   if (graph.canImportCell(cell))
+   *   {
+   *     var parent = graph.getDefaultParent();
+   *     var vertex = null;
+   *
+   *     graph.getModel().beginUpdate();
+   *     try
+   *     {
+   * 	     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
+   *     }
+   *     finally
+   *     {
+   *       graph.getModel().endUpdate();
+   *     }
+   *
+   *     graph.setSelectionCell(vertex);
+   *   }
+   * }
+   *
+   * var img = document.createElement('img');
+   * img.setAttribute('src', 'editors/images/rectangle.gif');
+   * img.style.position = 'absolute';
+   * img.style.left = '0px';
+   * img.style.top = '0px';
+   * img.style.width = '16px';
+   * img.style.height = '16px';
+   *
+   * var dragImage = img.cloneNode(true);
+   * dragImage.style.width = '32px';
+   * dragImage.style.height = '32px';
+   * mxUtils.makeDraggable(img, graph, funct, dragImage);
+   * document.body.appendChild(img);
+   * (end)
+   *
+   * Parameters:
+   *
+   * element - DOM element to make draggable.
+   * graphF -  that acts as the drop target or a function that takes a
+   * mouse event and returns the current .
+   * funct - Function to execute on a successful drop.
+   * dragElement - Optional DOM node to be used for the drag preview.
+   * dx - Optional horizontal offset between the cursor and the drag
+   * preview.
+   * dy - Optional vertical offset between the cursor and the drag
+   * preview.
+   * autoscroll - Optional boolean that specifies if autoscroll should be
+   * used. Default is mxGraph.autoscroll.
+   * scalePreview - Optional boolean that specifies if the preview element
+   * should be scaled according to the graph scale. If this is true, then
+   * the offsets will also be scaled. Default is false.
+   * highlightDropTargets - Optional boolean that specifies if dropTargets
+   * should be highlighted. Default is true.
+   * getDropTarget - Optional function to return the drop target for a given
+   * location (x, y). Default is mxGraph.getCellAt.
+   */
+  makeDraggable: function (
+    element,
+    graphF,
+    funct,
+    dragElement,
+    dx,
+    dy,
+    autoscroll,
+    scalePreview,
+    highlightDropTargets,
+    getDropTarget
+  ) {
+    var dragSource = new mxDragSource(element, funct);
+    dragSource.dragOffset = new mxPoint(dx != null ? dx : 0, dy != null ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
+    dragSource.autoscroll = autoscroll;
+
+    // Cannot enable this by default. This needs to be enabled in the caller
+    // if the funct argument uses the new x- and y-arguments.
+    dragSource.setGuidesEnabled(false);
+
+    if (highlightDropTargets != null) {
+      dragSource.highlightDropTargets = highlightDropTargets;
+    }
+
+    // Overrides function to find drop target cell
+    if (getDropTarget != null) {
+      dragSource.getDropTarget = getDropTarget;
+    }
+
+    // Overrides function to get current graph
+    dragSource.getGraphForEvent = function (evt) {
+      return typeof graphF == 'function' ? graphF(evt) : graphF;
+    };
+
+    // Translates switches into dragSource customizations
+    if (dragElement != null) {
+      dragSource.createDragElement = function () {
+        return dragElement.cloneNode(true);
+      };
+
+      if (scalePreview) {
+        dragSource.createPreviewElement = function (graph) {
+          var elt = dragElement.cloneNode(true);
+
+          var w = parseInt(elt.style.width);
+          var h = parseInt(elt.style.height);
+          elt.style.width = Math.round(w * graph.view.scale) + 'px';
+          elt.style.height = Math.round(h * graph.view.scale) + 'px';
+
+          return elt;
+        };
+      }
+    }
+
+    return dragSource;
+  },
+};
+
+/**
+ * util/mxConstants.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxConstants = {
+  /**
+   * Class: mxConstants
+   *
+   * Defines various global constants.
+   *
+   * Variable: DEFAULT_HOTSPOT
+   *
+   * Defines the portion of the cell which is to be used as a connectable
+   * region. Default is 0.3. Possible values are 0 < x <= 1.
+   */
+  DEFAULT_HOTSPOT: 0.3,
+
+  /**
+   * Variable: MIN_HOTSPOT_SIZE
+   *
+   * Defines the minimum size in pixels of the portion of the cell which is
+   * to be used as a connectable region. Default is 8.
+   */
+  MIN_HOTSPOT_SIZE: 8,
+
+  /**
+   * Variable: MAX_HOTSPOT_SIZE
+   *
+   * Defines the maximum size in pixels of the portion of the cell which is
+   * to be used as a connectable region. Use 0 for no maximum. Default is 0.
+   */
+  MAX_HOTSPOT_SIZE: 0,
+
+  /**
+   * Variable: RENDERING_HINT_EXACT
+   *
+   * Defines the exact rendering hint.
+   */
+  RENDERING_HINT_EXACT: 'exact',
+
+  /**
+   * Variable: RENDERING_HINT_FASTER
+   *
+   * Defines the faster rendering hint.
+   */
+  RENDERING_HINT_FASTER: 'faster',
+
+  /**
+   * Variable: RENDERING_HINT_FASTEST
+   *
+   * Defines the fastest rendering hint.
+   */
+  RENDERING_HINT_FASTEST: 'fastest',
+
+  /**
+   * Variable: DIALECT_SVG
+   *
+   * Defines the SVG display dialect name.
+   */
+  DIALECT_SVG: 'svg',
+
+  /**
+   * Variable: DIALECT_VML
+   *
+   * Defines the VML display dialect name.
+   */
+  DIALECT_VML: 'vml',
+
+  /**
+   * Variable: DIALECT_MIXEDHTML
+   *
+   * Defines the mixed HTML display dialect name.
+   */
+  DIALECT_MIXEDHTML: 'mixedHtml',
+
+  /**
+   * Variable: DIALECT_PREFERHTML
+   *
+   * Defines the preferred HTML display dialect name.
+   */
+  DIALECT_PREFERHTML: 'preferHtml',
+
+  /**
+   * Variable: DIALECT_STRICTHTML
+   *
+   * Defines the strict HTML display dialect.
+   */
+  DIALECT_STRICTHTML: 'strictHtml',
+
+  /**
+   * Variable: NS_SVG
+   *
+   * Defines the SVG namespace.
+   */
+  NS_SVG: 'http://www.w3.org/2000/svg',
+
+  /**
+   * Variable: NS_XHTML
+   *
+   * Defines the XHTML namespace.
+   */
+  NS_XHTML: 'http://www.w3.org/1999/xhtml',
+
+  /**
+   * Variable: NS_XLINK
+   *
+   * Defines the XLink namespace.
+   */
+  NS_XLINK: 'http://www.w3.org/1999/xlink',
+
+  /**
+   * Variable: SHADOWCOLOR
+   *
+   * Defines the color to be used to draw shadows in shapes and windows.
+   * Default is gray.
+   */
+  SHADOWCOLOR: 'gray',
+
+  /**
+   * Variable: VML_SHADOWCOLOR
+   *
+   * Used for shadow color in filters where transparency is not supported
+   * (Microsoft Internet Explorer). Default is gray.
+   */
+  VML_SHADOWCOLOR: 'gray',
+
+  /**
+   * Variable: SHADOW_OFFSET_X
+   *
+   * Specifies the x-offset of the shadow. Default is 2.
+   */
+  SHADOW_OFFSET_X: 2,
+
+  /**
+   * Variable: SHADOW_OFFSET_Y
+   *
+   * Specifies the y-offset of the shadow. Default is 3.
+   */
+  SHADOW_OFFSET_Y: 3,
+
+  /**
+   * Variable: SHADOW_OPACITY
+   *
+   * Defines the opacity for shadows. Default is 1.
+   */
+  SHADOW_OPACITY: 1,
+
+  /**
+   * Variable: NODETYPE_ELEMENT
+   *
+   * DOM node of type ELEMENT.
+   */
+  NODETYPE_ELEMENT: 1,
+
+  /**
+   * Variable: NODETYPE_ATTRIBUTE
+   *
+   * DOM node of type ATTRIBUTE.
+   */
+  NODETYPE_ATTRIBUTE: 2,
+
+  /**
+   * Variable: NODETYPE_TEXT
+   *
+   * DOM node of type TEXT.
+   */
+  NODETYPE_TEXT: 3,
+
+  /**
+   * Variable: NODETYPE_CDATA
+   *
+   * DOM node of type CDATA.
+   */
+  NODETYPE_CDATA: 4,
+
+  /**
+   * Variable: NODETYPE_ENTITY_REFERENCE
+   *
+   * DOM node of type ENTITY_REFERENCE.
+   */
+  NODETYPE_ENTITY_REFERENCE: 5,
+
+  /**
+   * Variable: NODETYPE_ENTITY
+   *
+   * DOM node of type ENTITY.
+   */
+  NODETYPE_ENTITY: 6,
+
+  /**
+   * Variable: NODETYPE_PROCESSING_INSTRUCTION
+   *
+   * DOM node of type PROCESSING_INSTRUCTION.
+   */
+  NODETYPE_PROCESSING_INSTRUCTION: 7,
+
+  /**
+   * Variable: NODETYPE_COMMENT
+   *
+   * DOM node of type COMMENT.
+   */
+  NODETYPE_COMMENT: 8,
+
+  /**
+   * Variable: NODETYPE_DOCUMENT
+   *
+   * DOM node of type DOCUMENT.
+   */
+  NODETYPE_DOCUMENT: 9,
+
+  /**
+   * Variable: NODETYPE_DOCUMENTTYPE
+   *
+   * DOM node of type DOCUMENTTYPE.
+   */
+  NODETYPE_DOCUMENTTYPE: 10,
+
+  /**
+   * Variable: NODETYPE_DOCUMENT_FRAGMENT
+   *
+   * DOM node of type DOCUMENT_FRAGMENT.
+   */
+  NODETYPE_DOCUMENT_FRAGMENT: 11,
+
+  /**
+   * Variable: NODETYPE_NOTATION
+   *
+   * DOM node of type NOTATION.
+   */
+  NODETYPE_NOTATION: 12,
+
+  /**
+   * Variable: TOOLTIP_VERTICAL_OFFSET
+   *
+   * Defines the vertical offset for the tooltip.
+   * Default is 16.
+   */
+  TOOLTIP_VERTICAL_OFFSET: 16,
+
+  /**
+   * Variable: DEFAULT_VALID_COLOR
+   *
+   * Specifies the default valid color. Default is #0000FF.
+   */
+  DEFAULT_VALID_COLOR: '#00FF00',
+
+  /**
+   * Variable: DEFAULT_INVALID_COLOR
+   *
+   * Specifies the default invalid color. Default is #FF0000.
+   */
+  DEFAULT_INVALID_COLOR: '#FF0000',
+
+  /**
+   * Variable: OUTLINE_HIGHLIGHT_COLOR
+   *
+   * Specifies the default highlight color for shape outlines.
+   * Default is #0000FF. This is used in .
+   */
+  OUTLINE_HIGHLIGHT_COLOR: '#00FF00',
+
+  /**
+   * Variable: OUTLINE_HIGHLIGHT_COLOR
+   *
+   * Defines the strokewidth to be used for shape outlines.
+   * Default is 5. This is used in .
+   */
+  OUTLINE_HIGHLIGHT_STROKEWIDTH: 5,
+
+  /**
+   * Variable: HIGHLIGHT_STROKEWIDTH
+   *
+   * Defines the strokewidth to be used for the highlights.
+   * Default is 3.
+   */
+  HIGHLIGHT_STROKEWIDTH: 3,
+
+  /**
+   * Variable: CONSTRAINT_HIGHLIGHT_SIZE
+   *
+   * Size of the constraint highlight (in px). Default is 2.
+   */
+  HIGHLIGHT_SIZE: 2,
+
+  /**
+   * Variable: HIGHLIGHT_OPACITY
+   *
+   * Opacity (in %) used for the highlights (including outline).
+   * Default is 100.
+   */
+  HIGHLIGHT_OPACITY: 100,
+
+  /**
+   * Variable: CURSOR_MOVABLE_VERTEX
+   *
+   * Defines the cursor for a movable vertex. Default is 'move'.
+   */
+  CURSOR_MOVABLE_VERTEX: 'move',
+
+  /**
+   * Variable: CURSOR_MOVABLE_EDGE
+   *
+   * Defines the cursor for a movable edge. Default is 'move'.
+   */
+  CURSOR_MOVABLE_EDGE: 'move',
+
+  /**
+   * Variable: CURSOR_LABEL_HANDLE
+   *
+   * Defines the cursor for a movable label. Default is 'default'.
+   */
+  CURSOR_LABEL_HANDLE: 'default',
+
+  /**
+   * Variable: CURSOR_TERMINAL_HANDLE
+   *
+   * Defines the cursor for a terminal handle. Default is 'pointer'.
+   */
+  CURSOR_TERMINAL_HANDLE: 'pointer',
+
+  /**
+   * Variable: CURSOR_BEND_HANDLE
+   *
+   * Defines the cursor for a movable bend. Default is 'crosshair'.
+   */
+  CURSOR_BEND_HANDLE: 'crosshair',
+
+  /**
+   * Variable: CURSOR_VIRTUAL_BEND_HANDLE
+   *
+   * Defines the cursor for a movable bend. Default is 'crosshair'.
+   */
+  CURSOR_VIRTUAL_BEND_HANDLE: 'crosshair',
+
+  /**
+   * Variable: CURSOR_CONNECT
+   *
+   * Defines the cursor for a connectable state. Default is 'pointer'.
+   */
+  CURSOR_CONNECT: 'pointer',
+
+  /**
+   * Variable: HIGHLIGHT_COLOR
+   *
+   * Defines the color to be used for the cell highlighting.
+   * Use 'none' for no color. Default is #00FF00.
+   */
+  HIGHLIGHT_COLOR: '#00FF00',
+
+  /**
+   * Variable: TARGET_HIGHLIGHT_COLOR
+   *
+   * Defines the color to be used for highlighting a target cell for a new
+   * or changed connection. Note that this may be either a source or
+   * target terminal in the graph. Use 'none' for no color.
+   * Default is #0000FF.
+   */
+  CONNECT_TARGET_COLOR: '#0000FF',
+
+  /**
+   * Variable: INVALID_CONNECT_TARGET_COLOR
+   *
+   * Defines the color to be used for highlighting a invalid target cells
+   * for a new or changed connections. Note that this may be either a source
+   * or target terminal in the graph. Use 'none' for no color. Default is
+   * #FF0000.
+   */
+  INVALID_CONNECT_TARGET_COLOR: '#FF0000',
+
+  /**
+   * Variable: DROP_TARGET_COLOR
+   *
+   * Defines the color to be used for the highlighting target parent cells
+   * (for drag and drop). Use 'none' for no color. Default is #0000FF.
+   */
+  DROP_TARGET_COLOR: '#0000FF',
+
+  /**
+   * Variable: VALID_COLOR
+   *
+   * Defines the color to be used for the coloring valid connection
+   * previews. Use 'none' for no color. Default is #FF0000.
+   */
+  VALID_COLOR: '#00FF00',
+
+  /**
+   * Variable: INVALID_COLOR
+   *
+   * Defines the color to be used for the coloring invalid connection
+   * previews. Use 'none' for no color. Default is #FF0000.
+   */
+  INVALID_COLOR: '#FF0000',
+
+  /**
+   * Variable: EDGE_SELECTION_COLOR
+   *
+   * Defines the color to be used for the selection border of edges. Use
+   * 'none' for no color. Default is #00FF00.
+   */
+  EDGE_SELECTION_COLOR: '#00FF00',
+
+  /**
+   * Variable: VERTEX_SELECTION_COLOR
+   *
+   * Defines the color to be used for the selection border of vertices. Use
+   * 'none' for no color. Default is #00FF00.
+   */
+  VERTEX_SELECTION_COLOR: '#00FF00',
+
+  /**
+   * Variable: VERTEX_SELECTION_STROKEWIDTH
+   *
+   * Defines the strokewidth to be used for vertex selections.
+   * Default is 1.
+   */
+  VERTEX_SELECTION_STROKEWIDTH: 1,
+
+  /**
+   * Variable: EDGE_SELECTION_STROKEWIDTH
+   *
+   * Defines the strokewidth to be used for edge selections.
+   * Default is 1.
+   */
+  EDGE_SELECTION_STROKEWIDTH: 1,
+
+  /**
+   * Variable: SELECTION_DASHED
+   *
+   * Defines the dashed state to be used for the vertex selection
+   * border. Default is true.
+   */
+  VERTEX_SELECTION_DASHED: true,
+
+  /**
+   * Variable: SELECTION_DASHED
+   *
+   * Defines the dashed state to be used for the edge selection
+   * border. Default is true.
+   */
+  EDGE_SELECTION_DASHED: true,
+
+  /**
+   * Variable: GUIDE_COLOR
+   *
+   * Defines the color to be used for the guidelines in mxGraphHandler.
+   * Default is #FF0000.
+   */
+  GUIDE_COLOR: '#FF0000',
+
+  /**
+   * Variable: GUIDE_STROKEWIDTH
+   *
+   * Defines the strokewidth to be used for the guidelines in mxGraphHandler.
+   * Default is 1.
+   */
+  GUIDE_STROKEWIDTH: 1,
+
+  /**
+   * Variable: OUTLINE_COLOR
+   *
+   * Defines the color to be used for the outline rectangle
+   * border.  Use 'none' for no color. Default is #0099FF.
+   */
+  OUTLINE_COLOR: '#0099FF',
+
+  /**
+   * Variable: OUTLINE_STROKEWIDTH
+   *
+   * Defines the strokewidth to be used for the outline rectangle
+   * stroke width. Default is 3.
+   */
+  OUTLINE_STROKEWIDTH: mxClient.IS_IE ? 2 : 3,
+
+  /**
+   * Variable: HANDLE_SIZE
+   *
+   * Defines the default size for handles. Default is 6.
+   */
+  HANDLE_SIZE: 6,
+
+  /**
+   * Variable: LABEL_HANDLE_SIZE
+   *
+   * Defines the default size for label handles. Default is 4.
+   */
+  LABEL_HANDLE_SIZE: 4,
+
+  /**
+   * Variable: HANDLE_FILLCOLOR
+   *
+   * Defines the color to be used for the handle fill color. Use 'none' for
+   * no color. Default is #00FF00 (green).
+   */
+  HANDLE_FILLCOLOR: '#00FF00',
+
+  /**
+   * Variable: HANDLE_STROKECOLOR
+   *
+   * Defines the color to be used for the handle stroke color. Use 'none' for
+   * no color. Default is black.
+   */
+  HANDLE_STROKECOLOR: 'black',
+
+  /**
+   * Variable: LABEL_HANDLE_FILLCOLOR
+   *
+   * Defines the color to be used for the label handle fill color. Use 'none'
+   * for no color. Default is yellow.
+   */
+  LABEL_HANDLE_FILLCOLOR: 'yellow',
+
+  /**
+   * Variable: CONNECT_HANDLE_FILLCOLOR
+   *
+   * Defines the color to be used for the connect handle fill color. Use
+   * 'none' for no color. Default is #0000FF (blue).
+   */
+  CONNECT_HANDLE_FILLCOLOR: '#0000FF',
+
+  /**
+   * Variable: LOCKED_HANDLE_FILLCOLOR
+   *
+   * Defines the color to be used for the locked handle fill color. Use
+   * 'none' for no color. Default is #FF0000 (red).
+   */
+  LOCKED_HANDLE_FILLCOLOR: '#FF0000',
+
+  /**
+   * Variable: OUTLINE_HANDLE_FILLCOLOR
+   *
+   * Defines the color to be used for the outline sizer fill color. Use
+   * 'none' for no color. Default is #00FFFF.
+   */
+  OUTLINE_HANDLE_FILLCOLOR: '#00FFFF',
+
+  /**
+   * Variable: OUTLINE_HANDLE_STROKECOLOR
+   *
+   * Defines the color to be used for the outline sizer stroke color. Use
+   * 'none' for no color. Default is #0033FF.
+   */
+  OUTLINE_HANDLE_STROKECOLOR: '#0033FF',
+
+  /**
+   * Variable: DEFAULT_FONTFAMILY
+   *
+   * Defines the default family for all fonts. Default is Arial,Helvetica.
+   */
+  DEFAULT_FONTFAMILY: 'Arial,Helvetica',
+
+  /**
+   * Variable: DEFAULT_FONTSIZE
+   *
+   * Defines the default size (in px). Default is 11.
+   */
+  DEFAULT_FONTSIZE: 11,
+
+  /**
+   * Variable: DEFAULT_TEXT_DIRECTION
+   *
+   * Defines the default value for the  if no value is
+   * defined for it in the style. Default value is an empty string which means
+   * the default system setting is used and no direction is set.
+   */
+  DEFAULT_TEXT_DIRECTION: '',
+
+  /**
+   * Variable: LINE_HEIGHT
+   *
+   * Defines the default line height for text labels. Default is 1.2.
+   */
+  LINE_HEIGHT: 1.2,
+
+  /**
+   * Variable: WORD_WRAP
+   *
+   * Defines the CSS value for the word-wrap property. Default is "normal".
+   * Change this to "break-word" to allow long words to be able to be broken
+   * and wrap onto the next line.
+   */
+  WORD_WRAP: 'normal',
+
+  /**
+   * Variable: ABSOLUTE_LINE_HEIGHT
+   *
+   * Specifies if absolute line heights should be used (px) in CSS. Default
+   * is false. Set this to true for backwards compatibility.
+   */
+  ABSOLUTE_LINE_HEIGHT: false,
+
+  /**
+   * Variable: DEFAULT_FONTSTYLE
+   *
+   * Defines the default style for all fonts. Default is 0. This can be set
+   * to any combination of font styles as follows.
+   *
+   * (code)
+   * mxConstants.DEFAULT_FONTSTYLE = mxConstants.FONT_BOLD | mxConstants.FONT_ITALIC;
+   * (end)
+   */
+  DEFAULT_FONTSTYLE: 0,
+
+  /**
+   * Variable: DEFAULT_STARTSIZE
+   *
+   * Defines the default start size for swimlanes. Default is 40.
+   */
+  DEFAULT_STARTSIZE: 40,
+
+  /**
+   * Variable: DEFAULT_MARKERSIZE
+   *
+   * Defines the default size for all markers. Default is 6.
+   */
+  DEFAULT_MARKERSIZE: 6,
+
+  /**
+   * Variable: DEFAULT_IMAGESIZE
+   *
+   * Defines the default width and height for images used in the
+   * label shape. Default is 24.
+   */
+  DEFAULT_IMAGESIZE: 24,
+
+  /**
+   * Variable: ENTITY_SEGMENT
+   *
+   * Defines the length of the horizontal segment of an Entity Relation.
+   * This can be overridden using  style.
+   * Default is 30.
+   */
+  ENTITY_SEGMENT: 30,
+
+  /**
+   * Variable: RECTANGLE_ROUNDING_FACTOR
+   *
+   * Defines the rounding factor for rounded rectangles in percent between
+   * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
+   */
+  RECTANGLE_ROUNDING_FACTOR: 0.15,
+
+  /**
+   * Variable: LINE_ARCSIZE
+   *
+   * Defines the size of the arcs for rounded edges. Default is 20.
+   */
+  LINE_ARCSIZE: 20,
+
+  /**
+   * Variable: ARROW_SPACING
+   *
+   * Defines the spacing between the arrow shape and its terminals. Default is 0.
+   */
+  ARROW_SPACING: 0,
+
+  /**
+   * Variable: ARROW_WIDTH
+   *
+   * Defines the width of the arrow shape. Default is 30.
+   */
+  ARROW_WIDTH: 30,
+
+  /**
+   * Variable: ARROW_SIZE
+   *
+   * Defines the size of the arrowhead in the arrow shape. Default is 30.
+   */
+  ARROW_SIZE: 30,
+
+  /**
+   * Variable: PAGE_FORMAT_A4_PORTRAIT
+   *
+   * Defines the rectangle for the A4 portrait page format. The dimensions
+   * of this page format are 826x1169 pixels.
+   */
+  PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 827, 1169),
+
+  /**
+   * Variable: PAGE_FORMAT_A4_PORTRAIT
+   *
+   * Defines the rectangle for the A4 portrait page format. The dimensions
+   * of this page format are 826x1169 pixels.
+   */
+  PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 827),
+
+  /**
+   * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+   *
+   * Defines the rectangle for the Letter portrait page format. The
+   * dimensions of this page format are 850x1100 pixels.
+   */
+  PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),
+
+  /**
+   * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+   *
+   * Defines the rectangle for the Letter portrait page format. The dimensions
+   * of this page format are 850x1100 pixels.
+   */
+  PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),
+
+  /**
+   * Variable: NONE
+   *
+   * Defines the value for none. Default is "none".
+   */
+  NONE: 'none',
+
+  /**
+   * Variable: STYLE_PERIMETER
+   *
+   * Defines the key for the perimeter style. This is a function that defines
+   * the perimeter around a particular shape. Possible values are the
+   * functions defined in . Alternatively, the constants in this
+   * class that start with "PERIMETER_" may be used to access
+   * perimeter styles in . Value is "perimeter".
+   */
+  STYLE_PERIMETER: 'perimeter',
+
+  /**
+   * Variable: STYLE_SOURCE_PORT
+   *
+   * Defines the ID of the cell that should be used for computing the
+   * perimeter point of the source for an edge. This allows for graphically
+   * connecting to a cell while keeping the actual terminal of the edge.
+   * Value is "sourcePort".
+   */
+  STYLE_SOURCE_PORT: 'sourcePort',
+
+  /**
+   * Variable: STYLE_TARGET_PORT
+   *
+   * Defines the ID of the cell that should be used for computing the
+   * perimeter point of the target for an edge. This allows for graphically
+   * connecting to a cell while keeping the actual terminal of the edge.
+   * Value is "targetPort".
+   */
+  STYLE_TARGET_PORT: 'targetPort',
+
+  /**
+   * Variable: STYLE_PORT_CONSTRAINT
+   *
+   * Defines the direction(s) that edges are allowed to connect to cells in.
+   * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH,
+   * DIRECTION_EAST" and "DIRECTION_WEST". Value is
+   * "portConstraint".
+   */
+  STYLE_PORT_CONSTRAINT: 'portConstraint',
+
+  /**
+   * Variable: STYLE_PORT_CONSTRAINT_ROTATION
+   *
+   * Define whether port constraint directions are rotated with vertex
+   * rotation. 0 (default) causes port constraints to remain absolute,
+   * relative to the graph, 1 causes the constraints to rotate with
+   * the vertex. Value is "portConstraintRotation".
+   */
+  STYLE_PORT_CONSTRAINT_ROTATION: 'portConstraintRotation',
+
+  /**
+   * Variable: STYLE_SOURCE_PORT_CONSTRAINT
+   *
+   * Defines the direction(s) that edges are allowed to connect to sources in.
+   * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+   * and "DIRECTION_WEST". Value is "sourcePortConstraint".
+   */
+  STYLE_SOURCE_PORT_CONSTRAINT: 'sourcePortConstraint',
+
+  /**
+   * Variable: STYLE_TARGET_PORT_CONSTRAINT
+   *
+   * Defines the direction(s) that edges are allowed to connect to targets in.
+   * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+   * and "DIRECTION_WEST". Value is "targetPortConstraint".
+   */
+  STYLE_TARGET_PORT_CONSTRAINT: 'targetPortConstraint',
+
+  /**
+   * Variable: STYLE_OPACITY
+   *
+   * Defines the key for the opacity style. The type of the value is
+   * numeric and the possible range is 0-100. Value is "opacity".
+   */
+  STYLE_OPACITY: 'opacity',
+
+  /**
+   * Variable: STYLE_FILL_OPACITY
+   *
+   * Defines the key for the fill opacity style. The type of the value is
+   * numeric and the possible range is 0-100. Value is "fillOpacity".
+   */
+  STYLE_FILL_OPACITY: 'fillOpacity',
+
+  /**
+   * Variable: STYLE_STROKE_OPACITY
+   *
+   * Defines the key for the stroke opacity style. The type of the value is
+   * numeric and the possible range is 0-100. Value is "strokeOpacity".
+   */
+  STYLE_STROKE_OPACITY: 'strokeOpacity',
+
+  /**
+   * Variable: STYLE_TEXT_OPACITY
+   *
+   * Defines the key for the text opacity style. The type of the value is
+   * numeric and the possible range is 0-100. Value is "textOpacity".
+   */
+  STYLE_TEXT_OPACITY: 'textOpacity',
+
+  /**
+   * Variable: STYLE_TEXT_DIRECTION
+   *
+   * Defines the key for the text direction style. Possible values are
+   * "TEXT_DIRECTION_DEFAULT, TEXT_DIRECTION_AUTO, TEXT_DIRECTION_LTR"
+   * and "TEXT_DIRECTION_RTL". Value is "textDirection".
+   * The default value for the style is defined in .
+   * It is used is no value is defined for this key in a given style. This is
+   * an experimental style that is currently ignored in the backends.
+   */
+  STYLE_TEXT_DIRECTION: 'textDirection',
+
+  /**
+   * Variable: STYLE_OVERFLOW
+   *
+   * Defines the key for the overflow style. Possible values are 'visible',
+   * 'hidden', 'fill' and 'width'. The default value is 'visible'. This value
+   * specifies how overlapping vertex labels are handled. A value of
+   * 'visible' will show the complete label. A value of 'hidden' will clip
+   * the label so that it does not overlap the vertex bounds. A value of
+   * 'fill' will use the vertex bounds and a value of 'width' will use the
+   * vertex width for the label. See . Note that
+   * the vertical alignment is ignored for overflow fill and for horizontal
+   * alignment, left should be used to avoid pixel offsets in Internet Explorer
+   * 11 and earlier or if foreignObjects are disabled. Value is "overflow".
+   */
+  STYLE_OVERFLOW: 'overflow',
+
+  /**
+   * Variable: STYLE_ORTHOGONAL
+   *
+   * Defines if the connection points on either end of the edge should be
+   * computed so that the edge is vertical or horizontal if possible and
+   * if the point is not at a fixed location. Default is false. This is
+   * used in , which also returns true if the edgeStyle
+   * of the edge is an elbow or entity. Value is "orthogonal".
+   */
+  STYLE_ORTHOGONAL: 'orthogonal',
+
+  /**
+   * Variable: STYLE_EXIT_X
+   *
+   * Defines the key for the horizontal relative coordinate connection point
+   * of an edge with its source terminal. Value is "exitX".
+   */
+  STYLE_EXIT_X: 'exitX',
+
+  /**
+   * Variable: STYLE_EXIT_Y
+   *
+   * Defines the key for the vertical relative coordinate connection point
+   * of an edge with its source terminal. Value is "exitY".
+   */
+  STYLE_EXIT_Y: 'exitY',
+
+  /**
+   * Variable: STYLE_EXIT_DX
+   *
+   * Defines the key for the horizontal offset of the connection point
+   * of an edge with its source terminal. Value is "exitDx".
+   */
+  STYLE_EXIT_DX: 'exitDx',
+
+  /**
+   * Variable: STYLE_EXIT_DY
+   *
+   * Defines the key for the vertical offset of the connection point
+   * of an edge with its source terminal. Value is "exitDy".
+   */
+  STYLE_EXIT_DY: 'exitDy',
+
+  /**
+   * Variable: STYLE_EXIT_PERIMETER
+   *
+   * Defines if the perimeter should be used to find the exact entry point
+   * along the perimeter of the source. Possible values are 0 (false) and
+   * 1 (true). Default is 1 (true). Value is "exitPerimeter".
+   */
+  STYLE_EXIT_PERIMETER: 'exitPerimeter',
+
+  /**
+   * Variable: STYLE_ENTRY_X
+   *
+   * Defines the key for the horizontal relative coordinate connection point
+   * of an edge with its target terminal. Value is "entryX".
+   */
+  STYLE_ENTRY_X: 'entryX',
+
+  /**
+   * Variable: STYLE_ENTRY_Y
+   *
+   * Defines the key for the vertical relative coordinate connection point
+   * of an edge with its target terminal. Value is "entryY".
+   */
+  STYLE_ENTRY_Y: 'entryY',
+
+  /**
+   * Variable: STYLE_ENTRY_DX
+   *
+   * Defines the key for the horizontal offset of the connection point
+   * of an edge with its target terminal. Value is "entryDx".
+   */
+  STYLE_ENTRY_DX: 'entryDx',
+
+  /**
+   * Variable: STYLE_ENTRY_DY
+   *
+   * Defines the key for the vertical offset of the connection point
+   * of an edge with its target terminal. Value is "entryDy".
+   */
+  STYLE_ENTRY_DY: 'entryDy',
+
+  /**
+   * Variable: STYLE_ENTRY_PERIMETER
+   *
+   * Defines if the perimeter should be used to find the exact entry point
+   * along the perimeter of the target. Possible values are 0 (false) and
+   * 1 (true). Default is 1 (true). Value is "entryPerimeter".
+   */
+  STYLE_ENTRY_PERIMETER: 'entryPerimeter',
+
+  /**
+   * Variable: STYLE_WHITE_SPACE
+   *
+   * Defines the key for the white-space style. Possible values are 'nowrap'
+   * and 'wrap'. The default value is 'nowrap'. This value specifies how
+   * white-space inside a HTML vertex label should be handled. A value of
+   * 'nowrap' means the text will never wrap to the next line until a
+   * linefeed is encountered. A value of 'wrap' means text will wrap when
+   * necessary. This style is only used for HTML labels.
+   * See . Value is "whiteSpace".
+   */
+  STYLE_WHITE_SPACE: 'whiteSpace',
+
+  /**
+   * Variable: STYLE_ROTATION
+   *
+   * Defines the key for the rotation style. The type of the value is
+   * numeric and the possible range is 0-360. Value is "rotation".
+   */
+  STYLE_ROTATION: 'rotation',
+
+  /**
+   * Variable: STYLE_FILLCOLOR
+   *
+   * Defines the key for the fill color. Possible values are all HTML color
+   * names or HEX codes, as well as special keywords such as 'swimlane,
+   * 'inherit' or 'indicated' to use the color code of a related cell or the
+   * indicator shape. Value is "fillColor".
+   */
+  STYLE_FILLCOLOR: 'fillColor',
+
+  /**
+   * Variable: STYLE_POINTER_EVENTS
+   *
+   * Specifies if pointer events should be fired on transparent backgrounds.
+   * This style is currently only supported in . Default
+   * is true. Value is "pointerEvents". This is typically set to
+   * false in groups where the transparent part should allow any underlying
+   * cells to be clickable.
+   */
+  STYLE_POINTER_EVENTS: 'pointerEvents',
+
+  /**
+   * Variable: STYLE_SWIMLANE_FILLCOLOR
+   *
+   * Defines the key for the fill color of the swimlane background. Possible
+   * values are all HTML color names or HEX codes. Default is no background.
+   * Value is "swimlaneFillColor".
+   */
+  STYLE_SWIMLANE_FILLCOLOR: 'swimlaneFillColor',
+
+  /**
+   * Variable: STYLE_MARGIN
+   *
+   * Defines the key for the margin between the ellipses in the double ellipse shape.
+   * Possible values are all positive numbers. Value is "margin".
+   */
+  STYLE_MARGIN: 'margin',
+
+  /**
+   * Variable: STYLE_GRADIENTCOLOR
+   *
+   * Defines the key for the gradient color. Possible values are all HTML color
+   * names or HEX codes, as well as special keywords such as 'swimlane,
+   * 'inherit' or 'indicated' to use the color code of a related cell or the
+   * indicator shape. This is ignored if no fill color is defined. Value is
+   * "gradientColor".
+   */
+  STYLE_GRADIENTCOLOR: 'gradientColor',
+
+  /**
+   * Variable: STYLE_GRADIENT_DIRECTION
+   *
+   * Defines the key for the gradient direction. Possible values are
+   * , ,  and
+   * . Default is . Generally, and by
+   * default in mxGraph, gradient painting is done from the value of
+   *  to the value of . Taking the
+   * example of , this means  color at the
+   * bottom of paint pattern and  at top, with a
+   * gradient in-between. Value is "gradientDirection".
+   */
+  STYLE_GRADIENT_DIRECTION: 'gradientDirection',
+
+  /**
+   * Variable: STYLE_STROKECOLOR
+   *
+   * Defines the key for the strokeColor style. Possible values are all HTML
+   * color names or HEX codes, as well as special keywords such as 'swimlane,
+   * 'inherit', 'indicated' to use the color code of a related cell or the
+   * indicator shape or 'none' for no color. Value is "strokeColor".
+   */
+  STYLE_STROKECOLOR: 'strokeColor',
+
+  /**
+   * Variable: STYLE_SEPARATORCOLOR
+   *
+   * Defines the key for the separatorColor style. Possible values are all
+   * HTML color names or HEX codes. This style is only used for
+   *  shapes. Value is "separatorColor".
+   */
+  STYLE_SEPARATORCOLOR: 'separatorColor',
+
+  /**
+   * Variable: STYLE_STROKEWIDTH
+   *
+   * Defines the key for the strokeWidth style. The type of the value is
+   * numeric and the possible range is any non-negative value larger or equal
+   * to 1. The value defines the stroke width in pixels. Note: To hide a
+   * stroke use strokeColor none. Value is "strokeWidth".
+   */
+  STYLE_STROKEWIDTH: 'strokeWidth',
+
+  /**
+   * Variable: STYLE_ALIGN
+   *
+   * Defines the key for the align style. Possible values are ,
+   *  and . This value defines how the lines of
+   * the label are horizontally aligned.  mean label text lines
+   * are aligned to left of the label bounds,  to the right of
+   * the label bounds and  means the center of the text lines
+   * are aligned in the center of the label bounds. Note this value doesn't
+   * affect the positioning of the overall label bounds relative to the
+   * vertex, to move the label bounds horizontally, use
+   * . Value is "align".
+   */
+  STYLE_ALIGN: 'align',
+
+  /**
+   * Variable: STYLE_VERTICAL_ALIGN
+   *
+   * Defines the key for the verticalAlign style. Possible values are
+   * ,  and . This value defines how
+   * the lines of the label are vertically aligned.  means the
+   * topmost label text line is aligned against the top of the label bounds,
+   *  means the bottom-most label text line is aligned against
+   * the bottom of the label bounds and  means there is equal
+   * spacing between the topmost text label line and the top of the label
+   * bounds and the bottom-most text label line and the bottom of the label
+   * bounds. Note this value doesn't affect the positioning of the overall
+   * label bounds relative to the vertex, to move the label bounds
+   * vertically, use . Value is "verticalAlign".
+   */
+  STYLE_VERTICAL_ALIGN: 'verticalAlign',
+
+  /**
+   * Variable: STYLE_LABEL_WIDTH
+   *
+   * Defines the key for the width of the label if the label position is not
+   * center. Value is "labelWidth".
+   */
+  STYLE_LABEL_WIDTH: 'labelWidth',
+
+  /**
+   * Variable: STYLE_LABEL_POSITION
+   *
+   * Defines the key for the horizontal label position of vertices. Possible
+   * values are ,  and . Default is
+   * . The label align defines the position of the label
+   * relative to the cell.  means the entire label bounds is
+   * placed completely just to the left of the vertex,  means
+   * adjust to the right and  means the label bounds are
+   * vertically aligned with the bounds of the vertex. Note this value
+   * doesn't affect the positioning of label within the label bounds, to move
+   * the label horizontally within the label bounds, use .
+   * Value is "labelPosition".
+   */
+  STYLE_LABEL_POSITION: 'labelPosition',
+
+  /**
+   * Variable: STYLE_VERTICAL_LABEL_POSITION
+   *
+   * Defines the key for the vertical label position of vertices. Possible
+   * values are ,  and . Default is
+   * . The label align defines the position of the label
+   * relative to the cell.  means the entire label bounds is
+   * placed completely just on the top of the vertex,  means
+   * adjust on the bottom and  means the label bounds are
+   * horizontally aligned with the bounds of the vertex. Note this value
+   * doesn't affect the positioning of label within the label bounds, to move
+   * the label vertically within the label bounds, use
+   * . Value is "verticalLabelPosition".
+   */
+  STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',
+
+  /**
+   * Variable: STYLE_IMAGE_ASPECT
+   *
+   * Defines the key for the image aspect style. Possible values are 0 (do
+   * not preserve aspect) or 1 (keep aspect). This is only used in
+   * . Default is 1. Value is "imageAspect".
+   */
+  STYLE_IMAGE_ASPECT: 'imageAspect',
+
+  /**
+   * Variable: STYLE_IMAGE_ALIGN
+   *
+   * Defines the key for the align style. Possible values are ,
+   *  and . The value defines how any image in the
+   * vertex label is aligned horizontally within the label bounds of a
+   *  shape. Value is "imageAlign".
+   */
+  STYLE_IMAGE_ALIGN: 'imageAlign',
+
+  /**
+   * Variable: STYLE_IMAGE_VERTICAL_ALIGN
+   *
+   * Defines the key for the verticalAlign style. Possible values are
+   * ,  and . The value defines how
+   * any image in the vertex label is aligned vertically within the label
+   * bounds of a  shape. Value is "imageVerticalAlign".
+   */
+  STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',
+
+  /**
+   * Variable: STYLE_GLASS
+   *
+   * Defines the key for the glass style. Possible values are 0 (disabled) and
+   * 1(enabled). The default value is 0. This is used in . Value is
+   * "glass".
+   */
+  STYLE_GLASS: 'glass',
+
+  /**
+   * Variable: STYLE_IMAGE
+   *
+   * Defines the key for the image style. Possible values are any image URL,
+   * the type of the value is String. This is the path to the image that is
+   * to be displayed within the label of a vertex. Data URLs should use the
+   * following format: data:image/png,xyz where xyz is the base64 encoded
+   * data (without the "base64"-prefix). Note that Data URLs are only
+   * supported in modern browsers. Value is "image".
+   */
+  STYLE_IMAGE: 'image',
+
+  /**
+   * Variable: STYLE_IMAGE_WIDTH
+   *
+   * Defines the key for the imageWidth style. The type of this value is
+   * int, the value is the image width in pixels and must be greater than 0.
+   * Value is "imageWidth".
+   */
+  STYLE_IMAGE_WIDTH: 'imageWidth',
+
+  /**
+   * Variable: STYLE_IMAGE_HEIGHT
+   *
+   * Defines the key for the imageHeight style. The type of this value is
+   * int, the value is the image height in pixels and must be greater than 0.
+   * Value is "imageHeight".
+   */
+  STYLE_IMAGE_HEIGHT: 'imageHeight',
+
+  /**
+   * Variable: STYLE_IMAGE_BACKGROUND
+   *
+   * Defines the key for the image background color. This style is only used
+   * in . Possible values are all HTML color names or HEX
+   * codes. Value is "imageBackground".
+   */
+  STYLE_IMAGE_BACKGROUND: 'imageBackground',
+
+  /**
+   * Variable: STYLE_IMAGE_BORDER
+   *
+   * Defines the key for the image border color. This style is only used in
+   * . Possible values are all HTML color names or HEX codes.
+   * Value is "imageBorder".
+   */
+  STYLE_IMAGE_BORDER: 'imageBorder',
+
+  /**
+   * Variable: STYLE_FLIPH
+   *
+   * Defines the key for the horizontal image flip. This style is only used
+   * in . Possible values are 0 and 1. Default is 0. Value is
+   * "flipH".
+   */
+  STYLE_FLIPH: 'flipH',
+
+  /**
+   * Variable: STYLE_FLIPV
+   *
+   * Defines the key for the vertical flip. Possible values are 0 and 1.
+   * Default is 0. Value is "flipV".
+   */
+  STYLE_FLIPV: 'flipV',
+
+  /**
+   * Variable: STYLE_NOLABEL
+   *
+   * Defines the key for the noLabel style. If this is true then no label is
+   * visible for a given cell. Possible values are true or false (1 or 0).
+   * Default is false. Value is "noLabel".
+   */
+  STYLE_NOLABEL: 'noLabel',
+
+  /**
+   * Variable: STYLE_NOEDGESTYLE
+   *
+   * Defines the key for the noEdgeStyle style. If this is true then no edge
+   * style is applied for a given edge. Possible values are true or false
+   * (1 or 0). Default is false. Value is "noEdgeStyle".
+   */
+  STYLE_NOEDGESTYLE: 'noEdgeStyle',
+
+  /**
+   * Variable: STYLE_LABEL_BACKGROUNDCOLOR
+   *
+   * Defines the key for the label background color. Possible values are all
+   * HTML color names or HEX codes. Value is "labelBackgroundColor".
+   */
+  STYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',
+
+  /**
+   * Variable: STYLE_LABEL_BORDERCOLOR
+   *
+   * Defines the key for the label border color. Possible values are all
+   * HTML color names or HEX codes. Value is "labelBorderColor".
+   */
+  STYLE_LABEL_BORDERCOLOR: 'labelBorderColor',
+
+  /**
+   * Variable: STYLE_LABEL_PADDING
+   *
+   * Defines the key for the label padding, ie. the space between the label
+   * border and the label. Value is "labelPadding".
+   */
+  STYLE_LABEL_PADDING: 'labelPadding',
+
+  /**
+   * Variable: STYLE_INDICATOR_SHAPE
+   *
+   * Defines the key for the indicator shape used within an .
+   * Possible values are all SHAPE_* constants or the names of any new
+   * shapes. The indicatorShape has precedence over the indicatorImage.
+   * Value is "indicatorShape".
+   */
+  STYLE_INDICATOR_SHAPE: 'indicatorShape',
+
+  /**
+   * Variable: STYLE_INDICATOR_IMAGE
+   *
+   * Defines the key for the indicator image used within an .
+   * Possible values are all image URLs. The indicatorShape has
+   * precedence over the indicatorImage. Value is "indicatorImage".
+   */
+  STYLE_INDICATOR_IMAGE: 'indicatorImage',
+
+  /**
+   * Variable: STYLE_INDICATOR_COLOR
+   *
+   * Defines the key for the indicatorColor style. Possible values are all
+   * HTML color names or HEX codes, as well as the special 'swimlane' keyword
+   * to refer to the color of the parent swimlane if one exists. Value is
+   * "indicatorColor".
+   */
+  STYLE_INDICATOR_COLOR: 'indicatorColor',
+
+  /**
+   * Variable: STYLE_INDICATOR_STROKECOLOR
+   *
+   * Defines the key for the indicator stroke color in .
+   * Possible values are all color codes. Value is "indicatorStrokeColor".
+   */
+  STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',
+
+  /**
+   * Variable: STYLE_INDICATOR_GRADIENTCOLOR
+   *
+   * Defines the key for the indicatorGradientColor style. Possible values
+   * are all HTML color names or HEX codes. This style is only supported in
+   *  shapes. Value is "indicatorGradientColor".
+   */
+  STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',
+
+  /**
+   * Variable: STYLE_INDICATOR_SPACING
+   *
+   * The defines the key for the spacing between the label and the
+   * indicator in . Possible values are in pixels. Value is
+   * "indicatorSpacing".
+   */
+  STYLE_INDICATOR_SPACING: 'indicatorSpacing',
+
+  /**
+   * Variable: STYLE_INDICATOR_WIDTH
+   *
+   * Defines the key for the indicator width. Possible values start at 0 (in
+   * pixels). Value is "indicatorWidth".
+   */
+  STYLE_INDICATOR_WIDTH: 'indicatorWidth',
+
+  /**
+   * Variable: STYLE_INDICATOR_HEIGHT
+   *
+   * Defines the key for the indicator height. Possible values start at 0 (in
+   * pixels). Value is "indicatorHeight".
+   */
+  STYLE_INDICATOR_HEIGHT: 'indicatorHeight',
+
+  /**
+   * Variable: STYLE_INDICATOR_DIRECTION
+   *
+   * Defines the key for the indicatorDirection style. The direction style is
+   * used to specify the direction of certain shapes (eg. ).
+   * Possible values are  (default), ,
+   *  and . Value is "indicatorDirection".
+   */
+  STYLE_INDICATOR_DIRECTION: 'indicatorDirection',
+
+  /**
+   * Variable: STYLE_SHADOW
+   *
+   * Defines the key for the shadow style. The type of the value is Boolean.
+   * Value is "shadow".
+   */
+  STYLE_SHADOW: 'shadow',
+
+  /**
+   * Variable: STYLE_SEGMENT
+   *
+   * Defines the key for the segment style. The type of this value is float
+   * and the value represents the size of the horizontal segment of the
+   * entity relation style. Default is ENTITY_SEGMENT. Value is "segment".
+   */
+  STYLE_SEGMENT: 'segment',
+
+  /**
+   * Variable: STYLE_ENDARROW
+   *
+   * Defines the key for the end arrow marker. Possible values are all
+   * constants with an ARROW-prefix. This is only used in .
+   * Value is "endArrow".
+   *
+   * Example:
+   * (code)
+   * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+   * (end)
+   */
+  STYLE_ENDARROW: 'endArrow',
+
+  /**
+   * Variable: STYLE_STARTARROW
+   *
+   * Defines the key for the start arrow marker. Possible values are all
+   * constants with an ARROW-prefix. This is only used in .
+   * See . Value is "startArrow".
+   */
+  STYLE_STARTARROW: 'startArrow',
+
+  /**
+   * Variable: STYLE_ENDSIZE
+   *
+   * Defines the key for the endSize style. The type of this value is numeric
+   * and the value represents the size of the end marker in pixels. Value is
+   * "endSize".
+   */
+  STYLE_ENDSIZE: 'endSize',
+
+  /**
+   * Variable: STYLE_STARTSIZE
+   *
+   * Defines the key for the startSize style. The type of this value is
+   * numeric and the value represents the size of the start marker or the
+   * size of the swimlane title region depending on the shape it is used for.
+   * Value is "startSize".
+   */
+  STYLE_STARTSIZE: 'startSize',
+
+  /**
+   * Variable: STYLE_SWIMLANE_LINE
+   *
+   * Defines the key for the swimlaneLine style. This style specifies whether
+   * the line between the title regio of a swimlane should be visible. Use 0
+   * for hidden or 1 (default) for visible. Value is "swimlaneLine".
+   */
+  STYLE_SWIMLANE_LINE: 'swimlaneLine',
+
+  /**
+   * Variable: STYLE_ENDFILL
+   *
+   * Defines the key for the endFill style. Use 0 for no fill or 1 (default)
+   * for fill. (This style is only exported via .) Value is
+   * "endFill".
+   */
+  STYLE_ENDFILL: 'endFill',
+
+  /**
+   * Variable: STYLE_STARTFILL
+   *
+   * Defines the key for the startFill style. Use 0 for no fill or 1 (default)
+   * for fill. (This style is only exported via .) Value is
+   * "startFill".
+   */
+  STYLE_STARTFILL: 'startFill',
+
+  /**
+   * Variable: STYLE_DASHED
+   *
+   * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
+   * for dashed. Value is "dashed".
+   */
+  STYLE_DASHED: 'dashed',
+
+  /**
+   * Variable: STYLE_DASH_PATTERN
+   *
+   * Defines the key for the dashed pattern style in SVG and image exports.
+   * The type of this value is a space separated list of numbers that specify
+   * a custom-defined dash pattern. Dash styles are defined in terms of the
+   * length of the dash (the drawn part of the stroke) and the length of the
+   * space between the dashes. The lengths are relative to the line width: a
+   * length of "1" is equal to the line width. VML ignores this style and
+   * uses dashStyle instead as defined in the VML specification. This style
+   * is only used in the  shape. Value is "dashPattern".
+   */
+  STYLE_DASH_PATTERN: 'dashPattern',
+
+  /**
+   * Variable: STYLE_FIX_DASH
+   *
+   * Defines the key for the fixDash style. Use 0 (default) for dash patterns
+   * that depend on the linewidth and 1 for dash patterns that ignore the
+   * line width. Value is "fixDash".
+   */
+  STYLE_FIX_DASH: 'fixDash',
+
+  /**
+   * Variable: STYLE_ROUNDED
+   *
+   * Defines the key for the rounded style. The type of this value is
+   * Boolean. For edges this determines whether or not joins between edges
+   * segments are smoothed to a rounded finish. For vertices that have the
+   * rectangle shape, this determines whether or not the rectangle is
+   * rounded. Use 0 (default) for non-rounded or 1 for rounded. Value is
+   * "rounded".
+   */
+  STYLE_ROUNDED: 'rounded',
+
+  /**
+   * Variable: STYLE_CURVED
+   *
+   * Defines the key for the curved style. The type of this value is
+   * Boolean. It is only applicable for connector shapes. Use 0 (default)
+   * for non-curved or 1 for curved. Value is "curved".
+   */
+  STYLE_CURVED: 'curved',
+
+  /**
+   * Variable: STYLE_ARCSIZE
+   *
+   * Defines the rounding factor for a rounded rectangle in percent (without
+   * the percent sign). Possible values are between 0 and 100. If this value
+   * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used. For
+   * edges, this defines the absolute size of rounded corners in pixels. If
+   * this values is not specified then LINE_ARCSIZE is used.
+   * (This style is only exported via .) Value is "arcSize".
+   */
+  STYLE_ARCSIZE: 'arcSize',
+
+  /**
+   * Variable: STYLE_ABSOLUTE_ARCSIZE
+   *
+   * Defines the key for the absolute arc size style. This specifies if
+   * arcSize for rectangles is abolute or relative. Possible values are 1
+   * and 0 (default). Value is "absoluteArcSize".
+   */
+  STYLE_ABSOLUTE_ARCSIZE: 'absoluteArcSize',
+
+  /**
+   * Variable: STYLE_SOURCE_PERIMETER_SPACING
+   *
+   * Defines the key for the source perimeter spacing. The type of this value
+   * is numeric. This is the distance between the source connection point of
+   * an edge and the perimeter of the source vertex in pixels. This style
+   * only applies to edges. Value is "sourcePerimeterSpacing".
+   */
+  STYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',
+
+  /**
+   * Variable: STYLE_TARGET_PERIMETER_SPACING
+   *
+   * Defines the key for the target perimeter spacing. The type of this value
+   * is numeric. This is the distance between the target connection point of
+   * an edge and the perimeter of the target vertex in pixels. This style
+   * only applies to edges. Value is "targetPerimeterSpacing".
+   */
+  STYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',
+
+  /**
+   * Variable: STYLE_PERIMETER_SPACING
+   *
+   * Defines the key for the perimeter spacing. This is the distance between
+   * the connection point and the perimeter in pixels. When used in a vertex
+   * style, this applies to all incoming edges to floating ports (edges that
+   * terminate on the perimeter of the vertex). When used in an edge style,
+   * this spacing applies to the source and target separately, if they
+   * terminate in floating ports (on the perimeter of the vertex). Value is
+   * "perimeterSpacing".
+   */
+  STYLE_PERIMETER_SPACING: 'perimeterSpacing',
+
+  /**
+   * Variable: STYLE_SPACING
+   *
+   * Defines the key for the spacing. The value represents the spacing, in
+   * pixels, added to each side of a label in a vertex (style applies to
+   * vertices only). Value is "spacing".
+   */
+  STYLE_SPACING: 'spacing',
+
+  /**
+   * Variable: STYLE_SPACING_TOP
+   *
+   * Defines the key for the spacingTop style. The value represents the
+   * spacing, in pixels, added to the top side of a label in a vertex (style
+   * applies to vertices only). Value is "spacingTop".
+   */
+  STYLE_SPACING_TOP: 'spacingTop',
+
+  /**
+   * Variable: STYLE_SPACING_LEFT
+   *
+   * Defines the key for the spacingLeft style. The value represents the
+   * spacing, in pixels, added to the left side of a label in a vertex (style
+   * applies to vertices only). Value is "spacingLeft".
+   */
+  STYLE_SPACING_LEFT: 'spacingLeft',
+
+  /**
+   * Variable: STYLE_SPACING_BOTTOM
+   *
+   * Defines the key for the spacingBottom style The value represents the
+   * spacing, in pixels, added to the bottom side of a label in a vertex
+   * (style applies to vertices only). Value is "spacingBottom".
+   */
+  STYLE_SPACING_BOTTOM: 'spacingBottom',
+
+  /**
+   * Variable: STYLE_SPACING_RIGHT
+   *
+   * Defines the key for the spacingRight style The value represents the
+   * spacing, in pixels, added to the right side of a label in a vertex (style
+   * applies to vertices only). Value is "spacingRight".
+   */
+  STYLE_SPACING_RIGHT: 'spacingRight',
+
+  /**
+   * Variable: STYLE_HORIZONTAL
+   *
+   * Defines the key for the horizontal style. Possible values are
+   * true or false. This value only applies to vertices. If the 
+   * is "SHAPE_SWIMLANE" a value of false indicates that the
+   * swimlane should be drawn vertically, true indicates to draw it
+   * horizontally. If the shape style does not indicate that this vertex is a
+   * swimlane, this value affects only whether the label is drawn
+   * horizontally or vertically. Value is "horizontal".
+   */
+  STYLE_HORIZONTAL: 'horizontal',
+
+  /**
+   * Variable: STYLE_DIRECTION
+   *
+   * Defines the key for the direction style. The direction style is used
+   * to specify the direction of certain shapes (eg. ).
+   * Possible values are  (default), ,
+   *  and . Value is "direction".
+   */
+  STYLE_DIRECTION: 'direction',
+
+  /**
+   * Variable: STYLE_ANCHOR_POINT_DIRECTION
+   *
+   * Defines the key for the anchorPointDirection style. The defines if the
+   * direction style should be taken into account when computing the fixed
+   * point location for connected edges. Default is 1 (yes). Set this to 0
+   * to ignore the direction style for fixed connection points. Value is
+   * "anchorPointDirection".
+   */
+  STYLE_ANCHOR_POINT_DIRECTION: 'anchorPointDirection',
+
+  /**
+   * Variable: STYLE_ELBOW
+   *
+   * Defines the key for the elbow style. Possible values are
+   *  and . Default is .
+   * This defines how the three segment orthogonal edge style leaves its
+   * terminal vertices. The vertical style leaves the terminal vertices at
+   * the top and bottom sides. Value is "elbow".
+   */
+  STYLE_ELBOW: 'elbow',
+
+  /**
+   * Variable: STYLE_FONTCOLOR
+   *
+   * Defines the key for the fontColor style. Possible values are all HTML
+   * color names or HEX codes. Value is "fontColor".
+   */
+  STYLE_FONTCOLOR: 'fontColor',
+
+  /**
+   * Variable: STYLE_FONTFAMILY
+   *
+   * Defines the key for the fontFamily style. Possible values are names such
+   * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.
+   * Value is fontFamily.
+   */
+  STYLE_FONTFAMILY: 'fontFamily',
+
+  /**
+   * Variable: STYLE_FONTSIZE
+   *
+   * Defines the key for the fontSize style (in px). The type of the value
+   * is int. Value is "fontSize".
+   */
+  STYLE_FONTSIZE: 'fontSize',
+
+  /**
+   * Variable: STYLE_FONTSTYLE
+   *
+   * Defines the key for the fontStyle style. Values may be any logical AND
+   * (sum) of ,  and .
+   * The type of the value is int. Value is "fontStyle".
+   */
+  STYLE_FONTSTYLE: 'fontStyle',
+
+  /**
+   * Variable: STYLE_ASPECT
+   *
+   * Defines the key for the aspect style. Possible values are empty or fixed.
+   * If fixed is used then the aspect ratio of the cell will be maintained
+   * when resizing. Default is empty. Value is "aspect".
+   */
+  STYLE_ASPECT: 'aspect',
+
+  /**
+   * Variable: STYLE_AUTOSIZE
+   *
+   * Defines the key for the autosize style. This specifies if a cell should be
+   * resized automatically if the value has changed. Possible values are 0 or 1.
+   * Default is 0. See . This is normally combined with
+   *  to disable manual sizing. Value is "autosize".
+   */
+  STYLE_AUTOSIZE: 'autosize',
+
+  /**
+   * Variable: STYLE_FOLDABLE
+   *
+   * Defines the key for the foldable style. This specifies if a cell is foldable
+   * using a folding icon. Possible values are 0 or 1. Default is 1. See
+   * . Value is "foldable".
+   */
+  STYLE_FOLDABLE: 'foldable',
+
+  /**
+   * Variable: STYLE_EDITABLE
+   *
+   * Defines the key for the editable style. This specifies if the value of
+   * a cell can be edited using the in-place editor. Possible values are 0 or
+   * 1. Default is 1. See . Value is "editable".
+   */
+  STYLE_EDITABLE: 'editable',
+
+  /**
+   * Variable: STYLE_BACKGROUND_OUTLINE
+   *
+   * Defines the key for the backgroundOutline style. This specifies if a
+   * only the background of a cell should be painted when it is highlighted.
+   * Possible values are 0 or 1. Default is 0. Value is "backgroundOutline".
+   */
+  STYLE_BACKGROUND_OUTLINE: 'backgroundOutline',
+
+  /**
+   * Variable: STYLE_BENDABLE
+   *
+   * Defines the key for the bendable style. This specifies if the control
+   * points of an edge can be moved. Possible values are 0 or 1. Default is
+   * 1. See . Value is "bendable".
+   */
+  STYLE_BENDABLE: 'bendable',
+
+  /**
+   * Variable: STYLE_MOVABLE
+   *
+   * Defines the key for the movable style. This specifies if a cell can
+   * be moved. Possible values are 0 or 1. Default is 1. See
+   * . Value is "movable".
+   */
+  STYLE_MOVABLE: 'movable',
+
+  /**
+   * Variable: STYLE_RESIZABLE
+   *
+   * Defines the key for the resizable style. This specifies if a cell can
+   * be resized. Possible values are 0 or 1. Default is 1. See
+   * . Value is "resizable".
+   */
+  STYLE_RESIZABLE: 'resizable',
+
+  /**
+   * Variable: STYLE_RESIZE_WIDTH
+   *
+   * Defines the key for the resizeWidth style. This specifies if a cell's
+   * width is resized if the parent is resized. If this is 1 then the width
+   * will be resized even if the cell's geometry is relative. If this is 0
+   * then the cell's width will not be resized. Default is not defined. Value
+   * is "resizeWidth".
+   */
+  STYLE_RESIZE_WIDTH: 'resizeWidth',
+
+  /**
+   * Variable: STYLE_RESIZE_WIDTH
+   *
+   * Defines the key for the resizeHeight style. This specifies if a cell's
+   * height if resize if the parent is resized. If this is 1 then the height
+   * will be resized even if the cell's geometry is relative. If this is 0
+   * then the cell's height will not be resized. Default is not defined. Value
+   * is "resizeHeight".
+   */
+  STYLE_RESIZE_HEIGHT: 'resizeHeight',
+
+  /**
+   * Variable: STYLE_ROTATABLE
+   *
+   * Defines the key for the rotatable style. This specifies if a cell can
+   * be rotated. Possible values are 0 or 1. Default is 1. See
+   * . Value is "rotatable".
+   */
+  STYLE_ROTATABLE: 'rotatable',
+
+  /**
+   * Variable: STYLE_CLONEABLE
+   *
+   * Defines the key for the cloneable style. This specifies if a cell can
+   * be cloned. Possible values are 0 or 1. Default is 1. See
+   * . Value is "cloneable".
+   */
+  STYLE_CLONEABLE: 'cloneable',
+
+  /**
+   * Variable: STYLE_DELETABLE
+   *
+   * Defines the key for the deletable style. This specifies if a cell can be
+   * deleted. Possible values are 0 or 1. Default is 1. See
+   * . Value is "deletable".
+   */
+  STYLE_DELETABLE: 'deletable',
+
+  /**
+   * Variable: STYLE_SHAPE
+   *
+   * Defines the key for the shape. Possible values are all constants with
+   * a SHAPE-prefix or any newly defined shape names. Value is "shape".
+   */
+  STYLE_SHAPE: 'shape',
+
+  /**
+   * Variable: STYLE_EDGE
+   *
+   * Defines the key for the edge style. Possible values are the functions
+   * defined in . Value is "edgeStyle".
+   */
+  STYLE_EDGE: 'edgeStyle',
+
+  /**
+   * Variable: STYLE_JETTY_SIZE
+   *
+   * Defines the key for the jetty size in .
+   * Default is 10. Possible values are all numeric values or "auto".
+   * Jetty size is the minimum length of the orthogonal segment before
+   * it attaches to a shape.
+   * Value is "jettySize".
+   */
+  STYLE_JETTY_SIZE: 'jettySize',
+
+  /**
+   * Variable: STYLE_SOURCE_JETTY_SIZE
+   *
+   * Defines the key for the jetty size in .
+   * Default is 10. Possible values are numeric values or "auto". This has
+   * precedence over . Value is "sourceJettySize".
+   */
+  STYLE_SOURCE_JETTY_SIZE: 'sourceJettySize',
+
+  /**
+   * Variable: targetJettySize
+   *
+   * Defines the key for the jetty size in .
+   * Default is 10. Possible values are numeric values or "auto". This has
+   * precedence over . Value is "targetJettySize".
+   */
+  STYLE_TARGET_JETTY_SIZE: 'targetJettySize',
+
+  /**
+   * Variable: STYLE_LOOP
+   *
+   * Defines the key for the loop style. Possible values are the functions
+   * defined in . Value is "loopStyle". Default is
+   * .
+   */
+  STYLE_LOOP: 'loopStyle',
+
+  /**
+   * Variable: STYLE_ORTHOGONAL_LOOP
+   *
+   * Defines the key for the orthogonal loop style. Possible values are 0 and
+   * 1. Default is 0. Value is "orthogonalLoop". Use this style to specify
+   * if loops with no waypoints and defined anchor points should be routed
+   * using  or not routed.
+   */
+  STYLE_ORTHOGONAL_LOOP: 'orthogonalLoop',
+
+  /**
+   * Variable: STYLE_ROUTING_CENTER_X
+   *
+   * Defines the key for the horizontal routing center. Possible values are
+   * between -0.5 and 0.5. This is the relative offset from the center used
+   * for connecting edges. The type of this value is numeric. Value is
+   * "routingCenterX".
+   */
+  STYLE_ROUTING_CENTER_X: 'routingCenterX',
+
+  /**
+   * Variable: STYLE_ROUTING_CENTER_Y
+   *
+   * Defines the key for the vertical routing center. Possible values are
+   * between -0.5 and 0.5. This is the relative offset from the center used
+   * for connecting edges. The type of this value is numeric. Value is
+   * "routingCenterY".
+   */
+  STYLE_ROUTING_CENTER_Y: 'routingCenterY',
+
+  /**
+   * Variable: FONT_BOLD
+   *
+   * Constant for bold fonts. Default is 1.
+   */
+  FONT_BOLD: 1,
+
+  /**
+   * Variable: FONT_ITALIC
+   *
+   * Constant for italic fonts. Default is 2.
+   */
+  FONT_ITALIC: 2,
+
+  /**
+   * Variable: FONT_UNDERLINE
+   *
+   * Constant for underlined fonts. Default is 4.
+   */
+  FONT_UNDERLINE: 4,
+
+  /**
+   * Variable: FONT_STRIKETHROUGH
+   *
+   * Constant for strikthrough fonts. Default is 8.
+   */
+  FONT_STRIKETHROUGH: 8,
+
+  /**
+   * Variable: SHAPE_RECTANGLE
+   *
+   * Name under which  is registered in .
+   * Default is rectangle.
+   */
+  SHAPE_RECTANGLE: 'rectangle',
+
+  /**
+   * Variable: SHAPE_ELLIPSE
+   *
+   * Name under which  is registered in .
+   * Default is ellipse.
+   */
+  SHAPE_ELLIPSE: 'ellipse',
+
+  /**
+   * Variable: SHAPE_DOUBLE_ELLIPSE
+   *
+   * Name under which  is registered in .
+   * Default is doubleEllipse.
+   */
+  SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',
+
+  /**
+   * Variable: SHAPE_RHOMBUS
+   *
+   * Name under which  is registered in .
+   * Default is rhombus.
+   */
+  SHAPE_RHOMBUS: 'rhombus',
+
+  /**
+   * Variable: SHAPE_LINE
+   *
+   * Name under which  is registered in .
+   * Default is line.
+   */
+  SHAPE_LINE: 'line',
+
+  /**
+   * Variable: SHAPE_IMAGE
+   *
+   * Name under which  is registered in .
+   * Default is image.
+   */
+  SHAPE_IMAGE: 'image',
+
+  /**
+   * Variable: SHAPE_ARROW
+   *
+   * Name under which  is registered in .
+   * Default is arrow.
+   */
+  SHAPE_ARROW: 'arrow',
+
+  /**
+   * Variable: SHAPE_ARROW_CONNECTOR
+   *
+   * Name under which  is registered in .
+   * Default is arrowConnector.
+   */
+  SHAPE_ARROW_CONNECTOR: 'arrowConnector',
+
+  /**
+   * Variable: SHAPE_LABEL
+   *
+   * Name under which  is registered in .
+   * Default is label.
+   */
+  SHAPE_LABEL: 'label',
+
+  /**
+   * Variable: SHAPE_CYLINDER
+   *
+   * Name under which  is registered in .
+   * Default is cylinder.
+   */
+  SHAPE_CYLINDER: 'cylinder',
+
+  /**
+   * Variable: SHAPE_SWIMLANE
+   *
+   * Name under which  is registered in .
+   * Default is swimlane.
+   */
+  SHAPE_SWIMLANE: 'swimlane',
+
+  /**
+   * Variable: SHAPE_CONNECTOR
+   *
+   * Name under which  is registered in .
+   * Default is connector.
+   */
+  SHAPE_CONNECTOR: 'connector',
+
+  /**
+   * Variable: SHAPE_ACTOR
+   *
+   * Name under which  is registered in .
+   * Default is actor.
+   */
+  SHAPE_ACTOR: 'actor',
+
+  /**
+   * Variable: SHAPE_CLOUD
+   *
+   * Name under which  is registered in .
+   * Default is cloud.
+   */
+  SHAPE_CLOUD: 'cloud',
+
+  /**
+   * Variable: SHAPE_TRIANGLE
+   *
+   * Name under which  is registered in .
+   * Default is triangle.
+   */
+  SHAPE_TRIANGLE: 'triangle',
+
+  /**
+   * Variable: SHAPE_HEXAGON
+   *
+   * Name under which  is registered in .
+   * Default is hexagon.
+   */
+  SHAPE_HEXAGON: 'hexagon',
+
+  /**
+   * Variable: ARROW_CLASSIC
+   *
+   * Constant for classic arrow markers.
+   */
+  ARROW_CLASSIC: 'classic',
+
+  /**
+   * Variable: ARROW_CLASSIC_THIN
+   *
+   * Constant for thin classic arrow markers.
+   */
+  ARROW_CLASSIC_THIN: 'classicThin',
+
+  /**
+   * Variable: ARROW_BLOCK
+   *
+   * Constant for block arrow markers.
+   */
+  ARROW_BLOCK: 'block',
+
+  /**
+   * Variable: ARROW_BLOCK_THIN
+   *
+   * Constant for thin block arrow markers.
+   */
+  ARROW_BLOCK_THIN: 'blockThin',
+
+  /**
+   * Variable: ARROW_OPEN
+   *
+   * Constant for open arrow markers.
+   */
+  ARROW_OPEN: 'open',
+
+  /**
+   * Variable: ARROW_OPEN_THIN
+   *
+   * Constant for thin open arrow markers.
+   */
+  ARROW_OPEN_THIN: 'openThin',
+
+  /**
+   * Variable: ARROW_OVAL
+   *
+   * Constant for oval arrow markers.
+   */
+  ARROW_OVAL: 'oval',
+
+  /**
+   * Variable: ARROW_DIAMOND
+   *
+   * Constant for diamond arrow markers.
+   */
+  ARROW_DIAMOND: 'diamond',
+
+  /**
+   * Variable: ARROW_DIAMOND_THIN
+   *
+   * Constant for thin diamond arrow markers.
+   */
+  ARROW_DIAMOND_THIN: 'diamondThin',
+
+  /**
+   * Variable: ALIGN_LEFT
+   *
+   * Constant for left horizontal alignment. Default is left.
+   */
+  ALIGN_LEFT: 'left',
+
+  /**
+   * Variable: ALIGN_CENTER
+   *
+   * Constant for center horizontal alignment. Default is center.
+   */
+  ALIGN_CENTER: 'center',
+
+  /**
+   * Variable: ALIGN_RIGHT
+   *
+   * Constant for right horizontal alignment. Default is right.
+   */
+  ALIGN_RIGHT: 'right',
+
+  /**
+   * Variable: ALIGN_TOP
+   *
+   * Constant for top vertical alignment. Default is top.
+   */
+  ALIGN_TOP: 'top',
+
+  /**
+   * Variable: ALIGN_MIDDLE
+   *
+   * Constant for middle vertical alignment. Default is middle.
+   */
+  ALIGN_MIDDLE: 'middle',
+
+  /**
+   * Variable: ALIGN_BOTTOM
+   *
+   * Constant for bottom vertical alignment. Default is bottom.
+   */
+  ALIGN_BOTTOM: 'bottom',
+
+  /**
+   * Variable: DIRECTION_NORTH
+   *
+   * Constant for direction north. Default is north.
+   */
+  DIRECTION_NORTH: 'north',
+
+  /**
+   * Variable: DIRECTION_SOUTH
+   *
+   * Constant for direction south. Default is south.
+   */
+  DIRECTION_SOUTH: 'south',
+
+  /**
+   * Variable: DIRECTION_EAST
+   *
+   * Constant for direction east. Default is east.
+   */
+  DIRECTION_EAST: 'east',
+
+  /**
+   * Variable: DIRECTION_WEST
+   *
+   * Constant for direction west. Default is west.
+   */
+  DIRECTION_WEST: 'west',
+
+  /**
+   * Variable: TEXT_DIRECTION_DEFAULT
+   *
+   * Constant for text direction default. Default is an empty string. Use
+   * this value to use the default text direction of the operating system.
+   */
+  TEXT_DIRECTION_DEFAULT: '',
+
+  /**
+   * Variable: TEXT_DIRECTION_AUTO
+   *
+   * Constant for text direction automatic. Default is auto. Use this value
+   * to find the direction for a given text with .
+   */
+  TEXT_DIRECTION_AUTO: 'auto',
+
+  /**
+   * Variable: TEXT_DIRECTION_LTR
+   *
+   * Constant for text direction left to right. Default is ltr. Use this
+   * value for left to right text direction.
+   */
+  TEXT_DIRECTION_LTR: 'ltr',
+
+  /**
+   * Variable: TEXT_DIRECTION_RTL
+   *
+   * Constant for text direction right to left. Default is rtl. Use this
+   * value for right to left text direction.
+   */
+  TEXT_DIRECTION_RTL: 'rtl',
+
+  /**
+   * Variable: DIRECTION_MASK_NONE
+   *
+   * Constant for no direction.
+   */
+  DIRECTION_MASK_NONE: 0,
+
+  /**
+   * Variable: DIRECTION_MASK_WEST
+   *
+   * Bitwise mask for west direction.
+   */
+  DIRECTION_MASK_WEST: 1,
+
+  /**
+   * Variable: DIRECTION_MASK_NORTH
+   *
+   * Bitwise mask for north direction.
+   */
+  DIRECTION_MASK_NORTH: 2,
+
+  /**
+   * Variable: DIRECTION_MASK_SOUTH
+   *
+   * Bitwise mask for south direction.
+   */
+  DIRECTION_MASK_SOUTH: 4,
+
+  /**
+   * Variable: DIRECTION_MASK_EAST
+   *
+   * Bitwise mask for east direction.
+   */
+  DIRECTION_MASK_EAST: 8,
+
+  /**
+   * Variable: DIRECTION_MASK_ALL
+   *
+   * Bitwise mask for all directions.
+   */
+  DIRECTION_MASK_ALL: 15,
+
+  /**
+   * Variable: ELBOW_VERTICAL
+   *
+   * Constant for elbow vertical. Default is horizontal.
+   */
+  ELBOW_VERTICAL: 'vertical',
+
+  /**
+   * Variable: ELBOW_HORIZONTAL
+   *
+   * Constant for elbow horizontal. Default is horizontal.
+   */
+  ELBOW_HORIZONTAL: 'horizontal',
+
+  /**
+   * Variable: EDGESTYLE_ELBOW
+   *
+   * Name of the elbow edge style. Can be used as a string value
+   * for the STYLE_EDGE style.
+   */
+  EDGESTYLE_ELBOW: 'elbowEdgeStyle',
+
+  /**
+   * Variable: EDGESTYLE_ENTITY_RELATION
+   *
+   * Name of the entity relation edge style. Can be used as a string value
+   * for the STYLE_EDGE style.
+   */
+  EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',
+
+  /**
+   * Variable: EDGESTYLE_LOOP
+   *
+   * Name of the loop edge style. Can be used as a string value
+   * for the STYLE_EDGE style.
+   */
+  EDGESTYLE_LOOP: 'loopEdgeStyle',
+
+  /**
+   * Variable: EDGESTYLE_SIDETOSIDE
+   *
+   * Name of the side to side edge style. Can be used as a string value
+   * for the STYLE_EDGE style.
+   */
+  EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',
+
+  /**
+   * Variable: EDGESTYLE_TOPTOBOTTOM
+   *
+   * Name of the top to bottom edge style. Can be used as a string value
+   * for the STYLE_EDGE style.
+   */
+  EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',
+
+  /**
+   * Variable: EDGESTYLE_ORTHOGONAL
+   *
+   * Name of the generic orthogonal edge style. Can be used as a string value
+   * for the STYLE_EDGE style.
+   */
+  EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',
+
+  /**
+   * Variable: EDGESTYLE_SEGMENT
+   *
+   * Name of the generic segment edge style. Can be used as a string value
+   * for the STYLE_EDGE style.
+   */
+  EDGESTYLE_SEGMENT: 'segmentEdgeStyle',
+
+  /**
+   * Variable: PERIMETER_ELLIPSE
+   *
+   * Name of the ellipse perimeter. Can be used as a string value
+   * for the STYLE_PERIMETER style.
+   */
+  PERIMETER_ELLIPSE: 'ellipsePerimeter',
+
+  /**
+   * Variable: PERIMETER_RECTANGLE
+   *
+   * Name of the rectangle perimeter. Can be used as a string value
+   * for the STYLE_PERIMETER style.
+   */
+  PERIMETER_RECTANGLE: 'rectanglePerimeter',
+
+  /**
+   * Variable: PERIMETER_RHOMBUS
+   *
+   * Name of the rhombus perimeter. Can be used as a string value
+   * for the STYLE_PERIMETER style.
+   */
+  PERIMETER_RHOMBUS: 'rhombusPerimeter',
+
+  /**
+   * Variable: PERIMETER_HEXAGON
+   *
+   * Name of the hexagon perimeter. Can be used as a string value
+   * for the STYLE_PERIMETER style.
+   */
+  PERIMETER_HEXAGON: 'hexagonPerimeter',
+
+  /**
+   * Variable: PERIMETER_TRIANGLE
+   *
+   * Name of the triangle perimeter. Can be used as a string value
+   * for the STYLE_PERIMETER style.
+   */
+  PERIMETER_TRIANGLE: 'trianglePerimeter',
+};
+
+/**
+ * util/mxEventObject.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventObject
+ *
+ * The mxEventObject is a wrapper for all properties of a single event.
+ * Additionally, it also offers functions to consume the event and check if it
+ * was consumed as follows:
+ *
+ * (code)
+ * evt.consume();
+ * INV: evt.isConsumed() == true
+ * (end)
+ *
+ * Constructor: mxEventObject
+ *
+ * Constructs a new event object with the specified name. An optional
+ * sequence of key, value pairs can be appended to define properties.
+ *
+ * Example:
+ *
+ * (code)
+ * new mxEventObject("eventName", key1, val1, .., keyN, valN)
+ * (end)
+ */
+function mxEventObject(name) {
+  this.name = name;
+  this.properties = [];
+
+  for (var i = 1; i < arguments.length; i += 2) {
+    if (arguments[i + 1] != null) {
+      this.properties[arguments[i]] = arguments[i + 1];
+    }
+  }
+}
+
+/**
+ * Variable: name
+ *
+ * Holds the name.
+ */
+mxEventObject.prototype.name = null;
+
+/**
+ * Variable: properties
+ *
+ * Holds the properties as an associative array.
+ */
+mxEventObject.prototype.properties = null;
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state. Default is false.
+ */
+mxEventObject.prototype.consumed = false;
+
+/**
+ * Function: getName
+ *
+ * Returns .
+ */
+mxEventObject.prototype.getName = function () {
+  return this.name;
+};
+
+/**
+ * Function: getProperties
+ *
+ * Returns .
+ */
+mxEventObject.prototype.getProperties = function () {
+  return this.properties;
+};
+
+/**
+ * Function: getProperty
+ *
+ * Returns the property for the given key.
+ */
+mxEventObject.prototype.getProperty = function (key) {
+  return this.properties[key];
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed.
+ */
+mxEventObject.prototype.isConsumed = function () {
+  return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Consumes the event.
+ */
+mxEventObject.prototype.consume = function () {
+  this.consumed = true;
+};
+
+/**
+ * util/mxMouseEvent.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMouseEvent
+ *
+ * Base class for all mouse events in mxGraph. A listener for this event should
+ * implement the following methods:
+ *
+ * (code)
+ * graph.addMouseListener(
+ * {
+ *   mouseDown: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseDown');
+ *   },
+ *   mouseMove: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseMove');
+ *   },
+ *   mouseUp: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseUp');
+ *   }
+ * });
+ * (end)
+ *
+ * Constructor: mxMouseEvent
+ *
+ * Constructs a new event object for the given arguments.
+ *
+ * Parameters:
+ *
+ * evt - Native mouse event.
+ * state - Optional  under the mouse.
+ *
+ */
+function mxMouseEvent(evt, state) {
+  this.evt = evt;
+  this.state = state;
+  this.sourceState = state;
+}
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state of this event.
+ */
+mxMouseEvent.prototype.consumed = false;
+
+/**
+ * Variable: evt
+ *
+ * Holds the inner event object.
+ */
+mxMouseEvent.prototype.evt = null;
+
+/**
+ * Variable: graphX
+ *
+ * Holds the x-coordinate of the event in the graph. This value is set in
+ * .
+ */
+mxMouseEvent.prototype.graphX = null;
+
+/**
+ * Variable: graphY
+ *
+ * Holds the y-coordinate of the event in the graph. This value is set in
+ * .
+ */
+mxMouseEvent.prototype.graphY = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the optional  associated with this event.
+ */
+mxMouseEvent.prototype.state = null;
+
+/**
+ * Variable: sourceState
+ *
+ * Holds the  that was passed to the constructor. This can be
+ * different from  depending on the result of .
+ */
+mxMouseEvent.prototype.sourceState = null;
+
+/**
+ * Function: getEvent
+ *
+ * Returns .
+ */
+mxMouseEvent.prototype.getEvent = function () {
+  return this.evt;
+};
+
+/**
+ * Function: getSource
+ *
+ * Returns the target DOM element using  for .
+ */
+mxMouseEvent.prototype.getSource = function () {
+  return mxEvent.getSource(this.evt);
+};
+
+/**
+ * Function: isSource
+ *
+ * Returns true if the given  is the source of .
+ */
+mxMouseEvent.prototype.isSource = function (shape) {
+  if (shape != null) {
+    return mxUtils.isAncestorNode(shape.node, this.getSource());
+  }
+
+  return false;
+};
+
+/**
+ * Function: getX
+ *
+ * Returns .
+ */
+mxMouseEvent.prototype.getX = function () {
+  return mxEvent.getClientX(this.getEvent());
+};
+
+/**
+ * Function: getY
+ *
+ * Returns .
+ */
+mxMouseEvent.prototype.getY = function () {
+  return mxEvent.getClientY(this.getEvent());
+};
+
+/**
+ * Function: getGraphX
+ *
+ * Returns .
+ */
+mxMouseEvent.prototype.getGraphX = function () {
+  return this.graphX;
+};
+
+/**
+ * Function: getGraphY
+ *
+ * Returns .
+ */
+mxMouseEvent.prototype.getGraphY = function () {
+  return this.graphY;
+};
+
+/**
+ * Function: getState
+ *
+ * Returns .
+ */
+mxMouseEvent.prototype.getState = function () {
+  return this.state;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the  in  is not null.
+ */
+mxMouseEvent.prototype.getCell = function () {
+  var state = this.getState();
+
+  if (state != null) {
+    return state.cell;
+  }
+
+  return null;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger.
+ */
+mxMouseEvent.prototype.isPopupTrigger = function () {
+  return mxEvent.isPopupTrigger(this.getEvent());
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns .
+ */
+mxMouseEvent.prototype.isConsumed = function () {
+  return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Sets  to true and invokes preventDefault on the native event
+ * if such a method is defined. This is used mainly to avoid the cursor from
+ * being changed to a text cursor in Webkit. You can use the preventDefault
+ * flag to disable this functionality.
+ *
+ * Parameters:
+ *
+ * preventDefault - Specifies if the native event should be canceled. Default
+ * is true.
+ */
+mxMouseEvent.prototype.consume = function (preventDefault) {
+  preventDefault = preventDefault != null ? preventDefault : this.evt.touches != null || mxEvent.isMouseEvent(this.evt);
+
+  if (preventDefault && this.evt.preventDefault) {
+    this.evt.preventDefault();
+  }
+
+  // Workaround for images being dragged in IE
+  // Does not change returnValue in Opera
+  if (mxClient.IS_IE) {
+    this.evt.returnValue = true;
+  }
+
+  // Sets local consumed state
+  this.consumed = true;
+};
+
+/**
+ * util/mxEventSource.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventSource
+ *
+ * Base class for objects that dispatch named events. To create a subclass that
+ * inherits from mxEventSource, the following code is used.
+ *
+ * (code)
+ * function MyClass() { };
+ *
+ * MyClass.prototype = new mxEventSource();
+ * MyClass.prototype.constructor = MyClass;
+ * (end)
+ *
+ * Known Subclasses:
+ *
+ * , , , , ,
+ * , 
+ *
+ * Constructor: mxEventSource
+ *
+ * Constructs a new event source.
+ */
+function mxEventSource(eventSource) {
+  this.setEventSource(eventSource);
+}
+
+/**
+ * Variable: eventListeners
+ *
+ * Holds the event names and associated listeners in an array. The array
+ * contains the event name followed by the respective listener for each
+ * registered listener.
+ */
+mxEventSource.prototype.eventListeners = null;
+
+/**
+ * Variable: eventsEnabled
+ *
+ * Specifies if events can be fired. Default is true.
+ */
+mxEventSource.prototype.eventsEnabled = true;
+
+/**
+ * Variable: eventSource
+ *
+ * Optional source for events. Default is null.
+ */
+mxEventSource.prototype.eventSource = null;
+
+/**
+ * Function: isEventsEnabled
+ *
+ * Returns .
+ */
+mxEventSource.prototype.isEventsEnabled = function () {
+  return this.eventsEnabled;
+};
+
+/**
+ * Function: setEventsEnabled
+ *
+ * Sets .
+ */
+mxEventSource.prototype.setEventsEnabled = function (value) {
+  this.eventsEnabled = value;
+};
+
+/**
+ * Function: getEventSource
+ *
+ * Returns .
+ */
+mxEventSource.prototype.getEventSource = function () {
+  return this.eventSource;
+};
+
+/**
+ * Function: setEventSource
+ *
+ * Sets .
+ */
+mxEventSource.prototype.setEventSource = function (value) {
+  this.eventSource = value;
+};
+
+/**
+ * Function: addListener
+ *
+ * Binds the specified function to the given event name. If no event name
+ * is given, then the listener is registered for all events.
+ *
+ * The parameters of the listener are the sender and an .
+ */
+mxEventSource.prototype.addListener = function (name, funct) {
+  if (this.eventListeners == null) {
+    this.eventListeners = [];
+  }
+
+  this.eventListeners.push(name);
+  this.eventListeners.push(funct);
+};
+
+/**
+ * Function: removeListener
+ *
+ * Removes all occurrences of the given listener from .
+ */
+mxEventSource.prototype.removeListener = function (funct) {
+  if (this.eventListeners != null) {
+    var i = 0;
+
+    while (i < this.eventListeners.length) {
+      if (this.eventListeners[i + 1] == funct) {
+        this.eventListeners.splice(i, 2);
+      } else {
+        i += 2;
+      }
+    }
+  }
+};
+
+/**
+ * Function: fireEvent
+ *
+ * Dispatches the given event to the listeners which are registered for
+ * the event. The sender argument is optional. The current execution scope
+ * ("this") is used for the listener invocation (see ).
+ *
+ * Example:
+ *
+ * (code)
+ * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
+ * (end)
+ *
+ * Parameters:
+ *
+ * evt -  that represents the event.
+ * sender - Optional sender to be passed to the listener. Default value is
+ * the return value of .
+ */
+mxEventSource.prototype.fireEvent = function (evt, sender) {
+  if (this.eventListeners != null && this.isEventsEnabled()) {
+    if (evt == null) {
+      evt = new mxEventObject();
+    }
+
+    if (sender == null) {
+      sender = this.getEventSource();
+    }
+
+    if (sender == null) {
+      sender = this;
+    }
+
+    var args = [sender, evt];
+
+    for (var i = 0; i < this.eventListeners.length; i += 2) {
+      var listen = this.eventListeners[i];
+
+      if (listen == null || listen == evt.getName()) {
+        this.eventListeners[i + 1].apply(this, args);
+      }
+    }
+  }
+};
+
+/**
+ * util/mxEvent.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEvent = {
+  /**
+   * Class: mxEvent
+   *
+   * Cross-browser DOM event support. For internal event handling,
+   *  and the graph event dispatch loop in  are used.
+   *
+   * Memory Leaks:
+   *
+   * Use this class for adding and removing listeners to/from DOM nodes. The
+   *  function is provided to remove all listeners that
+   * have been added using . The function should be invoked when
+   * the last reference is removed in the JavaScript code, typically when the
+   * referenced DOM node is removed from the DOM.
+   *
+   * Function: addListener
+   *
+   * Binds the function to the specified event on the given element. Use
+   *  in order to bind the "this" keyword inside the function
+   * to a given execution scope.
+   */
+  addListener: (function () {
+    var updateListenerList = function (element, eventName, funct) {
+      if (element.mxListenerList == null) {
+        element.mxListenerList = [];
+      }
+
+      var entry = { name: eventName, f: funct };
+      element.mxListenerList.push(entry);
+    };
+
+    if (window.addEventListener) {
+      // Checks if passive event listeners are supported
+      // see https://github.com/Modernizr/Modernizr/issues/1894
+      var supportsPassive = false;
+
+      try {
+        document.addEventListener(
+          'test',
+          function () {},
+          Object.defineProperty &&
+            Object.defineProperty({}, 'passive', {
+              get: function () {
+                supportsPassive = true;
+              },
+            })
+        );
+      } catch (e) {
+        // ignore
+      }
+
+      return function (element, eventName, funct) {
+        element.addEventListener(eventName, funct, supportsPassive ? { passive: false } : false);
+        updateListenerList(element, eventName, funct);
+      };
+    } else {
+      return function (element, eventName, funct) {
+        element.attachEvent('on' + eventName, funct);
+        updateListenerList(element, eventName, funct);
+      };
+    }
+  })(),
+
+  /**
+   * Function: removeListener
+   *
+   * Removes the specified listener from the given element.
+   */
+  removeListener: (function () {
+    var updateListener = function (element, eventName, funct) {
+      if (element.mxListenerList != null) {
+        var listenerCount = element.mxListenerList.length;
+
+        for (var i = 0; i < listenerCount; i++) {
+          var entry = element.mxListenerList[i];
+
+          if (entry.f == funct) {
+            element.mxListenerList.splice(i, 1);
+            break;
+          }
+        }
+
+        if (element.mxListenerList.length == 0) {
+          element.mxListenerList = null;
+        }
+      }
+    };
+
+    if (window.removeEventListener) {
+      return function (element, eventName, funct) {
+        element.removeEventListener(eventName, funct, false);
+        updateListener(element, eventName, funct);
+      };
+    } else {
+      return function (element, eventName, funct) {
+        element.detachEvent('on' + eventName, funct);
+        updateListener(element, eventName, funct);
+      };
+    }
+  })(),
+
+  /**
+   * Function: removeAllListeners
+   *
+   * Removes all listeners from the given element.
+   */
+  removeAllListeners: function (element) {
+    var list = element.mxListenerList;
+
+    if (list != null) {
+      while (list.length > 0) {
+        var entry = list[0];
+        mxEvent.removeListener(element, entry.name, entry.f);
+      }
+    }
+  },
+
+  /**
+   * Function: addGestureListeners
+   *
+   * Adds the given listeners for touch, mouse and/or pointer events. If
+   *  is true then pointer events will be registered,
+   * else the respective mouse events will be registered. If 
+   * is false and  is true then the respective touch events
+   * will be registered as well as the mouse events.
+   */
+  addGestureListeners: function (node, startListener, moveListener, endListener) {
+    if (startListener != null) {
+      mxEvent.addListener(node, mxClient.IS_POINTER ? 'pointerdown' : 'mousedown', startListener);
+    }
+
+    if (moveListener != null) {
+      mxEvent.addListener(node, mxClient.IS_POINTER ? 'pointermove' : 'mousemove', moveListener);
+    }
+
+    if (endListener != null) {
+      mxEvent.addListener(node, mxClient.IS_POINTER ? 'pointerup' : 'mouseup', endListener);
+    }
+
+    if (!mxClient.IS_POINTER && mxClient.IS_TOUCH) {
+      if (startListener != null) {
+        mxEvent.addListener(node, 'touchstart', startListener);
+      }
+
+      if (moveListener != null) {
+        mxEvent.addListener(node, 'touchmove', moveListener);
+      }
+
+      if (endListener != null) {
+        mxEvent.addListener(node, 'touchend', endListener);
+      }
+    }
+  },
+
+  /**
+   * Function: removeGestureListeners
+   *
+   * Removes the given listeners from mousedown, mousemove, mouseup and the
+   * respective touch events if  is true.
+   */
+  removeGestureListeners: function (node, startListener, moveListener, endListener) {
+    if (startListener != null) {
+      mxEvent.removeListener(node, mxClient.IS_POINTER ? 'pointerdown' : 'mousedown', startListener);
+    }
+
+    if (moveListener != null) {
+      mxEvent.removeListener(node, mxClient.IS_POINTER ? 'pointermove' : 'mousemove', moveListener);
+    }
+
+    if (endListener != null) {
+      mxEvent.removeListener(node, mxClient.IS_POINTER ? 'pointerup' : 'mouseup', endListener);
+    }
+
+    if (!mxClient.IS_POINTER && mxClient.IS_TOUCH) {
+      if (startListener != null) {
+        mxEvent.removeListener(node, 'touchstart', startListener);
+      }
+
+      if (moveListener != null) {
+        mxEvent.removeListener(node, 'touchmove', moveListener);
+      }
+
+      if (endListener != null) {
+        mxEvent.removeListener(node, 'touchend', endListener);
+      }
+    }
+  },
+
+  /**
+   * Function: redirectMouseEvents
+   *
+   * Redirects the mouse events from the given DOM node to the graph dispatch
+   * loop using the event and given state as event arguments. State can
+   * either be an instance of  or a function that returns an
+   * . The down, move, up and dblClick arguments are optional
+   * functions that take the trigger event as arguments and replace the
+   * default behaviour.
+   */
+  redirectMouseEvents: function (node, graph, state, down, move, up, dblClick) {
+    var getState = function (evt) {
+      return typeof state == 'function' ? state(evt) : state;
+    };
+
+    mxEvent.addGestureListeners(
+      node,
+      function (evt) {
+        if (down != null) {
+          down(evt);
+        } else if (!mxEvent.isConsumed(evt)) {
+          graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState(evt)));
+        }
+      },
+      function (evt) {
+        if (move != null) {
+          move(evt);
+        } else if (!mxEvent.isConsumed(evt)) {
+          graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+        }
+      },
+      function (evt) {
+        if (up != null) {
+          up(evt);
+        } else if (!mxEvent.isConsumed(evt)) {
+          graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+        }
+      }
+    );
+
+    mxEvent.addListener(node, 'dblclick', function (evt) {
+      if (dblClick != null) {
+        dblClick(evt);
+      } else if (!mxEvent.isConsumed(evt)) {
+        var tmp = getState(evt);
+        graph.dblClick(evt, tmp != null ? tmp.cell : null);
+      }
+    });
+  },
+
+  /**
+   * Function: release
+   *
+   * Removes the known listeners from the given DOM node and its descendants.
+   *
+   * Parameters:
+   *
+   * element - DOM node to remove the listeners from.
+   */
+  release: function (element) {
+    try {
+      if (element != null) {
+        mxEvent.removeAllListeners(element);
+
+        var children = element.childNodes;
+
+        if (children != null) {
+          var childCount = children.length;
+
+          for (var i = 0; i < childCount; i += 1) {
+            mxEvent.release(children[i]);
+          }
+        }
+      }
+    } catch (e) {
+      // ignores errors as this is typically called in cleanup code
+    }
+  },
+
+  /**
+   * Function: addMouseWheelListener
+   *
+   * Installs the given function as a handler for mouse wheel events. The
+   * function has two arguments: the mouse event and a boolean that specifies
+   * if the wheel was moved up or down.
+   *
+   * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
+   * Safari. It does currently not work on Safari for Mac.
+   *
+   * Example:
+   *
+   * (code)
+   * mxEvent.addMouseWheelListener(function (evt, up, pinch)
+   * {
+   *   mxLog.show();
+   *   mxLog.debug('mouseWheel: up='+up);
+   * });
+   *(end)
+   *
+   * Parameters:
+   *
+   * funct - Handler function that takes the event argument, a boolean argument
+   * for the mousewheel direction and a boolean to specify if the underlying
+   * event was a pinch gesture on a touch device.
+   * target - Target for installing the listener in Google Chrome. See
+   * https://www.chromestatus.com/features/6662647093133312.
+   */
+  addMouseWheelListener: function (funct, target) {
+    if (funct != null) {
+      var wheelHandler = function (evt) {
+        // IE does not give an event object but the
+        // global event object is the mousewheel event
+        // at this point in time.
+        if (evt == null) {
+          evt = window.event;
+        }
+
+        //To prevent window zoom on trackpad pinch
+        if (evt.ctrlKey) {
+          evt.preventDefault();
+        }
+
+        // Handles the event using the given function
+        if (Math.abs(evt.deltaX) > 0.5 || Math.abs(evt.deltaY) > 0.5) {
+          funct(evt, evt.deltaY == 0 ? -evt.deltaX > 0 : -evt.deltaY > 0);
+        }
+      };
+
+      target = target != null ? target : window;
+
+      if (mxClient.IS_SF && !mxClient.IS_TOUCH) {
+        var scale = 1;
+
+        mxEvent.addListener(target, 'gesturestart', function (evt) {
+          mxEvent.consume(evt);
+          scale = 1;
+        });
+
+        mxEvent.addListener(target, 'gesturechange', function (evt) {
+          mxEvent.consume(evt);
+          var diff = scale - evt.scale;
+
+          if (Math.abs(diff) > 0.2) {
+            funct(evt, diff < 0, true);
+            scale = evt.scale;
+          }
+        });
+
+        mxEvent.addListener(target, 'gestureend', function (evt) {
+          mxEvent.consume(evt);
+        });
+      } else {
+        var evtCache = [];
+        var dx0 = 0;
+        var dy0 = 0;
+
+        // Adds basic listeners for graph event dispatching
+        mxEvent.addGestureListeners(
+          target,
+          mxUtils.bind(this, function (evt) {
+            if (!mxEvent.isMouseEvent(evt) && evt.pointerId != null) {
+              evtCache.push(evt);
+            }
+          }),
+          mxUtils.bind(this, function (evt) {
+            if (!mxEvent.isMouseEvent(evt) && evtCache.length == 2) {
+              // Find this event in the cache and update its record with this event
+              for (var i = 0; i < evtCache.length; i++) {
+                if (evt.pointerId == evtCache[i].pointerId) {
+                  evtCache[i] = evt;
+                  break;
+                }
+              }
+
+              // Calculate the distance between the two pointers
+              var dx = Math.abs(evtCache[0].clientX - evtCache[1].clientX);
+              var dy = Math.abs(evtCache[0].clientY - evtCache[1].clientY);
+              var tx = Math.abs(dx - dx0);
+              var ty = Math.abs(dy - dy0);
+
+              if (tx > mxEvent.PINCH_THRESHOLD || ty > mxEvent.PINCH_THRESHOLD) {
+                var cx = evtCache[0].clientX + (evtCache[1].clientX - evtCache[0].clientX) / 2;
+                var cy = evtCache[0].clientY + (evtCache[1].clientY - evtCache[0].clientY) / 2;
+
+                funct(evtCache[0], tx > ty ? dx > dx0 : dy > dy0, true, cx, cy);
+
+                // Cache the distance for the next move event
+                dx0 = dx;
+                dy0 = dy;
+              }
+            }
+          }),
+          mxUtils.bind(this, function (evt) {
+            evtCache = [];
+            dx0 = 0;
+            dy0 = 0;
+          })
+        );
+      }
+
+      mxEvent.addListener(target, 'wheel', wheelHandler);
+    }
+  },
+
+  /**
+   * Function: disableContextMenu
+   *
+   * Disables the context menu for the given element.
+   */
+  disableContextMenu: function (element) {
+    mxEvent.addListener(element, 'contextmenu', function (evt) {
+      if (evt.preventDefault) {
+        evt.preventDefault();
+      }
+
+      return false;
+    });
+  },
+
+  /**
+   * Function: getSource
+   *
+   * Returns the event's target or srcElement depending on the browser.
+   */
+  getSource: function (evt) {
+    return evt.srcElement != null ? evt.srcElement : evt.target;
+  },
+
+  /**
+   * Function: isConsumed
+   *
+   * Returns true if the event has been consumed using .
+   */
+  isConsumed: function (evt) {
+    return evt.isConsumed != null && evt.isConsumed;
+  },
+
+  /**
+   * Function: isTouchEvent
+   *
+   * Returns true if the event was generated using a touch device (not a pen or mouse).
+   */
+  isTouchEvent: function (evt) {
+    return evt.pointerType != null
+      ? evt.pointerType == 'touch' || evt.pointerType === evt.MSPOINTER_TYPE_TOUCH
+      : evt.mozInputSource != null
+      ? evt.mozInputSource == 5
+      : evt.type.indexOf('touch') == 0;
+  },
+
+  /**
+   * Function: isPenEvent
+   *
+   * Returns true if the event was generated using a pen (not a touch device or mouse).
+   */
+  isPenEvent: function (evt) {
+    return evt.pointerType != null
+      ? evt.pointerType == 'pen' || evt.pointerType === evt.MSPOINTER_TYPE_PEN
+      : evt.mozInputSource != null
+      ? evt.mozInputSource == 2
+      : evt.type.indexOf('pen') == 0;
+  },
+
+  /**
+   * Function: isMultiTouchEvent
+   *
+   * Returns true if the event was generated using a touch device (not a pen or mouse).
+   */
+  isMultiTouchEvent: function (evt) {
+    return evt.type != null && evt.type.indexOf('touch') == 0 && evt.touches != null && evt.touches.length > 1;
+  },
+
+  /**
+   * Function: isMouseEvent
+   *
+   * Returns true if the event was generated using a mouse (not a pen or touch device).
+   */
+  isMouseEvent: function (evt) {
+    return evt.pointerType != null
+      ? evt.pointerType == 'mouse' || evt.pointerType === evt.MSPOINTER_TYPE_MOUSE
+      : evt.mozInputSource != null
+      ? evt.mozInputSource == 1
+      : evt.type.indexOf('mouse') == 0;
+  },
+
+  /**
+   * Function: isLeftMouseButton
+   *
+   * Returns true if the left mouse button is pressed for the given event.
+   * To check if a button is pressed during a mouseMove you should use the
+   *  property. Note that this returns true in Firefox
+   * for control+left-click on the Mac.
+   */
+  isLeftMouseButton: function (evt) {
+    // Special case for mousemove and mousedown we check the buttons
+    // if it exists because which is 0 even if no button is pressed
+    if ('buttons' in evt && (evt.type == 'mousedown' || evt.type == 'mousemove')) {
+      return evt.buttons == 1;
+    } else if ('which' in evt) {
+      return evt.which === 1;
+    } else {
+      return evt.button === 1;
+    }
+  },
+
+  /**
+   * Function: isMiddleMouseButton
+   *
+   * Returns true if the middle mouse button is pressed for the given event.
+   * To check if a button is pressed during a mouseMove you should use the
+   *  property.
+   */
+  isMiddleMouseButton: function (evt) {
+    if ('which' in evt) {
+      return evt.which === 2;
+    } else {
+      return evt.button === 4;
+    }
+  },
+
+  /**
+   * Function: isRightMouseButton
+   *
+   * Returns true if the right mouse button was pressed. Note that this
+   * button might not be available on some systems. For handling a popup
+   * trigger  should be used.
+   */
+  isRightMouseButton: function (evt) {
+    if ('which' in evt) {
+      return evt.which === 3;
+    } else {
+      return evt.button === 2;
+    }
+  },
+
+  /**
+   * Function: isPopupTrigger
+   *
+   * Returns true if the event is a popup trigger. This implementation
+   * returns true if the right button or the left button and control was
+   * pressed on a Mac.
+   */
+  isPopupTrigger: function (evt) {
+    return (
+      mxEvent.isRightMouseButton(evt) ||
+      (mxClient.IS_MAC &&
+        mxEvent.isControlDown(evt) &&
+        !mxEvent.isShiftDown(evt) &&
+        !mxEvent.isMetaDown(evt) &&
+        !mxEvent.isAltDown(evt))
+    );
+  },
+
+  /**
+   * Function: isShiftDown
+   *
+   * Returns true if the shift key is pressed for the given event.
+   */
+  isShiftDown: function (evt) {
+    return evt != null ? evt.shiftKey : false;
+  },
+
+  /**
+   * Function: isAltDown
+   *
+   * Returns true if the alt key is pressed for the given event.
+   */
+  isAltDown: function (evt) {
+    return evt != null ? evt.altKey : false;
+  },
+
+  /**
+   * Function: isControlDown
+   *
+   * Returns true if the control key is pressed for the given event.
+   */
+  isControlDown: function (evt) {
+    return evt != null ? evt.ctrlKey : false;
+  },
+
+  /**
+   * Function: isMetaDown
+   *
+   * Returns true if the meta key is pressed for the given event.
+   */
+  isMetaDown: function (evt) {
+    return evt != null ? evt.metaKey : false;
+  },
+
+  /**
+   * Function: getMainEvent
+   *
+   * Returns the touch or mouse event that contains the mouse coordinates.
+   */
+  getMainEvent: function (e) {
+    if ((e.type == 'touchstart' || e.type == 'touchmove') && e.touches != null && e.touches[0] != null) {
+      e = e.touches[0];
+    } else if (e.type == 'touchend' && e.changedTouches != null && e.changedTouches[0] != null) {
+      e = e.changedTouches[0];
+    }
+
+    return e;
+  },
+
+  /**
+   * Function: getClientX
+   *
+   * Returns true if the meta key is pressed for the given event.
+   */
+  getClientX: function (e) {
+    return mxEvent.getMainEvent(e).clientX;
+  },
+
+  /**
+   * Function: getClientY
+   *
+   * Returns true if the meta key is pressed for the given event.
+   */
+  getClientY: function (e) {
+    return mxEvent.getMainEvent(e).clientY;
+  },
+
+  /**
+   * Function: consume
+   *
+   * Consumes the given event.
+   *
+   * Parameters:
+   *
+   * evt - Native event to be consumed.
+   * preventDefault - Optional boolean to prevent the default for the event.
+   * Default is true.
+   * stopPropagation - Option boolean to stop event propagation. Default is
+   * true.
+   */
+  consume: function (evt, preventDefault, stopPropagation) {
+    preventDefault = preventDefault != null ? preventDefault : true;
+    stopPropagation = stopPropagation != null ? stopPropagation : true;
+
+    if (preventDefault) {
+      if (evt.preventDefault) {
+        if (stopPropagation) {
+          evt.stopPropagation();
+        }
+
+        evt.preventDefault();
+      } else if (stopPropagation) {
+        evt.cancelBubble = true;
+      }
+    }
+
+    // Opera
+    evt.isConsumed = true;
+
+    // Other browsers
+    if (!evt.preventDefault) {
+      evt.returnValue = false;
+    }
+  },
+
+  //
+  // Special handles in mouse events
+  //
+
+  /**
+   * Variable: LABEL_HANDLE
+   *
+   * Index for the label handle in an mxMouseEvent. This should be a negative
+   * value that does not interfere with any possible handle indices. Default
+   * is -1.
+   */
+  LABEL_HANDLE: -1,
+
+  /**
+   * Variable: ROTATION_HANDLE
+   *
+   * Index for the rotation handle in an mxMouseEvent. This should be a
+   * negative value that does not interfere with any possible handle indices.
+   * Default is -2.
+   */
+  ROTATION_HANDLE: -2,
+
+  /**
+   * Variable: CUSTOM_HANDLE
+   *
+   * Start index for the custom handles in an mxMouseEvent. This should be a
+   * negative value and is the start index which is decremented for each
+   * custom handle. Default is -100.
+   */
+  CUSTOM_HANDLE: -100,
+
+  /**
+   * Variable: VIRTUAL_HANDLE
+   *
+   * Start index for the virtual handles in an mxMouseEvent. This should be a
+   * negative value and is the start index which is decremented for each
+   * virtual handle. Default is -100000. This assumes that there are no more
+   * than VIRTUAL_HANDLE - CUSTOM_HANDLE custom handles.
+   *
+   */
+  VIRTUAL_HANDLE: -100000,
+
+  //
+  // Event names
+  //
+
+  /**
+   * Variable: MOUSE_DOWN
+   *
+   * Specifies the event name for mouseDown.
+   */
+  MOUSE_DOWN: 'mouseDown',
+
+  /**
+   * Variable: MOUSE_MOVE
+   *
+   * Specifies the event name for mouseMove.
+   */
+  MOUSE_MOVE: 'mouseMove',
+
+  /**
+   * Variable: MOUSE_UP
+   *
+   * Specifies the event name for mouseUp.
+   */
+  MOUSE_UP: 'mouseUp',
+
+  /**
+   * Variable: ACTIVATE
+   *
+   * Specifies the event name for activate.
+   */
+  ACTIVATE: 'activate',
+
+  /**
+   * Variable: RESIZE_START
+   *
+   * Specifies the event name for resizeStart.
+   */
+  RESIZE_START: 'resizeStart',
+
+  /**
+   * Variable: RESIZE
+   *
+   * Specifies the event name for resize.
+   */
+  RESIZE: 'resize',
+
+  /**
+   * Variable: RESIZE_END
+   *
+   * Specifies the event name for resizeEnd.
+   */
+  RESIZE_END: 'resizeEnd',
+
+  /**
+   * Variable: MOVE_START
+   *
+   * Specifies the event name for moveStart.
+   */
+  MOVE_START: 'moveStart',
+
+  /**
+   * Variable: MOVE
+   *
+   * Specifies the event name for move.
+   */
+  MOVE: 'move',
+
+  /**
+   * Variable: MOVE_END
+   *
+   * Specifies the event name for moveEnd.
+   */
+  MOVE_END: 'moveEnd',
+
+  /**
+   * Variable: PAN_START
+   *
+   * Specifies the event name for panStart.
+   */
+  PAN_START: 'panStart',
+
+  /**
+   * Variable: PAN
+   *
+   * Specifies the event name for pan.
+   */
+  PAN: 'pan',
+
+  /**
+   * Variable: PAN_END
+   *
+   * Specifies the event name for panEnd.
+   */
+  PAN_END: 'panEnd',
+
+  /**
+   * Variable: MINIMIZE
+   *
+   * Specifies the event name for minimize.
+   */
+  MINIMIZE: 'minimize',
+
+  /**
+   * Variable: NORMALIZE
+   *
+   * Specifies the event name for normalize.
+   */
+  NORMALIZE: 'normalize',
+
+  /**
+   * Variable: MAXIMIZE
+   *
+   * Specifies the event name for maximize.
+   */
+  MAXIMIZE: 'maximize',
+
+  /**
+   * Variable: HIDE
+   *
+   * Specifies the event name for hide.
+   */
+  HIDE: 'hide',
+
+  /**
+   * Variable: SHOW
+   *
+   * Specifies the event name for show.
+   */
+  SHOW: 'show',
+
+  /**
+   * Variable: CLOSE
+   *
+   * Specifies the event name for close.
+   */
+  CLOSE: 'close',
+
+  /**
+   * Variable: DESTROY
+   *
+   * Specifies the event name for destroy.
+   */
+  DESTROY: 'destroy',
+
+  /**
+   * Variable: REFRESH
+   *
+   * Specifies the event name for refresh.
+   */
+  REFRESH: 'refresh',
+
+  /**
+   * Variable: SIZE
+   *
+   * Specifies the event name for size.
+   */
+  SIZE: 'size',
+
+  /**
+   * Variable: SELECT
+   *
+   * Specifies the event name for select.
+   */
+  SELECT: 'select',
+
+  /**
+   * Variable: FIRED
+   *
+   * Specifies the event name for fired.
+   */
+  FIRED: 'fired',
+
+  /**
+   * Variable: FIRE_MOUSE_EVENT
+   *
+   * Specifies the event name for fireMouseEvent.
+   */
+  FIRE_MOUSE_EVENT: 'fireMouseEvent',
+
+  /**
+   * Variable: GESTURE
+   *
+   * Specifies the event name for gesture.
+   */
+  GESTURE: 'gesture',
+
+  /**
+   * Variable: TAP_AND_HOLD
+   *
+   * Specifies the event name for tapAndHold.
+   */
+  TAP_AND_HOLD: 'tapAndHold',
+
+  /**
+   * Variable: GET
+   *
+   * Specifies the event name for get.
+   */
+  GET: 'get',
+
+  /**
+   * Variable: RECEIVE
+   *
+   * Specifies the event name for receive.
+   */
+  RECEIVE: 'receive',
+
+  /**
+   * Variable: CONNECT
+   *
+   * Specifies the event name for connect.
+   */
+  CONNECT: 'connect',
+
+  /**
+   * Variable: DISCONNECT
+   *
+   * Specifies the event name for disconnect.
+   */
+  DISCONNECT: 'disconnect',
+
+  /**
+   * Variable: SUSPEND
+   *
+   * Specifies the event name for suspend.
+   */
+  SUSPEND: 'suspend',
+
+  /**
+   * Variable: RESUME
+   *
+   * Specifies the event name for suspend.
+   */
+  RESUME: 'resume',
+
+  /**
+   * Variable: MARK
+   *
+   * Specifies the event name for mark.
+   */
+  MARK: 'mark',
+
+  /**
+   * Variable: ROOT
+   *
+   * Specifies the event name for root.
+   */
+  ROOT: 'root',
+
+  /**
+   * Variable: POST
+   *
+   * Specifies the event name for post.
+   */
+  POST: 'post',
+
+  /**
+   * Variable: OPEN
+   *
+   * Specifies the event name for open.
+   */
+  OPEN: 'open',
+
+  /**
+   * Variable: SAVE
+   *
+   * Specifies the event name for open.
+   */
+  SAVE: 'save',
+
+  /**
+   * Variable: BEFORE_ADD_VERTEX
+   *
+   * Specifies the event name for beforeAddVertex.
+   */
+  BEFORE_ADD_VERTEX: 'beforeAddVertex',
+
+  /**
+   * Variable: ADD_VERTEX
+   *
+   * Specifies the event name for addVertex.
+   */
+  ADD_VERTEX: 'addVertex',
+
+  /**
+   * Variable: AFTER_ADD_VERTEX
+   *
+   * Specifies the event name for afterAddVertex.
+   */
+  AFTER_ADD_VERTEX: 'afterAddVertex',
+
+  /**
+   * Variable: DONE
+   *
+   * Specifies the event name for done.
+   */
+  DONE: 'done',
+
+  /**
+   * Variable: EXECUTE
+   *
+   * Specifies the event name for execute.
+   */
+  EXECUTE: 'execute',
+
+  /**
+   * Variable: EXECUTED
+   *
+   * Specifies the event name for executed.
+   */
+  EXECUTED: 'executed',
+
+  /**
+   * Variable: BEGIN_UPDATE
+   *
+   * Specifies the event name for beginUpdate.
+   */
+  BEGIN_UPDATE: 'beginUpdate',
+
+  /**
+   * Variable: START_EDIT
+   *
+   * Specifies the event name for startEdit.
+   */
+  START_EDIT: 'startEdit',
+
+  /**
+   * Variable: END_UPDATE
+   *
+   * Specifies the event name for endUpdate.
+   */
+  END_UPDATE: 'endUpdate',
+
+  /**
+   * Variable: END_EDIT
+   *
+   * Specifies the event name for endEdit.
+   */
+  END_EDIT: 'endEdit',
+
+  /**
+   * Variable: BEFORE_UNDO
+   *
+   * Specifies the event name for beforeUndo.
+   */
+  BEFORE_UNDO: 'beforeUndo',
+
+  /**
+   * Variable: UNDO
+   *
+   * Specifies the event name for undo.
+   */
+  UNDO: 'undo',
+
+  /**
+   * Variable: REDO
+   *
+   * Specifies the event name for redo.
+   */
+  REDO: 'redo',
+
+  /**
+   * Variable: CHANGE
+   *
+   * Specifies the event name for change.
+   */
+  CHANGE: 'change',
+
+  /**
+   * Variable: NOTIFY
+   *
+   * Specifies the event name for notify.
+   */
+  NOTIFY: 'notify',
+
+  /**
+   * Variable: LAYOUT_CELLS
+   *
+   * Specifies the event name for layoutCells.
+   */
+  LAYOUT_CELLS: 'layoutCells',
+
+  /**
+   * Variable: CLICK
+   *
+   * Specifies the event name for click.
+   */
+  CLICK: 'click',
+
+  /**
+   * Variable: SCALE
+   *
+   * Specifies the event name for scale.
+   */
+  SCALE: 'scale',
+
+  /**
+   * Variable: TRANSLATE
+   *
+   * Specifies the event name for translate.
+   */
+  TRANSLATE: 'translate',
+
+  /**
+   * Variable: SCALE_AND_TRANSLATE
+   *
+   * Specifies the event name for scaleAndTranslate.
+   */
+  SCALE_AND_TRANSLATE: 'scaleAndTranslate',
+
+  /**
+   * Variable: UP
+   *
+   * Specifies the event name for up.
+   */
+  UP: 'up',
+
+  /**
+   * Variable: DOWN
+   *
+   * Specifies the event name for down.
+   */
+  DOWN: 'down',
+
+  /**
+   * Variable: ADD
+   *
+   * Specifies the event name for add.
+   */
+  ADD: 'add',
+
+  /**
+   * Variable: REMOVE
+   *
+   * Specifies the event name for remove.
+   */
+  REMOVE: 'remove',
+
+  /**
+   * Variable: CLEAR
+   *
+   * Specifies the event name for clear.
+   */
+  CLEAR: 'clear',
+
+  /**
+   * Variable: ADD_CELLS
+   *
+   * Specifies the event name for addCells.
+   */
+  ADD_CELLS: 'addCells',
+
+  /**
+   * Variable: CELLS_ADDED
+   *
+   * Specifies the event name for cellsAdded.
+   */
+  CELLS_ADDED: 'cellsAdded',
+
+  /**
+   * Variable: MOVE_CELLS
+   *
+   * Specifies the event name for moveCells.
+   */
+  MOVE_CELLS: 'moveCells',
+
+  /**
+   * Variable: CELLS_MOVED
+   *
+   * Specifies the event name for cellsMoved.
+   */
+  CELLS_MOVED: 'cellsMoved',
+
+  /**
+   * Variable: RESIZE_CELLS
+   *
+   * Specifies the event name for resizeCells.
+   */
+  RESIZE_CELLS: 'resizeCells',
+
+  /**
+   * Variable: CELLS_RESIZED
+   *
+   * Specifies the event name for cellsResized.
+   */
+  CELLS_RESIZED: 'cellsResized',
+
+  /**
+   * Variable: TOGGLE_CELLS
+   *
+   * Specifies the event name for toggleCells.
+   */
+  TOGGLE_CELLS: 'toggleCells',
+
+  /**
+   * Variable: CELLS_TOGGLED
+   *
+   * Specifies the event name for cellsToggled.
+   */
+  CELLS_TOGGLED: 'cellsToggled',
+
+  /**
+   * Variable: ORDER_CELLS
+   *
+   * Specifies the event name for orderCells.
+   */
+  ORDER_CELLS: 'orderCells',
+
+  /**
+   * Variable: CELLS_ORDERED
+   *
+   * Specifies the event name for cellsOrdered.
+   */
+  CELLS_ORDERED: 'cellsOrdered',
+
+  /**
+   * Variable: REMOVE_CELLS
+   *
+   * Specifies the event name for removeCells.
+   */
+  REMOVE_CELLS: 'removeCells',
+
+  /**
+   * Variable: CELLS_REMOVED
+   *
+   * Specifies the event name for cellsRemoved.
+   */
+  CELLS_REMOVED: 'cellsRemoved',
+
+  /**
+   * Variable: GROUP_CELLS
+   *
+   * Specifies the event name for groupCells.
+   */
+  GROUP_CELLS: 'groupCells',
+
+  /**
+   * Variable: UNGROUP_CELLS
+   *
+   * Specifies the event name for ungroupCells.
+   */
+  UNGROUP_CELLS: 'ungroupCells',
+
+  /**
+   * Variable: REMOVE_CELLS_FROM_PARENT
+   *
+   * Specifies the event name for removeCellsFromParent.
+   */
+  REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',
+
+  /**
+   * Variable: FOLD_CELLS
+   *
+   * Specifies the event name for foldCells.
+   */
+  FOLD_CELLS: 'foldCells',
+
+  /**
+   * Variable: CELLS_FOLDED
+   *
+   * Specifies the event name for cellsFolded.
+   */
+  CELLS_FOLDED: 'cellsFolded',
+
+  /**
+   * Variable: ALIGN_CELLS
+   *
+   * Specifies the event name for alignCells.
+   */
+  ALIGN_CELLS: 'alignCells',
+
+  /**
+   * Variable: LABEL_CHANGED
+   *
+   * Specifies the event name for labelChanged.
+   */
+  LABEL_CHANGED: 'labelChanged',
+
+  /**
+   * Variable: CONNECT_CELL
+   *
+   * Specifies the event name for connectCell.
+   */
+  CONNECT_CELL: 'connectCell',
+
+  /**
+   * Variable: CELL_CONNECTED
+   *
+   * Specifies the event name for cellConnected.
+   */
+  CELL_CONNECTED: 'cellConnected',
+
+  /**
+   * Variable: SPLIT_EDGE
+   *
+   * Specifies the event name for splitEdge.
+   */
+  SPLIT_EDGE: 'splitEdge',
+
+  /**
+   * Variable: FLIP_EDGE
+   *
+   * Specifies the event name for flipEdge.
+   */
+  FLIP_EDGE: 'flipEdge',
+
+  /**
+   * Variable: START_EDITING
+   *
+   * Specifies the event name for startEditing.
+   */
+  START_EDITING: 'startEditing',
+
+  /**
+   * Variable: EDITING_STARTED
+   *
+   * Specifies the event name for editingStarted.
+   */
+  EDITING_STARTED: 'editingStarted',
+
+  /**
+   * Variable: EDITING_STOPPED
+   *
+   * Specifies the event name for editingStopped.
+   */
+  EDITING_STOPPED: 'editingStopped',
+
+  /**
+   * Variable: ADD_OVERLAY
+   *
+   * Specifies the event name for addOverlay.
+   */
+  ADD_OVERLAY: 'addOverlay',
+
+  /**
+   * Variable: REMOVE_OVERLAY
+   *
+   * Specifies the event name for removeOverlay.
+   */
+  REMOVE_OVERLAY: 'removeOverlay',
+
+  /**
+   * Variable: UPDATE_CELL_SIZE
+   *
+   * Specifies the event name for updateCellSize.
+   */
+  UPDATE_CELL_SIZE: 'updateCellSize',
+
+  /**
+   * Variable: ESCAPE
+   *
+   * Specifies the event name for escape.
+   */
+  ESCAPE: 'escape',
+
+  /**
+   * Variable: DOUBLE_CLICK
+   *
+   * Specifies the event name for doubleClick.
+   */
+  DOUBLE_CLICK: 'doubleClick',
+
+  /**
+   * Variable: START
+   *
+   * Specifies the event name for start.
+   */
+  START: 'start',
+
+  /**
+   * Variable: RESET
+   *
+   * Specifies the event name for reset.
+   */
+  RESET: 'reset',
+
+  /**
+   * Variable: PINCH_THRESHOLD
+   *
+   * Threshold for pinch gestures to fire a mouse wheel event.
+   * Default value is 10.
+   */
+  PINCH_THRESHOLD: 10,
+};
+
+/**
+ * util/mxXmlRequest.js
+ */
+/**
+ * Copyright (c) 2006-2020, JGraph Ltd
+ * Copyright (c) 2006-2020, draw.io AG
+ */
+/**
+ * Class: mxXmlRequest
+ *
+ * XML HTTP request wrapper. See also: ,  and
+ * . This class provides a cross-browser abstraction for Ajax
+ * requests.
+ *
+ * Encoding:
+ *
+ * For encoding parameter values, the built-in encodeURIComponent JavaScript
+ * method must be used. For automatic encoding of post data in  the
+ *  switch can be set to true (default). The encoding
+ * will be carried out using the conte type of the page. That is, the page
+ * containting the editor should contain a meta tag in the header, eg.
+ * 
+ *
+ * Example:
+ *
+ * (code)
+ * var onload = function(req)
+ * {
+ *   mxUtils.alert(req.getDocumentElement());
+ * }
+ *
+ * var onerror = function(req)
+ * {
+ *   mxUtils.alert('Error');
+ * }
+ * new mxXmlRequest(url, 'key=value').send(onload, onerror);
+ * (end)
+ *
+ * Sends an asynchronous POST request to the specified URL.
+ *
+ * Example:
+ *
+ * (code)
+ * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
+ * req.send();
+ * mxUtils.alert(req.getDocumentElement());
+ * (end)
+ *
+ * Sends a synchronous POST request to the specified URL.
+ *
+ * Example:
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = encodeURIComponent(mxUtils.getXml(result));
+ * new mxXmlRequest(url, 'xml='+xml).send();
+ * (end)
+ *
+ * Sends an encoded graph model to the specified URL using xml as the
+ * parameter name. The parameter can then be retrieved in C# as follows:
+ *
+ * (code)
+ * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
+ * (end)
+ *
+ * Or in Java as follows:
+ *
+ * (code)
+ * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "
");
+ * (end)
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image.
+ *
+ * Constructor: mxXmlRequest
+ *
+ * Constructs an XML HTTP request.
+ *
+ * Parameters:
+ *
+ * url - Target URL of the request.
+ * params - Form encoded parameters to send with a POST request.
+ * method - String that specifies the request method. Possible values are
+ * POST and GET. Default is POST.
+ * async - Boolean specifying if an asynchronous request should be used.
+ * Default is true.
+ * username - String specifying the username to be used for the request.
+ * password - String specifying the password to be used for the request.
+ */
+function mxXmlRequest(url, params, method, async, username, password) {
+  this.url = url;
+  this.params = params;
+  this.method = method || 'POST';
+  this.async = async != null ? async : true;
+  this.username = username;
+  this.password = password;
+}
+
+/**
+ * Variable: url
+ *
+ * Holds the target URL of the request.
+ */
+mxXmlRequest.prototype.url = null;
+
+/**
+ * Variable: params
+ *
+ * Holds the form encoded data for the POST request.
+ */
+mxXmlRequest.prototype.params = null;
+
+/**
+ * Variable: method
+ *
+ * Specifies the request method. Possible values are POST and GET. Default
+ * is POST.
+ */
+mxXmlRequest.prototype.method = null;
+
+/**
+ * Variable: async
+ *
+ * Boolean indicating if the request is asynchronous.
+ */
+mxXmlRequest.prototype.async = null;
+
+/**
+ * Variable: binary
+ *
+ * Boolean indicating if the request is binary. This option is ignored in IE.
+ * In all other browsers the requested mime type is set to
+ * text/plain; charset=x-user-defined. Default is false.
+ */
+mxXmlRequest.prototype.binary = false;
+
+/**
+ * Variable: withCredentials
+ *
+ * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is
+ * false.
+ */
+mxXmlRequest.prototype.withCredentials = false;
+
+/**
+ * Variable: username
+ *
+ * Specifies the username to be used for authentication.
+ */
+mxXmlRequest.prototype.username = null;
+
+/**
+ * Variable: password
+ *
+ * Specifies the password to be used for authentication.
+ */
+mxXmlRequest.prototype.password = null;
+
+/**
+ * Variable: request
+ *
+ * Holds the inner, browser-specific request object.
+ */
+mxXmlRequest.prototype.request = null;
+
+/**
+ * Variable: decodeSimulateValues
+ *
+ * Specifies if request values should be decoded as URIs before setting the
+ * textarea value in . Defaults to false for backwards compatibility,
+ * to avoid another decode on the server this should be set to true.
+ */
+mxXmlRequest.prototype.decodeSimulateValues = false;
+
+/**
+ * Function: isBinary
+ *
+ * Returns .
+ */
+mxXmlRequest.prototype.isBinary = function () {
+  return this.binary;
+};
+
+/**
+ * Function: setBinary
+ *
+ * Sets .
+ */
+mxXmlRequest.prototype.setBinary = function (value) {
+  this.binary = value;
+};
+
+/**
+ * Function: getText
+ *
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function () {
+  return this.request.responseText;
+};
+
+/**
+ * Function: isReady
+ *
+ * Returns true if the response is ready.
+ */
+mxXmlRequest.prototype.isReady = function () {
+  return this.request.readyState == 4;
+};
+
+/**
+ * Function: getDocumentElement
+ *
+ * Returns the document element of the response XML document.
+ */
+mxXmlRequest.prototype.getDocumentElement = function () {
+  var doc = this.getXml();
+
+  if (doc != null) {
+    return doc.documentElement;
+  }
+
+  return null;
+};
+
+/**
+ * Function: getXml
+ *
+ * Returns the response as an XML document. Use  to get
+ * the document element of the XML document.
+ */
+mxXmlRequest.prototype.getXml = function () {
+  var xml = this.request.responseXML;
+
+  // Handles missing response headers in IE, the first condition handles
+  // the case where responseXML is there, but using its nodes leads to
+  // type errors in the mxCellCodec when putting the nodes into a new
+  // document. This happens in IE9 standards mode and with XML user
+  // objects only, as they are used directly as values in cells.
+  if (document.documentMode >= 9 || xml == null || xml.documentElement == null) {
+    xml = mxUtils.parseXml(this.request.responseText);
+  }
+
+  return xml;
+};
+
+/**
+ * Function: getStatus
+ *
+ * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
+ * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
+ */
+mxXmlRequest.prototype.getStatus = function () {
+  return this.request != null ? this.request.status : null;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the inner  object.
+ */
+mxXmlRequest.prototype.create = (function () {
+  if (window.XMLHttpRequest) {
+    return function () {
+      var req = new XMLHttpRequest();
+
+      // TODO: Check for overrideMimeType required here?
+      if (this.isBinary() && req.overrideMimeType) {
+        req.overrideMimeType('text/plain; charset=x-user-defined');
+      }
+
+      return req;
+    };
+  } else if (typeof ActiveXObject != 'undefined') {
+    return function () {
+      // TODO: Implement binary option
+      return new ActiveXObject('Microsoft.XMLHTTP');
+    };
+  }
+})();
+
+/**
+ * Function: send
+ *
+ * Send the  to the target URL using the specified functions to
+ * process the response asychronously.
+ *
+ * Note: Due to technical limitations, onerror is currently ignored.
+ *
+ * Parameters:
+ *
+ * onload - Function to be invoked if a successful response was received.
+ * onerror - Function to be called on any error. Unused in this implementation, intended for overriden function.
+ * timeout - Optional timeout in ms before calling ontimeout.
+ * ontimeout - Optional function to execute on timeout.
+ */
+mxXmlRequest.prototype.send = function (onload, onerror, timeout, ontimeout) {
+  this.request = this.create();
+
+  if (this.request != null) {
+    if (onload != null) {
+      this.request.onreadystatechange = mxUtils.bind(this, function () {
+        if (this.isReady()) {
+          onload(this);
+          this.request.onreadystatechange = null;
+        }
+      });
+    }
+
+    this.request.open(this.method, this.url, this.async, this.username, this.password);
+    this.setRequestHeaders(this.request, this.params);
+
+    if (window.XMLHttpRequest && this.withCredentials) {
+      this.request.withCredentials = 'true';
+    }
+
+    if (
+      !mxClient.IS_QUIRKS &&
+      (document.documentMode == null || document.documentMode > 9) &&
+      window.XMLHttpRequest &&
+      timeout != null &&
+      ontimeout != null
+    ) {
+      this.request.timeout = timeout;
+      this.request.ontimeout = ontimeout;
+    }
+
+    this.request.send(this.params);
+  }
+};
+
+/**
+ * Function: setRequestHeaders
+ *
+ * Sets the headers for the given request and parameters. This sets the
+ * content-type to application/x-www-form-urlencoded if any params exist.
+ *
+ * Example:
+ *
+ * (code)
+ * request.setRequestHeaders = function(request, params)
+ * {
+ *   if (params != null)
+ *   {
+ *     request.setRequestHeader('Content-Type',
+ *             'multipart/form-data');
+ *     request.setRequestHeader('Content-Length',
+ *             params.length);
+ *   }
+ * };
+ * (end)
+ *
+ * Use the code above before calling  if you require a
+ * multipart/form-data request.
+ */
+mxXmlRequest.prototype.setRequestHeaders = function (request, params) {
+  if (params != null) {
+    request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+  }
+};
+
+/**
+ * Function: simulate
+ *
+ * Creates and posts a request to the given target URL using a dynamically
+ * created form inside the given document.
+ *
+ * Parameters:
+ *
+ * docs - Document that contains the form element.
+ * target - Target to send the form result to.
+ */
+mxXmlRequest.prototype.simulate = function (doc, target) {
+  doc = doc || document;
+  var old = null;
+
+  if (doc == document) {
+    old = window.onbeforeunload;
+    window.onbeforeunload = null;
+  }
+
+  var form = doc.createElement('form');
+  form.setAttribute('method', this.method);
+  form.setAttribute('action', this.url);
+
+  if (target != null) {
+    form.setAttribute('target', target);
+  }
+
+  form.style.display = 'none';
+  form.style.visibility = 'hidden';
+
+  var pars = this.params.indexOf('&') > 0 ? this.params.split('&') : this.params.split();
+
+  // Adds the parameters as textareas to the form
+  for (var i = 0; i < pars.length; i++) {
+    var pos = pars[i].indexOf('=');
+
+    if (pos > 0) {
+      var name = pars[i].substring(0, pos);
+      var value = pars[i].substring(pos + 1);
+
+      if (this.decodeSimulateValues) {
+        value = decodeURIComponent(value);
+      }
+
+      var textarea = doc.createElement('textarea');
+      textarea.setAttribute('wrap', 'off');
+      textarea.setAttribute('name', name);
+      mxUtils.write(textarea, value);
+      form.appendChild(textarea);
+    }
+  }
+
+  doc.body.appendChild(form);
+  form.submit();
+
+  if (form.parentNode != null) {
+    form.parentNode.removeChild(form);
+  }
+
+  if (old != null) {
+    window.onbeforeunload = old;
+  }
+};
+
+/**
+ * util/mxClipboard.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxClipboard = {
+  /**
+   * Class: mxClipboard
+   *
+   * Singleton that implements a clipboard for graph cells.
+   *
+   * Example:
+   *
+   * (code)
+   * mxClipboard.copy(graph);
+   * mxClipboard.paste(graph2);
+   * (end)
+   *
+   * This copies the selection cells from the graph to the clipboard and
+   * pastes them into graph2.
+   *
+   * For fine-grained control of the clipboard data the 
+   * and  functions can be overridden.
+   *
+   * To restore previous parents for pasted cells, the implementation for
+   *  and  can be changed as follows.
+   *
+   * (code)
+   * mxClipboard.copy = function(graph, cells)
+   * {
+   *   cells = cells || graph.getSelectionCells();
+   *   var result = graph.getExportableCells(cells);
+   *
+   *   mxClipboard.parents = new Object();
+   *
+   *   for (var i = 0; i < result.length; i++)
+   *   {
+   *     mxClipboard.parents[i] = graph.model.getParent(cells[i]);
+   *   }
+   *
+   *   mxClipboard.insertCount = 1;
+   *   mxClipboard.setCells(graph.cloneCells(result));
+   *
+   *   return result;
+   * };
+   *
+   * mxClipboard.paste = function(graph)
+   * {
+   *   if (!mxClipboard.isEmpty())
+   *   {
+   *     var cells = graph.getImportableCells(mxClipboard.getCells());
+   *     var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+   *     var parent = graph.getDefaultParent();
+   *
+   *     graph.model.beginUpdate();
+   *     try
+   *     {
+   *       for (var i = 0; i < cells.length; i++)
+   *       {
+   *         var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?
+   *              mxClipboard.parents[i] : parent;
+   *         cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];
+   *       }
+   *     }
+   *     finally
+   *     {
+   *       graph.model.endUpdate();
+   *     }
+   *
+   *     // Increments the counter and selects the inserted cells
+   *     mxClipboard.insertCount++;
+   *     graph.setSelectionCells(cells);
+   *   }
+   * };
+   * (end)
+   *
+   * Variable: STEPSIZE
+   *
+   * Defines the step size to offset the cells after each paste operation.
+   * Default is 10.
+   */
+  STEPSIZE: 10,
+
+  /**
+   * Variable: insertCount
+   *
+   * Counts the number of times the clipboard data has been inserted.
+   */
+  insertCount: 1,
+
+  /**
+   * Variable: cells
+   *
+   * Holds the array of  currently in the clipboard.
+   */
+  cells: null,
+
+  /**
+   * Function: setCells
+   *
+   * Sets the cells in the clipboard. Fires a  event.
+   */
+  setCells: function (cells) {
+    mxClipboard.cells = cells;
+  },
+
+  /**
+   * Function: getCells
+   *
+   * Returns  the cells in the clipboard.
+   */
+  getCells: function () {
+    return mxClipboard.cells;
+  },
+
+  /**
+   * Function: isEmpty
+   *
+   * Returns true if the clipboard currently has not data stored.
+   */
+  isEmpty: function () {
+    return mxClipboard.getCells() == null;
+  },
+
+  /**
+   * Function: cut
+   *
+   * Cuts the given array of  from the specified graph.
+   * If cells is null then the selection cells of the graph will
+   * be used. Returns the cells that have been cut from the graph.
+   *
+   * Parameters:
+   *
+   * graph -  that contains the cells to be cut.
+   * cells - Optional array of  to be cut.
+   */
+  cut: function (graph, cells) {
+    cells = mxClipboard.copy(graph, cells);
+    mxClipboard.insertCount = 0;
+    mxClipboard.removeCells(graph, cells);
+
+    return cells;
+  },
+
+  /**
+   * Function: removeCells
+   *
+   * Hook to remove the given cells from the given graph after
+   * a cut operation.
+   *
+   * Parameters:
+   *
+   * graph -  that contains the cells to be cut.
+   * cells - Array of  to be cut.
+   */
+  removeCells: function (graph, cells) {
+    graph.removeCells(cells);
+  },
+
+  /**
+   * Function: copy
+   *
+   * Copies the given array of  from the specified
+   * graph to . Returns the original array of cells that has
+   * been cloned. Descendants of cells in the array are ignored.
+   *
+   * Parameters:
+   *
+   * graph -  that contains the cells to be copied.
+   * cells - Optional array of  to be copied.
+   */
+  copy: function (graph, cells) {
+    cells = cells || graph.getSelectionCells();
+    var result = graph.getExportableCells(graph.model.getTopmostCells(cells));
+    mxClipboard.insertCount = 1;
+    mxClipboard.setCells(graph.cloneCells(result));
+
+    return result;
+  },
+
+  /**
+   * Function: paste
+   *
+   * Pastes the  into the specified graph restoring
+   * the relation to , if possible. If the parents
+   * are no longer in the graph or invisible then the
+   * cells are added to the graph's default or into the
+   * swimlane under the cell's new location if one exists.
+   * The cells are added to the graph using 
+   * and returned.
+   *
+   * Parameters:
+   *
+   * graph -  to paste the  into.
+   */
+  paste: function (graph) {
+    var cells = null;
+
+    if (!mxClipboard.isEmpty()) {
+      cells = graph.getImportableCells(mxClipboard.getCells());
+      var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+      var parent = graph.getDefaultParent();
+      cells = graph.importCells(cells, delta, delta, parent);
+
+      // Increments the counter and selects the inserted cells
+      mxClipboard.insertCount++;
+      graph.setSelectionCells(cells);
+    }
+
+    return cells;
+  },
+};
+
+/**
+ * util/mxWindow.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxWindow
+ *
+ * Basic window inside a document.
+ *
+ * Examples:
+ *
+ * Creating a simple window.
+ *
+ * (code)
+ * var tb = document.createElement('div');
+ * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
+ * wnd.setVisible(true);
+ * (end)
+ *
+ * Creating a window that contains an iframe.
+ *
+ * (code)
+ * var frame = document.createElement('iframe');
+ * frame.setAttribute('width', '192px');
+ * frame.setAttribute('height', '172px');
+ * frame.setAttribute('src', 'http://www.example.com/');
+ * frame.style.backgroundColor = 'white';
+ *
+ * var w = document.body.clientWidth;
+ * var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
+ * wnd.setVisible(true);
+ * (end)
+ *
+ * To limit the movement of a window, eg. to keep it from being moved beyond
+ * the top, left corner the following method can be overridden (recommended):
+ *
+ * (code)
+ * wnd.setLocation = function(x, y)
+ * {
+ *   x = Math.max(0, x);
+ *   y = Math.max(0, y);
+ *   mxWindow.prototype.setLocation.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Or the following event handler can be used:
+ *
+ * (code)
+ * wnd.addListener(mxEvent.MOVE, function(e)
+ * {
+ *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
+ * });
+ * (end)
+ *
+ * To keep a window inside the current window:
+ *
+ * (code)
+ * mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()
+ * {
+ *   var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+ *   var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+ *
+ *   var x = this.window.getX();
+ *   var y = this.window.getY();
+ *
+ *   if (x + this.window.table.clientWidth > iw)
+ *   {
+ *     x = Math.max(0, iw - this.window.table.clientWidth);
+ *   }
+ *
+ *   if (y + this.window.table.clientHeight > ih)
+ *   {
+ *     y = Math.max(0, ih - this.window.table.clientHeight);
+ *   }
+ *
+ *   if (this.window.getX() != x || this.window.getY() != y)
+ *   {
+ *     this.window.setLocation(x, y);
+ *   }
+ * }));
+ * (end)
+ *
+ * Event: mxEvent.MOVE_START
+ *
+ * Fires before the window is moved. The event property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE
+ *
+ * Fires while the window is being moved. The event property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE_END
+ *
+ * Fires after the window is moved. The event property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_START
+ *
+ * Fires before the window is resized. The event property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE
+ *
+ * Fires while the window is being resized. The event property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_END
+ *
+ * Fires after the window is resized. The event property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MAXIMIZE
+ *
+ * Fires after the window is maximized. The event property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MINIMIZE
+ *
+ * Fires after the window is minimized. The event property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.NORMALIZE
+ *
+ * Fires after the window is normalized, that is, it returned from
+ * maximized or minimized state. The event property contains the
+ * corresponding mouse event.
+ *
+ * Event: mxEvent.ACTIVATE
+ *
+ * Fires after a window is activated. The previousWindow property
+ * contains the previous window. The event sender is the active window.
+ *
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the window is shown. This event has no properties.
+ *
+ * Event: mxEvent.HIDE
+ *
+ * Fires after the window is hidden. This event has no properties.
+ *
+ * Event: mxEvent.CLOSE
+ *
+ * Fires before the window is closed. The event property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.DESTROY
+ *
+ * Fires before the window is destroyed. This event has no properties.
+ *
+ * Constructor: mxWindow
+ *
+ * Constructs a new window with the given dimension and title to display
+ * the specified content. The window elements use the given style as a
+ * prefix for the classnames of the respective window elements, namely,
+ * the window title and window pane. The respective postfixes are appended
+ * to the given stylename as follows:
+ *
+ *   style - Base style for the window.
+ *   style+Title - Style for the window title.
+ *   style+Pane - Style for the window pane.
+ *
+ * The default value for style is mxWindow, resulting in the following
+ * classnames for the window elements: mxWindow, mxWindowTitle and
+ * mxWindowPane.
+ *
+ * If replaceNode is given then the window replaces the given DOM node in
+ * the document.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the new window.
+ * content - DOM node that is used as the window content.
+ * x - X-coordinate of the window location.
+ * y - Y-coordinate of the window location.
+ * width - Width of the window.
+ * height - Optional height of the window. Default is to match the height
+ * of the content at the specified width.
+ * minimizable - Optional boolean indicating if the window is minimizable.
+ * Default is true.
+ * movable - Optional boolean indicating if the window is movable. Default
+ * is true.
+ * replaceNode - Optional DOM node that the window should replace.
+ * style - Optional base classname for the window elements. Default is
+ * mxWindow.
+ */
+function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style) {
+  if (content != null) {
+    minimizable = minimizable != null ? minimizable : true;
+    this.content = content;
+    this.init(x, y, width, height, style);
+
+    this.installMaximizeHandler();
+    this.installMinimizeHandler();
+    this.installCloseHandler();
+    this.setMinimizable(minimizable);
+    this.setTitle(title);
+
+    if (movable == null || movable) {
+      this.installMoveHandler();
+    }
+
+    if (replaceNode != null && replaceNode.parentNode != null) {
+      replaceNode.parentNode.replaceChild(this.div, replaceNode);
+    } else {
+      document.body.appendChild(this.div);
+    }
+  }
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxWindow.prototype = new mxEventSource();
+mxWindow.prototype.constructor = mxWindow;
+
+/**
+ * Variable: closeImage
+ *
+ * URL of the image to be used for the close icon in the titlebar.
+ */
+mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';
+
+/**
+ * Variable: minimizeImage
+ *
+ * URL of the image to be used for the minimize icon in the titlebar.
+ */
+mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';
+
+/**
+ * Variable: normalizeImage
+ *
+ * URL of the image to be used for the normalize icon in the titlebar.
+ */
+mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';
+
+/**
+ * Variable: maximizeImage
+ *
+ * URL of the image to be used for the maximize icon in the titlebar.
+ */
+mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';
+
+/**
+ * Variable: resizeImage
+ *
+ * URL of the image to be used for the resize icon.
+ */
+mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';
+
+/**
+ * Variable: visible
+ *
+ * Boolean flag that represents the visible state of the window.
+ */
+mxWindow.prototype.visible = false;
+
+/**
+ * Variable: minimumSize
+ *
+ *  that specifies the minimum width and height of the window.
+ * Default is (50, 40).
+ */
+mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);
+
+/**
+ * Variable: destroyOnClose
+ *
+ * Specifies if the window should be destroyed when it is closed. If this
+ * is false then the window is hidden using . Default is true.
+ */
+mxWindow.prototype.destroyOnClose = true;
+
+/**
+ * Variable: contentHeightCorrection
+ *
+ * Defines the correction factor for computing the height of the contentWrapper.
+ * Default is 6 for IE 7/8 standards mode and 2 for all other browsers and modes.
+ */
+mxWindow.prototype.contentHeightCorrection = document.documentMode == 8 || document.documentMode == 7 ? 6 : 2;
+
+/**
+ * Variable: title
+ *
+ * Reference to the DOM node (TD) that contains the title.
+ */
+mxWindow.prototype.title = null;
+
+/**
+ * Variable: content
+ *
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = null;
+
+/**
+ * Function: init
+ *
+ * Initializes the DOM tree that represents the window.
+ */
+mxWindow.prototype.init = function (x, y, width, height, style) {
+  style = style != null ? style : 'mxWindow';
+
+  this.div = document.createElement('div');
+  this.div.className = style;
+
+  this.div.style.left = x + 'px';
+  this.div.style.top = y + 'px';
+  this.table = document.createElement('table');
+  this.table.className = style;
+
+  // Disables built-in pan and zoom in IE10 and later
+  if (mxClient.IS_POINTER) {
+    this.div.style.touchAction = 'none';
+  }
+
+  // Workaround for table size problems in FF
+  if (width != null) {
+    if (!mxClient.IS_QUIRKS) {
+      this.div.style.width = width + 'px';
+    }
+
+    this.table.style.width = width + 'px';
+  }
+
+  if (height != null) {
+    if (!mxClient.IS_QUIRKS) {
+      this.div.style.height = height + 'px';
+    }
+
+    this.table.style.height = height + 'px';
+  }
+
+  // Creates title row
+  var tbody = document.createElement('tbody');
+  var tr = document.createElement('tr');
+
+  this.title = document.createElement('td');
+  this.title.className = style + 'Title';
+
+  this.buttons = document.createElement('div');
+  this.buttons.style.position = 'absolute';
+  this.buttons.style.display = 'inline-block';
+  this.buttons.style.right = '4px';
+  this.buttons.style.top = '5px';
+  this.title.appendChild(this.buttons);
+
+  tr.appendChild(this.title);
+  tbody.appendChild(tr);
+
+  // Creates content row and table cell
+  tr = document.createElement('tr');
+  this.td = document.createElement('td');
+  this.td.className = style + 'Pane';
+
+  if (document.documentMode == 7) {
+    this.td.style.height = '100%';
+  }
+
+  this.contentWrapper = document.createElement('div');
+  this.contentWrapper.className = style + 'Pane';
+  this.contentWrapper.style.width = '100%';
+  this.contentWrapper.appendChild(this.content);
+
+  // Workaround for div around div restricts height
+  // of inner div if outerdiv has hidden overflow
+  if (mxClient.IS_QUIRKS || this.content.nodeName.toUpperCase() != 'DIV') {
+    this.contentWrapper.style.height = '100%';
+  }
+
+  // Puts all content into the DOM
+  this.td.appendChild(this.contentWrapper);
+  tr.appendChild(this.td);
+  tbody.appendChild(tr);
+  this.table.appendChild(tbody);
+  this.div.appendChild(this.table);
+
+  // Puts the window on top of other windows when clicked
+  var activator = mxUtils.bind(this, function (evt) {
+    this.activate();
+  });
+
+  mxEvent.addGestureListeners(this.title, activator);
+  mxEvent.addGestureListeners(this.table, activator);
+
+  this.hide();
+};
+
+/**
+ * Function: setTitle
+ *
+ * Sets the window title to the given string. HTML markup inside the title
+ * will be escaped.
+ */
+mxWindow.prototype.setTitle = function (title) {
+  // Removes all text content nodes (normally just one)
+  var child = this.title.firstChild;
+
+  while (child != null) {
+    var next = child.nextSibling;
+
+    if (child.nodeType == mxConstants.NODETYPE_TEXT) {
+      child.parentNode.removeChild(child);
+    }
+
+    child = next;
+  }
+
+  mxUtils.write(this.title, title || '');
+  this.title.appendChild(this.buttons);
+};
+
+/**
+ * Function: setScrollable
+ *
+ * Sets if the window contents should be scrollable.
+ */
+mxWindow.prototype.setScrollable = function (scrollable) {
+  // Workaround for hang in Presto 2.5.22 (Opera 10.5)
+  if (navigator.userAgent == null || navigator.userAgent.indexOf('Presto/2.5') < 0) {
+    if (scrollable) {
+      this.contentWrapper.style.overflow = 'auto';
+    } else {
+      this.contentWrapper.style.overflow = 'hidden';
+    }
+  }
+};
+
+/**
+ * Function: activate
+ *
+ * Puts the window on top of all other windows.
+ */
+mxWindow.prototype.activate = function () {
+  if (mxWindow.activeWindow != this) {
+    var style = mxUtils.getCurrentStyle(this.getElement());
+    var index = style != null ? style.zIndex : 3;
+
+    if (mxWindow.activeWindow) {
+      var elt = mxWindow.activeWindow.getElement();
+
+      if (elt != null && elt.style != null) {
+        elt.style.zIndex = index;
+      }
+    }
+
+    var previousWindow = mxWindow.activeWindow;
+    this.getElement().style.zIndex = parseInt(index) + 1;
+    mxWindow.activeWindow = this;
+
+    this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
+  }
+};
+
+/**
+ * Function: getElement
+ *
+ * Returuns the outermost DOM node that makes up the window.
+ */
+mxWindow.prototype.getElement = function () {
+  return this.div;
+};
+
+/**
+ * Function: fit
+ *
+ * Makes sure the window is inside the client area of the window.
+ */
+mxWindow.prototype.fit = function () {
+  mxUtils.fit(this.div);
+};
+
+/**
+ * Function: isResizable
+ *
+ * Returns true if the window is resizable.
+ */
+mxWindow.prototype.isResizable = function () {
+  if (this.resize != null) {
+    return this.resize.style.display != 'none';
+  }
+
+  return false;
+};
+
+/**
+ * Function: setResizable
+ *
+ * Sets if the window should be resizable. To avoid interference with some
+ * built-in features of IE10 and later, the use of the following code is
+ * recommended if there are resizable s in the page:
+ *
+ * (code)
+ * if (mxClient.IS_POINTER)
+ * {
+ *   document.body.style.msTouchAction = 'none';
+ * }
+ * (end)
+ */
+mxWindow.prototype.setResizable = function (resizable) {
+  if (resizable) {
+    if (this.resize == null) {
+      this.resize = document.createElement('img');
+      this.resize.style.position = 'absolute';
+      this.resize.style.bottom = '2px';
+      this.resize.style.right = '2px';
+
+      this.resize.setAttribute('src', this.resizeImage);
+      this.resize.style.cursor = 'nw-resize';
+
+      var startX = null;
+      var startY = null;
+      var width = null;
+      var height = null;
+
+      var start = mxUtils.bind(this, function (evt) {
+        // LATER: pointerdown starting on border of resize does start
+        // the drag operation but does not fire consecutive events via
+        // one of the listeners below (does pan instead).
+        // Workaround: document.body.style.msTouchAction = 'none'
+        this.activate();
+        startX = mxEvent.getClientX(evt);
+        startY = mxEvent.getClientY(evt);
+        width = this.div.offsetWidth;
+        height = this.div.offsetHeight;
+
+        mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+        this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
+        mxEvent.consume(evt);
+      });
+
+      // Adds a temporary pair of listeners to intercept
+      // the gesture event in the document
+      var dragHandler = mxUtils.bind(this, function (evt) {
+        if (startX != null && startY != null) {
+          var dx = mxEvent.getClientX(evt) - startX;
+          var dy = mxEvent.getClientY(evt) - startY;
+
+          this.setSize(width + dx, height + dy);
+
+          this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
+          mxEvent.consume(evt);
+        }
+      });
+
+      var dropHandler = mxUtils.bind(this, function (evt) {
+        if (startX != null && startY != null) {
+          startX = null;
+          startY = null;
+          mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+          this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
+          mxEvent.consume(evt);
+        }
+      });
+
+      mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler);
+      this.div.appendChild(this.resize);
+    } else {
+      this.resize.style.display = 'inline';
+    }
+  } else if (this.resize != null) {
+    this.resize.style.display = 'none';
+  }
+};
+
+/**
+ * Function: setSize
+ *
+ * Sets the size of the window.
+ */
+mxWindow.prototype.setSize = function (width, height) {
+  width = Math.max(this.minimumSize.width, width);
+  height = Math.max(this.minimumSize.height, height);
+
+  // Workaround for table size problems in FF
+  if (!mxClient.IS_QUIRKS) {
+    this.div.style.width = width + 'px';
+    this.div.style.height = height + 'px';
+  }
+
+  this.table.style.width = width + 'px';
+  this.table.style.height = height + 'px';
+
+  if (!mxClient.IS_QUIRKS) {
+    this.contentWrapper.style.height =
+      this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
+  }
+};
+
+/**
+ * Function: setMinimizable
+ *
+ * Sets if the window is minimizable.
+ */
+mxWindow.prototype.setMinimizable = function (minimizable) {
+  this.minimize.style.display = minimizable ? '' : 'none';
+};
+
+/**
+ * Function: getMinimumSize
+ *
+ * Returns an  that specifies the size for the minimized window.
+ * A width or height of 0 means keep the existing width or height. This
+ * implementation returns the height of the window title and keeps the width.
+ */
+mxWindow.prototype.getMinimumSize = function () {
+  return new mxRectangle(0, 0, 0, this.title.offsetHeight);
+};
+
+/**
+ * Function: installMinimizeHandler
+ *
+ * Installs the event listeners required for minimizing the window.
+ */
+mxWindow.prototype.installMinimizeHandler = function () {
+  this.minimize = document.createElement('img');
+
+  this.minimize.setAttribute('src', this.minimizeImage);
+  this.minimize.setAttribute('title', 'Minimize');
+  this.minimize.style.cursor = 'pointer';
+  this.minimize.style.marginLeft = '2px';
+  this.minimize.style.display = 'none';
+
+  this.buttons.appendChild(this.minimize);
+
+  var minimized = false;
+  var maxDisplay = null;
+  var height = null;
+
+  var funct = mxUtils.bind(this, function (evt) {
+    this.activate();
+
+    if (!minimized) {
+      minimized = true;
+
+      this.minimize.setAttribute('src', this.normalizeImage);
+      this.minimize.setAttribute('title', 'Normalize');
+      this.contentWrapper.style.display = 'none';
+      maxDisplay = this.maximize.style.display;
+
+      this.maximize.style.display = 'none';
+      height = this.table.style.height;
+
+      var minSize = this.getMinimumSize();
+
+      if (minSize.height > 0) {
+        if (!mxClient.IS_QUIRKS) {
+          this.div.style.height = minSize.height + 'px';
+        }
+
+        this.table.style.height = minSize.height + 'px';
+      }
+
+      if (minSize.width > 0) {
+        if (!mxClient.IS_QUIRKS) {
+          this.div.style.width = minSize.width + 'px';
+        }
+
+        this.table.style.width = minSize.width + 'px';
+      }
+
+      if (this.resize != null) {
+        this.resize.style.visibility = 'hidden';
+      }
+
+      this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
+    } else {
+      minimized = false;
+
+      this.minimize.setAttribute('src', this.minimizeImage);
+      this.minimize.setAttribute('title', 'Minimize');
+      this.contentWrapper.style.display = ''; // default
+      this.maximize.style.display = maxDisplay;
+
+      if (!mxClient.IS_QUIRKS) {
+        this.div.style.height = height;
+      }
+
+      this.table.style.height = height;
+
+      if (this.resize != null) {
+        this.resize.style.visibility = '';
+      }
+
+      this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+    }
+
+    mxEvent.consume(evt);
+  });
+
+  mxEvent.addGestureListeners(this.minimize, funct);
+};
+
+/**
+ * Function: setMaximizable
+ *
+ * Sets if the window is maximizable.
+ */
+mxWindow.prototype.setMaximizable = function (maximizable) {
+  this.maximize.style.display = maximizable ? '' : 'none';
+};
+
+/**
+ * Function: installMaximizeHandler
+ *
+ * Installs the event listeners required for maximizing the window.
+ */
+mxWindow.prototype.installMaximizeHandler = function () {
+  this.maximize = document.createElement('img');
+
+  this.maximize.setAttribute('src', this.maximizeImage);
+  this.maximize.setAttribute('title', 'Maximize');
+  this.maximize.style.cursor = 'default';
+  this.maximize.style.marginLeft = '2px';
+  this.maximize.style.cursor = 'pointer';
+  this.maximize.style.display = 'none';
+
+  this.buttons.appendChild(this.maximize);
+
+  var maximized = false;
+  var x = null;
+  var y = null;
+  var height = null;
+  var width = null;
+  var minDisplay = null;
+
+  var funct = mxUtils.bind(this, function (evt) {
+    this.activate();
+
+    if (this.maximize.style.display != 'none') {
+      if (!maximized) {
+        maximized = true;
+
+        this.maximize.setAttribute('src', this.normalizeImage);
+        this.maximize.setAttribute('title', 'Normalize');
+        this.contentWrapper.style.display = '';
+        minDisplay = this.minimize.style.display;
+        this.minimize.style.display = 'none';
+
+        // Saves window state
+        x = parseInt(this.div.style.left);
+        y = parseInt(this.div.style.top);
+        height = this.table.style.height;
+        width = this.table.style.width;
+
+        this.div.style.left = '0px';
+        this.div.style.top = '0px';
+        var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0);
+
+        if (!mxClient.IS_QUIRKS) {
+          this.div.style.width = document.body.clientWidth - 2 + 'px';
+          this.div.style.height = docHeight - 2 + 'px';
+        }
+
+        this.table.style.width = document.body.clientWidth - 2 + 'px';
+        this.table.style.height = docHeight - 2 + 'px';
+
+        if (this.resize != null) {
+          this.resize.style.visibility = 'hidden';
+        }
+
+        if (!mxClient.IS_QUIRKS) {
+          var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+          if (style.overflow == 'auto' || this.resize != null) {
+            this.contentWrapper.style.height =
+              this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
+          }
+        }
+
+        this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
+      } else {
+        maximized = false;
+
+        this.maximize.setAttribute('src', this.maximizeImage);
+        this.maximize.setAttribute('title', 'Maximize');
+        this.contentWrapper.style.display = '';
+        this.minimize.style.display = minDisplay;
+
+        // Restores window state
+        this.div.style.left = x + 'px';
+        this.div.style.top = y + 'px';
+
+        if (!mxClient.IS_QUIRKS) {
+          this.div.style.height = height;
+          this.div.style.width = width;
+
+          var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+          if (style.overflow == 'auto' || this.resize != null) {
+            this.contentWrapper.style.height =
+              this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
+          }
+        }
+
+        this.table.style.height = height;
+        this.table.style.width = width;
+
+        if (this.resize != null) {
+          this.resize.style.visibility = '';
+        }
+
+        this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+      }
+
+      mxEvent.consume(evt);
+    }
+  });
+
+  mxEvent.addGestureListeners(this.maximize, funct);
+  mxEvent.addListener(this.title, 'dblclick', funct);
+};
+
+/**
+ * Function: installMoveHandler
+ *
+ * Installs the event listeners required for moving the window.
+ */
+mxWindow.prototype.installMoveHandler = function () {
+  this.title.style.cursor = 'move';
+
+  mxEvent.addGestureListeners(
+    this.title,
+    mxUtils.bind(this, function (evt) {
+      var startX = mxEvent.getClientX(evt);
+      var startY = mxEvent.getClientY(evt);
+      var x = this.getX();
+      var y = this.getY();
+
+      // Adds a temporary pair of listeners to intercept
+      // the gesture event in the document
+      var dragHandler = mxUtils.bind(this, function (evt) {
+        var dx = mxEvent.getClientX(evt) - startX;
+        var dy = mxEvent.getClientY(evt) - startY;
+        this.setLocation(x + dx, y + dy);
+        this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
+        mxEvent.consume(evt);
+      });
+
+      var dropHandler = mxUtils.bind(this, function (evt) {
+        mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+        this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
+        mxEvent.consume(evt);
+      });
+
+      mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+      this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
+      mxEvent.consume(evt);
+    })
+  );
+
+  // Disables built-in pan and zoom in IE10 and later
+  if (mxClient.IS_POINTER) {
+    this.title.style.touchAction = 'none';
+  }
+};
+
+/**
+ * Function: setLocation
+ *
+ * Sets the upper, left corner of the window.
+ */
+mxWindow.prototype.setLocation = function (x, y) {
+  this.div.style.left = x + 'px';
+  this.div.style.top = y + 'px';
+};
+
+/**
+ * Function: getX
+ *
+ * Returns the current position on the x-axis.
+ */
+mxWindow.prototype.getX = function () {
+  return parseInt(this.div.style.left);
+};
+
+/**
+ * Function: getY
+ *
+ * Returns the current position on the y-axis.
+ */
+mxWindow.prototype.getY = function () {
+  return parseInt(this.div.style.top);
+};
+
+/**
+ * Function: installCloseHandler
+ *
+ * Adds the  as a new image node in  and installs the
+ *  event.
+ */
+mxWindow.prototype.installCloseHandler = function () {
+  this.closeImg = document.createElement('img');
+
+  this.closeImg.setAttribute('src', this.closeImage);
+  this.closeImg.setAttribute('title', 'Close');
+  this.closeImg.style.marginLeft = '2px';
+  this.closeImg.style.cursor = 'pointer';
+  this.closeImg.style.display = 'none';
+
+  this.buttons.appendChild(this.closeImg);
+
+  mxEvent.addGestureListeners(
+    this.closeImg,
+    mxUtils.bind(this, function (evt) {
+      this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));
+
+      if (this.destroyOnClose) {
+        this.destroy();
+      } else {
+        this.setVisible(false);
+      }
+
+      mxEvent.consume(evt);
+    })
+  );
+};
+
+/**
+ * Function: setImage
+ *
+ * Sets the image associated with the window.
+ *
+ * Parameters:
+ *
+ * image - URL of the image to be used.
+ */
+mxWindow.prototype.setImage = function (image) {
+  this.image = document.createElement('img');
+  this.image.setAttribute('src', image);
+  this.image.setAttribute('align', 'left');
+  this.image.style.marginRight = '4px';
+  this.image.style.marginLeft = '0px';
+  this.image.style.marginTop = '-2px';
+
+  this.title.insertBefore(this.image, this.title.firstChild);
+};
+
+/**
+ * Function: setClosable
+ *
+ * Sets the image associated with the window.
+ *
+ * Parameters:
+ *
+ * closable - Boolean specifying if the window should be closable.
+ */
+mxWindow.prototype.setClosable = function (closable) {
+  this.closeImg.style.display = closable ? '' : 'none';
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the window is visible.
+ */
+mxWindow.prototype.isVisible = function () {
+  if (this.div != null) {
+    return this.div.style.display != 'none';
+  }
+
+  return false;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Shows or hides the window depending on the given flag.
+ *
+ * Parameters:
+ *
+ * visible - Boolean indicating if the window should be made visible.
+ */
+mxWindow.prototype.setVisible = function (visible) {
+  if (this.div != null && this.isVisible() != visible) {
+    if (visible) {
+      this.show();
+    } else {
+      this.hide();
+    }
+  }
+};
+
+/**
+ * Function: show
+ *
+ * Shows the window.
+ */
+mxWindow.prototype.show = function () {
+  this.div.style.display = '';
+  this.activate();
+
+  var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+  if (
+    !mxClient.IS_QUIRKS &&
+    (style.overflow == 'auto' || this.resize != null) &&
+    this.contentWrapper.style.display != 'none'
+  ) {
+    this.contentWrapper.style.height =
+      this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
+  }
+
+  this.fireEvent(new mxEventObject(mxEvent.SHOW));
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the window.
+ */
+mxWindow.prototype.hide = function () {
+  this.div.style.display = 'none';
+  this.fireEvent(new mxEventObject(mxEvent.HIDE));
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the window and removes all associated resources. Fires a
+ *  event prior to destroying the window.
+ */
+mxWindow.prototype.destroy = function () {
+  this.fireEvent(new mxEventObject(mxEvent.DESTROY));
+
+  if (this.div != null) {
+    mxEvent.release(this.div);
+    this.div.parentNode.removeChild(this.div);
+    this.div = null;
+  }
+
+  this.title = null;
+  this.content = null;
+  this.contentWrapper = null;
+};
+
+/**
+ * util/mxForm.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxForm
+ *
+ * A simple class for creating HTML forms.
+ *
+ * Constructor: mxForm
+ *
+ * Creates a HTML table using the specified classname.
+ */
+function mxForm(className) {
+  this.table = document.createElement('table');
+  this.table.className = className;
+  this.body = document.createElement('tbody');
+
+  this.table.appendChild(this.body);
+}
+
+/**
+ * Variable: table
+ *
+ * Holds the DOM node that represents the table.
+ */
+mxForm.prototype.table = null;
+
+/**
+ * Variable: body
+ *
+ * Holds the DOM node that represents the tbody (table body). New rows
+ * can be added to this object using DOM API.
+ */
+mxForm.prototype.body = false;
+
+/**
+ * Function: getTable
+ *
+ * Returns the table that contains this form.
+ */
+mxForm.prototype.getTable = function () {
+  return this.table;
+};
+
+/**
+ * Function: addButtons
+ *
+ * Helper method to add an OK and Cancel button using the respective
+ * functions.
+ */
+mxForm.prototype.addButtons = function (okFunct, cancelFunct) {
+  var tr = document.createElement('tr');
+  var td = document.createElement('td');
+  tr.appendChild(td);
+  td = document.createElement('td');
+
+  // Adds the ok button
+  var button = document.createElement('button');
+  mxUtils.write(button, mxResources.get('ok') || 'OK');
+  td.appendChild(button);
+
+  mxEvent.addListener(button, 'click', function () {
+    okFunct();
+  });
+
+  // Adds the cancel button
+  button = document.createElement('button');
+  mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
+  td.appendChild(button);
+
+  mxEvent.addListener(button, 'click', function () {
+    cancelFunct();
+  });
+
+  tr.appendChild(td);
+  this.body.appendChild(tr);
+};
+
+/**
+ * Function: addText
+ *
+ * Adds an input for the given name, type and value and returns it.
+ */
+mxForm.prototype.addText = function (name, value, type) {
+  var input = document.createElement('input');
+
+  input.setAttribute('type', type || 'text');
+  input.value = value;
+
+  return this.addField(name, input);
+};
+
+/**
+ * Function: addCheckbox
+ *
+ * Adds a checkbox for the given name and value and returns the textfield.
+ */
+mxForm.prototype.addCheckbox = function (name, value) {
+  var input = document.createElement('input');
+
+  input.setAttribute('type', 'checkbox');
+  this.addField(name, input);
+
+  // IE can only change the checked value if the input is inside the DOM
+  if (value) {
+    input.checked = true;
+  }
+
+  return input;
+};
+
+/**
+ * Function: addTextarea
+ *
+ * Adds a textarea for the given name and value and returns the textarea.
+ */
+mxForm.prototype.addTextarea = function (name, value, rows) {
+  var input = document.createElement('textarea');
+
+  if (mxClient.IS_NS) {
+    rows--;
+  }
+
+  input.setAttribute('rows', rows || 2);
+  input.value = value;
+
+  return this.addField(name, input);
+};
+
+/**
+ * Function: addCombo
+ *
+ * Adds a combo for the given name and returns the combo.
+ */
+mxForm.prototype.addCombo = function (name, isMultiSelect, size) {
+  var select = document.createElement('select');
+
+  if (size != null) {
+    select.setAttribute('size', size);
+  }
+
+  if (isMultiSelect) {
+    select.setAttribute('multiple', 'true');
+  }
+
+  return this.addField(name, select);
+};
+
+/**
+ * Function: addOption
+ *
+ * Adds an option for the given label to the specified combo.
+ */
+mxForm.prototype.addOption = function (combo, label, value, isSelected) {
+  var option = document.createElement('option');
+
+  mxUtils.writeln(option, label);
+  option.setAttribute('value', value);
+
+  if (isSelected) {
+    option.setAttribute('selected', isSelected);
+  }
+
+  combo.appendChild(option);
+};
+
+/**
+ * Function: addField
+ *
+ * Adds a new row with the name and the input field in two columns and
+ * returns the given input.
+ */
+mxForm.prototype.addField = function (name, input) {
+  var tr = document.createElement('tr');
+  var td = document.createElement('td');
+  mxUtils.write(td, name);
+  tr.appendChild(td);
+
+  td = document.createElement('td');
+  td.appendChild(input);
+  tr.appendChild(td);
+  this.body.appendChild(tr);
+
+  return input;
+};
+
+/**
+ * util/mxImage.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImage
+ *
+ * Encapsulates the URL, width and height of an image.
+ *
+ * Constructor: mxImage
+ *
+ * Constructs a new image.
+ */
+function mxImage(src, width, height) {
+  this.src = src;
+  this.width = width;
+  this.height = height;
+}
+
+/**
+ * Variable: src
+ *
+ * String that specifies the URL of the image.
+ */
+mxImage.prototype.src = null;
+
+/**
+ * Variable: width
+ *
+ * Integer that specifies the width of the image.
+ */
+mxImage.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Integer that specifies the height of the image.
+ */
+mxImage.prototype.height = null;
+
+/**
+ * util/mxDivResizer.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDivResizer
+ *
+ * Maintains the size of a div element in Internet Explorer. This is a
+ * workaround for the right and bottom style being ignored in IE.
+ *
+ * If you need a div to cover the scrollwidth and -height of a document,
+ * then you can use this class as follows:
+ *
+ * (code)
+ * var resizer = new mxDivResizer(background);
+ * resizer.getDocumentHeight = function()
+ * {
+ *   return document.body.scrollHeight;
+ * }
+ * resizer.getDocumentWidth = function()
+ * {
+ *   return document.body.scrollWidth;
+ * }
+ * resizer.resize();
+ * (end)
+ *
+ * Constructor: mxDivResizer
+ *
+ * Constructs an object that maintains the size of a div
+ * element when the window is being resized. This is only
+ * required for Internet Explorer as it ignores the respective
+ * stylesheet information for DIV elements.
+ *
+ * Parameters:
+ *
+ * div - Reference to the DOM node whose size should be maintained.
+ * container - Optional Container that contains the div. Default is the
+ * window.
+ */
+function mxDivResizer(div, container) {
+  if (div.nodeName.toLowerCase() == 'div') {
+    if (container == null) {
+      container = window;
+    }
+
+    this.div = div;
+    var style = mxUtils.getCurrentStyle(div);
+
+    if (style != null) {
+      this.resizeWidth = style.width == 'auto';
+      this.resizeHeight = style.height == 'auto';
+    }
+
+    mxEvent.addListener(
+      container,
+      'resize',
+      mxUtils.bind(this, function (evt) {
+        if (!this.handlingResize) {
+          this.handlingResize = true;
+          this.resize();
+          this.handlingResize = false;
+        }
+      })
+    );
+
+    this.resize();
+  }
+}
+
+/**
+ * Function: resizeWidth
+ *
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.resizeWidth = true;
+
+/**
+ * Function: resizeHeight
+ *
+ * Boolean specifying if the height should be updated.
+ */
+mxDivResizer.prototype.resizeHeight = true;
+
+/**
+ * Function: handlingResize
+ *
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.handlingResize = false;
+
+/**
+ * Function: resize
+ *
+ * Updates the style of the DIV after the window has been resized.
+ */
+mxDivResizer.prototype.resize = function () {
+  var w = this.getDocumentWidth();
+  var h = this.getDocumentHeight();
+
+  var l = parseInt(this.div.style.left);
+  var r = parseInt(this.div.style.right);
+  var t = parseInt(this.div.style.top);
+  var b = parseInt(this.div.style.bottom);
+
+  if (this.resizeWidth && !isNaN(l) && !isNaN(r) && l >= 0 && r >= 0 && w - r - l > 0) {
+    this.div.style.width = w - r - l + 'px';
+  }
+
+  if (this.resizeHeight && !isNaN(t) && !isNaN(b) && t >= 0 && b >= 0 && h - t - b > 0) {
+    this.div.style.height = h - t - b + 'px';
+  }
+};
+
+/**
+ * Function: getDocumentWidth
+ *
+ * Hook for subclassers to return the width of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentWidth = function () {
+  return document.body.clientWidth;
+};
+
+/**
+ * Function: getDocumentHeight
+ *
+ * Hook for subclassers to return the height of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentHeight = function () {
+  return document.body.clientHeight;
+};
+
+/**
+ * util/mxDragSource.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDragSource
+ *
+ * Wrapper to create a drag source from a DOM element so that the element can
+ * be dragged over a graph and dropped into the graph as a new cell.
+ *
+ * Problem is that in the dropHandler the current preview location is not
+ * available, so the preview and the dropHandler must match.
+ *
+ * Constructor: mxDragSource
+ *
+ * Constructs a new drag source for the given element.
+ */
+function mxDragSource(element, dropHandler) {
+  this.element = element;
+  this.dropHandler = dropHandler;
+
+  // Handles a drag gesture on the element
+  mxEvent.addGestureListeners(
+    element,
+    mxUtils.bind(this, function (evt) {
+      this.mouseDown(evt);
+    })
+  );
+
+  // Prevents native drag and drop
+  mxEvent.addListener(element, 'dragstart', function (evt) {
+    mxEvent.consume(evt);
+  });
+
+  this.eventConsumer = function (sender, evt) {
+    var evtName = evt.getProperty('eventName');
+    var me = evt.getProperty('event');
+
+    if (evtName != mxEvent.MOUSE_DOWN) {
+      me.consume();
+    }
+  };
+}
+
+/**
+ * Variable: element
+ *
+ * Reference to the DOM node which was made draggable.
+ */
+mxDragSource.prototype.element = null;
+
+/**
+ * Variable: dropHandler
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dropHandler = null;
+
+/**
+ * Variable: dragOffset
+ *
+ *  that specifies the offset of the . Default is null.
+ */
+mxDragSource.prototype.dragOffset = null;
+
+/**
+ * Variable: dragElement
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dragElement = null;
+
+/**
+ * Variable: previewElement
+ *
+ * Optional  that specifies the unscaled size of the preview.
+ */
+mxDragSource.prototype.previewElement = null;
+
+/**
+ * Variable: previewOffset
+ *
+ * Optional  that specifies the offset of the preview in pixels.
+ */
+mxDragSource.prototype.previewOffset = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if this drag source is enabled. Default is true.
+ */
+mxDragSource.prototype.enabled = true;
+
+/**
+ * Variable: currentGraph
+ *
+ * Reference to the  that is the current drop target.
+ */
+mxDragSource.prototype.currentGraph = null;
+
+/**
+ * Variable: currentDropTarget
+ *
+ * Holds the current drop target under the mouse.
+ */
+mxDragSource.prototype.currentDropTarget = null;
+
+/**
+ * Variable: currentPoint
+ *
+ * Holds the current drop location.
+ */
+mxDragSource.prototype.currentPoint = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an  for the  if  is not null.
+ */
+mxDragSource.prototype.currentGuide = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an  for the  if  is not null.
+ */
+mxDragSource.prototype.currentHighlight = null;
+
+/**
+ * Variable: autoscroll
+ *
+ * Specifies if the graph should scroll automatically. Default is true.
+ */
+mxDragSource.prototype.autoscroll = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if  should be enabled. Default is true.
+ */
+mxDragSource.prototype.guidesEnabled = true;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid should be allowed. Default is true.
+ */
+mxDragSource.prototype.gridEnabled = true;
+
+/**
+ * Variable: highlightDropTargets
+ *
+ * Specifies if drop targets should be highlighted. Default is true.
+ */
+mxDragSource.prototype.highlightDropTargets = true;
+
+/**
+ * Variable: dragElementZIndex
+ *
+ * ZIndex for the drag element. Default is 100.
+ */
+mxDragSource.prototype.dragElementZIndex = 100;
+
+/**
+ * Variable: dragElementOpacity
+ *
+ * Opacity of the drag element in %. Default is 70.
+ */
+mxDragSource.prototype.dragElementOpacity = 70;
+
+/**
+ * Variable: checkEventSource
+ *
+ * Whether the event source should be checked in . Default
+ * is true.
+ */
+mxDragSource.prototype.checkEventSource = true;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns .
+ */
+mxDragSource.prototype.isEnabled = function () {
+  return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets .
+ */
+mxDragSource.prototype.setEnabled = function (value) {
+  this.enabled = value;
+};
+
+/**
+ * Function: isGuidesEnabled
+ *
+ * Returns .
+ */
+mxDragSource.prototype.isGuidesEnabled = function () {
+  return this.guidesEnabled;
+};
+
+/**
+ * Function: setGuidesEnabled
+ *
+ * Sets .
+ */
+mxDragSource.prototype.setGuidesEnabled = function (value) {
+  this.guidesEnabled = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns .
+ */
+mxDragSource.prototype.isGridEnabled = function () {
+  return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ *
+ * Sets .
+ */
+mxDragSource.prototype.setGridEnabled = function (value) {
+  this.gridEnabled = value;
+};
+
+/**
+ * Function: getGraphForEvent
+ *
+ * Returns the graph for the given mouse event. This implementation returns
+ * null.
+ */
+mxDragSource.prototype.getGraphForEvent = function (evt) {
+  return null;
+};
+
+/**
+ * Function: getDropTarget
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses .
+ */
+mxDragSource.prototype.getDropTarget = function (graph, x, y, evt) {
+  return graph.getCellAt(x, y);
+};
+
+/**
+ * Function: createDragElement
+ *
+ * Creates and returns a clone of the  or the 
+ * if the former is not defined.
+ */
+mxDragSource.prototype.createDragElement = function (evt) {
+  return this.element.cloneNode(true);
+};
+
+/**
+ * Function: createPreviewElement
+ *
+ * Creates and returns an element which can be used as a preview in the given
+ * graph.
+ */
+mxDragSource.prototype.createPreviewElement = function (graph) {
+  return null;
+};
+
+/**
+ * Function: isActive
+ *
+ * Returns true if this drag source is active.
+ */
+mxDragSource.prototype.isActive = function () {
+  return this.mouseMoveHandler != null;
+};
+
+/**
+ * Function: reset
+ *
+ * Stops and removes everything and restores the state of the object.
+ */
+mxDragSource.prototype.reset = function () {
+  if (this.currentGraph != null) {
+    this.dragExit(this.currentGraph);
+    this.currentGraph = null;
+  }
+
+  this.removeDragElement();
+  this.removeListeners();
+  this.stopDrag();
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses .
+ *
+ * To ignore popup menu events for a drag source, this function can be
+ * overridden as follows.
+ *
+ * (code)
+ * var mouseDown = dragSource.mouseDown;
+ *
+ * dragSource.mouseDown = function(evt)
+ * {
+ *   if (!mxEvent.isPopupTrigger(evt))
+ *   {
+ *     mouseDown.apply(this, arguments);
+ *   }
+ * };
+ * (end)
+ */
+mxDragSource.prototype.mouseDown = function (evt) {
+  if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null) {
+    this.startDrag(evt);
+    this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
+    this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);
+    mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+
+    if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt)) {
+      this.eventSource = mxEvent.getSource(evt);
+      mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+    }
+  }
+};
+
+/**
+ * Function: startDrag
+ *
+ * Creates the  using .
+ */
+mxDragSource.prototype.startDrag = function (evt) {
+  this.dragElement = this.createDragElement(evt);
+  this.dragElement.style.position = 'absolute';
+  this.dragElement.style.zIndex = this.dragElementZIndex;
+  mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);
+
+  if (this.checkEventSource && mxClient.IS_SVG) {
+    this.dragElement.style.pointerEvents = 'none';
+  }
+};
+
+/**
+ * Function: stopDrag
+ *
+ * Invokes .
+ */
+mxDragSource.prototype.stopDrag = function () {
+  // LATER: This used to have a mouse event. If that is still needed we need to add another
+  // final call to the DnD protocol to add a cleanup step in the case of escape press, which
+  // is not associated with a mouse event and which currently calles this method.
+  this.removeDragElement();
+};
+
+/**
+ * Function: removeDragElement
+ *
+ * Removes and destroys the .
+ */
+mxDragSource.prototype.removeDragElement = function () {
+  if (this.dragElement != null) {
+    if (this.dragElement.parentNode != null) {
+      this.dragElement.parentNode.removeChild(this.dragElement);
+    }
+
+    this.dragElement = null;
+  }
+};
+
+/**
+ * Function: getElementForEvent
+ *
+ * Returns the topmost element under the given event.
+ */
+mxDragSource.prototype.getElementForEvent = function (evt) {
+  return mxEvent.isTouchEvent(evt) || mxEvent.isPenEvent(evt)
+    ? document.elementFromPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt))
+    : mxEvent.getSource(evt);
+};
+
+/**
+ * Function: graphContainsEvent
+ *
+ * Returns true if the given graph contains the given event.
+ */
+mxDragSource.prototype.graphContainsEvent = function (graph, evt) {
+  var x = mxEvent.getClientX(evt);
+  var y = mxEvent.getClientY(evt);
+  var offset = mxUtils.getOffset(graph.container);
+  var origin = mxUtils.getScrollOrigin();
+  var elt = this.getElementForEvent(evt);
+
+  if (this.checkEventSource) {
+    while (elt != null && elt != graph.container) {
+      elt = elt.parentNode;
+    }
+  }
+
+  // Checks if event is inside the bounds of the graph container
+  return (
+    elt != null &&
+    x >= offset.x - origin.x &&
+    y >= offset.y - origin.y &&
+    x <= offset.x - origin.x + graph.container.offsetWidth &&
+    y <= offset.y - origin.y + graph.container.offsetHeight
+  );
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Gets the graph for the given event using , updates the
+ * , calling  and  on the new and old graph,
+ * respectively, and invokes  if  is not null.
+ */
+mxDragSource.prototype.mouseMove = function (evt) {
+  var graph = this.getGraphForEvent(evt);
+
+  // Checks if event is inside the bounds of the graph container
+  if (graph != null && !this.graphContainsEvent(graph, evt)) {
+    graph = null;
+  }
+
+  if (graph != this.currentGraph) {
+    if (this.currentGraph != null) {
+      this.dragExit(this.currentGraph, evt);
+    }
+
+    this.currentGraph = graph;
+
+    if (this.currentGraph != null) {
+      this.dragEnter(this.currentGraph, evt);
+    }
+  }
+
+  if (this.currentGraph != null) {
+    this.dragOver(this.currentGraph, evt);
+  }
+
+  if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible')) {
+    var x = mxEvent.getClientX(evt);
+    var y = mxEvent.getClientY(evt);
+
+    if (this.dragElement.parentNode == null) {
+      document.body.appendChild(this.dragElement);
+    }
+
+    this.dragElement.style.visibility = 'visible';
+
+    if (this.dragOffset != null) {
+      x += this.dragOffset.x;
+      y += this.dragOffset.y;
+    }
+
+    var offset = mxUtils.getDocumentScrollOrigin(document);
+
+    this.dragElement.style.left = x + offset.x + 'px';
+    this.dragElement.style.top = y + offset.y + 'px';
+  } else if (this.dragElement != null) {
+    this.dragElement.style.visibility = 'hidden';
+  }
+
+  mxEvent.consume(evt);
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Processes the mouse up event and invokes ,  and 
+ * as required.
+ */
+mxDragSource.prototype.mouseUp = function (evt) {
+  if (this.currentGraph != null) {
+    if (
+      this.currentPoint != null &&
+      (this.previewElement == null || this.previewElement.style.visibility != 'hidden')
+    ) {
+      var scale = this.currentGraph.view.scale;
+      var tr = this.currentGraph.view.translate;
+      var x = this.currentPoint.x / scale - tr.x;
+      var y = this.currentPoint.y / scale - tr.y;
+
+      this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
+    }
+
+    this.dragExit(this.currentGraph);
+    this.currentGraph = null;
+  }
+
+  this.stopDrag();
+  this.removeListeners();
+
+  mxEvent.consume(evt);
+};
+
+/**
+ * Function: removeListeners
+ *
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.removeListeners = function () {
+  if (this.eventSource != null) {
+    mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+    this.eventSource = null;
+  }
+
+  mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+  this.mouseMoveHandler = null;
+  this.mouseUpHandler = null;
+};
+
+/**
+ * Function: dragEnter
+ *
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.dragEnter = function (graph, evt) {
+  graph.isMouseDown = true;
+  graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
+  this.previewElement = this.createPreviewElement(graph);
+
+  if (this.previewElement != null && this.checkEventSource && mxClient.IS_SVG) {
+    this.previewElement.style.pointerEvents = 'none';
+  }
+
+  // Guide is only needed if preview element is used
+  if (this.isGuidesEnabled() && this.previewElement != null) {
+    this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
+  }
+
+  if (this.highlightDropTargets) {
+    this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
+  }
+
+  // Consumes all events in the current graph before they are fired
+  graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer);
+};
+
+/**
+ * Function: dragExit
+ *
+ * Deactivates the given graph as a drop target.
+ */
+mxDragSource.prototype.dragExit = function (graph, evt) {
+  this.currentDropTarget = null;
+  this.currentPoint = null;
+  graph.isMouseDown = false;
+
+  // Consumes all events in the current graph before they are fired
+  graph.removeListener(this.eventConsumer);
+
+  if (this.previewElement != null) {
+    if (this.previewElement.parentNode != null) {
+      this.previewElement.parentNode.removeChild(this.previewElement);
+    }
+
+    this.previewElement = null;
+  }
+
+  if (this.currentGuide != null) {
+    this.currentGuide.destroy();
+    this.currentGuide = null;
+  }
+
+  if (this.currentHighlight != null) {
+    this.currentHighlight.destroy();
+    this.currentHighlight = null;
+  }
+};
+
+/**
+ * Function: dragOver
+ *
+ * Implements autoscroll, updates the , highlights any drop
+ * targets and updates the preview.
+ */
+mxDragSource.prototype.dragOver = function (graph, evt) {
+  var offset = mxUtils.getOffset(graph.container);
+  var origin = mxUtils.getScrollOrigin(graph.container);
+  var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx;
+  var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy;
+
+  if (graph.autoScroll && (this.autoscroll == null || this.autoscroll)) {
+    graph.scrollPointToVisible(x, y, graph.autoExtend);
+  }
+
+  // Highlights the drop target under the mouse
+  if (this.currentHighlight != null && graph.isDropEnabled()) {
+    this.currentDropTarget = this.getDropTarget(graph, x, y, evt);
+    var state = graph.getView().getState(this.currentDropTarget);
+    this.currentHighlight.highlight(state);
+  }
+
+  // Updates the location of the preview
+  if (this.previewElement != null) {
+    if (this.previewElement.parentNode == null) {
+      graph.container.appendChild(this.previewElement);
+
+      this.previewElement.style.zIndex = '3';
+      this.previewElement.style.position = 'absolute';
+    }
+
+    var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
+    var hideGuide = true;
+
+    // Grid and guides
+    if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt)) {
+      // LATER: HTML preview appears smaller than SVG preview
+      var w = parseInt(this.previewElement.style.width);
+      var h = parseInt(this.previewElement.style.height);
+      var bounds = new mxRectangle(0, 0, w, h);
+      var delta = new mxPoint(x, y);
+      delta = this.currentGuide.move(bounds, delta, gridEnabled, true);
+      hideGuide = false;
+      x = delta.x;
+      y = delta.y;
+    } else if (gridEnabled) {
+      var scale = graph.view.scale;
+      var tr = graph.view.translate;
+      var off = graph.gridSize / 2;
+      x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
+      y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
+    }
+
+    if (this.currentGuide != null && hideGuide) {
+      this.currentGuide.hide();
+    }
+
+    if (this.previewOffset != null) {
+      x += this.previewOffset.x;
+      y += this.previewOffset.y;
+    }
+
+    this.previewElement.style.left = Math.round(x) + 'px';
+    this.previewElement.style.top = Math.round(y) + 'px';
+    this.previewElement.style.visibility = 'visible';
+  }
+
+  this.currentPoint = new mxPoint(x, y);
+};
+
+/**
+ * Function: drop
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses .
+ */
+mxDragSource.prototype.drop = function (graph, evt, dropTarget, x, y) {
+  this.dropHandler.apply(this, arguments);
+
+  // Had to move this to after the insert because it will
+  // affect the scrollbars of the window in IE to try and
+  // make the complete container visible.
+  // LATER: Should be made optional.
+  if (graph.container.style.visibility != 'hidden') {
+    graph.container.focus();
+  }
+};
+
+/**
+ * util/mxToolbar.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxToolbar
+ *
+ * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
+ * buttons and combo boxes.
+ *
+ * Event: mxEvent.SELECT
+ *
+ * Fires when an item was selected in the toolbar. The function
+ * property contains the function that was selected in .
+ *
+ * Constructor: mxToolbar
+ *
+ * Constructs a toolbar in the specified container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+function mxToolbar(container) {
+  this.container = container;
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxToolbar.prototype = new mxEventSource();
+mxToolbar.prototype.constructor = mxToolbar;
+
+/**
+ * Variable: container
+ *
+ * Reference to the DOM nodes that contains the toolbar.
+ */
+mxToolbar.prototype.container = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxToolbar.prototype.enabled = true;
+
+/**
+ * Variable: noReset
+ *
+ * Specifies if  requires a forced flag of true for resetting
+ * the current mode in the toolbar. Default is false. This is set to true
+ * if the toolbar item is double clicked to avoid a reset after a single
+ * use of the item.
+ */
+mxToolbar.prototype.noReset = false;
+
+/**
+ * Variable: updateDefaultMode
+ *
+ * Boolean indicating if the default mode should be the last selected
+ * switch mode or the first inserted switch mode. Default is true, that
+ * is the last selected switch mode is the default mode. The default mode
+ * is the mode to be selected after a reset of the toolbar. If this is
+ * false, then the default mode is the first inserted mode item regardless
+ * of what was last selected. Otherwise, the selected item after a reset is
+ * the previously selected item.
+ */
+mxToolbar.prototype.updateDefaultMode = true;
+
+/**
+ * Function: addItem
+ *
+ * Adds the given function as an image with the specified title and icon
+ * and returns the new image node.
+ *
+ * Parameters:
+ *
+ * title - Optional string that is used as the tooltip.
+ * icon - Optional URL of the image to be used. If no URL is given, then a
+ * button is created.
+ * funct - Function to execute on a mouse click.
+ * pressedIcon - Optional URL of the pressed image. Default is a gray
+ * background.
+ * style - Optional style classname. Default is mxToolbarItem.
+ * factoryMethod - Optional factory method for popup menu, eg.
+ * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
+ */
+mxToolbar.prototype.addItem = function (title, icon, funct, pressedIcon, style, factoryMethod) {
+  var img = document.createElement(icon != null ? 'img' : 'button');
+  var initialClassName = style || (factoryMethod != null ? 'mxToolbarMode' : 'mxToolbarItem');
+  img.className = initialClassName;
+  img.setAttribute('src', icon);
+
+  if (title != null) {
+    if (icon != null) {
+      img.setAttribute('title', title);
+    } else {
+      mxUtils.write(img, title);
+    }
+  }
+
+  this.container.appendChild(img);
+
+  // Invokes the function on a click on the toolbar item
+  if (funct != null) {
+    mxEvent.addListener(img, 'click', funct);
+
+    if (mxClient.IS_TOUCH) {
+      mxEvent.addListener(img, 'touchend', funct);
+    }
+  }
+
+  var mouseHandler = mxUtils.bind(this, function (evt) {
+    if (pressedIcon != null) {
+      img.setAttribute('src', icon);
+    } else {
+      img.style.backgroundColor = '';
+    }
+  });
+
+  // Highlights the toolbar item with a gray background
+  // while it is being clicked with the mouse
+  mxEvent.addGestureListeners(
+    img,
+    mxUtils.bind(this, function (evt) {
+      if (pressedIcon != null) {
+        img.setAttribute('src', pressedIcon);
+      } else {
+        img.style.backgroundColor = 'gray';
+      }
+
+      // Popup Menu
+      if (factoryMethod != null) {
+        if (this.menu == null) {
+          this.menu = new mxPopupMenu();
+          this.menu.init();
+        }
+
+        var last = this.currentImg;
+
+        if (this.menu.isMenuShowing()) {
+          this.menu.hideMenu();
+        }
+
+        if (last != img) {
+          // Redirects factory method to local factory method
+          this.currentImg = img;
+          this.menu.factoryMethod = factoryMethod;
+
+          var point = new mxPoint(img.offsetLeft, img.offsetTop + img.offsetHeight);
+          this.menu.popup(point.x, point.y, null, evt);
+
+          // Sets and overrides to restore classname
+          if (this.menu.isMenuShowing()) {
+            img.className = initialClassName + 'Selected';
+
+            this.menu.hideMenu = function () {
+              mxPopupMenu.prototype.hideMenu.apply(this);
+              img.className = initialClassName;
+              this.currentImg = null;
+            };
+          }
+        }
+      }
+    }),
+    null,
+    mouseHandler
+  );
+
+  mxEvent.addListener(img, 'mouseout', mouseHandler);
+
+  return img;
+};
+
+/**
+ * Function: addCombo
+ *
+ * Adds and returns a new SELECT element using the given style. The element
+ * is placed inside a DIV with the mxToolbarComboContainer style classname.
+ *
+ * Parameters:
+ *
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addCombo = function (style) {
+  var div = document.createElement('div');
+  div.style.display = 'inline';
+  div.className = 'mxToolbarComboContainer';
+
+  var select = document.createElement('select');
+  select.className = style || 'mxToolbarCombo';
+  div.appendChild(select);
+
+  this.container.appendChild(div);
+
+  return select;
+};
+
+/**
+ * Function: addActionCombo
+ *
+ * Adds and returns a new SELECT element using the given title as the
+ * default element. The selection is reset to this element after each
+ * change.
+ *
+ * Parameters:
+ *
+ * title - String that specifies the title of the default element.
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addActionCombo = function (title, style) {
+  var select = document.createElement('select');
+  select.className = style || 'mxToolbarCombo';
+  this.addOption(select, title, null);
+
+  mxEvent.addListener(select, 'change', function (evt) {
+    var value = select.options[select.selectedIndex];
+    select.selectedIndex = 0;
+
+    if (value.funct != null) {
+      value.funct(evt);
+    }
+  });
+
+  this.container.appendChild(select);
+
+  return select;
+};
+
+/**
+ * Function: addOption
+ *
+ * Adds and returns a new OPTION element inside the given SELECT element.
+ * If the given value is a function then it is stored in the option's funct
+ * field.
+ *
+ * Parameters:
+ *
+ * combo - SELECT element that will contain the new entry.
+ * title - String that specifies the title of the option.
+ * value - Specifies the value associated with this option.
+ */
+mxToolbar.prototype.addOption = function (combo, title, value) {
+  var option = document.createElement('option');
+  mxUtils.writeln(option, title);
+
+  if (typeof value == 'function') {
+    option.funct = value;
+  } else {
+    option.setAttribute('value', value);
+  }
+
+  combo.appendChild(option);
+
+  return option;
+};
+
+/**
+ * Function: addSwitchMode
+ *
+ * Adds a new selectable item to the toolbar. Only one switch mode item may
+ * be selected at a time. The currently selected item is the default item
+ * after a reset of the toolbar.
+ */
+mxToolbar.prototype.addSwitchMode = function (title, icon, funct, pressedIcon, style) {
+  var img = document.createElement('img');
+  img.initialClassName = style || 'mxToolbarMode';
+  img.className = img.initialClassName;
+  img.setAttribute('src', icon);
+  img.altIcon = pressedIcon;
+
+  if (title != null) {
+    img.setAttribute('title', title);
+  }
+
+  mxEvent.addListener(
+    img,
+    'click',
+    mxUtils.bind(this, function (evt) {
+      var tmp = this.selectedMode.altIcon;
+
+      if (tmp != null) {
+        this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+        this.selectedMode.setAttribute('src', tmp);
+      } else {
+        this.selectedMode.className = this.selectedMode.initialClassName;
+      }
+
+      if (this.updateDefaultMode) {
+        this.defaultMode = img;
+      }
+
+      this.selectedMode = img;
+
+      var tmp = img.altIcon;
+
+      if (tmp != null) {
+        img.altIcon = img.getAttribute('src');
+        img.setAttribute('src', tmp);
+      } else {
+        img.className = img.initialClassName + 'Selected';
+      }
+
+      this.fireEvent(new mxEventObject(mxEvent.SELECT));
+      funct();
+    })
+  );
+
+  this.container.appendChild(img);
+
+  if (this.defaultMode == null) {
+    this.defaultMode = img;
+
+    // Function should fire only once so
+    // do not pass it with the select event
+    this.selectMode(img);
+    funct();
+  }
+
+  return img;
+};
+
+/**
+ * Function: addMode
+ *
+ * Adds a new item to the toolbar. The selection is typically reset after
+ * the item has been consumed, for example by adding a new vertex to the
+ * graph. The reset is not carried out if the item is double clicked.
+ *
+ * The function argument uses the following signature: funct(evt, cell) where
+ * evt is the native mouse event and cell is the cell under the mouse.
+ */
+mxToolbar.prototype.addMode = function (title, icon, funct, pressedIcon, style, toggle) {
+  toggle = toggle != null ? toggle : true;
+  var img = document.createElement(icon != null ? 'img' : 'button');
+
+  img.initialClassName = style || 'mxToolbarMode';
+  img.className = img.initialClassName;
+  img.setAttribute('src', icon);
+  img.altIcon = pressedIcon;
+
+  if (title != null) {
+    img.setAttribute('title', title);
+  }
+
+  if (this.enabled && toggle) {
+    mxEvent.addListener(
+      img,
+      'click',
+      mxUtils.bind(this, function (evt) {
+        this.selectMode(img, funct);
+        this.noReset = false;
+      })
+    );
+
+    mxEvent.addListener(
+      img,
+      'dblclick',
+      mxUtils.bind(this, function (evt) {
+        this.selectMode(img, funct);
+        this.noReset = true;
+      })
+    );
+
+    if (this.defaultMode == null) {
+      this.defaultMode = img;
+      this.defaultFunction = funct;
+      this.selectMode(img, funct);
+    }
+  }
+
+  this.container.appendChild(img);
+
+  return img;
+};
+
+/**
+ * Function: selectMode
+ *
+ * Resets the state of the previously selected mode and displays the given
+ * DOM node as selected. This function fires a select event with the given
+ * function as a parameter.
+ */
+mxToolbar.prototype.selectMode = function (domNode, funct) {
+  if (this.selectedMode != domNode) {
+    if (this.selectedMode != null) {
+      var tmp = this.selectedMode.altIcon;
+
+      if (tmp != null) {
+        this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+        this.selectedMode.setAttribute('src', tmp);
+      } else {
+        this.selectedMode.className = this.selectedMode.initialClassName;
+      }
+    }
+
+    this.selectedMode = domNode;
+    var tmp = this.selectedMode.altIcon;
+
+    if (tmp != null) {
+      this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+      this.selectedMode.setAttribute('src', tmp);
+    } else {
+      this.selectedMode.className = this.selectedMode.initialClassName + 'Selected';
+    }
+
+    this.fireEvent(new mxEventObject(mxEvent.SELECT, 'function', funct));
+  }
+};
+
+/**
+ * Function: resetMode
+ *
+ * Selects the default mode and resets the state of the previously selected
+ * mode.
+ */
+mxToolbar.prototype.resetMode = function (forced) {
+  if ((forced || !this.noReset) && this.selectedMode != this.defaultMode) {
+    // The last selected switch mode will be activated
+    // so the function was already executed and is
+    // no longer required here
+    this.selectMode(this.defaultMode, this.defaultFunction);
+  }
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds the specifies image as a separator.
+ *
+ * Parameters:
+ *
+ * icon - URL of the separator icon.
+ */
+mxToolbar.prototype.addSeparator = function (icon) {
+  return this.addItem(null, icon, null);
+};
+
+/**
+ * Function: addBreak
+ *
+ * Adds a break to the container.
+ */
+mxToolbar.prototype.addBreak = function () {
+  mxUtils.br(this.container);
+};
+
+/**
+ * Function: addLine
+ *
+ * Adds a horizontal line to the container.
+ */
+mxToolbar.prototype.addLine = function () {
+  var hr = document.createElement('hr');
+
+  hr.style.marginRight = '6px';
+  hr.setAttribute('size', '1');
+
+  this.container.appendChild(hr);
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes the toolbar and all its associated resources.
+ */
+mxToolbar.prototype.destroy = function () {
+  mxEvent.release(this.container);
+  this.container = null;
+  this.defaultMode = null;
+  this.defaultFunction = null;
+  this.selectedMode = null;
+
+  if (this.menu != null) {
+    this.menu.destroy();
+  }
+};
+
+/**
+ * util/mxUndoableEdit.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoableEdit
+ *
+ * Implements a composite undoable edit. Here is an example for a custom change
+ * which gets executed via the model:
+ *
+ * (code)
+ * function CustomChange(model, name)
+ * {
+ *   this.model = model;
+ *   this.name = name;
+ *   this.previous = name;
+ * };
+ *
+ * CustomChange.prototype.execute = function()
+ * {
+ *   var tmp = this.model.name;
+ *   this.model.name = this.previous;
+ *   this.previous = tmp;
+ * };
+ *
+ * var name = prompt('Enter name');
+ * graph.model.execute(new CustomChange(graph.model, name));
+ * (end)
+ *
+ * Event: mxEvent.EXECUTED
+ *
+ * Fires between START_EDIT and END_EDIT after an atomic change was executed.
+ * The change property contains the change that was executed.
+ *
+ * Event: mxEvent.START_EDIT
+ *
+ * Fires before a set of changes will be executed in  or .
+ * This event contains no properties.
+ *
+ * Event: mxEvent.END_EDIT
+ *
+ * Fires after a set of changeswas executed in  or .
+ * This event contains no properties.
+ *
+ * Constructor: mxUndoableEdit
+ *
+ * Constructs a new undoable edit for the given source.
+ */
+function mxUndoableEdit(source, significant) {
+  this.source = source;
+  this.changes = [];
+  this.significant = significant != null ? significant : true;
+}
+
+/**
+ * Variable: source
+ *
+ * Specifies the source of the edit.
+ */
+mxUndoableEdit.prototype.source = null;
+
+/**
+ * Variable: changes
+ *
+ * Array that contains the changes that make up this edit. The changes are
+ * expected to either have an undo and redo function, or an execute
+ * function. Default is an empty array.
+ */
+mxUndoableEdit.prototype.changes = null;
+
+/**
+ * Variable: significant
+ *
+ * Specifies if the undoable change is significant.
+ * Default is true.
+ */
+mxUndoableEdit.prototype.significant = null;
+
+/**
+ * Variable: undone
+ *
+ * Specifies if this edit has been undone. Default is false.
+ */
+mxUndoableEdit.prototype.undone = false;
+
+/**
+ * Variable: redone
+ *
+ * Specifies if this edit has been redone. Default is false.
+ */
+mxUndoableEdit.prototype.redone = false;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if the this edit contains no changes.
+ */
+mxUndoableEdit.prototype.isEmpty = function () {
+  return this.changes.length == 0;
+};
+
+/**
+ * Function: isSignificant
+ *
+ * Returns .
+ */
+mxUndoableEdit.prototype.isSignificant = function () {
+  return this.significant;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the specified change to this edit. The change is an object that is
+ * expected to either have an undo and redo, or an execute function.
+ */
+mxUndoableEdit.prototype.add = function (change) {
+  this.changes.push(change);
+};
+
+/**
+ * Function: notify
+ *
+ * Hook to notify any listeners of the changes after an  or 
+ * has been carried out. This implementation is empty.
+ */
+mxUndoableEdit.prototype.notify = function () {};
+
+/**
+ * Function: die
+ *
+ * Hook to free resources after the edit has been removed from the command
+ * history. This implementation is empty.
+ */
+mxUndoableEdit.prototype.die = function () {};
+
+/**
+ * Function: undo
+ *
+ * Undoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.undo = function () {
+  if (!this.undone) {
+    this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+    var count = this.changes.length;
+
+    for (var i = count - 1; i >= 0; i--) {
+      var change = this.changes[i];
+
+      if (change.execute != null) {
+        change.execute();
+      } else if (change.undo != null) {
+        change.undo();
+      }
+
+      // New global executed event
+      this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+    }
+
+    this.undone = true;
+    this.redone = false;
+    this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+  }
+
+  this.notify();
+};
+
+/**
+ * Function: redo
+ *
+ * Redoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.redo = function () {
+  if (!this.redone) {
+    this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+    var count = this.changes.length;
+
+    for (var i = 0; i < count; i++) {
+      var change = this.changes[i];
+
+      if (change.execute != null) {
+        change.execute();
+      } else if (change.redo != null) {
+        change.redo();
+      }
+
+      // New global executed event
+      this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+    }
+
+    this.undone = false;
+    this.redone = true;
+    this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+  }
+
+  this.notify();
+};
+
+/**
+ * util/mxUndoManager.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoManager
+ *
+ * Implements a command history. When changing the graph model, an
+ *  object is created at the start of the transaction (when
+ * model.beginUpdate is called). All atomic changes are then added to this
+ * object until the last model.endUpdate call, at which point the
+ *  is dispatched in an event, and added to the history inside
+ * . This is done by an event listener in
+ * .
+ *
+ * Each atomic change of the model is represented by an object (eg.
+ * , ,  etc) which contains the
+ * complete undo information. The  also listens to the
+ *  and stores it's changes to the current root as insignificant
+ * undoable changes, so that drilling (step into, step up) is undone.
+ *
+ * This means when you execute an atomic change on the model, then change the
+ * current root on the view and click undo, the change of the root will be
+ * undone together with the change of the model so that the display represents
+ * the state at which the model was changed. However, these changes are not
+ * transmitted for sharing as they do not represent a state change.
+ *
+ * Example:
+ *
+ * When adding an undo manager to a graph, make sure to add it
+ * to the model and the view as well to maintain a consistent
+ * display across multiple undo/redo steps.
+ *
+ * (code)
+ * var undoManager = new mxUndoManager();
+ * var listener = function(sender, evt)
+ * {
+ *   undoManager.undoableEditHappened(evt.getProperty('edit'));
+ * };
+ * graph.getModel().addListener(mxEvent.UNDO, listener);
+ * graph.getView().addListener(mxEvent.UNDO, listener);
+ * (end)
+ *
+ * The code creates a function that informs the undoManager
+ * of an undoable edit and binds it to the undo event of
+ *  and  using
+ * .
+ *
+ * Event: mxEvent.CLEAR
+ *
+ * Fires after  was invoked. This event has no properties.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires afer a significant edit was undone in . The edit
+ * property contains the  that was undone.
+ *
+ * Event: mxEvent.REDO
+ *
+ * Fires afer a significant edit was redone in . The edit
+ * property contains the  that was redone.
+ *
+ * Event: mxEvent.ADD
+ *
+ * Fires after an undoable edit was added to the history. The edit
+ * property contains the  that was added.
+ *
+ * Constructor: mxUndoManager
+ *
+ * Constructs a new undo manager with the given history size. If no history
+ * size is given, then a default size of 100 steps is used.
+ */
+function mxUndoManager(size) {
+  this.size = size != null ? size : 100;
+  this.clear();
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxUndoManager.prototype = new mxEventSource();
+mxUndoManager.prototype.constructor = mxUndoManager;
+
+/**
+ * Variable: size
+ *
+ * Maximum command history size. 0 means unlimited history. Default is
+ * 100.
+ */
+mxUndoManager.prototype.size = null;
+
+/**
+ * Variable: history
+ *
+ * Array that contains the steps of the command history.
+ */
+mxUndoManager.prototype.history = null;
+
+/**
+ * Variable: indexOfNextAdd
+ *
+ * Index of the element to be added next.
+ */
+mxUndoManager.prototype.indexOfNextAdd = 0;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if the history is empty.
+ */
+mxUndoManager.prototype.isEmpty = function () {
+  return this.history.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the command history.
+ */
+mxUndoManager.prototype.clear = function () {
+  this.history = [];
+  this.indexOfNextAdd = 0;
+  this.fireEvent(new mxEventObject(mxEvent.CLEAR));
+};
+
+/**
+ * Function: canUndo
+ *
+ * Returns true if an undo is possible.
+ */
+mxUndoManager.prototype.canUndo = function () {
+  return this.indexOfNextAdd > 0;
+};
+
+/**
+ * Function: undo
+ *
+ * Undoes the last change.
+ */
+mxUndoManager.prototype.undo = function () {
+  while (this.indexOfNextAdd > 0) {
+    var edit = this.history[--this.indexOfNextAdd];
+    edit.undo();
+
+    if (edit.isSignificant()) {
+      this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+      break;
+    }
+  }
+};
+
+/**
+ * Function: canRedo
+ *
+ * Returns true if a redo is possible.
+ */
+mxUndoManager.prototype.canRedo = function () {
+  return this.indexOfNextAdd < this.history.length;
+};
+
+/**
+ * Function: redo
+ *
+ * Redoes the last change.
+ */
+mxUndoManager.prototype.redo = function () {
+  var n = this.history.length;
+
+  while (this.indexOfNextAdd < n) {
+    var edit = this.history[this.indexOfNextAdd++];
+    edit.redo();
+
+    if (edit.isSignificant()) {
+      this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
+      break;
+    }
+  }
+};
+
+/**
+ * Function: undoableEditHappened
+ *
+ * Method to be called to add new undoable edits to the .
+ */
+mxUndoManager.prototype.undoableEditHappened = function (undoableEdit) {
+  this.trim();
+
+  if (this.size > 0 && this.size == this.history.length) {
+    this.history.shift();
+  }
+
+  this.history.push(undoableEdit);
+  this.indexOfNextAdd = this.history.length;
+  this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
+};
+
+/**
+ * Function: trim
+ *
+ * Removes all pending steps after  from the history,
+ * invoking die on each edit. This is called from .
+ */
+mxUndoManager.prototype.trim = function () {
+  if (this.history.length > this.indexOfNextAdd) {
+    var edits = this.history.splice(this.indexOfNextAdd, this.history.length - this.indexOfNextAdd);
+
+    for (var i = 0; i < edits.length; i++) {
+      edits[i].die();
+    }
+  }
+};
+
+/**
+ * util/mxUrlConverter.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxUrlConverter
+ *
+ * Converts relative and absolute URLs to absolute URLs with protocol and domain.
+ */
+var mxUrlConverter = function () {
+  // Empty constructor
+};
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if the converter is enabled. Default is true.
+ */
+mxUrlConverter.prototype.enabled = true;
+
+/**
+ * Variable: baseUrl
+ *
+ * Specifies the base URL to be used as a prefix for relative URLs.
+ */
+mxUrlConverter.prototype.baseUrl = null;
+
+/**
+ * Variable: baseDomain
+ *
+ * Specifies the base domain to be used as a prefix for absolute URLs.
+ */
+mxUrlConverter.prototype.baseDomain = null;
+
+/**
+ * Function: updateBaseUrl
+ *
+ * Private helper function to update the base URL.
+ */
+mxUrlConverter.prototype.updateBaseUrl = function () {
+  this.baseDomain = location.protocol + '//' + location.host;
+  this.baseUrl = this.baseDomain + location.pathname;
+  var tmp = this.baseUrl.lastIndexOf('/');
+
+  // Strips filename etc
+  if (tmp > 0) {
+    this.baseUrl = this.baseUrl.substring(0, tmp + 1);
+  }
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns .
+ */
+mxUrlConverter.prototype.isEnabled = function () {
+  return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets .
+ */
+mxUrlConverter.prototype.setEnabled = function (value) {
+  this.enabled = value;
+};
+
+/**
+ * Function: getBaseUrl
+ *
+ * Returns .
+ */
+mxUrlConverter.prototype.getBaseUrl = function () {
+  return this.baseUrl;
+};
+
+/**
+ * Function: setBaseUrl
+ *
+ * Sets .
+ */
+mxUrlConverter.prototype.setBaseUrl = function (value) {
+  this.baseUrl = value;
+};
+
+/**
+ * Function: getBaseDomain
+ *
+ * Returns .
+ */
+mxUrlConverter.prototype.getBaseDomain = function () {
+  return this.baseDomain;
+};
+
+/**
+ * Function: setBaseDomain
+ *
+ * Sets .
+ */
+mxUrlConverter.prototype.setBaseDomain = function (value) {
+  this.baseDomain = value;
+};
+
+/**
+ * Function: isRelativeUrl
+ *
+ * Returns true if the given URL is relative.
+ */
+mxUrlConverter.prototype.isRelativeUrl = function (url) {
+  return (
+    url != null &&
+    url.substring(0, 2) != '//' &&
+    url.substring(0, 7) != 'http://' &&
+    url.substring(0, 8) != 'https://' &&
+    url.substring(0, 10) != 'data:image' &&
+    url.substring(0, 7) != 'file://'
+  );
+};
+
+/**
+ * Function: convert
+ *
+ * Converts the given URL to an absolute URL with protol and domain.
+ * Relative URLs are first converted to absolute URLs.
+ */
+mxUrlConverter.prototype.convert = function (url) {
+  if (this.isEnabled() && this.isRelativeUrl(url)) {
+    if (this.getBaseUrl() == null) {
+      this.updateBaseUrl();
+    }
+
+    if (url.charAt(0) == '/') {
+      url = this.getBaseDomain() + url;
+    } else {
+      url = this.getBaseUrl() + url;
+    }
+  }
+
+  return url;
+};
+
+/**
+ * util/mxPanningManager.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPanningManager
+ *
+ * Implements a handler for panning.
+ */
+function mxPanningManager(graph) {
+  this.thread = null;
+  this.active = false;
+  this.tdx = 0;
+  this.tdy = 0;
+  this.t0x = 0;
+  this.t0y = 0;
+  this.dx = 0;
+  this.dy = 0;
+  this.scrollbars = false;
+  this.scrollLeft = 0;
+  this.scrollTop = 0;
+
+  this.mouseListener = {
+    mouseDown: function (sender, me) {},
+    mouseMove: function (sender, me) {},
+    mouseUp: mxUtils.bind(this, function (sender, me) {
+      if (this.active) {
+        this.stop();
+      }
+    }),
+  };
+
+  graph.addMouseListener(this.mouseListener);
+
+  this.mouseUpListener = mxUtils.bind(this, function () {
+    if (this.active) {
+      this.stop();
+    }
+  });
+
+  // Stops scrolling on every mouseup anywhere in the document
+  mxEvent.addListener(document, 'mouseup', this.mouseUpListener);
+
+  var createThread = mxUtils.bind(this, function () {
+    this.scrollbars = mxUtils.hasScrollbars(graph.container);
+    this.scrollLeft = graph.container.scrollLeft;
+    this.scrollTop = graph.container.scrollTop;
+
+    return window.setInterval(
+      mxUtils.bind(this, function () {
+        this.tdx -= this.dx;
+        this.tdy -= this.dy;
+
+        if (this.scrollbars) {
+          var left = -graph.container.scrollLeft - Math.ceil(this.dx);
+          var top = -graph.container.scrollTop - Math.ceil(this.dy);
+          graph.panGraph(left, top);
+          graph.panDx = this.scrollLeft - graph.container.scrollLeft;
+          graph.panDy = this.scrollTop - graph.container.scrollTop;
+          graph.fireEvent(new mxEventObject(mxEvent.PAN));
+          // TODO: Implement graph.autoExtend
+        } else {
+          graph.panGraph(this.getDx(), this.getDy());
+        }
+      }),
+      this.delay
+    );
+  });
+
+  this.isActive = function () {
+    return active;
+  };
+
+  this.getDx = function () {
+    return Math.round(this.tdx);
+  };
+
+  this.getDy = function () {
+    return Math.round(this.tdy);
+  };
+
+  this.start = function () {
+    this.t0x = graph.view.translate.x;
+    this.t0y = graph.view.translate.y;
+    this.active = true;
+  };
+
+  this.panTo = function (x, y, w, h) {
+    if (!this.active) {
+      this.start();
+    }
+
+    this.scrollLeft = graph.container.scrollLeft;
+    this.scrollTop = graph.container.scrollTop;
+
+    w = w != null ? w : 0;
+    h = h != null ? h : 0;
+
+    var c = graph.container;
+    this.dx = x + w - c.scrollLeft - c.clientWidth;
+
+    if (this.dx < 0 && Math.abs(this.dx) < this.border) {
+      this.dx = this.border + this.dx;
+    } else if (this.handleMouseOut) {
+      this.dx = Math.max(this.dx, 0);
+    } else {
+      this.dx = 0;
+    }
+
+    if (this.dx == 0) {
+      this.dx = x - c.scrollLeft;
+
+      if (this.dx > 0 && this.dx < this.border) {
+        this.dx = this.dx - this.border;
+      } else if (this.handleMouseOut) {
+        this.dx = Math.min(0, this.dx);
+      } else {
+        this.dx = 0;
+      }
+    }
+
+    this.dy = y + h - c.scrollTop - c.clientHeight;
+
+    if (this.dy < 0 && Math.abs(this.dy) < this.border) {
+      this.dy = this.border + this.dy;
+    } else if (this.handleMouseOut) {
+      this.dy = Math.max(this.dy, 0);
+    } else {
+      this.dy = 0;
+    }
+
+    if (this.dy == 0) {
+      this.dy = y - c.scrollTop;
+
+      if (this.dy > 0 && this.dy < this.border) {
+        this.dy = this.dy - this.border;
+      } else if (this.handleMouseOut) {
+        this.dy = Math.min(0, this.dy);
+      } else {
+        this.dy = 0;
+      }
+    }
+
+    if (this.dx != 0 || this.dy != 0) {
+      this.dx *= this.damper;
+      this.dy *= this.damper;
+
+      if (this.thread == null) {
+        this.thread = createThread();
+      }
+    } else if (this.thread != null) {
+      window.clearInterval(this.thread);
+      this.thread = null;
+    }
+  };
+
+  this.stop = function () {
+    if (this.active) {
+      this.active = false;
+
+      if (this.thread != null) {
+        window.clearInterval(this.thread);
+        this.thread = null;
+      }
+
+      this.tdx = 0;
+      this.tdy = 0;
+
+      if (!this.scrollbars) {
+        var px = graph.panDx;
+        var py = graph.panDy;
+
+        if (px != 0 || py != 0) {
+          graph.panGraph(0, 0);
+          graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
+        }
+      } else {
+        graph.panDx = 0;
+        graph.panDy = 0;
+        graph.fireEvent(new mxEventObject(mxEvent.PAN));
+      }
+    }
+  };
+
+  this.destroy = function () {
+    graph.removeMouseListener(this.mouseListener);
+    mxEvent.removeListener(document, 'mouseup', this.mouseUpListener);
+  };
+}
+
+/**
+ * Variable: damper
+ *
+ * Damper value for the panning. Default is 1/6.
+ */
+mxPanningManager.prototype.damper = 1 / 6;
+
+/**
+ * Variable: delay
+ *
+ * Delay in milliseconds for the panning. Default is 10.
+ */
+mxPanningManager.prototype.delay = 10;
+
+/**
+ * Variable: handleMouseOut
+ *
+ * Specifies if mouse events outside of the component should be handled. Default is true.
+ */
+mxPanningManager.prototype.handleMouseOut = true;
+
+/**
+ * Variable: border
+ *
+ * Border to handle automatic panning inside the component. Default is 0 (disabled).
+ */
+mxPanningManager.prototype.border = 0;
+
+/**
+ * util/mxPopupMenu.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPopupMenu
+ *
+ * Basic popup menu. To add a vertical scrollbar to a given submenu, the
+ * following code can be used.
+ *
+ * (code)
+ * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;
+ * mxPopupMenu.prototype.showMenu = function()
+ * {
+ *   mxPopupMenuShowMenu.apply(this, arguments);
+ *
+ *   this.div.style.overflowY = 'auto';
+ *   this.div.style.overflowX = 'hidden';
+ *   this.div.style.maxHeight = '160px';
+ * };
+ * (end)
+ *
+ * Constructor: mxPopupMenu
+ *
+ * Constructs a popupmenu.
+ *
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the menu has been shown in .
+ */
+function mxPopupMenu(factoryMethod, root) {
+  this.factoryMethod = factoryMethod;
+
+  if (factoryMethod != null) {
+    this.init(root);
+  }
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxPopupMenu.prototype = new mxEventSource();
+mxPopupMenu.prototype.constructor = mxPopupMenu;
+
+/**
+ * Variable: submenuImage
+ *
+ * URL of the image to be used for the submenu icon.
+ */
+mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';
+
+/**
+ * Variable: zIndex
+ *
+ * Specifies the zIndex for the popupmenu and its shadow. Default is 10006.
+ */
+mxPopupMenu.prototype.zIndex = 10006;
+
+/**
+ * Variable: factoryMethod
+ *
+ * Function that is used to create the popup menu. The function takes the
+ * current panning handler, the  under the mouse and the mouse
+ * event that triggered the call as arguments.
+ */
+mxPopupMenu.prototype.factoryMethod = null;
+
+/**
+ * Variable: useLeftButtonForPopup
+ *
+ * Specifies if popupmenus should be activated by clicking the left mouse
+ * button. Default is false.
+ */
+mxPopupMenu.prototype.useLeftButtonForPopup = false;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxPopupMenu.prototype.enabled = true;
+
+/**
+ * Variable: itemCount
+ *
+ * Contains the number of times  has been called for a new menu.
+ */
+mxPopupMenu.prototype.itemCount = 0;
+
+/**
+ * Variable: autoExpand
+ *
+ * Specifies if submenus should be expanded on mouseover. Default is false.
+ */
+mxPopupMenu.prototype.autoExpand = false;
+
+/**
+ * Variable: smartSeparators
+ *
+ * Specifies if separators should only be added if a menu item follows them.
+ * Default is false.
+ */
+mxPopupMenu.prototype.smartSeparators = false;
+
+/**
+ * Variable: labels
+ *
+ * Specifies if any labels should be visible. Default is true.
+ */
+mxPopupMenu.prototype.labels = true;
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenu.prototype.init = function (root) {
+  this.root = root || document.body;
+  // Adds the inner table
+  this.table = document.createElement('table');
+  this.table.className = 'mxPopupMenu';
+
+  this.tbody = document.createElement('tbody');
+  this.table.appendChild(this.tbody);
+
+  // Adds the outer div
+  this.div = document.createElement('div');
+  this.div.className = 'mxPopupMenu';
+  this.div.style.display = 'inline';
+  this.div.style.zIndex = this.zIndex;
+  this.div.appendChild(this.table);
+
+  // Disables the context menu on the outer div
+  mxEvent.disableContextMenu(this.div);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns .
+ */
+mxPopupMenu.prototype.isEnabled = function () {
+  return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates .
+ */
+mxPopupMenu.prototype.setEnabled = function (enabled) {
+  this.enabled = enabled;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the given event is a popupmenu trigger for the optional
+ * given cell.
+ *
+ * Parameters:
+ *
+ * me -  that represents the mouse event.
+ */
+mxPopupMenu.prototype.isPopupTrigger = function (me) {
+  return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds the given item to the given parent item. If no parent item is specified
+ * then the item is added to the top-level menu. The return value may be used
+ * as the parent argument, ie. as a submenu item. The return value is the table
+ * row that represents the item.
+ *
+ * Paramters:
+ *
+ * title - String that represents the title of the menu item.
+ * image - Optional URL for the image icon.
+ * funct - Function associated that takes a mouseup or touchend event.
+ * parent - Optional item returned by .
+ * iconCls - Optional string that represents the CSS class for the image icon.
+ * IconsCls is ignored if image is given.
+ * enabled - Optional boolean indicating if the item is enabled. Default is true.
+ * active - Optional boolean indicating if the menu should implement any event handling.
+ * Default is true.
+ * noHover - Optional boolean to disable hover state.
+ */
+mxPopupMenu.prototype.addItem = function (title, image, funct, parent, iconCls, enabled, active, noHover) {
+  parent = parent || this;
+  this.itemCount++;
+
+  // Smart separators only added if element contains items
+  if (parent.willAddSeparator) {
+    if (parent.containsItems) {
+      this.addSeparator(parent, true);
+    }
+
+    parent.willAddSeparator = false;
+  }
+
+  parent.containsItems = true;
+  var tr = document.createElement('tr');
+  tr.className = 'mxPopupMenuItem';
+  var col1 = document.createElement('td');
+  col1.className = 'mxPopupMenuIcon';
+
+  // Adds the given image into the first column
+  if (image != null) {
+    var img = document.createElement('img');
+    img.src = image;
+    col1.appendChild(img);
+  } else if (iconCls != null) {
+    var div = document.createElement('div');
+    div.className = iconCls;
+    col1.appendChild(div);
+  }
+
+  tr.appendChild(col1);
+
+  if (this.labels) {
+    var col2 = document.createElement('td');
+    col2.className = 'mxPopupMenuItem' + (enabled != null && !enabled ? ' mxDisabled' : '');
+
+    mxUtils.write(col2, title);
+    col2.align = 'left';
+    tr.appendChild(col2);
+
+    var col3 = document.createElement('td');
+    col3.className = 'mxPopupMenuItem' + (enabled != null && !enabled ? ' mxDisabled' : '');
+    col3.style.paddingRight = '6px';
+    col3.style.textAlign = 'right';
+
+    tr.appendChild(col3);
+
+    if (parent.div == null) {
+      this.createSubmenu(parent);
+    }
+  }
+
+  parent.tbody.appendChild(tr);
+
+  if (active != false && enabled != false) {
+    var currentSelection = null;
+
+    mxEvent.addGestureListeners(
+      tr,
+      mxUtils.bind(this, function (evt) {
+        this.eventReceiver = tr;
+
+        if (parent.activeRow != tr && parent.activeRow != parent) {
+          if (parent.activeRow != null && parent.activeRow.div.parentNode != null) {
+            this.hideSubmenu(parent);
+          }
+
+          if (tr.div != null) {
+            this.showSubmenu(parent, tr);
+            parent.activeRow = tr;
+          }
+        }
+
+        // Workaround for lost current selection in page because of focus in IE
+        if (document.selection != null && (mxClient.IS_QUIRKS || document.documentMode == 8)) {
+          currentSelection = document.selection.createRange();
+        }
+
+        mxEvent.consume(evt);
+      }),
+      mxUtils.bind(this, function (evt) {
+        if (parent.activeRow != tr && parent.activeRow != parent) {
+          if (parent.activeRow != null && parent.activeRow.div.parentNode != null) {
+            this.hideSubmenu(parent);
+          }
+
+          if (this.autoExpand && tr.div != null) {
+            this.showSubmenu(parent, tr);
+            parent.activeRow = tr;
+          }
+        }
+
+        // Sets hover style because TR in IE doesn't have hover
+        if (!noHover) {
+          tr.className = 'mxPopupMenuItemHover';
+        }
+      }),
+      mxUtils.bind(this, function (evt) {
+        // EventReceiver avoids clicks on a submenu item
+        // which has just been shown in the mousedown
+        if (this.eventReceiver == tr) {
+          if (parent.activeRow != tr) {
+            this.hideMenu();
+          }
+
+          // Workaround for lost current selection in page because of focus in IE
+          if (currentSelection != null) {
+            // Workaround for "unspecified error" in IE8 standards
+            try {
+              currentSelection.select();
+            } catch (e) {
+              // ignore
+            }
+
+            currentSelection = null;
+          }
+
+          if (funct != null) {
+            funct(evt);
+          }
+        }
+
+        this.eventReceiver = null;
+        mxEvent.consume(evt);
+      })
+    );
+
+    // Resets hover style because TR in IE doesn't have hover
+    if (!noHover) {
+      mxEvent.addListener(
+        tr,
+        'mouseout',
+        mxUtils.bind(this, function (evt) {
+          tr.className = 'mxPopupMenuItem';
+        })
+      );
+    }
+  }
+
+  return tr;
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+mxPopupMenu.prototype.addCheckmark = function (item, img) {
+  var td = item.firstChild.nextSibling;
+  td.style.backgroundImage = "url('" + img + "')";
+  td.style.backgroundRepeat = 'no-repeat';
+  td.style.backgroundPosition = '2px 50%';
+};
+
+/**
+ * Function: createSubmenu
+ *
+ * Creates the nodes required to add submenu items inside the given parent
+ * item. This is called in  if a parent item is used for the first
+ * time. This adds various DOM nodes and a  to the parent.
+ *
+ * Parameters:
+ *
+ * parent - An item returned by .
+ */
+mxPopupMenu.prototype.createSubmenu = function (parent) {
+  parent.table = document.createElement('table');
+  parent.table.className = 'mxPopupMenu';
+
+  parent.tbody = document.createElement('tbody');
+  parent.table.appendChild(parent.tbody);
+
+  parent.div = document.createElement('div');
+  parent.div.className = 'mxPopupMenu';
+
+  parent.div.style.position = 'absolute';
+  parent.div.style.display = 'inline';
+  parent.div.style.zIndex = this.zIndex;
+
+  parent.div.appendChild(parent.table);
+
+  var img = document.createElement('img');
+  img.setAttribute('src', this.submenuImage);
+
+  // Last column of the submenu item in the parent menu
+  td = parent.firstChild.nextSibling.nextSibling;
+  td.appendChild(img);
+};
+
+/**
+ * Function: showSubmenu
+ *
+ * Shows the submenu inside the given parent row.
+ */
+mxPopupMenu.prototype.showSubmenu = function (parent, row) {
+  if (row.div != null) {
+    row.div.style.left = parent.div.offsetLeft + row.offsetLeft + row.offsetWidth - 1 + 'px';
+    row.div.style.top = parent.div.offsetTop + row.offsetTop + 'px';
+    document.body.appendChild(row.div);
+
+    // Moves the submenu to the left side if there is no space
+    var left = parseInt(row.div.offsetLeft);
+    var width = parseInt(row.div.offsetWidth);
+    var offset = mxUtils.getDocumentScrollOrigin(document);
+
+    var b = document.body;
+    var d = document.documentElement;
+
+    var right = offset.x + (b.clientWidth || d.clientWidth);
+
+    if (left + width > right) {
+      row.div.style.left = Math.max(0, parent.div.offsetLeft - width + (mxClient.IS_IE ? 6 : -6)) + 'px';
+    }
+
+    mxUtils.fit(row.div);
+  }
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a horizontal separator in the given parent item or the top-level menu
+ * if no parent is specified.
+ *
+ * Parameters:
+ *
+ * parent - Optional item returned by .
+ * force - Optional boolean to ignore . Default is false.
+ */
+mxPopupMenu.prototype.addSeparator = function (parent, force) {
+  parent = parent || this;
+
+  if (this.smartSeparators && !force) {
+    parent.willAddSeparator = true;
+  } else if (parent.tbody != null) {
+    parent.willAddSeparator = false;
+    var tr = document.createElement('tr');
+
+    var col1 = document.createElement('td');
+    col1.className = 'mxPopupMenuIcon';
+    col1.style.padding = '0 0 0 0px';
+
+    tr.appendChild(col1);
+
+    var col2 = document.createElement('td');
+    col2.style.padding = '0 0 0 0px';
+    col2.setAttribute('colSpan', '2');
+
+    var hr = document.createElement('hr');
+    hr.setAttribute('size', '1');
+    col2.appendChild(hr);
+
+    tr.appendChild(col2);
+
+    parent.tbody.appendChild(tr);
+  }
+};
+
+/**
+ * Function: popup
+ *
+ * Shows the popup menu for the given event and cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.panningHandler.popup = function(x, y, cell, evt)
+ * {
+ *   mxUtils.alert('Hello, World!');
+ * }
+ * (end)
+ */
+mxPopupMenu.prototype.popup = function (x, y, cell, evt) {
+  if (this.div != null && this.tbody != null && this.factoryMethod != null) {
+    this.div.style.left = x + 'px';
+    this.div.style.top = y + 'px';
+
+    // Removes all child nodes from the existing menu
+    while (this.tbody.firstChild != null) {
+      mxEvent.release(this.tbody.firstChild);
+      this.tbody.removeChild(this.tbody.firstChild);
+    }
+
+    this.itemCount = 0;
+    this.factoryMethod(this, cell, evt);
+
+    if (this.itemCount > 0) {
+      this.showMenu();
+      this.fireEvent(new mxEventObject(mxEvent.SHOW));
+    }
+  }
+};
+
+/**
+ * Function: isMenuShowing
+ *
+ * Returns true if the menu is showing.
+ */
+mxPopupMenu.prototype.isMenuShowing = function () {
+  return this.div != null && this.div.parentNode == document.body;
+};
+
+/**
+ * Function: showMenu
+ *
+ * Shows the menu.
+ */
+mxPopupMenu.prototype.showMenu = function () {
+  // Disables filter-based shadow in IE9 standards mode
+  if (document.documentMode >= 9) {
+    this.div.style.filter = 'none';
+  }
+
+  // Fits the div inside the viewport
+  (this.root || document.body).appendChild(this.div);
+  // mxUtils.fit(this.div);
+};
+
+/**
+ * Function: hideMenu
+ *
+ * Removes the menu and all submenus.
+ */
+mxPopupMenu.prototype.hideMenu = function () {
+  if (this.div != null) {
+    if (this.div.parentNode != null) {
+      this.div.parentNode.removeChild(this.div);
+    }
+
+    this.hideSubmenu(this);
+    this.containsItems = false;
+    this.fireEvent(new mxEventObject(mxEvent.HIDE));
+  }
+};
+
+/**
+ * Function: hideSubmenu
+ *
+ * Removes all submenus inside the given parent.
+ *
+ * Parameters:
+ *
+ * parent - An item returned by .
+ */
+mxPopupMenu.prototype.hideSubmenu = function (parent) {
+  if (parent.activeRow != null) {
+    this.hideSubmenu(parent.activeRow);
+
+    if (parent.activeRow.div.parentNode != null) {
+      parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
+    }
+
+    parent.activeRow = null;
+  }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenu.prototype.destroy = function () {
+  if (this.div != null) {
+    mxEvent.release(this.div);
+
+    if (this.div.parentNode != null) {
+      this.div.parentNode.removeChild(this.div);
+    }
+
+    this.div = null;
+  }
+};
+
+/**
+ * util/mxAutoSaveManager.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAutoSaveManager
+ *
+ * Manager for automatically saving diagrams. The  hook must be
+ * implemented.
+ *
+ * Example:
+ *
+ * (code)
+ * var mgr = new mxAutoSaveManager(editor.graph);
+ * mgr.save = function()
+ * {
+ *   mxLog.show();
+ *   mxLog.debug('save');
+ * };
+ * (end)
+ *
+ * Constructor: mxAutoSaveManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxAutoSaveManager(graph) {
+  // Notifies the manager of a change
+  this.changeHandler = mxUtils.bind(this, function (sender, evt) {
+    if (this.isEnabled()) {
+      this.graphModelChanged(evt.getProperty('edit').changes);
+    }
+  });
+
+  this.setGraph(graph);
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxAutoSaveManager.prototype = new mxEventSource();
+mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing .
+ */
+mxAutoSaveManager.prototype.graph = null;
+
+/**
+ * Variable: autoSaveDelay
+ *
+ * Minimum amount of seconds between two consecutive autosaves. Eg. a
+ * value of 1 (s) means the graph is not stored more than once per second.
+ * Default is 10.
+ */
+mxAutoSaveManager.prototype.autoSaveDelay = 10;
+
+/**
+ * Variable: autoSaveThrottle
+ *
+ * Minimum amount of seconds between two consecutive autosaves triggered by
+ * more than  changes within a timespan of less than
+ *  seconds. Eg. a value of 1 (s) means the graph is not
+ * stored more than once per second even if there are more than
+ *  changes within that timespan. Default is 2.
+ */
+mxAutoSaveManager.prototype.autoSaveThrottle = 2;
+
+/**
+ * Variable: autoSaveThreshold
+ *
+ * Minimum amount of ignored changes before an autosave. Eg. a value of 2
+ * means after 2 change of the graph model the autosave will trigger if the
+ * condition below is true. Default is 5.
+ */
+mxAutoSaveManager.prototype.autoSaveThreshold = 5;
+
+/**
+ * Variable: ignoredChanges
+ *
+ * Counter for ignored changes in autosave.
+ */
+mxAutoSaveManager.prototype.ignoredChanges = 0;
+
+/**
+ * Variable: lastSnapshot
+ *
+ * Used for autosaving. See .
+ */
+mxAutoSaveManager.prototype.lastSnapshot = 0;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxAutoSaveManager.prototype.enabled = true;
+
+/**
+ * Variable: changeHandler
+ *
+ * Holds the function that handles graph model changes.
+ */
+mxAutoSaveManager.prototype.changeHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns .
+ */
+mxAutoSaveManager.prototype.isEnabled = function () {
+  return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates .
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxAutoSaveManager.prototype.setEnabled = function (value) {
+  this.enabled = value;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxAutoSaveManager.prototype.setGraph = function (graph) {
+  if (this.graph != null) {
+    this.graph.getModel().removeListener(this.changeHandler);
+  }
+
+  this.graph = graph;
+
+  if (this.graph != null) {
+    this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+  }
+};
+
+/**
+ * Function: save
+ *
+ * Empty hook that is called if the graph should be saved.
+ */
+mxAutoSaveManager.prototype.save = function () {
+  // empty
+};
+
+/**
+ * Function: graphModelChanged
+ *
+ * Invoked when the graph model has changed.
+ */
+mxAutoSaveManager.prototype.graphModelChanged = function (changes) {
+  var now = new Date().getTime();
+  var dt = (now - this.lastSnapshot) / 1000;
+
+  if (dt > this.autoSaveDelay || (this.ignoredChanges >= this.autoSaveThreshold && dt > this.autoSaveThrottle)) {
+    this.save();
+    this.reset();
+  } else {
+    // Increments the number of ignored changes
+    this.ignoredChanges++;
+  }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets all counters.
+ */
+mxAutoSaveManager.prototype.reset = function () {
+  this.lastSnapshot = new Date().getTime();
+  this.ignoredChanges = 0;
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the  and deletes the reference to it.
+ */
+mxAutoSaveManager.prototype.destroy = function () {
+  this.setGraph(null);
+};
+
+/**
+ * util/mxAnimation.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxAnimation
+ *
+ * Implements a basic animation in JavaScript.
+ *
+ * Constructor: mxAnimation
+ *
+ * Constructs an animation.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing .
+ */
+function mxAnimation(delay) {
+  this.delay = delay != null ? delay : 20;
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxAnimation.prototype = new mxEventSource();
+mxAnimation.prototype.constructor = mxAnimation;
+
+/**
+ * Variable: delay
+ *
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxAnimation.prototype.delay = null;
+
+/**
+ * Variable: thread
+ *
+ * Reference to the thread while the animation is running.
+ */
+mxAnimation.prototype.thread = null;
+
+/**
+ * Function: isRunning
+ *
+ * Returns true if the animation is running.
+ */
+mxAnimation.prototype.isRunning = function () {
+  return this.thread != null;
+};
+
+/**
+ * Function: startAnimation
+ *
+ * Starts the animation by repeatedly invoking updateAnimation.
+ */
+mxAnimation.prototype.startAnimation = function () {
+  if (this.thread == null) {
+    this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
+  }
+};
+
+/**
+ * Function: updateAnimation
+ *
+ * Hook for subclassers to implement the animation. Invoke stopAnimation
+ * when finished, startAnimation to resume. This is called whenever the
+ * timer fires and fires an mxEvent.EXECUTE event with no properties.
+ */
+mxAnimation.prototype.updateAnimation = function () {
+  this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
+};
+
+/**
+ * Function: stopAnimation
+ *
+ * Stops the animation by deleting the timer and fires an .
+ */
+mxAnimation.prototype.stopAnimation = function () {
+  if (this.thread != null) {
+    window.clearInterval(this.thread);
+    this.thread = null;
+    this.fireEvent(new mxEventObject(mxEvent.DONE));
+  }
+};
+
+/**
+ * util/mxMorphing.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxMorphing
+ *
+ * Implements animation for morphing cells. Here is an example of
+ * using this class for animating the result of a layout algorithm:
+ *
+ * (code)
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ *   var circleLayout = new mxCircleLayout(graph);
+ *   circleLayout.execute(graph.getDefaultParent());
+ * }
+ * finally
+ * {
+ *   var morph = new mxMorphing(graph);
+ *   morph.addListener(mxEvent.DONE, function()
+ *   {
+ *     graph.getModel().endUpdate();
+ *   });
+ *
+ *   morph.startAnimation();
+ * }
+ * (end)
+ *
+ * Constructor: mxMorphing
+ *
+ * Constructs an animation.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing .
+ * steps - Optional number of steps in the morphing animation. Default is 6.
+ * ease - Optional easing constant for the animation. Default is 1.5.
+ * delay - Optional delay between the animation steps. Passed to .
+ */
+function mxMorphing(graph, steps, ease, delay) {
+  mxAnimation.call(this, delay);
+  this.graph = graph;
+  this.steps = steps != null ? steps : 6;
+  this.ease = ease != null ? ease : 1.5;
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxMorphing.prototype = new mxAnimation();
+mxMorphing.prototype.constructor = mxMorphing;
+
+/**
+ * Variable: graph
+ *
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxMorphing.prototype.graph = null;
+
+/**
+ * Variable: steps
+ *
+ * Specifies the maximum number of steps for the morphing.
+ */
+mxMorphing.prototype.steps = null;
+
+/**
+ * Variable: step
+ *
+ * Contains the current step.
+ */
+mxMorphing.prototype.step = 0;
+
+/**
+ * Variable: ease
+ *
+ * Ease-off for movement towards the given vector. Larger values are
+ * slower and smoother. Default is 4.
+ */
+mxMorphing.prototype.ease = null;
+
+/**
+ * Variable: cells
+ *
+ * Optional array of cells to be animated. If this is not specified
+ * then all cells are checked and animated if they have been moved
+ * in the current transaction.
+ */
+mxMorphing.prototype.cells = null;
+
+/**
+ * Function: updateAnimation
+ *
+ * Animation step.
+ */
+mxMorphing.prototype.updateAnimation = function () {
+  mxAnimation.prototype.updateAnimation.apply(this, arguments);
+  var move = new mxCellStatePreview(this.graph);
+
+  if (this.cells != null) {
+    // Animates the given cells individually without recursion
+    for (var i = 0; i < this.cells.length; i++) {
+      this.animateCell(this.cells[i], move, false);
+    }
+  } else {
+    // Animates all changed cells by using recursion to find
+    // the changed cells but not for the animation itself
+    this.animateCell(this.graph.getModel().getRoot(), move, true);
+  }
+
+  this.show(move);
+
+  if (move.isEmpty() || this.step++ >= this.steps) {
+    this.stopAnimation();
+  }
+};
+
+/**
+ * Function: show
+ *
+ * Shows the changes in the given .
+ */
+mxMorphing.prototype.show = function (move) {
+  move.show();
+};
+
+/**
+ * Function: animateCell
+ *
+ * Animates the given cell state using .
+ */
+mxMorphing.prototype.animateCell = function (cell, move, recurse) {
+  var state = this.graph.getView().getState(cell);
+  var delta = null;
+
+  if (state != null) {
+    // Moves the animated state from where it will be after the model
+    // change by subtracting the given delta vector from that location
+    delta = this.getDelta(state);
+
+    if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0)) {
+      var translate = this.graph.view.getTranslate();
+      var scale = this.graph.view.getScale();
+
+      delta.x += translate.x * scale;
+      delta.y += translate.y * scale;
+
+      move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
+    }
+  }
+
+  if (recurse && !this.stopRecursion(state, delta)) {
+    var childCount = this.graph.getModel().getChildCount(cell);
+
+    for (var i = 0; i < childCount; i++) {
+      this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
+    }
+  }
+};
+
+/**
+ * Function: stopRecursion
+ *
+ * Returns true if the animation should not recursively find more
+ * deltas for children if the given parent state has been animated.
+ */
+mxMorphing.prototype.stopRecursion = function (state, delta) {
+  return delta != null && (delta.x != 0 || delta.y != 0);
+};
+
+/**
+ * Function: getDelta
+ *
+ * Returns the vector between the current rendered state and the future
+ * location of the state after the display will be updated.
+ */
+mxMorphing.prototype.getDelta = function (state) {
+  var origin = this.getOriginForCell(state.cell);
+  var translate = this.graph.getView().getTranslate();
+  var scale = this.graph.getView().getScale();
+  var x = state.x / scale - translate.x;
+  var y = state.y / scale - translate.y;
+
+  return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale);
+};
+
+/**
+ * Function: getOriginForCell
+ *
+ * Returns the top, left corner of the given cell. TODO: Improve performance
+ * by using caching inside this method as the result per cell never changes
+ * during the lifecycle of this object.
+ */
+mxMorphing.prototype.getOriginForCell = function (cell) {
+  var result = null;
+
+  if (cell != null) {
+    var parent = this.graph.getModel().getParent(cell);
+    var geo = this.graph.getCellGeometry(cell);
+    result = this.getOriginForCell(parent);
+
+    // TODO: Handle offsets
+    if (geo != null) {
+      if (geo.relative) {
+        var pgeo = this.graph.getCellGeometry(parent);
+
+        if (pgeo != null) {
+          result.x += geo.x * pgeo.width;
+          result.y += geo.y * pgeo.height;
+        }
+      } else {
+        result.x += geo.x;
+        result.y += geo.y;
+      }
+    }
+  }
+
+  if (result == null) {
+    var t = this.graph.view.getTranslate();
+    result = new mxPoint(-t.x, -t.y);
+  }
+
+  return result;
+};
+
+/**
+ * util/mxImageBundle.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageBundle
+ *
+ * Maps from keys to base64 encoded images or file locations. All values must
+ * be URLs or use the format data:image/format followed by a comma and the base64
+ * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
+ * image data.
+ *
+ * To add a new image bundle to an existing graph, the following code is used:
+ *
+ * (code)
+ * var bundle = new mxImageBundle(alt);
+ * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
+ *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
+ *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
+ *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
+ * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent(
+ *   '' +
+ *   '' +
+ *   '' +
+ *   ''), fallback);
+ * graph.addImageBundle(bundle);
+ * (end);
+ *
+ * Alt is an optional boolean (default is false) that specifies if the value
+ * or the fallback should be returned in .
+ *
+ * The image can then be referenced in any cell style using image=myImage.
+ * If you are using mxOutline, you should use the same image bundles in the
+ * graph that renders the outline.
+ *
+ * The keys for images are resolved in  and
+ * turned into a data URI if the returned value has a short data URI format
+ * as specified above.
+ *
+ * A typical value for the fallback is a MTHML link as defined in RFC 2557.
+ * Note that this format requires a file to be dynamically created on the
+ * server-side, or the page that contains the graph to be modified to contain
+ * the resources, this can be done by adding a comment that contains the
+ * resource in the HEAD section of the page after the title tag.
+ *
+ * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
+ * support data URIs, but the maximum size is limited to 32 KB, which means
+ * all data URIs should be limited to 32 KB.
+ */
+function mxImageBundle(alt) {
+  this.images = [];
+  this.alt = alt != null ? alt : false;
+}
+
+/**
+ * Variable: images
+ *
+ * Maps from keys to images.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Variable: alt
+ *
+ * Specifies if the fallback representation should be returned.
+ */
+mxImageBundle.prototype.alt = null;
+
+/**
+ * Function: putImage
+ *
+ * Adds the specified entry to the map. The entry is an object with a value and
+ * fallback property as specified in the arguments.
+ */
+mxImageBundle.prototype.putImage = function (key, value, fallback) {
+  this.images[key] = { value: value, fallback: fallback };
+};
+
+/**
+ * Function: getImage
+ *
+ * Returns the value for the given key. This returns the value
+ * or fallback, depending on . The fallback is returned if
+ *  is true, the value is returned otherwise.
+ */
+mxImageBundle.prototype.getImage = function (key) {
+  var result = null;
+
+  if (key != null) {
+    var img = this.images[key];
+
+    if (img != null) {
+      result = this.alt ? img.fallback : img.value;
+    }
+  }
+
+  return result;
+};
+
+/**
+ * util/mxImageExport.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageExport
+ *
+ * Creates a new image export instance to be used with an export canvas. Here
+ * is an example that uses this class to create an image via a backend using
+ * .
+ *
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ *
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * var imgExport = new mxImageExport();
+ * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+ *
+ * var bounds = graph.getGraphBounds();
+ * var w = Math.ceil(bounds.x + bounds.width);
+ * var h = Math.ceil(bounds.y + bounds.height);
+ *
+ * var xml = mxUtils.getXml(root);
+ * new mxXmlRequest('export', 'format=png&w=' + w +
+ * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
+ * 		.simulate(document, '_blank');
+ * (end)
+ *
+ * Constructor: mxImageExport
+ *
+ * Constructs a new image export.
+ */
+function mxImageExport() {}
+
+/**
+ * Variable: includeOverlays
+ *
+ * Specifies if overlays should be included in the export. Default is false.
+ */
+mxImageExport.prototype.includeOverlays = false;
+
+/**
+ * Function: drawState
+ *
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.drawState = function (state, canvas) {
+  if (state != null) {
+    this.visitStatesRecursive(
+      state,
+      canvas,
+      mxUtils.bind(this, function () {
+        this.drawCellState.apply(this, arguments);
+      })
+    );
+
+    // Paints the overlays
+    if (this.includeOverlays) {
+      this.visitStatesRecursive(
+        state,
+        canvas,
+        mxUtils.bind(this, function () {
+          this.drawOverlays.apply(this, arguments);
+        })
+      );
+    }
+  }
+};
+
+/**
+ * Function: visitStatesRecursive
+ *
+ * Visits the given state and all its descendants to the given canvas recursively.
+ */
+mxImageExport.prototype.visitStatesRecursive = function (state, canvas, visitor) {
+  if (state != null) {
+    visitor(state, canvas);
+
+    var graph = state.view.graph;
+    var childCount = graph.model.getChildCount(state.cell);
+
+    for (var i = 0; i < childCount; i++) {
+      var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
+      this.visitStatesRecursive(childState, canvas, visitor);
+    }
+  }
+};
+
+/**
+ * Function: getLinkForCellState
+ *
+ * Returns the link for the given cell state and canvas. This returns null.
+ */
+mxImageExport.prototype.getLinkForCellState = function (state, canvas) {
+  return null;
+};
+
+/**
+ * Function: drawCellState
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawCellState = function (state, canvas) {
+  // Experimental feature
+  var link = this.getLinkForCellState(state, canvas);
+
+  if (link != null) {
+    canvas.setLink(link);
+  }
+
+  // Paints the shape and text
+  this.drawShape(state, canvas);
+  this.drawText(state, canvas);
+
+  if (link != null) {
+    canvas.setLink(null);
+  }
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws the shape of the given state.
+ */
+mxImageExport.prototype.drawShape = function (state, canvas) {
+  if (state.shape instanceof mxShape && state.shape.checkBounds()) {
+    canvas.save();
+
+    state.shape.beforePaint(canvas);
+    state.shape.paint(canvas);
+    state.shape.afterPaint(canvas);
+
+    canvas.restore();
+  }
+};
+
+/**
+ * Function: drawText
+ *
+ * Draws the text of the given state.
+ */
+mxImageExport.prototype.drawText = function (state, canvas) {
+  if (state.text != null && state.text.checkBounds()) {
+    canvas.save();
+
+    state.text.beforePaint(canvas);
+    state.text.paint(canvas);
+    state.text.afterPaint(canvas);
+
+    canvas.restore();
+  }
+};
+
+/**
+ * Function: drawOverlays
+ *
+ * Draws the overlays for the given state. This is called if 
+ * is true.
+ */
+mxImageExport.prototype.drawOverlays = function (state, canvas) {
+  if (state.overlays != null) {
+    state.overlays.visit(function (id, shape) {
+      if (shape instanceof mxShape) {
+        shape.paint(canvas);
+      }
+    });
+  }
+};
+
+/**
+ * util/mxAbstractCanvas2D.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAbstractCanvas2D
+ *
+ * Base class for all canvases. A description of the public API is available in .
+ * All color values of  will be converted to null in the state.
+ *
+ * Constructor: mxAbstractCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxAbstractCanvas2D() {
+  /**
+   * Variable: converter
+   *
+   * Holds the  to convert image URLs.
+   */
+  this.converter = this.createUrlConverter();
+
+  this.reset();
+}
+
+/**
+ * Variable: state
+ *
+ * Holds the current state.
+ */
+mxAbstractCanvas2D.prototype.state = null;
+
+/**
+ * Variable: states
+ *
+ * Stack of states.
+ */
+mxAbstractCanvas2D.prototype.states = null;
+
+/**
+ * Variable: path
+ *
+ * Holds the current path as an array.
+ */
+mxAbstractCanvas2D.prototype.path = null;
+
+/**
+ * Variable: rotateHtml
+ *
+ * Switch for rotation of HTML. Default is false.
+ */
+mxAbstractCanvas2D.prototype.rotateHtml = true;
+
+/**
+ * Variable: lastX
+ *
+ * Holds the last x coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastX = 0;
+
+/**
+ * Variable: lastY
+ *
+ * Holds the last y coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastY = 0;
+
+/**
+ * Variable: moveOp
+ *
+ * Contains the string used for moving in paths. Default is 'M'.
+ */
+mxAbstractCanvas2D.prototype.moveOp = 'M';
+
+/**
+ * Variable: lineOp
+ *
+ * Contains the string used for moving in paths. Default is 'L'.
+ */
+mxAbstractCanvas2D.prototype.lineOp = 'L';
+
+/**
+ * Variable: quadOp
+ *
+ * Contains the string used for quadratic paths. Default is 'Q'.
+ */
+mxAbstractCanvas2D.prototype.quadOp = 'Q';
+
+/**
+ * Variable: curveOp
+ *
+ * Contains the string used for bezier curves. Default is 'C'.
+ */
+mxAbstractCanvas2D.prototype.curveOp = 'C';
+
+/**
+ * Variable: closeOp
+ *
+ * Holds the operator for closing curves. Default is 'Z'.
+ */
+mxAbstractCanvas2D.prototype.closeOp = 'Z';
+
+/**
+ * Variable: pointerEvents
+ *
+ * Boolean value that specifies if events should be handled. Default is false.
+ */
+mxAbstractCanvas2D.prototype.pointerEvents = false;
+
+/**
+ * Function: createUrlConverter
+ *
+ * Create a new  and returns it.
+ */
+mxAbstractCanvas2D.prototype.createUrlConverter = function () {
+  return new mxUrlConverter();
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this canvas.
+ */
+mxAbstractCanvas2D.prototype.reset = function () {
+  this.state = this.createState();
+  this.states = [];
+};
+
+/**
+ * Function: createState
+ *
+ * Creates the state of the this canvas.
+ */
+mxAbstractCanvas2D.prototype.createState = function () {
+  return {
+    dx: 0,
+    dy: 0,
+    scale: 1,
+    alpha: 1,
+    fillAlpha: 1,
+    strokeAlpha: 1,
+    fillColor: null,
+    gradientFillAlpha: 1,
+    gradientColor: null,
+    gradientAlpha: 1,
+    gradientDirection: null,
+    strokeColor: null,
+    strokeWidth: 1,
+    dashed: false,
+    dashPattern: '3 3',
+    fixDash: false,
+    lineCap: 'flat',
+    lineJoin: 'miter',
+    miterLimit: 10,
+    fontColor: '#000000',
+    fontBackgroundColor: null,
+    fontBorderColor: null,
+    fontSize: mxConstants.DEFAULT_FONTSIZE,
+    fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+    fontStyle: 0,
+    shadow: false,
+    shadowColor: mxConstants.SHADOWCOLOR,
+    shadowAlpha: mxConstants.SHADOW_OPACITY,
+    shadowDx: mxConstants.SHADOW_OFFSET_X,
+    shadowDy: mxConstants.SHADOW_OFFSET_Y,
+    rotation: 0,
+    rotationCx: 0,
+    rotationCy: 0,
+  };
+};
+
+/**
+ * Function: format
+ *
+ * Rounds all numbers to integers.
+ */
+mxAbstractCanvas2D.prototype.format = function (value) {
+  return Math.round(parseFloat(value));
+};
+
+/**
+ * Function: addOp
+ *
+ * Adds the given operation to the path.
+ */
+mxAbstractCanvas2D.prototype.addOp = function () {
+  if (this.path != null) {
+    this.path.push(arguments[0]);
+
+    if (arguments.length > 2) {
+      var s = this.state;
+
+      for (var i = 2; i < arguments.length; i += 2) {
+        this.lastX = arguments[i - 1];
+        this.lastY = arguments[i];
+
+        this.path.push(this.format((this.lastX + s.dx) * s.scale));
+        this.path.push(this.format((this.lastY + s.dy) * s.scale));
+      }
+    }
+  }
+};
+
+/**
+ * Function: rotatePoint
+ *
+ * Rotates the given point and returns the result as an .
+ */
+mxAbstractCanvas2D.prototype.rotatePoint = function (x, y, theta, cx, cy) {
+  var rad = theta * (Math.PI / 180);
+
+  return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad), Math.sin(rad), new mxPoint(cx, cy));
+};
+
+/**
+ * Function: save
+ *
+ * Saves the current state.
+ */
+mxAbstractCanvas2D.prototype.save = function () {
+  this.states.push(this.state);
+  this.state = mxUtils.clone(this.state);
+};
+
+/**
+ * Function: restore
+ *
+ * Restores the current state.
+ */
+mxAbstractCanvas2D.prototype.restore = function () {
+  if (this.states.length > 0) {
+    this.state = this.states.pop();
+  }
+};
+
+/**
+ * Function: setLink
+ *
+ * Sets the current link. Hook for subclassers.
+ */
+mxAbstractCanvas2D.prototype.setLink = function (link) {
+  // nop
+};
+
+/**
+ * Function: scale
+ *
+ * Scales the current state.
+ */
+mxAbstractCanvas2D.prototype.scale = function (value) {
+  this.state.scale *= value;
+  this.state.strokeWidth *= value;
+};
+
+/**
+ * Function: translate
+ *
+ * Translates the current state.
+ */
+mxAbstractCanvas2D.prototype.translate = function (dx, dy) {
+  this.state.dx += dx;
+  this.state.dy += dy;
+};
+
+/**
+ * Function: rotate
+ *
+ * Rotates the current state.
+ */
+mxAbstractCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
+  // nop
+};
+
+/**
+ * Function: setAlpha
+ *
+ * Sets the current alpha.
+ */
+mxAbstractCanvas2D.prototype.setAlpha = function (value) {
+  this.state.alpha = value;
+};
+
+/**
+ * Function: setFillAlpha
+ *
+ * Sets the current solid fill alpha.
+ */
+mxAbstractCanvas2D.prototype.setFillAlpha = function (value) {
+  this.state.fillAlpha = value;
+};
+
+/**
+ * Function: setStrokeAlpha
+ *
+ * Sets the current stroke alpha.
+ */
+mxAbstractCanvas2D.prototype.setStrokeAlpha = function (value) {
+  this.state.strokeAlpha = value;
+};
+
+/**
+ * Function: setFillColor
+ *
+ * Sets the current fill color.
+ */
+mxAbstractCanvas2D.prototype.setFillColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  this.state.fillColor = value;
+  this.state.gradientColor = null;
+};
+
+/**
+ * Function: setGradient
+ *
+ * Sets the current gradient.
+ */
+mxAbstractCanvas2D.prototype.setGradient = function (color1, color2, x, y, w, h, direction, alpha1, alpha2) {
+  var s = this.state;
+  s.fillColor = color1;
+  s.gradientFillAlpha = alpha1 != null ? alpha1 : 1;
+  s.gradientColor = color2;
+  s.gradientAlpha = alpha2 != null ? alpha2 : 1;
+  s.gradientDirection = direction;
+};
+
+/**
+ * Function: setStrokeColor
+ *
+ * Sets the current stroke color.
+ */
+mxAbstractCanvas2D.prototype.setStrokeColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  this.state.strokeColor = value;
+};
+
+/**
+ * Function: setStrokeWidth
+ *
+ * Sets the current stroke width.
+ */
+mxAbstractCanvas2D.prototype.setStrokeWidth = function (value) {
+  this.state.strokeWidth = value;
+};
+
+/**
+ * Function: setDashed
+ *
+ * Enables or disables dashed lines.
+ */
+mxAbstractCanvas2D.prototype.setDashed = function (value, fixDash) {
+  this.state.dashed = value;
+  this.state.fixDash = fixDash;
+};
+
+/**
+ * Function: setDashPattern
+ *
+ * Sets the current dash pattern.
+ */
+mxAbstractCanvas2D.prototype.setDashPattern = function (value) {
+  this.state.dashPattern = value;
+};
+
+/**
+ * Function: setLineCap
+ *
+ * Sets the current line cap.
+ */
+mxAbstractCanvas2D.prototype.setLineCap = function (value) {
+  this.state.lineCap = value;
+};
+
+/**
+ * Function: setLineJoin
+ *
+ * Sets the current line join.
+ */
+mxAbstractCanvas2D.prototype.setLineJoin = function (value) {
+  this.state.lineJoin = value;
+};
+
+/**
+ * Function: setMiterLimit
+ *
+ * Sets the current miter limit.
+ */
+mxAbstractCanvas2D.prototype.setMiterLimit = function (value) {
+  this.state.miterLimit = value;
+};
+
+/**
+ * Function: setFontColor
+ *
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  this.state.fontColor = value;
+};
+
+/**
+ * Function: setFontBackgroundColor
+ *
+ * Sets the current font background color.
+ */
+mxAbstractCanvas2D.prototype.setFontBackgroundColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  this.state.fontBackgroundColor = value;
+};
+
+/**
+ * Function: setFontBorderColor
+ *
+ * Sets the current font border color.
+ */
+mxAbstractCanvas2D.prototype.setFontBorderColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  this.state.fontBorderColor = value;
+};
+
+/**
+ * Function: setFontSize
+ *
+ * Sets the current font size.
+ */
+mxAbstractCanvas2D.prototype.setFontSize = function (value) {
+  this.state.fontSize = parseFloat(value);
+};
+
+/**
+ * Function: setFontFamily
+ *
+ * Sets the current font family.
+ */
+mxAbstractCanvas2D.prototype.setFontFamily = function (value) {
+  this.state.fontFamily = value;
+};
+
+/**
+ * Function: setFontStyle
+ *
+ * Sets the current font style.
+ */
+mxAbstractCanvas2D.prototype.setFontStyle = function (value) {
+  if (value == null) {
+    value = 0;
+  }
+
+  this.state.fontStyle = value;
+};
+
+/**
+ * Function: setShadow
+ *
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadow = function (enabled) {
+  this.state.shadow = enabled;
+};
+
+/**
+ * Function: setShadowColor
+ *
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  this.state.shadowColor = value;
+};
+
+/**
+ * Function: setShadowAlpha
+ *
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowAlpha = function (value) {
+  this.state.shadowAlpha = value;
+};
+
+/**
+ * Function: setShadowOffset
+ *
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowOffset = function (dx, dy) {
+  this.state.shadowDx = dx;
+  this.state.shadowDy = dy;
+};
+
+/**
+ * Function: begin
+ *
+ * Starts a new path.
+ */
+mxAbstractCanvas2D.prototype.begin = function () {
+  this.lastX = 0;
+  this.lastY = 0;
+  this.path = [];
+};
+
+/**
+ * Function: moveTo
+ *
+ *  Moves the current path the given coordinates.
+ */
+mxAbstractCanvas2D.prototype.moveTo = function (x, y) {
+  this.addOp(this.moveOp, x, y);
+};
+
+/**
+ * Function: lineTo
+ *
+ * Draws a line to the given coordinates. Uses moveTo with the op argument.
+ */
+mxAbstractCanvas2D.prototype.lineTo = function (x, y) {
+  this.addOp(this.lineOp, x, y);
+};
+
+/**
+ * Function: quadTo
+ *
+ * Adds a quadratic curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.quadTo = function (x1, y1, x2, y2) {
+  this.addOp(this.quadOp, x1, y1, x2, y2);
+};
+
+/**
+ * Function: curveTo
+ *
+ * Adds a bezier curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.curveTo = function (x1, y1, x2, y2, x3, y3) {
+  this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);
+};
+
+/**
+ * Function: arcTo
+ *
+ * Adds the given arc to the current path. This is a synthetic operation that
+ * is broken down into curves.
+ */
+mxAbstractCanvas2D.prototype.arcTo = function (rx, ry, angle, largeArcFlag, sweepFlag, x, y) {
+  var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);
+
+  if (curves != null) {
+    for (var i = 0; i < curves.length; i += 6) {
+      this.curveTo(curves[i], curves[i + 1], curves[i + 2], curves[i + 3], curves[i + 4], curves[i + 5]);
+    }
+  }
+};
+
+/**
+ * Function: close
+ *
+ * Closes the current path.
+ */
+mxAbstractCanvas2D.prototype.close = function (x1, y1, x2, y2, x3, y3) {
+  this.addOp(this.closeOp);
+};
+
+/**
+ * Function: end
+ *
+ * Empty implementation for backwards compatibility. This will be removed.
+ */
+mxAbstractCanvas2D.prototype.end = function () {};
+
+/**
+ * util/mxXmlCanvas2D.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxXmlCanvas2D
+ *
+ * Base class for all canvases. The following methods make up the public
+ * interface of the canvas 2D for all painting in mxGraph:
+ *
+ * - , 
+ * - , , 
+ * - , , , , ,
+ *   , , , , ,
+ *   , 
+ * - , , , ,
+ *   , 
+ * - , , , 
+ * - , , , , 
+ * - , , , , 
+ * - , , 
+ *
+ *  is an additional method for drawing paths. This is
+ * a synthetic method, meaning that it is turned into a sequence of curves by
+ * default. Subclassers may add native support for arcs.
+ *
+ * Constructor: mxXmlCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxXmlCanvas2D(root) {
+  mxAbstractCanvas2D.call(this);
+
+  /**
+   * Variable: root
+   *
+   * Reference to the container for the SVG content.
+   */
+  this.root = root;
+
+  // Writes default settings;
+  this.writeDefaults();
+}
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Variable: textEnabled
+ *
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxXmlCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: compressed
+ *
+ * Specifies if the output should be compressed by removing redundant calls.
+ * Default is true.
+ */
+mxXmlCanvas2D.prototype.compressed = true;
+
+/**
+ * Function: writeDefaults
+ *
+ * Writes the rendering defaults to :
+ */
+mxXmlCanvas2D.prototype.writeDefaults = function () {
+  var elem;
+
+  // Writes font defaults
+  elem = this.createElement('fontfamily');
+  elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY);
+  this.root.appendChild(elem);
+
+  elem = this.createElement('fontsize');
+  elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE);
+  this.root.appendChild(elem);
+
+  // Writes shadow defaults
+  elem = this.createElement('shadowcolor');
+  elem.setAttribute('color', mxConstants.SHADOWCOLOR);
+  this.root.appendChild(elem);
+
+  elem = this.createElement('shadowalpha');
+  elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY);
+  this.root.appendChild(elem);
+
+  elem = this.createElement('shadowoffset');
+  elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X);
+  elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: format
+ *
+ * Returns a formatted number with 2 decimal places.
+ */
+mxXmlCanvas2D.prototype.format = function (value) {
+  return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: createElement
+ *
+ * Creates the given element using the owner document of .
+ */
+mxXmlCanvas2D.prototype.createElement = function (name) {
+  return this.root.ownerDocument.createElement(name);
+};
+
+/**
+ * Function: save
+ *
+ * Saves the drawing state.
+ */
+mxXmlCanvas2D.prototype.save = function () {
+  if (this.compressed) {
+    mxAbstractCanvas2D.prototype.save.apply(this, arguments);
+  }
+
+  this.root.appendChild(this.createElement('save'));
+};
+
+/**
+ * Function: restore
+ *
+ * Restores the drawing state.
+ */
+mxXmlCanvas2D.prototype.restore = function () {
+  if (this.compressed) {
+    mxAbstractCanvas2D.prototype.restore.apply(this, arguments);
+  }
+
+  this.root.appendChild(this.createElement('restore'));
+};
+
+/**
+ * Function: scale
+ *
+ * Scales the output.
+ *
+ * Parameters:
+ *
+ * scale - Number that represents the scale where 1 is equal to 100%.
+ */
+mxXmlCanvas2D.prototype.scale = function (value) {
+  var elem = this.createElement('scale');
+  elem.setAttribute('scale', value);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: translate
+ *
+ * Translates the output.
+ *
+ * Parameters:
+ *
+ * dx - Number that specifies the horizontal translation.
+ * dy - Number that specifies the vertical translation.
+ */
+mxXmlCanvas2D.prototype.translate = function (dx, dy) {
+  var elem = this.createElement('translate');
+  elem.setAttribute('dx', this.format(dx));
+  elem.setAttribute('dy', this.format(dy));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: rotate
+ *
+ * Rotates and/or flips the output around a given center. (Note: Due to
+ * limitations in VML, the rotation cannot be concatenated.)
+ *
+ * Parameters:
+ *
+ * theta - Number that represents the angle of the rotation (in degrees).
+ * flipH - Boolean indicating if the output should be flipped horizontally.
+ * flipV - Boolean indicating if the output should be flipped vertically.
+ * cx - Number that represents the x-coordinate of the rotation center.
+ * cy - Number that represents the y-coordinate of the rotation center.
+ */
+mxXmlCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
+  var elem = this.createElement('rotate');
+
+  if (theta != 0 || flipH || flipV) {
+    elem.setAttribute('theta', this.format(theta));
+    elem.setAttribute('flipH', flipH ? '1' : '0');
+    elem.setAttribute('flipV', flipV ? '1' : '0');
+    elem.setAttribute('cx', this.format(cx));
+    elem.setAttribute('cy', this.format(cy));
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setAlpha
+ *
+ * Sets the current alpha.
+ *
+ * Parameters:
+ *
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setAlpha = function (value) {
+  if (this.compressed) {
+    if (this.state.alpha == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments);
+  }
+
+  var elem = this.createElement('alpha');
+  elem.setAttribute('alpha', this.format(value));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillAlpha
+ *
+ * Sets the current fill alpha.
+ *
+ * Parameters:
+ *
+ * value - Number that represents the new fill alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setFillAlpha = function (value) {
+  if (this.compressed) {
+    if (this.state.fillAlpha == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setFillAlpha.apply(this, arguments);
+  }
+
+  var elem = this.createElement('fillalpha');
+  elem.setAttribute('alpha', this.format(value));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeAlpha
+ *
+ * Sets the current stroke alpha.
+ *
+ * Parameters:
+ *
+ * value - Number that represents the new stroke alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setStrokeAlpha = function (value) {
+  if (this.compressed) {
+    if (this.state.strokeAlpha == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this, arguments);
+  }
+
+  var elem = this.createElement('strokealpha');
+  elem.setAttribute('alpha', this.format(value));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillColor
+ *
+ * Sets the current fill color.
+ *
+ * Parameters:
+ *
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFillColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  if (this.compressed) {
+    if (this.state.fillColor == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments);
+  }
+
+  var elem = this.createElement('fillcolor');
+  elem.setAttribute('color', value != null ? value : mxConstants.NONE);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setGradient
+ *
+ * Sets the gradient. Note that the coordinates may be ignored by some implementations.
+ *
+ * Parameters:
+ *
+ * color1 - Hexadecimal representation of the start color.
+ * color2 - Hexadecimal representation of the end color.
+ * x - X-coordinate of the gradient region.
+ * y - y-coordinate of the gradient region.
+ * w - Width of the gradient region.
+ * h - Height of the gradient region.
+ * direction - One of , ,
+ *  or .
+ * alpha1 - Optional alpha of the start color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ * alpha2 - Optional alpha of the end color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setGradient = function (color1, color2, x, y, w, h, direction, alpha1, alpha2) {
+  if (color1 != null && color2 != null) {
+    mxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);
+
+    var elem = this.createElement('gradient');
+    elem.setAttribute('c1', color1);
+    elem.setAttribute('c2', color2);
+    elem.setAttribute('x', this.format(x));
+    elem.setAttribute('y', this.format(y));
+    elem.setAttribute('w', this.format(w));
+    elem.setAttribute('h', this.format(h));
+
+    // Default direction is south
+    if (direction != null) {
+      elem.setAttribute('direction', direction);
+    }
+
+    if (alpha1 != null) {
+      elem.setAttribute('alpha1', alpha1);
+    }
+
+    if (alpha2 != null) {
+      elem.setAttribute('alpha2', alpha2);
+    }
+
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setStrokeColor
+ *
+ * Sets the current stroke color.
+ *
+ * Parameters:
+ *
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setStrokeColor = function (value) {
+  if (value == mxConstants.NONE) {
+    value = null;
+  }
+
+  if (this.compressed) {
+    if (this.state.strokeColor == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments);
+  }
+
+  var elem = this.createElement('strokecolor');
+  elem.setAttribute('color', value != null ? value : mxConstants.NONE);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeWidth
+ *
+ * Sets the current stroke width.
+ *
+ * Parameters:
+ *
+ * value - Numeric representation of the stroke width.
+ */
+mxXmlCanvas2D.prototype.setStrokeWidth = function (value) {
+  if (this.compressed) {
+    if (this.state.strokeWidth == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments);
+  }
+
+  var elem = this.createElement('strokewidth');
+  elem.setAttribute('width', this.format(value));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashed
+ *
+ * Enables or disables dashed lines.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies if dashed lines should be enabled.
+ * value - Boolean that specifies if the stroke width should be ignored
+ * for the dash pattern. Default is false.
+ */
+mxXmlCanvas2D.prototype.setDashed = function (value, fixDash) {
+  if (this.compressed) {
+    if (this.state.dashed == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setDashed.apply(this, arguments);
+  }
+
+  var elem = this.createElement('dashed');
+  elem.setAttribute('dashed', value ? '1' : '0');
+
+  if (fixDash != null) {
+    elem.setAttribute('fixDash', fixDash ? '1' : '0');
+  }
+
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashPattern
+ *
+ * Sets the current dash pattern. Default is '3 3'.
+ *
+ * Parameters:
+ *
+ * value - String that represents the dash pattern, which is a sequence of
+ * numbers defining the length of the dashes and the length of the spaces
+ * between the dashes. The lengths are relative to the line width - a length
+ * of 1 is equals to the line width.
+ */
+mxXmlCanvas2D.prototype.setDashPattern = function (value) {
+  if (this.compressed) {
+    if (this.state.dashPattern == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments);
+  }
+
+  var elem = this.createElement('dashpattern');
+  elem.setAttribute('pattern', value);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineCap
+ *
+ * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG.
+ *
+ * Parameters:
+ *
+ * value - String that represents the line cap. Possible values are flat, round
+ * and square.
+ */
+mxXmlCanvas2D.prototype.setLineCap = function (value) {
+  if (this.compressed) {
+    if (this.state.lineCap == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments);
+  }
+
+  var elem = this.createElement('linecap');
+  elem.setAttribute('cap', value);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineJoin
+ *
+ * Sets the line join. Default is 'miter'.
+ *
+ * Parameters:
+ *
+ * value - String that represents the line join. Possible values are miter,
+ * round and bevel.
+ */
+mxXmlCanvas2D.prototype.setLineJoin = function (value) {
+  if (this.compressed) {
+    if (this.state.lineJoin == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments);
+  }
+
+  var elem = this.createElement('linejoin');
+  elem.setAttribute('join', value);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setMiterLimit
+ *
+ * Sets the miter limit. Default is 10.
+ *
+ * Parameters:
+ *
+ * value - Number that represents the miter limit.
+ */
+mxXmlCanvas2D.prototype.setMiterLimit = function (value) {
+  if (this.compressed) {
+    if (this.state.miterLimit == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments);
+  }
+
+  var elem = this.createElement('miterlimit');
+  elem.setAttribute('limit', value);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFontColor
+ *
+ * Sets the current font color. Default is '#000000'.
+ *
+ * Parameters:
+ *
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontColor = function (value) {
+  if (this.textEnabled) {
+    if (value == mxConstants.NONE) {
+      value = null;
+    }
+
+    if (this.compressed) {
+      if (this.state.fontColor == value) {
+        return;
+      }
+
+      mxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments);
+    }
+
+    var elem = this.createElement('fontcolor');
+    elem.setAttribute('color', value != null ? value : mxConstants.NONE);
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setFontBackgroundColor
+ *
+ * Sets the current font background color.
+ *
+ * Parameters:
+ *
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBackgroundColor = function (value) {
+  if (this.textEnabled) {
+    if (value == mxConstants.NONE) {
+      value = null;
+    }
+
+    if (this.compressed) {
+      if (this.state.fontBackgroundColor == value) {
+        return;
+      }
+
+      mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments);
+    }
+
+    var elem = this.createElement('fontbackgroundcolor');
+    elem.setAttribute('color', value != null ? value : mxConstants.NONE);
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setFontBorderColor
+ *
+ * Sets the current font border color.
+ *
+ * Parameters:
+ *
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBorderColor = function (value) {
+  if (this.textEnabled) {
+    if (value == mxConstants.NONE) {
+      value = null;
+    }
+
+    if (this.compressed) {
+      if (this.state.fontBorderColor == value) {
+        return;
+      }
+
+      mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments);
+    }
+
+    var elem = this.createElement('fontbordercolor');
+    elem.setAttribute('color', value != null ? value : mxConstants.NONE);
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setFontSize
+ *
+ * Sets the current font size. Default is .
+ *
+ * Parameters:
+ *
+ * value - Numeric representation of the font size.
+ */
+mxXmlCanvas2D.prototype.setFontSize = function (value) {
+  if (this.textEnabled) {
+    if (this.compressed) {
+      if (this.state.fontSize == value) {
+        return;
+      }
+
+      mxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments);
+    }
+
+    var elem = this.createElement('fontsize');
+    elem.setAttribute('size', value);
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setFontFamily
+ *
+ * Sets the current font family. Default is .
+ *
+ * Parameters:
+ *
+ * value - String representation of the font family. This handles the same
+ * values as the CSS font-family property.
+ */
+mxXmlCanvas2D.prototype.setFontFamily = function (value) {
+  if (this.textEnabled) {
+    if (this.compressed) {
+      if (this.state.fontFamily == value) {
+        return;
+      }
+
+      mxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments);
+    }
+
+    var elem = this.createElement('fontfamily');
+    elem.setAttribute('family', value);
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setFontStyle
+ *
+ * Sets the current font style.
+ *
+ * Parameters:
+ *
+ * value - Numeric representation of the font family. This is the sum of the
+ * font styles from .
+ */
+mxXmlCanvas2D.prototype.setFontStyle = function (value) {
+  if (this.textEnabled) {
+    if (value == null) {
+      value = 0;
+    }
+
+    if (this.compressed) {
+      if (this.state.fontStyle == value) {
+        return;
+      }
+
+      mxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments);
+    }
+
+    var elem = this.createElement('fontstyle');
+    elem.setAttribute('style', value);
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: setShadow
+ *
+ * Enables or disables shadows.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies if shadows should be enabled.
+ */
+mxXmlCanvas2D.prototype.setShadow = function (value) {
+  if (this.compressed) {
+    if (this.state.shadow == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setShadow.apply(this, arguments);
+  }
+
+  var elem = this.createElement('shadow');
+  elem.setAttribute('enabled', value ? '1' : '0');
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowColor
+ *
+ * Sets the current shadow color. Default is .
+ *
+ * Parameters:
+ *
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setShadowColor = function (value) {
+  if (this.compressed) {
+    if (value == mxConstants.NONE) {
+      value = null;
+    }
+
+    if (this.state.shadowColor == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments);
+  }
+
+  var elem = this.createElement('shadowcolor');
+  elem.setAttribute('color', value != null ? value : mxConstants.NONE);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowAlpha
+ *
+ * Sets the current shadows alpha. Default is .
+ *
+ * Parameters:
+ *
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setShadowAlpha = function (value) {
+  if (this.compressed) {
+    if (this.state.shadowAlpha == value) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments);
+  }
+
+  var elem = this.createElement('shadowalpha');
+  elem.setAttribute('alpha', value);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowOffset
+ *
+ * Sets the current shadow offset.
+ *
+ * Parameters:
+ *
+ * dx - Number that represents the horizontal offset of the shadow.
+ * dy - Number that represents the vertical offset of the shadow.
+ */
+mxXmlCanvas2D.prototype.setShadowOffset = function (dx, dy) {
+  if (this.compressed) {
+    if (this.state.shadowDx == dx && this.state.shadowDy == dy) {
+      return;
+    }
+
+    mxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments);
+  }
+
+  var elem = this.createElement('shadowoffset');
+  elem.setAttribute('dx', dx);
+  elem.setAttribute('dy', dy);
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: rect
+ *
+ * Puts a rectangle into the drawing buffer.
+ *
+ * Parameters:
+ *
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ */
+mxXmlCanvas2D.prototype.rect = function (x, y, w, h) {
+  var elem = this.createElement('rect');
+  elem.setAttribute('x', this.format(x));
+  elem.setAttribute('y', this.format(y));
+  elem.setAttribute('w', this.format(w));
+  elem.setAttribute('h', this.format(h));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: roundrect
+ *
+ * Puts a rounded rectangle into the drawing buffer.
+ *
+ * Parameters:
+ *
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ * dx - Number that represents the horizontal rounding.
+ * dy - Number that represents the vertical rounding.
+ */
+mxXmlCanvas2D.prototype.roundrect = function (x, y, w, h, dx, dy) {
+  var elem = this.createElement('roundrect');
+  elem.setAttribute('x', this.format(x));
+  elem.setAttribute('y', this.format(y));
+  elem.setAttribute('w', this.format(w));
+  elem.setAttribute('h', this.format(h));
+  elem.setAttribute('dx', this.format(dx));
+  elem.setAttribute('dy', this.format(dy));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: ellipse
+ *
+ * Puts an ellipse into the drawing buffer.
+ *
+ * Parameters:
+ *
+ * x - Number that represents the x-coordinate of the ellipse.
+ * y - Number that represents the y-coordinate of the ellipse.
+ * w - Number that represents the width of the ellipse.
+ * h - Number that represents the height of the ellipse.
+ */
+mxXmlCanvas2D.prototype.ellipse = function (x, y, w, h) {
+  var elem = this.createElement('ellipse');
+  elem.setAttribute('x', this.format(x));
+  elem.setAttribute('y', this.format(y));
+  elem.setAttribute('w', this.format(w));
+  elem.setAttribute('h', this.format(h));
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: image
+ *
+ * Paints an image.
+ *
+ * Parameters:
+ *
+ * x - Number that represents the x-coordinate of the image.
+ * y - Number that represents the y-coordinate of the image.
+ * w - Number that represents the width of the image.
+ * h - Number that represents the height of the image.
+ * src - String that specifies the URL of the image.
+ * aspect - Boolean indicating if the aspect of the image should be preserved.
+ * flipH - Boolean indicating if the image should be flipped horizontally.
+ * flipV - Boolean indicating if the image should be flipped vertically.
+ */
+mxXmlCanvas2D.prototype.image = function (x, y, w, h, src, aspect, flipH, flipV) {
+  src = this.converter.convert(src);
+
+  // LATER: Add option for embedding images as base64.
+  var elem = this.createElement('image');
+  elem.setAttribute('x', this.format(x));
+  elem.setAttribute('y', this.format(y));
+  elem.setAttribute('w', this.format(w));
+  elem.setAttribute('h', this.format(h));
+  elem.setAttribute('src', src);
+  elem.setAttribute('aspect', aspect ? '1' : '0');
+  elem.setAttribute('flipH', flipH ? '1' : '0');
+  elem.setAttribute('flipV', flipV ? '1' : '0');
+  this.root.appendChild(elem);
+};
+
+/**
+ * Function: begin
+ *
+ * Starts a new path and puts it into the drawing buffer.
+ */
+mxXmlCanvas2D.prototype.begin = function () {
+  this.root.appendChild(this.createElement('begin'));
+  this.lastX = 0;
+  this.lastY = 0;
+};
+
+/**
+ * Function: moveTo
+ *
+ * Moves the current path the given point.
+ *
+ * Parameters:
+ *
+ * x - Number that represents the x-coordinate of the point.
+ * y - Number that represents the y-coordinate of the point.
+ */
+mxXmlCanvas2D.prototype.moveTo = function (x, y) {
+  var elem = this.createElement('move');
+  elem.setAttribute('x', this.format(x));
+  elem.setAttribute('y', this.format(y));
+  this.root.appendChild(elem);
+  this.lastX = x;
+  this.lastY = y;
+};
+
+/**
+ * Function: lineTo
+ *
+ * Draws a line to the given coordinates.
+ *
+ * Parameters:
+ *
+ * x - Number that represents the x-coordinate of the endpoint.
+ * y - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.lineTo = function (x, y) {
+  var elem = this.createElement('line');
+  elem.setAttribute('x', this.format(x));
+  elem.setAttribute('y', this.format(y));
+  this.root.appendChild(elem);
+  this.lastX = x;
+  this.lastY = y;
+};
+
+/**
+ * Function: quadTo
+ *
+ * Adds a quadratic curve to the current path.
+ *
+ * Parameters:
+ *
+ * x1 - Number that represents the x-coordinate of the control point.
+ * y1 - Number that represents the y-coordinate of the control point.
+ * x2 - Number that represents the x-coordinate of the endpoint.
+ * y2 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.quadTo = function (x1, y1, x2, y2) {
+  var elem = this.createElement('quad');
+  elem.setAttribute('x1', this.format(x1));
+  elem.setAttribute('y1', this.format(y1));
+  elem.setAttribute('x2', this.format(x2));
+  elem.setAttribute('y2', this.format(y2));
+  this.root.appendChild(elem);
+  this.lastX = x2;
+  this.lastY = y2;
+};
+
+/**
+ * Function: curveTo
+ *
+ * Adds a bezier curve to the current path.
+ *
+ * Parameters:
+ *
+ * x1 - Number that represents the x-coordinate of the first control point.
+ * y1 - Number that represents the y-coordinate of the first control point.
+ * x2 - Number that represents the x-coordinate of the second control point.
+ * y2 - Number that represents the y-coordinate of the second control point.
+ * x3 - Number that represents the x-coordinate of the endpoint.
+ * y3 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.curveTo = function (x1, y1, x2, y2, x3, y3) {
+  var elem = this.createElement('curve');
+  elem.setAttribute('x1', this.format(x1));
+  elem.setAttribute('y1', this.format(y1));
+  elem.setAttribute('x2', this.format(x2));
+  elem.setAttribute('y2', this.format(y2));
+  elem.setAttribute('x3', this.format(x3));
+  elem.setAttribute('y3', this.format(y3));
+  this.root.appendChild(elem);
+  this.lastX = x3;
+  this.lastY = y3;
+};
+
+/**
+ * Function: close
+ *
+ * Closes the current path.
+ */
+mxXmlCanvas2D.prototype.close = function () {
+  this.root.appendChild(this.createElement('close'));
+};
+
+/**
+ * Function: text
+ *
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup. Background and border color as well
+ * as clipping is not available in plain text labels for VML. HTML labels
+ * are not available as part of shapes with no foreignObject support in SVG
+ * (eg. IE9, IE10).
+ *
+ * Parameters:
+ *
+ * x - Number that represents the x-coordinate of the text.
+ * y - Number that represents the y-coordinate of the text.
+ * w - Number that represents the available width for the text or 0 for automatic width.
+ * h - Number that represents the available height for the text or 0 for automatic height.
+ * str - String that specifies the text to be painted.
+ * align - String that represents the horizontal alignment.
+ * valign - String that represents the vertical alignment.
+ * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.
+ * format - Empty string for plain text or 'html' for HTML markup.
+ * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.
+ * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.
+ * rotation - Number that specifies the angle of the rotation around the anchor point of the text.
+ * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.
+ */
+mxXmlCanvas2D.prototype.text = function (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) {
+  if (this.textEnabled && str != null) {
+    if (mxUtils.isNode(str)) {
+      str = mxUtils.getOuterHtml(str);
+    }
+
+    var elem = this.createElement('text');
+    elem.setAttribute('x', this.format(x));
+    elem.setAttribute('y', this.format(y));
+    elem.setAttribute('w', this.format(w));
+    elem.setAttribute('h', this.format(h));
+    elem.setAttribute('str', str);
+
+    if (align != null) {
+      elem.setAttribute('align', align);
+    }
+
+    if (valign != null) {
+      elem.setAttribute('valign', valign);
+    }
+
+    elem.setAttribute('wrap', wrap ? '1' : '0');
+
+    if (format == null) {
+      format = '';
+    }
+
+    elem.setAttribute('format', format);
+
+    if (overflow != null) {
+      elem.setAttribute('overflow', overflow);
+    }
+
+    if (clip != null) {
+      elem.setAttribute('clip', clip ? '1' : '0');
+    }
+
+    if (rotation != null) {
+      elem.setAttribute('rotation', rotation);
+    }
+
+    if (dir != null) {
+      elem.setAttribute('dir', dir);
+    }
+
+    this.root.appendChild(elem);
+  }
+};
+
+/**
+ * Function: stroke
+ *
+ * Paints the outline of the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.stroke = function () {
+  this.root.appendChild(this.createElement('stroke'));
+};
+
+/**
+ * Function: fill
+ *
+ * Fills the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.fill = function () {
+  this.root.appendChild(this.createElement('fill'));
+};
+
+/**
+ * Function: fillAndStroke
+ *
+ * Fills the current drawing buffer and its outline.
+ */
+mxXmlCanvas2D.prototype.fillAndStroke = function () {
+  this.root.appendChild(this.createElement('fillstroke'));
+};
+
+/**
+ * util/mxSvgCanvas2D.js
+ */
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSvgCanvas2D
+ *
+ * Extends  to implement a canvas for SVG. This canvas writes all
+ * calls as SVG output to the given SVG root node.
+ *
+ * (code)
+ * var svgDoc = mxUtils.createXmlDocument();
+ * var root = (svgDoc.createElementNS != null) ?
+ * 		svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
+ *
+ * if (svgDoc.createElementNS == null)
+ * {
+ *   root.setAttribute('xmlns', mxConstants.NS_SVG);
+ *   root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ * else
+ * {
+ *   root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ *
+ * var bounds = graph.getGraphBounds();
+ * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
+ * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
+ * root.setAttribute('version', '1.1');
+ *
+ * svgDoc.appendChild(root);
+ *
+ * var svgCanvas = new mxSvgCanvas2D(root);
+ * (end)
+ *
+ * A description of the public API is available in .
+ *
+ * To disable anti-aliasing in the output, use the following code.
+ *
+ * (code)
+ * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');
+ * (end)
+ *
+ * Or set the respective attribute in the SVG element directly.
+ *
+ * Constructor: mxSvgCanvas2D
+ *
+ * Constructs a new SVG canvas.
+ *
+ * Parameters:
+ *
+ * root - SVG container for the output.
+ * styleEnabled - Optional boolean that specifies if a style section should be
+ * added. The style section sets the default font-size, font-family and
+ * stroke-miterlimit globally. Default is false.
+ */
+function mxSvgCanvas2D(root, styleEnabled) {
+  mxAbstractCanvas2D.call(this);
+
+  /**
+   * Variable: root
+   *
+   * Reference to the container for the SVG content.
+   */
+  this.root = root;
+
+  /**
+   * Variable: gradients
+   *
+   * Local cache of gradients for quick lookups.
+   */
+  this.gradients = [];
+
+  /**
+   * Variable: defs
+   *
+   * Reference to the defs section of the SVG document. Only for export.
+   */
+  this.defs = null;
+
+  /**
+   * Variable: styleEnabled
+   *
+   * Stores the value of styleEnabled passed to the constructor.
+   */
+  this.styleEnabled = styleEnabled != null ? styleEnabled : false;
+
+  var svg = null;
+
+  // Adds optional defs section for export
+  if (root.ownerDocument != document) {
+    var node = root;
+
+    // Finds owner SVG element in XML DOM
+    while (node != null && node.nodeName != 'svg') {
+      node = node.parentNode;
+    }
+
+    svg = node;
+  }
+
+  if (svg != null) {
+    // Tries to get existing defs section
+    var tmp = svg.getElementsByTagName('defs');
+
+    if (tmp.length > 0) {
+      this.defs = svg.getElementsByTagName('defs')[0];
+    }
+
+    // Adds defs section if none exists
+    if (this.defs == null) {
+      this.defs = this.createElement('defs');
+
+      if (svg.firstChild != null) {
+        svg.insertBefore(this.defs, svg.firstChild);
+      } else {
+        svg.appendChild(this.defs);
+      }
+    }
+
+    // Adds stylesheet
+    if (this.styleEnabled) {
+      this.defs.appendChild(this.createStyle());
+    }
+  }
+}
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Capability check for DOM parser and checks if base tag is used.
+ */
+(function () {
+  mxSvgCanvas2D.prototype.useDomParser =
+    !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';
+
+  if (mxSvgCanvas2D.prototype.useDomParser) {
+    // Checks using a generic test text if the parsing actually works. This is a workaround
+    // for older browsers where the capability check returns true but the parsing fails.
+    try {
+      var doc = new DOMParser().parseFromString('test text', 'text/html');
+      mxSvgCanvas2D.prototype.useDomParser = doc != null;
+    } catch (e) {
+      mxSvgCanvas2D.prototype.useDomParser = false;
+    }
+  }
+
+  // Activates workaround for gradient ID resolution if base tag is used.
+  mxSvgCanvas2D.prototype.useAbsoluteIds =
+    !mxClient.IS_CHROMEAPP &&
+    !mxClient.IS_IE &&
+    !mxClient.IS_IE11 &&
+    !mxClient.IS_EDGE &&
+    document.getElementsByTagName('base').length > 0;
+})();
+
+/**
+ * Variable: path
+ *
+ * Holds the current DOM node.
+ */
+mxSvgCanvas2D.prototype.node = null;
+
+/**
+ * Variable: matchHtmlAlignment
+ *
+ * Specifies if plain text output should match the vertical HTML alignment.
+ * Defaul is true.
+ */
+mxSvgCanvas2D.prototype.matchHtmlAlignment = true;
+
+/**
+ * Variable: textEnabled
+ *
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxSvgCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: foEnabled
+ *
+ * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
+ */
+mxSvgCanvas2D.prototype.foEnabled = true;
+
+/**
+ * Variable: foAltText
+ *
+ * Specifies the fallback text for unsupported foreignObjects in exported
+ * documents. Default is '[Object]'. If this is set to null then no fallback
+ * text is added to the exported document.
+ */
+mxSvgCanvas2D.prototype.foAltText = '[Object]';
+
+/**
+ * Variable: foOffset
+ *
+ * Offset to be used for foreignObjects.
+ */
+mxSvgCanvas2D.prototype.foOffset = 0;
+
+/**
+ * Variable: textOffset
+ *
+ * Offset to be used for text elements.
+ */
+mxSvgCanvas2D.prototype.textOffset = 0;
+
+/**
+ * Variable: imageOffset
+ *
+ * Offset to be used for image elements.
+ */
+mxSvgCanvas2D.prototype.imageOffset = 0;
+
+/**
+ * Variable: strokeTolerance
+ *
+ * Adds transparent paths for strokes.
+ */
+mxSvgCanvas2D.prototype.strokeTolerance = 0;
+
+/**
+ * Variable: minStrokeWidth
+ *
+ * Minimum stroke width for output.
+ */
+mxSvgCanvas2D.prototype.minStrokeWidth = 1;
+
+/**
+ * Variable: refCount
+ *
+ * Local counter for references in SVG export.
+ */
+mxSvgCanvas2D.prototype.refCount = 0;
+
+/**
+ * Variable: lineHeightCorrection
+ *
+ * Correction factor for  in HTML output. Default is 1.
+ */
+mxSvgCanvas2D.prototype.lineHeightCorrection = 1;
+
+/**
+ * Variable: pointerEventsValue
+ *
+ * Default value for active pointer events. Default is all.
+ */
+mxSvgCanvas2D.prototype.pointerEventsValue = 'all';
+
+/**
+ * Variable: fontMetricsPadding
+ *
+ * Padding to be added for text that is not wrapped to account for differences
+ * in font metrics on different platforms in pixels. Default is 10.
+ */
+mxSvgCanvas2D.prototype.fontMetricsPadding = 10;
+
+/**
+ * Variable: cacheOffsetSize
+ *
+ * Specifies if offsetWidth and offsetHeight should be cached. Default is true.
+ * This is used to speed up repaint of text in .
+ */
+mxSvgCanvas2D.prototype.cacheOffsetSize = true;
+
+/**
+ * Function: format
+ *
+ * Rounds all numbers to 2 decimal points.
+ */
+mxSvgCanvas2D.prototype.format = function (value) {
+  return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: getBaseUrl
+ *
+ * Returns the URL of the page without the hash part. This needs to use href to
+ * include any search part with no params (ie question mark alone). This is a
+ * workaround for the fact that window.location.search is empty if there is
+ * no search string behind the question mark.
+ */
+mxSvgCanvas2D.prototype.getBaseUrl = function () {
+  var href = window.location.href;
+  var hash = href.lastIndexOf('#');
+
+  if (hash > 0) {
+    href = href.substring(0, hash);
+  }
+
+  return href;
+};
+
+/**
+ * Function: reset
+ *
+ * Returns any offsets for rendering pixels.
+ */
+mxSvgCanvas2D.prototype.reset = function () {
+  mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
+  this.gradients = [];
+};
+
+/**
+ * Function: createStyle
+ *
+ * Creates the optional style section.
+ */
+mxSvgCanvas2D.prototype.createStyle = function (x) {
+  var style = this.createElement('style');
+  style.setAttribute('type', 'text/css');
+  mxUtils.write(
+    style,
+    'svg{font-family:' +
+      mxConstants.DEFAULT_FONTFAMILY +
+      ';font-size:' +
+      mxConstants.DEFAULT_FONTSIZE +
+      ';fill:none;stroke-miterlimit:10}'
+  );
+
+  return style;
+};
+
+/**
+ * Function: createElement
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createElement = function (tagName, namespace) {
+  if (this.root.ownerDocument.createElementNS != null) {
+    return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);
+  } else {
+    var elt = this.root.ownerDocument.createElement(tagName);
+
+    if (namespace != null) {
+      elt.setAttribute('xmlns', namespace);
+    }
+
+    return elt;
+  }
+};
+
+/**
+ * Function: getAlternateText
+ *
+ * Returns the alternate text string for the given foreignObject.
+ */
+mxSvgCanvas2D.prototype.getAlternateText = function (
+  fo,
+  x,
+  y,
+  w,
+  h,
+  str,
+  align,
+  valign,
+  wrap,
+  format,
+  overflow,
+  clip,
+  rotation
+) {
+  return str != null ? this.foAltText : null;
+};
+
+/**
+ * Function: getAlternateContent
+ *
+ * Returns the alternate content for the given foreignObject.
+ */
+mxSvgCanvas2D.prototype.createAlternateContent = function (
+  fo,
+  x,
+  y,
+  w,
+  h,
+  str,
+  align,
+  valign,
+  wrap,
+  format,
+  overflow,
+  clip,
+  rotation
+) {
+  var text = this.getAlternateText(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
+  var s = this.state;
+
+  if (text != null && s.fontSize > 0) {
+    var dy = valign == mxConstants.ALIGN_TOP ? 1 : valign == mxConstants.ALIGN_BOTTOM ? 0 : 0.3;
+    var anchor = align == mxConstants.ALIGN_RIGHT ? 'end' : align == mxConstants.ALIGN_LEFT ? 'start' : 'middle';
+
+    var alt = this.createElement('text');
+    alt.setAttribute('x', Math.round(x + s.dx));
+    alt.setAttribute('y', Math.round(y + s.dy + dy * s.fontSize));
+    alt.setAttribute('fill', s.fontColor || 'black');
+    alt.setAttribute('font-family', s.fontFamily);
+    alt.setAttribute('font-size', Math.round(s.fontSize) + 'px');
+
+    // Text-anchor start is default in SVG
+    if (anchor != 'start') {
+      alt.setAttribute('text-anchor', anchor);
+    }
+
+    if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
+      alt.setAttribute('font-weight', 'bold');
+    }
+
+    if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
+      alt.setAttribute('font-style', 'italic');
+    }
+
+    var txtDecor = [];
+
+    if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
+      txtDecor.push('underline');
+    }
+
+    if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
+      txtDecor.push('line-through');
+    }
+
+    if (txtDecor.length > 0) {
+      alt.setAttribute('text-decoration', txtDecor.join(' '));
+    }
+
+    mxUtils.write(alt, text);
+
+    return alt;
+  } else {
+    return null;
+  }
+};
+
+/**
+ * Function: createGradientId
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createGradientId = function (start, end, alpha1, alpha2, direction) {
+  // Removes illegal characters from gradient ID
+  if (start.charAt(0) == '#') {
+    start = start.substring(1);
+  }
+
+  if (end.charAt(0) == '#') {
+    end = end.substring(1);
+  }
+
+  // Workaround for gradient IDs not working in Safari 5 / Chrome 6
+  // if they contain uppercase characters
+  start = start.toLowerCase() + '-' + alpha1;
+  end = end.toLowerCase() + '-' + alpha2;
+
+  // Wrong gradient directions possible?
+  var dir = null;
+
+  if (direction == null || direction == mxConstants.DIRECTION_SOUTH) {
+    dir = 's';
+  } else if (direction == mxConstants.DIRECTION_EAST) {
+    dir = 'e';
+  } else {
+    var tmp = start;
+    start = end;
+    end = tmp;
+
+    if (direction == mxConstants.DIRECTION_NORTH) {
+      dir = 's';
+    } else if (direction == mxConstants.DIRECTION_WEST) {
+      dir = 'e';
+    }
+  }
+
+  return 'mx-gradient-' + start + '-' + end + '-' + dir;
+};
+
+/**
+ * Function: getSvgGradient
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.getSvgGradient = function (start, end, alpha1, alpha2, direction) {
+  var id = this.createGradientId(start, end, alpha1, alpha2, direction);
+  var gradient = this.gradients[id];
+
+  if (gradient == null) {
+    var svg = this.root.ownerSVGElement;
+
+    var counter = 0;
+    var tmpId = id + '-' + counter;
+
+    if (svg != null) {
+      gradient = svg.ownerDocument.getElementById(tmpId);
+
+      while (gradient != null && gradient.ownerSVGElement != svg) {
+        tmpId = id + '-' + counter++;
+        gradient = svg.ownerDocument.getElementById(tmpId);
+      }
+    } else {
+      // Uses shorter IDs for export
+      tmpId = 'id' + ++this.refCount;
+    }
+
+    if (gradient == null) {
+      gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);
+      gradient.setAttribute('id', tmpId);
+
+      if (this.defs != null) {
+        this.defs.appendChild(gradient);
+      } else {
+        svg.appendChild(gradient);
+      }
+    }
+
+    this.gradients[id] = gradient;
+  }
+
+  return gradient.getAttribute('id');
+};
+
+/**
+ * Function: createSvgGradient
+ *
+ * Creates the given SVG gradient.
+ */
+mxSvgCanvas2D.prototype.createSvgGradient = function (start, end, alpha1, alpha2, direction) {
+  var gradient = this.createElement('linearGradient');
+  gradient.setAttribute('x1', '0%');
+  gradient.setAttribute('y1', '0%');
+  gradient.setAttribute('x2', '0%');
+  gradient.setAttribute('y2', '0%');
+
+  if (direction == null || direction == mxConstants.DIRECTION_SOUTH) {
+    gradient.setAttribute('y2', '100%');
+  } else if (direction == mxConstants.DIRECTION_EAST) {
+    gradient.setAttribute('x2', '100%');
+  } else if (direction == mxConstants.DIRECTION_NORTH) {
+    gradient.setAttribute('y1', '100%');
+  } else if (direction == mxConstants.DIRECTION_WEST) {
+    gradient.setAttribute('x1', '100%');
+  }
+
+  var op = alpha1 < 1 ? ';stop-opacity:' + alpha1 : '';
+
+  var stop = this.createElement('stop');
+  stop.setAttribute('offset', '0%');
+  stop.setAttribute('style', 'stop-color:' + start + op);
+  gradient.appendChild(stop);
+
+  op = alpha2 < 1 ? ';stop-opacity:' + alpha2 : '';
+
+  stop = this.createElement('stop');
+  stop.setAttribute('offset', '100%');
+  stop.setAttribute('style', 'stop-color:' + end + op);
+  gradient.appendChild(stop);
+
+  return gradient;
+};
+
+/**
+ * Function: addNode
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.addNode = function (filled, stroked) {
+  var node = this.node;
+  var s = this.state;
+
+  if (node != null) {
+    if (node.nodeName == 'path') {
+      // Checks if the path is not empty
+      if (this.path != null && this.path.length > 0) {
+        node.setAttribute('d', this.path.join(' '));
+      } else {
+        return;
+      }
+    }
+
+    if (filled && s.fillColor != null) {
+      this.updateFill();
+    } else if (!this.styleEnabled) {
+      // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952
+      if (node.nodeName == 'ellipse' && mxClient.IS_FF) {
+        node.setAttribute('fill', 'transparent');
+      } else {
+        node.setAttribute('fill', 'none');
+      }
+
+      // Sets the actual filled state for stroke tolerance
+      filled = false;
+    }
+
+    if (stroked && s.strokeColor != null) {
+      this.updateStroke();
+    } else if (!this.styleEnabled) {
+      node.setAttribute('stroke', 'none');
+    }
+
+    if (s.transform != null && s.transform.length > 0) {
+      node.setAttribute('transform', s.transform);
+    }
+
+    if (s.shadow) {
+      this.root.appendChild(this.createShadow(node));
+    }
+
+    // Adds stroke tolerance
+    if (this.strokeTolerance > 0 && !filled) {
+      this.root.appendChild(this.createTolerance(node));
+    }
+
+    // Adds pointer events
+    if (this.pointerEvents) {
+      node.setAttribute('pointer-events', this.pointerEventsValue);
+    }
+    // Enables clicks for nodes inside a link element
+    else if (!this.pointerEvents && this.originalRoot == null) {
+      node.setAttribute('pointer-events', 'none');
+    }
+
+    // Removes invisible nodes from output if they don't handle events
+    if (
+      (node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||
+      (node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||
+      node.getAttribute('stroke') != 'none' ||
+      node.getAttribute('pointer-events') != 'none'
+    ) {
+      // LATER: Update existing DOM for performance
+      this.root.appendChild(node);
+    }
+
+    this.node = null;
+  }
+};
+
+/**
+ * Function: updateFill
+ *
+ * Transfers the stroke attributes from  to .
+ */
+mxSvgCanvas2D.prototype.updateFill = function () {
+  var s = this.state;
+
+  if (s.alpha < 1 || s.fillAlpha < 1) {
+    this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);
+  }
+
+  if (s.fillColor != null) {
+    if (s.gradientColor != null) {
+      var id = this.getSvgGradient(
+        String(s.fillColor),
+        String(s.gradientColor),
+        s.gradientFillAlpha,
+        s.gradientAlpha,
+        s.gradientDirection
+      );
+
+      if (this.root.ownerDocument == document && this.useAbsoluteIds) {
+        // Workaround for no fill with base tag in page (escape brackets)
+        var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
+        this.node.setAttribute('fill', 'url(' + base + '#' + id + ')');
+      } else {
+        this.node.setAttribute('fill', 'url(#' + id + ')');
+      }
+    } else {
+      this.node.setAttribute('fill', String(s.fillColor).toLowerCase());
+    }
+  }
+};
+
+/**
+ * Function: getCurrentStrokeWidth
+ *
+ * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
+ */
+mxSvgCanvas2D.prototype.getCurrentStrokeWidth = function () {
+  return Math.max(this.minStrokeWidth, Math.max(0.01, this.format(this.state.strokeWidth * this.state.scale)));
+};
+
+/**
+ * Function: updateStroke
+ *
+ * Transfers the stroke attributes from  to .
+ */
+mxSvgCanvas2D.prototype.updateStroke = function () {
+  var s = this.state;
+
+  this.node.setAttribute('stroke', String(s.strokeColor).toLowerCase());
+
+  if (s.alpha < 1 || s.strokeAlpha < 1) {
+    this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);
+  }
+
+  var sw = this.getCurrentStrokeWidth();
+
+  if (sw != 1) {
+    this.node.setAttribute('stroke-width', sw);
+  }
+
+  if (this.node.nodeName == 'path') {
+    this.updateStrokeAttributes();
+  }
+
+  if (s.dashed) {
+    this.node.setAttribute('stroke-dasharray', this.createDashPattern((s.fixDash ? 1 : s.strokeWidth) * s.scale));
+  }
+};
+
+/**
+ * Function: updateStrokeAttributes
+ *
+ * Transfers the stroke attributes from  to .
+ */
+mxSvgCanvas2D.prototype.updateStrokeAttributes = function () {
+  var s = this.state;
+
+  // Linejoin miter is default in SVG
+  if (s.lineJoin != null && s.lineJoin != 'miter') {
+    this.node.setAttribute('stroke-linejoin', s.lineJoin);
+  }
+
+  if (s.lineCap != null) {
+    // flat is called butt in SVG
+    var value = s.lineCap;
+
+    if (value == 'flat') {
+      value = 'butt';
+    }
+
+    // Linecap butt is default in SVG
+    if (value != 'butt') {
+      this.node.setAttribute('stroke-linecap', value);
+    }
+  }
+
+  // Miterlimit 10 is default in our document
+  if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10)) {
+    this.node.setAttribute('stroke-miterlimit', s.miterLimit);
+  }
+};
+
+/**
+ * Function: createDashPattern
+ *
+ * Creates the SVG dash pattern for the given state.
+ */
+mxSvgCanvas2D.prototype.createDashPattern = function (scale) {
+  var pat = [];
+
+  if (typeof this.state.dashPattern === 'string') {
+    var dash = this.state.dashPattern.split(' ');
+
+    if (dash.length > 0) {
+      for (var i = 0; i < dash.length; i++) {
+        pat[i] = Number(dash[i]) * scale;
+      }
+    }
+  }
+
+  return pat.join(' ');
+};
+
+/**
+ * Function: createTolerance
+ *
+ * Creates a hit detection tolerance shape for the given node.
+ */
+mxSvgCanvas2D.prototype.createTolerance = function (node) {
+  var tol = node.cloneNode(true);
+  var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;
+  tol.setAttribute('pointer-events', 'stroke');
+  tol.setAttribute('visibility', 'hidden');
+  tol.removeAttribute('stroke-dasharray');
+  tol.setAttribute('stroke-width', sw);
+  tol.setAttribute('fill', 'none');
+
+  // Workaround for Opera ignoring the visiblity attribute above while
+  // other browsers need a stroke color to perform the hit-detection but
+  // do not ignore the visibility attribute. Side-effect is that Opera's
+  // hit detection for horizontal/vertical edges seems to ignore the tol.
+  tol.setAttribute('stroke', mxClient.IS_OT ? 'none' : 'white');
+
+  return tol;
+};
+
+/**
+ * Function: createShadow
+ *
+ * Creates a shadow for the given node.
+ */
+mxSvgCanvas2D.prototype.createShadow = function (node) {
+  var shadow = node.cloneNode(true);
+  var s = this.state;
+
+  // Firefox uses transparent for no fill in ellipses
+  if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent')) {
+    shadow.setAttribute('fill', s.shadowColor);
+  }
+
+  if (shadow.getAttribute('stroke') != 'none') {
+    shadow.setAttribute('stroke', s.shadowColor);
+  }
+
+  shadow.setAttribute(
+    'transform',
+    'translate(' +
+      this.format(s.shadowDx * s.scale) +
+      ',' +
+      this.format(s.shadowDy * s.scale) +
+      ')' +
+      (s.transform || '')
+  );
+  shadow.setAttribute('opacity', s.shadowAlpha);
+
+  return shadow;
+};
+
+/**
+ * Function: setLink
+ *
+ * Experimental implementation for hyperlinks.
+ */
+mxSvgCanvas2D.prototype.setLink = function (link) {
+  if (link == null) {
+    this.root = this.originalRoot;
+  } else {
+    this.originalRoot = this.root;
+
+    var node = this.createElement('a');
+
+    // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
+    // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
+    if (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null)) {
+      node.setAttribute('xlink:href', link);
+    } else {
+      node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);
+    }
+
+    this.root.appendChild(node);
+    this.root = node;
+  }
+};
+
+/**
+ * Function: rotate
+ *
+ * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
+ */
+mxSvgCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
+  if (theta != 0 || flipH || flipV) {
+    var s = this.state;
+    cx += s.dx;
+    cy += s.dy;
+
+    cx *= s.scale;
+    cy *= s.scale;
+
+    s.transform = s.transform || '';
+
+    // This implementation uses custom scale/translate and built-in rotation
+    // Rotation state is part of the AffineTransform in state.transform
+    if (flipH && flipV) {
+      theta += 180;
+    } else if (flipH != flipV) {
+      var tx = flipH ? cx : 0;
+      var sx = flipH ? -1 : 1;
+
+      var ty = flipV ? cy : 0;
+      var sy = flipV ? -1 : 1;
+
+      s.transform +=
+        'translate(' +
+        this.format(tx) +
+        ',' +
+        this.format(ty) +
+        ')' +
+        'scale(' +
+        this.format(sx) +
+        ',' +
+        this.format(sy) +
+        ')' +
+        'translate(' +
+        this.format(-tx) +
+        ',' +
+        this.format(-ty) +
+        ')';
+    }
+
+    if (flipH ? !flipV : flipV) {
+      theta *= -1;
+    }
+
+    if (theta != 0) {
+      s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';
+    }
+
+    s.rotation = s.rotation + theta;
+    s.rotationCx = cx;
+    s.rotationCy = cy;
+  }
+};
+
+/**
+ * Function: begin
+ *
+ * Extends superclass to create path.
+ */
+mxSvgCanvas2D.prototype.begin = function () {
+  mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+  this.node = this.createElement('path');
+};
+
+/**
+ * Function: rect
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.rect = function (x, y, w, h) {
+  var s = this.state;
+  var n = this.createElement('rect');
+  n.setAttribute('x', this.format((x + s.dx) * s.scale));
+  n.setAttribute('y', this.format((y + s.dy) * s.scale));
+  n.setAttribute('width', this.format(w * s.scale));
+  n.setAttribute('height', this.format(h * s.scale));
+
+  this.node = n;
+};
+
+/**
+ * Function: roundrect
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.roundrect = function (x, y, w, h, dx, dy) {
+  this.rect(x, y, w, h);
+
+  if (dx > 0) {
+    this.node.setAttribute('rx', this.format(dx * this.state.scale));
+  }
+
+  if (dy > 0) {
+    this.node.setAttribute('ry', this.format(dy * this.state.scale));
+  }
+};
+
+/**
+ * Function: ellipse
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.ellipse = function (x, y, w, h) {
+  var s = this.state;
+  var n = this.createElement('ellipse');
+  // No rounding for consistent output with 1.x
+  n.setAttribute('cx', this.format((x + w / 2 + s.dx) * s.scale));
+  n.setAttribute('cy', this.format((y + h / 2 + s.dy) * s.scale));
+  n.setAttribute('rx', (w / 2) * s.scale);
+  n.setAttribute('ry', (h / 2) * s.scale);
+  this.node = n;
+};
+
+/**
+ * Function: image
+ *
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.image = function (x, y, w, h, src, aspect, flipH, flipV) {
+  src = this.converter.convert(src);
+
+  // LATER: Add option for embedding images as base64.
+  aspect = aspect != null ? aspect : true;
+  flipH = flipH != null ? flipH : false;
+  flipV = flipV != null ? flipV : false;
+
+  var s = this.state;
+  x += s.dx;
+  y += s.dy;
+
+  var node = this.createElement('image');
+  node.setAttribute('x', this.format(x * s.scale) + this.imageOffset);
+  node.setAttribute('y', this.format(y * s.scale) + this.imageOffset);
+  node.setAttribute('width', this.format(w * s.scale));
+  node.setAttribute('height', this.format(h * s.scale));
+
+  // Workaround for missing namespace support
+  if (node.setAttributeNS == null) {
+    node.setAttribute('xlink:href', src);
+  } else {
+    node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+  }
+
+  if (!aspect) {
+    node.setAttribute('preserveAspectRatio', 'none');
+  }
+
+  if (s.alpha < 1 || s.fillAlpha < 1) {
+    node.setAttribute('opacity', s.alpha * s.fillAlpha);
+  }
+
+  var tr = this.state.transform || '';
+
+  if (flipH || flipV) {
+    var sx = 1;
+    var sy = 1;
+    var dx = 0;
+    var dy = 0;
+
+    if (flipH) {
+      sx = -1;
+      dx = -w - 2 * x;
+    }
+
+    if (flipV) {
+      sy = -1;
+      dy = -h - 2 * y;
+    }
+
+    // Adds image tansformation to existing transform
+    tr += 'scale(' + sx + ',' + sy + ')translate(' + dx * s.scale + ',' + dy * s.scale + ')';
+  }
+
+  if (tr.length > 0) {
+    node.setAttribute('transform', tr);
+  }
+
+  if (!this.pointerEvents) {
+    node.setAttribute('pointer-events', 'none');
+  }
+
+  this.root.appendChild(node);
+};
+
+/**
+ * Function: convertHtml
+ *
+ * Converts the given HTML string to XHTML.
+ */
+mxSvgCanvas2D.prototype.convertHtml = function (val) {
+  if (this.useDomParser) {
+    var doc = new DOMParser().parseFromString(val, 'text/html');
+
+    if (doc != null) {
+      val = new XMLSerializer().serializeToString(doc.body);
+
+      // Extracts body content from DOM
+      if (val.substring(0, 5) == '', 5) + 1);
+      }
+
+      if (val.substring(val.length - 7, val.length) == '') {
+        val = val.substring(0, val.length - 7);
+      }
+    }
+  } else if (document.implementation != null && document.implementation.createDocument != null) {
+    var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
+    var xb = xd.createElement('body');
+    xd.documentElement.appendChild(xb);
+
+    var div = document.createElement('div');
+    div.innerHTML = val;
+    var child = div.firstChild;
+
+    while (child != null) {
+      var next = child.nextSibling;
+      xb.appendChild(xd.adoptNode(child));
+      child = next;
+    }
+
+    return xb.innerHTML;
+  } else {
+    var ta = document.createElement('textarea');
+
+    // Handles special HTML entities < and > and double escaping
+    // and converts unclosed br, hr and img tags to XHTML
+    // LATER: Convert all unclosed tags
+    ta.innerHTML = val
+      .replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(//g, '>');
+    val = ta.value
+      .replace(/&/g, '&')
+      .replace(/&lt;/g, '<')
+      .replace(/&gt;/g, '>')
+      .replace(/&amp;/g, '&')
+      .replace(/
/g, '
') + .replace(/
/g, '
') + .replace(/(]+)>/gm, '$1 />'); + } + + return val; +}; + +/** + * Function: createDiv + * + * Private helper function to create SVG elements + */ +mxSvgCanvas2D.prototype.createDiv = function (str) { + var val = str; + + if (!mxUtils.isNode(val)) { + val = '
' + this.convertHtml(val) + '
'; + } + + // IE uses this code for export as it cannot render foreignObjects + if (!mxClient.IS_IE && !mxClient.IS_IE11 && document.createElementNS) { + var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); + + if (mxUtils.isNode(val)) { + var div2 = document.createElement('div'); + var div3 = div2.cloneNode(false); + + // Creates a copy for export + if (this.root.ownerDocument != document) { + div2.appendChild(val.cloneNode(true)); + } else { + div2.appendChild(val); + } + + div3.appendChild(div2); + div.appendChild(div3); + } else { + div.innerHTML = val; + } + + return div; + } else { + if (mxUtils.isNode(val)) { + val = '
' + mxUtils.getXml(val) + '
'; + } + + val = '
' + val + '
'; + + // NOTE: FF 3.6 crashes if content CSS contains "height:100%" + return mxUtils.parseXml(val).documentElement; + } +}; + +/** + * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below. + */ +mxSvgCanvas2D.prototype.updateText = function (x, y, w, h, align, valign, wrap, overflow, clip, rotation, node) { + if (node != null && node.firstChild != null && node.firstChild.firstChild != null) { + this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node.firstChild); + } +}; + +/** + * Function: addForeignObject + * + * Creates a foreignObject for the given string and adds it to the given root. + */ +mxSvgCanvas2D.prototype.addForeignObject = function ( + x, + y, + w, + h, + str, + align, + valign, + wrap, + format, + overflow, + clip, + rotation, + dir, + div, + root +) { + var group = this.createElement('g'); + var fo = this.createElement('foreignObject'); + + // Workarounds for print clipping and static position in Safari + fo.setAttribute('style', 'overflow: visible; text-align: left;'); + fo.setAttribute('pointer-events', 'none'); + + // Import needed for older versions of IE + if (div.ownerDocument != document) { + div = mxUtils.importNodeImplementation(fo.ownerDocument, div, true); + } + + fo.appendChild(div); + group.appendChild(fo); + + this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, group); + + // Alternate content if foreignObject not supported + if (this.root.ownerDocument != document) { + var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); + + if (alt != null) { + fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility'); + var sw = this.createElement('switch'); + sw.appendChild(fo); + sw.appendChild(alt); + group.appendChild(sw); + } + } + + root.appendChild(group); +}; + +/** + * Updates existing DOM nodes for text rendering. + */ +mxSvgCanvas2D.prototype.updateTextNodes = function (x, y, w, h, align, valign, wrap, overflow, clip, rotation, g) { + var s = this.state.scale; + + mxSvgCanvas2D.createCss( + w + 2, + h, + align, + valign, + wrap, + overflow, + clip, + this.state.fontBackgroundColor != null ? this.state.fontBackgroundColor : null, + this.state.fontBorderColor != null ? this.state.fontBorderColor : null, + 'display: flex; align-items: unsafe ' + + (valign == mxConstants.ALIGN_TOP ? 'flex-start' : valign == mxConstants.ALIGN_BOTTOM ? 'flex-end' : 'center') + + '; ' + + 'justify-content: unsafe ' + + (align == mxConstants.ALIGN_LEFT ? 'flex-start' : align == mxConstants.ALIGN_RIGHT ? 'flex-end' : 'center') + + '; ', + this.getTextCss(), + s, + mxUtils.bind(this, function (dx, dy, flex, item, block) { + x += this.state.dx; + y += this.state.dy; + + var fo = g.firstChild; + var div = fo.firstChild; + var box = div.firstChild; + var text = box.firstChild; + var r = (this.rotateHtml ? this.state.rotation : 0) + (rotation != null ? rotation : 0); + var t = + (this.foOffset != 0 ? 'translate(' + this.foOffset + ' ' + this.foOffset + ')' : '') + + (s != 1 ? 'scale(' + s + ')' : ''); + + text.setAttribute('style', block); + box.setAttribute('style', item); + + // Workaround for clipping in Webkit with scrolling and zoom + fo.setAttribute('width', Math.ceil((1 / Math.min(1, s)) * 100) + '%'); + fo.setAttribute('height', Math.ceil((1 / Math.min(1, s)) * 100) + '%'); + var yp = Math.round(y + dy); + + // Allows for negative values which are causing problems with + // transformed content where the top edge of the foreignObject + // limits the text box being moved further up in the diagram. + // KNOWN: Possible clipping problems with zoom and scrolling + // but this is normally not used with scrollbars as the + // coordinates are always positive with scrollbars. + // Margin-top is ignored in Safari and no negative values allowed + // for padding. + if (yp < 0) { + fo.setAttribute('y', yp); + } else { + fo.removeAttribute('y'); + flex += 'padding-top: ' + yp + 'px; '; + } + + div.setAttribute('style', flex + 'margin-left: ' + Math.round(x + dx) + 'px;'); + t += r != 0 ? 'rotate(' + r + ' ' + x + ' ' + y + ')' : ''; + + // Output allows for reflow but Safari cannot use absolute position, + // transforms or opacity. https://bugs.webkit.org/show_bug.cgi?id=23113 + if (t != '') { + g.setAttribute('transform', t); + } else { + g.removeAttribute('transform'); + } + + if (this.state.alpha != 1) { + g.setAttribute('opacity', this.state.alpha); + } else { + g.removeAttribute('opacity'); + } + }) + ); +}; + +/** + * Updates existing DOM nodes for text rendering. + */ +mxSvgCanvas2D.createCss = function (w, h, align, valign, wrap, overflow, clip, bg, border, flex, block, s, callback) { + var item = + 'box-sizing: border-box; font-size: 0; text-align: ' + + (align == mxConstants.ALIGN_LEFT ? 'left' : align == mxConstants.ALIGN_RIGHT ? 'right' : 'center') + + '; '; + var pt = mxUtils.getAlignmentAsPoint(align, valign); + var ofl = 'overflow: hidden; '; + var fw = 'width: 1px; '; + var fh = 'height: 1px; '; + var dx = pt.x * w; + var dy = pt.y * h; + + if (clip) { + fw = 'width: ' + Math.round(w) + 'px; '; + item += 'max-height: ' + Math.round(h) + 'px; '; + dy = 0; + } else if (overflow == 'fill') { + fw = 'width: ' + Math.round(w) + 'px; '; + fh = 'height: ' + Math.round(h) + 'px; '; + block += 'width: 100%; height: 100%; '; + item += fw + fh; + } else if (overflow == 'width') { + fw = 'width: ' + Math.round(w) + 'px; '; + block += 'width: 100%; '; + item += fw; + dy = 0; + + if (h > 0) { + item += 'max-height: ' + Math.round(h) + 'px; '; + } + } else { + ofl = ''; + dy = 0; + } + + var bgc = ''; + + if (bg != null) { + bgc += 'background-color: ' + bg + '; '; + } + + if (border != null) { + bgc += 'border: 1px solid ' + border + '; '; + } + + if (ofl == '' || clip) { + block += bgc; + } else { + item += bgc; + } + + if (wrap && w > 0) { + block += 'white-space: normal; word-wrap: ' + mxConstants.WORD_WRAP + '; '; + fw = 'width: ' + Math.round(w) + 'px; '; + + if (ofl != '' && overflow != 'fill') { + dy = 0; + } + } else { + block += 'white-space: nowrap; '; + + if (ofl == '') { + dx = 0; + } + } + + callback(dx, dy, flex + fw + fh, item + ofl, block, ofl); +}; + +/** + * Function: getTextCss + * + * Private helper function to create SVG elements + */ +mxSvgCanvas2D.prototype.getTextCss = function () { + var s = this.state; + var lh = mxConstants.ABSOLUTE_LINE_HEIGHT + ? s.fontSize * mxConstants.LINE_HEIGHT + 'px' + : mxConstants.LINE_HEIGHT * this.lineHeightCorrection; + var css = + 'display: inline-block; font-size: ' + + s.fontSize + + 'px; ' + + 'font-family: ' + + s.fontFamily + + '; color: ' + + s.fontColor + + '; line-height: ' + + lh + + '; pointer-events: ' + + (this.pointerEvents ? this.pointerEventsValue : 'none') + + '; '; + + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + css += 'font-weight: bold; '; + } + + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + css += 'font-style: italic; '; + } + + var deco = []; + + if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + deco.push('underline'); + } + + if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { + deco.push('line-through'); + } + + if (deco.length > 0) { + css += 'text-decoration: ' + deco.join(' ') + '; '; + } + + return css; +}; + +/** + * Function: text + * + * Paints the given text. Possible values for format are empty string for plain + * text and html for HTML markup. Note that HTML markup is only supported if + * foreignObject is supported and is true. (This means IE9 and later + * does currently not support HTML text as part of shapes.) + */ +mxSvgCanvas2D.prototype.text = function (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) { + if (this.textEnabled && str != null) { + rotation = rotation != null ? rotation : 0; + + if (this.foEnabled && format == 'html') { + var div = this.createDiv(str); + + // Ignores invalid XHTML labels + if (div != null) { + if (dir != null) { + div.setAttribute('dir', dir); + } + + this.addForeignObject( + x, + y, + w, + h, + str, + align, + valign, + wrap, + format, + overflow, + clip, + rotation, + dir, + div, + this.root + ); + } + } else { + this.plainText( + x + this.state.dx, + y + this.state.dy, + w, + h, + str, + align, + valign, + wrap, + overflow, + clip, + rotation, + dir + ); + } + } +}; + +/** + * Function: createClip + * + * Creates a clip for the given coordinates. + */ +mxSvgCanvas2D.prototype.createClip = function (x, y, w, h) { + x = Math.round(x); + y = Math.round(y); + w = Math.round(w); + h = Math.round(h); + + var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h; + + var counter = 0; + var tmp = id + '-' + counter; + + // Resolves ID conflicts + while (document.getElementById(tmp) != null) { + tmp = id + '-' + ++counter; + } + + clip = this.createElement('clipPath'); + clip.setAttribute('id', tmp); + + var rect = this.createElement('rect'); + rect.setAttribute('x', x); + rect.setAttribute('y', y); + rect.setAttribute('width', w); + rect.setAttribute('height', h); + + clip.appendChild(rect); + + return clip; +}; + +/** + * Function: plainText + * + * Paints the given text. Possible values for format are empty string for + * plain text and html for HTML markup. + */ +mxSvgCanvas2D.prototype.plainText = function (x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir) { + rotation = rotation != null ? rotation : 0; + var s = this.state; + var size = s.fontSize; + var node = this.createElement('g'); + var tr = s.transform || ''; + this.updateFont(node); + + // Ignores pointer events + if (!this.pointerEvents && this.originalRoot == null) { + node.setAttribute('pointer-events', 'none'); + } + + // Non-rotated text + if (rotation != 0) { + tr += 'rotate(' + rotation + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')'; + } + + if (dir != null) { + node.setAttribute('direction', dir); + } + + if (clip && w > 0 && h > 0) { + var cx = x; + var cy = y; + + if (align == mxConstants.ALIGN_CENTER) { + cx -= w / 2; + } else if (align == mxConstants.ALIGN_RIGHT) { + cx -= w; + } + + if (overflow != 'fill') { + if (valign == mxConstants.ALIGN_MIDDLE) { + cy -= h / 2; + } else if (valign == mxConstants.ALIGN_BOTTOM) { + cy -= h; + } + } + + // LATER: Remove spacing from clip rectangle + var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4); + + if (this.defs != null) { + this.defs.appendChild(c); + } else { + // Makes sure clip is removed with referencing node + this.root.appendChild(c); + } + + if ( + !mxClient.IS_CHROMEAPP && + !mxClient.IS_IE && + !mxClient.IS_IE11 && + !mxClient.IS_EDGE && + this.root.ownerDocument == document + ) { + // Workaround for potential base tag + var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1'); + node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')'); + } else { + node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')'); + } + } + + // Default is left + var anchor = align == mxConstants.ALIGN_RIGHT ? 'end' : align == mxConstants.ALIGN_CENTER ? 'middle' : 'start'; + + // Text-anchor start is default in SVG + if (anchor != 'start') { + node.setAttribute('text-anchor', anchor); + } + + if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE) { + node.setAttribute('font-size', size * s.scale + 'px'); + } + + if (tr.length > 0) { + node.setAttribute('transform', tr); + } + + if (s.alpha < 1) { + node.setAttribute('opacity', s.alpha); + } + + var lines = str.split('\n'); + var lh = Math.round(size * mxConstants.LINE_HEIGHT); + var textHeight = size + (lines.length - 1) * lh; + + var cy = y + size - 1; + + if (valign == mxConstants.ALIGN_MIDDLE) { + if (overflow == 'fill') { + cy -= h / 2; + } else { + var dy = (this.matchHtmlAlignment && clip && h > 0 ? Math.min(textHeight, h) : textHeight) / 2; + cy -= dy; + } + } else if (valign == mxConstants.ALIGN_BOTTOM) { + if (overflow == 'fill') { + cy -= h; + } else { + var dy = this.matchHtmlAlignment && clip && h > 0 ? Math.min(textHeight, h) : textHeight; + cy -= dy + 1; + } + } + + for (var i = 0; i < lines.length; i++) { + // Workaround for bounding box of empty lines and spaces + if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0) { + var text = this.createElement('text'); + // LATER: Match horizontal HTML alignment + text.setAttribute('x', this.format(x * s.scale) + this.textOffset); + text.setAttribute('y', this.format(cy * s.scale) + this.textOffset); + + mxUtils.write(text, lines[i]); + node.appendChild(text); + } + + cy += lh; + } + + this.root.appendChild(node); + this.addTextBackground(node, str, x, y, w, overflow == 'fill' ? h : textHeight, align, valign, overflow); +}; + +/** + * Function: updateFont + * + * Updates the text properties for the given node. (NOTE: For this to work in + * IE, the given node must be a text or tspan element.) + */ +mxSvgCanvas2D.prototype.updateFont = function (node) { + var s = this.state; + + node.setAttribute('fill', s.fontColor); + + if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY) { + node.setAttribute('font-family', s.fontFamily); + } + + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + node.setAttribute('font-weight', 'bold'); + } + + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + node.setAttribute('font-style', 'italic'); + } + + var txtDecor = []; + + if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + txtDecor.push('underline'); + } + + if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { + txtDecor.push('line-through'); + } + + if (txtDecor.length > 0) { + node.setAttribute('text-decoration', txtDecor.join(' ')); + } +}; + +/** + * Function: addTextBackground + * + * Background color and border + */ +mxSvgCanvas2D.prototype.addTextBackground = function (node, str, x, y, w, h, align, valign, overflow) { + var s = this.state; + + if (s.fontBackgroundColor != null || s.fontBorderColor != null) { + var bbox = null; + + if (overflow == 'fill' || overflow == 'width') { + if (align == mxConstants.ALIGN_CENTER) { + x -= w / 2; + } else if (align == mxConstants.ALIGN_RIGHT) { + x -= w; + } + + if (valign == mxConstants.ALIGN_MIDDLE) { + y -= h / 2; + } else if (valign == mxConstants.ALIGN_BOTTOM) { + y -= h; + } + + bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale); + } else if (node.getBBox != null && this.root.ownerDocument == document) { + // Uses getBBox only if inside document for correct size + try { + bbox = node.getBBox(); + var ie = mxClient.IS_IE && mxClient.IS_SVG; + bbox = new mxRectangle(bbox.x, bbox.y + (ie ? 0 : 1), bbox.width, bbox.height + (ie ? 1 : 0)); + } catch (e) { + // Ignores NS_ERROR_FAILURE in FF if container display is none. + } + } + + if (bbox == null || bbox.width == 0 || bbox.height == 0) { + // Computes size if not in document or no getBBox available + var div = document.createElement('div'); + + // Wrapping and clipping can be ignored here + div.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT + ? s.fontSize * mxConstants.LINE_HEIGHT + 'px' + : mxConstants.LINE_HEIGHT; + div.style.fontSize = s.fontSize + 'px'; + div.style.fontFamily = s.fontFamily; + div.style.whiteSpace = 'nowrap'; + div.style.position = 'absolute'; + div.style.visibility = 'hidden'; + div.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block'; + div.style.zoom = '1'; + + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + div.style.fontWeight = 'bold'; + } + + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + div.style.fontStyle = 'italic'; + } + + str = mxUtils.htmlEntities(str, false); + div.innerHTML = str.replace(/\n/g, '
'); + + document.body.appendChild(div); + var w = div.offsetWidth; + var h = div.offsetHeight; + div.parentNode.removeChild(div); + + if (align == mxConstants.ALIGN_CENTER) { + x -= w / 2; + } else if (align == mxConstants.ALIGN_RIGHT) { + x -= w; + } + + if (valign == mxConstants.ALIGN_MIDDLE) { + y -= h / 2; + } else if (valign == mxConstants.ALIGN_BOTTOM) { + y -= h; + } + + bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale); + } + + if (bbox != null) { + var n = this.createElement('rect'); + n.setAttribute('fill', s.fontBackgroundColor || 'none'); + n.setAttribute('stroke', s.fontBorderColor || 'none'); + n.setAttribute('x', Math.floor(bbox.x - 1)); + n.setAttribute('y', Math.floor(bbox.y - 1)); + n.setAttribute('width', Math.ceil(bbox.width + 2)); + n.setAttribute('height', Math.ceil(bbox.height)); + + var sw = s.fontBorderColor != null ? Math.max(1, this.format(s.scale)) : 0; + n.setAttribute('stroke-width', sw); + + // Workaround for crisp rendering - only required if not exporting + if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1) { + n.setAttribute('transform', 'translate(0.5, 0.5)'); + } + + node.insertBefore(n, node.firstChild); + } + } +}; + +/** + * Function: stroke + * + * Paints the outline of the current path. + */ +mxSvgCanvas2D.prototype.stroke = function () { + this.addNode(false, true); +}; + +/** + * Function: fill + * + * Fills the current path. + */ +mxSvgCanvas2D.prototype.fill = function () { + this.addNode(true, false); +}; + +/** + * Function: fillAndStroke + * + * Fills and paints the outline of the current path. + */ +mxSvgCanvas2D.prototype.fillAndStroke = function () { + this.addNode(true, true); +}; + +/** + * util/mxVmlCanvas2D.js + */ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * + * Class: mxVmlCanvas2D + * + * Implements a canvas to be used for rendering VML. Here is an example of implementing a + * fallback for SVG images which are not supported in VML-based browsers. + * + * (code) + * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image; + * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV) + * { + * if (src.substring(src.length - 4, src.length) == '.svg') + * { + * src = 'http://www.jgraph.com/images/mxgraph.gif'; + * } + * + * mxVmlCanvas2DImage.apply(this, arguments); + * }; + * (end) + * + * To disable anti-aliasing in the output, use the following code. + * + * (code) + * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}'; + * (end) + * + * A description of the public API is available in . Note that + * there is a known issue in VML where gradients are painted using the outer + * bounding box of rotated shapes, not the actual bounds of the shape. See + * also for plain text label restrictions in shapes for VML. + */ +var mxVmlCanvas2D = function (root) { + mxAbstractCanvas2D.call(this); + + /** + * Variable: root + * + * Reference to the container for the SVG content. + */ + this.root = root; +}; + +/** + * Extends mxAbstractCanvas2D + */ +mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D); + +/** + * Variable: path + * + * Holds the current DOM node. + */ +mxVmlCanvas2D.prototype.node = null; + +/** + * Variable: textEnabled + * + * Specifies if text output should be enabledetB. Default is true. + */ +mxVmlCanvas2D.prototype.textEnabled = true; + +/** + * Variable: moveOp + * + * Contains the string used for moving in paths. Default is 'm'. + */ +mxVmlCanvas2D.prototype.moveOp = 'm'; + +/** + * Variable: lineOp + * + * Contains the string used for moving in paths. Default is 'l'. + */ +mxVmlCanvas2D.prototype.lineOp = 'l'; + +/** + * Variable: curveOp + * + * Contains the string used for bezier curves. Default is 'c'. + */ +mxVmlCanvas2D.prototype.curveOp = 'c'; + +/** + * Variable: closeOp + * + * Holds the operator for closing curves. Default is 'x e'. + */ +mxVmlCanvas2D.prototype.closeOp = 'x'; + +/** + * Variable: rotatedHtmlBackground + * + * Background color for rotated HTML. Default is ''. This can be set to eg. + * white to improve rendering of rotated text in VML for IE9. + */ +mxVmlCanvas2D.prototype.rotatedHtmlBackground = ''; + +/** + * Variable: vmlScale + * + * Specifies the scale used to draw VML shapes. + */ +mxVmlCanvas2D.prototype.vmlScale = 1; + +/** + * Function: createElement + * + * Creates the given element using the document. + */ +mxVmlCanvas2D.prototype.createElement = function (name) { + return document.createElement(name); +}; + +/** + * Function: createVmlElement + * + * Creates a new element using and prefixes the given name with + * . + */ +mxVmlCanvas2D.prototype.createVmlElement = function (name) { + return this.createElement(mxClient.VML_PREFIX + ':' + name); +}; + +/** + * Function: addNode + * + * Adds the current node to the . + */ +mxVmlCanvas2D.prototype.addNode = function (filled, stroked) { + var node = this.node; + var s = this.state; + + if (node != null) { + if (node.nodeName == 'shape') { + // Checks if the path is not empty + if (this.path != null && this.path.length > 0) { + node.path = this.path.join(' ') + ' e'; + node.style.width = this.root.style.width; + node.style.height = this.root.style.height; + node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height); + } else { + return; + } + } + + node.strokeweight = this.format(Math.max(1, (s.strokeWidth * s.scale) / this.vmlScale)) + 'px'; + + if (s.shadow) { + this.root.appendChild(this.createShadow(node, filled && s.fillColor != null, stroked && s.strokeColor != null)); + } + + if (stroked && s.strokeColor != null) { + node.stroked = 'true'; + node.strokecolor = s.strokeColor; + } else { + node.stroked = 'false'; + } + + node.appendChild(this.createStroke()); + + if (filled && s.fillColor != null) { + node.appendChild(this.createFill()); + } else if (this.pointerEvents && (node.nodeName != 'shape' || this.path[this.path.length - 1] == this.closeOp)) { + node.appendChild(this.createTransparentFill()); + } else { + node.filled = 'false'; + } + + // LATER: Update existing DOM for performance + this.root.appendChild(node); + } +}; + +/** + * Function: createTransparentFill + * + * Creates a transparent fill. + */ +mxVmlCanvas2D.prototype.createTransparentFill = function () { + var fill = this.createVmlElement('fill'); + fill.src = mxClient.imageBasePath + '/transparent.gif'; + fill.type = 'tile'; + + return fill; +}; + +/** + * Function: createFill + * + * Creates a fill for the current state. + */ +mxVmlCanvas2D.prototype.createFill = function () { + var s = this.state; + + // Gradients in foregrounds not supported because special gradients + // with bounds must be created for each element in graphics-canvases + var fill = this.createVmlElement('fill'); + fill.color = s.fillColor; + + if (s.gradientColor != null) { + fill.type = 'gradient'; + fill.method = 'none'; + fill.color2 = s.gradientColor; + var angle = 180 - s.rotation; + + if (s.gradientDirection == mxConstants.DIRECTION_WEST) { + angle -= 90 + (this.root.style.flip == 'x' ? 180 : 0); + } else if (s.gradientDirection == mxConstants.DIRECTION_EAST) { + angle += 90 + (this.root.style.flip == 'x' ? 180 : 0); + } else if (s.gradientDirection == mxConstants.DIRECTION_NORTH) { + angle -= 180 + (this.root.style.flip == 'y' ? -180 : 0); + } else { + angle += this.root.style.flip == 'y' ? -180 : 0; + } + + if (this.root.style.flip == 'x' || this.root.style.flip == 'y') { + angle *= -1; + } + + // LATER: Fix outer bounding box for rotated shapes used in VML. + fill.angle = mxUtils.mod(angle, 360); + fill.opacity = s.alpha * s.gradientFillAlpha * 100 + '%'; + fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', s.alpha * s.gradientAlpha * 100 + '%'); + } else if (s.alpha < 1 || s.fillAlpha < 1) { + fill.opacity = s.alpha * s.fillAlpha * 100 + '%'; + } + + return fill; +}; +/** + * Function: createStroke + * + * Creates a fill for the current state. + */ +mxVmlCanvas2D.prototype.createStroke = function () { + var s = this.state; + var stroke = this.createVmlElement('stroke'); + stroke.endcap = s.lineCap || 'flat'; + stroke.joinstyle = s.lineJoin || 'miter'; + stroke.miterlimit = s.miterLimit || '10'; + + if (s.alpha < 1 || s.strokeAlpha < 1) { + stroke.opacity = s.alpha * s.strokeAlpha * 100 + '%'; + } + + if (s.dashed) { + stroke.dashstyle = this.getVmlDashStyle(); + } + + return stroke; +}; + +/** + * Function: getVmlDashPattern + * + * Returns a VML dash pattern for the current dashPattern. + * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx + */ +mxVmlCanvas2D.prototype.getVmlDashStyle = function () { + var result = 'dash'; + + if (typeof this.state.dashPattern === 'string') { + var tok = this.state.dashPattern.split(' '); + + if (tok.length > 0 && tok[0] == 1) { + result = '0 2'; + } + } + + return result; +}; + +/** + * Function: createShadow + * + * Creates a shadow for the given node. + */ +mxVmlCanvas2D.prototype.createShadow = function (node, filled, stroked) { + var s = this.state; + var rad = -s.rotation * (Math.PI / 180); + var cos = Math.cos(rad); + var sin = Math.sin(rad); + + var dx = s.shadowDx * s.scale; + var dy = s.shadowDy * s.scale; + + if (this.root.style.flip == 'x') { + dx *= -1; + } else if (this.root.style.flip == 'y') { + dy *= -1; + } + + var shadow = node.cloneNode(true); + shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px'; + shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px'; + + // Workaround for wrong cloning in IE8 standards mode + if (document.documentMode == 8) { + shadow.strokeweight = node.strokeweight; + + if (node.nodeName == 'shape') { + shadow.path = this.path.join(' ') + ' e'; + shadow.style.width = this.root.style.width; + shadow.style.height = this.root.style.height; + shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height); + } + } + + if (stroked) { + shadow.strokecolor = s.shadowColor; + shadow.appendChild(this.createShadowStroke()); + } else { + shadow.stroked = 'false'; + } + + if (filled) { + shadow.appendChild(this.createShadowFill()); + } else { + shadow.filled = 'false'; + } + + return shadow; +}; + +/** + * Function: createShadowFill + * + * Creates the fill for the shadow. + */ +mxVmlCanvas2D.prototype.createShadowFill = function () { + var fill = this.createVmlElement('fill'); + fill.color = this.state.shadowColor; + fill.opacity = this.state.alpha * this.state.shadowAlpha * 100 + '%'; + + return fill; +}; + +/** + * Function: createShadowStroke + * + * Creates the stroke for the shadow. + */ +mxVmlCanvas2D.prototype.createShadowStroke = function () { + var stroke = this.createStroke(); + stroke.opacity = this.state.alpha * this.state.shadowAlpha * 100 + '%'; + + return stroke; +}; + +/** + * Function: rotate + * + * Sets the rotation of the canvas. Note that rotation cannot be concatenated. + */ +mxVmlCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) { + if (flipH && flipV) { + theta += 180; + } else if (flipH) { + this.root.style.flip = 'x'; + } else if (flipV) { + this.root.style.flip = 'y'; + } + + if (flipH ? !flipV : flipV) { + theta *= -1; + } + + this.root.style.rotation = theta; + this.state.rotation = this.state.rotation + theta; + this.state.rotationCx = cx; + this.state.rotationCy = cy; +}; + +/** + * Function: begin + * + * Extends superclass to create path. + */ +mxVmlCanvas2D.prototype.begin = function () { + mxAbstractCanvas2D.prototype.begin.apply(this, arguments); + this.node = this.createVmlElement('shape'); + this.node.style.position = 'absolute'; +}; + +/** + * Function: quadTo + * + * Replaces quadratic curve with bezier curve in VML. + */ +mxVmlCanvas2D.prototype.quadTo = function (x1, y1, x2, y2) { + var s = this.state; + + var cpx0 = (this.lastX + s.dx) * s.scale; + var cpy0 = (this.lastY + s.dy) * s.scale; + var qpx1 = (x1 + s.dx) * s.scale; + var qpy1 = (y1 + s.dy) * s.scale; + var cpx3 = (x2 + s.dx) * s.scale; + var cpy3 = (y2 + s.dy) * s.scale; + + var cpx1 = cpx0 + (2 / 3) * (qpx1 - cpx0); + var cpy1 = cpy0 + (2 / 3) * (qpy1 - cpy0); + + var cpx2 = cpx3 + (2 / 3) * (qpx1 - cpx3); + var cpy2 = cpy3 + (2 / 3) * (qpy1 - cpy3); + + this.path.push( + 'c ' + + this.format(cpx1) + + ' ' + + this.format(cpy1) + + ' ' + + this.format(cpx2) + + ' ' + + this.format(cpy2) + + ' ' + + this.format(cpx3) + + ' ' + + this.format(cpy3) + ); + this.lastX = cpx3 / s.scale - s.dx; + this.lastY = cpy3 / s.scale - s.dy; +}; + +/** + * Function: createRect + * + * Sets the glass gradient. + */ +mxVmlCanvas2D.prototype.createRect = function (nodeName, x, y, w, h) { + var s = this.state; + var n = this.createVmlElement(nodeName); + n.style.position = 'absolute'; + n.style.left = this.format((x + s.dx) * s.scale) + 'px'; + n.style.top = this.format((y + s.dy) * s.scale) + 'px'; + n.style.width = this.format(w * s.scale) + 'px'; + n.style.height = this.format(h * s.scale) + 'px'; + + return n; +}; + +/** + * Function: rect + * + * Sets the current path to a rectangle. + */ +mxVmlCanvas2D.prototype.rect = function (x, y, w, h) { + this.node = this.createRect('rect', x, y, w, h); +}; + +/** + * Function: roundrect + * + * Sets the current path to a rounded rectangle. + */ +mxVmlCanvas2D.prototype.roundrect = function (x, y, w, h, dx, dy) { + this.node = this.createRect('roundrect', x, y, w, h); + // SetAttribute needed here for IE8 + this.node.setAttribute('arcsize', Math.max((dx * 100) / w, (dy * 100) / h) + '%'); +}; + +/** + * Function: ellipse + * + * Sets the current path to an ellipse. + */ +mxVmlCanvas2D.prototype.ellipse = function (x, y, w, h) { + this.node = this.createRect('oval', x, y, w, h); +}; + +/** + * Function: image + * + * Paints an image. + */ +mxVmlCanvas2D.prototype.image = function (x, y, w, h, src, aspect, flipH, flipV) { + var node = null; + + if (!aspect) { + node = this.createRect('image', x, y, w, h); + node.src = src; + } else { + // Uses fill with aspect to avoid asynchronous update of size + node = this.createRect('rect', x, y, w, h); + node.stroked = 'false'; + + // Handles image aspect via fill + var fill = this.createVmlElement('fill'); + fill.aspect = aspect ? 'atmost' : 'ignore'; + fill.rotate = 'true'; + fill.type = 'frame'; + fill.src = src; + + node.appendChild(fill); + } + + if (flipH && flipV) { + node.style.rotation = '180'; + } else if (flipH) { + node.style.flip = 'x'; + } else if (flipV) { + node.style.flip = 'y'; + } + + if (this.state.alpha < 1 || this.state.fillAlpha < 1) { + // KNOWN: Borders around transparent images in IE<9. Using fill.opacity + // fixes this problem by adding a white background in all IE versions. + node.style.filter += 'alpha(opacity=' + this.state.alpha * this.state.fillAlpha * 100 + ')'; + } + + this.root.appendChild(node); +}; + +/** + * Function: createText + * + * Creates the innermost element that contains the HTML text. + */ +mxVmlCanvas2D.prototype.createDiv = function (str, align, valign, overflow) { + var div = this.createElement('div'); + var state = this.state; + + var css = ''; + + if (state.fontBackgroundColor != null) { + css += 'background-color:' + mxUtils.htmlEntities(state.fontBackgroundColor) + ';'; + } + + if (state.fontBorderColor != null) { + css += 'border:1px solid ' + mxUtils.htmlEntities(state.fontBorderColor) + ';'; + } + + if (mxUtils.isNode(str)) { + div.appendChild(str); + } else { + if (overflow != 'fill' && overflow != 'width') { + var div2 = this.createElement('div'); + div2.style.cssText = css; + div2.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block'; + div2.style.zoom = '1'; + div2.style.textDecoration = 'inherit'; + div2.innerHTML = str; + div.appendChild(div2); + } else { + div.style.cssText = css; + div.innerHTML = str; + } + } + + var style = div.style; + + style.fontSize = state.fontSize / this.vmlScale + 'px'; + style.fontFamily = state.fontFamily; + style.color = state.fontColor; + style.verticalAlign = 'top'; + style.textAlign = align || 'left'; + style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT + ? (state.fontSize * mxConstants.LINE_HEIGHT) / this.vmlScale + 'px' + : mxConstants.LINE_HEIGHT; + + if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + style.fontWeight = 'bold'; + } + + if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + style.fontStyle = 'italic'; + } + + if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + style.textDecoration = 'underline'; + } + + return div; +}; + +/** + * Function: text + * + * Paints the given text. Possible values for format are empty string for plain + * text and html for HTML markup. Clipping, text background and border are not + * supported for plain text in VML. + */ +mxVmlCanvas2D.prototype.text = function (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) { + if (this.textEnabled && str != null) { + var s = this.state; + + if (format == 'html') { + if (s.rotation != null) { + var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy); + + x = pt.x; + y = pt.y; + } + + if (document.documentMode == 8 && !mxClient.IS_EM) { + x += s.dx; + y += s.dy; + + // Workaround for rendering offsets + if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP) { + y -= 1; + } + } else { + x *= s.scale; + y *= s.scale; + } + + // Adds event transparency in IE8 standards without the transparent background + // filter which cannot be used due to bugs in the zoomed bounding box (too slow) + // FIXME: No event transparency if inside v:rect (ie part of shape) + // KNOWN: Offset wrong for rotated text with word that are longer than the wrapping + // width in IE8 because real width of text cannot be determined here. + // This should be fixed in mxText.updateBoundingBox by calling before this and + // passing the real width to this method if not clipped and wrapped. + var abs = + document.documentMode == 8 && !mxClient.IS_EM ? this.createVmlElement('group') : this.createElement('div'); + abs.style.position = 'absolute'; + abs.style.display = 'inline'; + abs.style.left = this.format(x) + 'px'; + abs.style.top = this.format(y) + 'px'; + abs.style.zoom = s.scale; + + var box = this.createElement('div'); + box.style.position = 'relative'; + box.style.display = 'inline'; + + var margin = mxUtils.getAlignmentAsPoint(align, valign); + var dx = margin.x; + var dy = margin.y; + + var div = this.createDiv(str, align, valign, overflow); + var inner = this.createElement('div'); + + if (dir != null) { + div.setAttribute('dir', dir); + } + + if (wrap && w > 0) { + if (!clip) { + div.style.width = Math.round(w) + 'px'; + } + + div.style.wordWrap = mxConstants.WORD_WRAP; + div.style.whiteSpace = 'normal'; + + // LATER: Check if other cases need to be handled + if (div.style.wordWrap == 'break-word') { + var tmp = div; + + if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV') { + tmp.firstChild.style.width = '100%'; + } + } + } else { + div.style.whiteSpace = 'nowrap'; + } + + var rot = s.rotation + (rotation || 0); + + if (this.rotateHtml && rot != 0) { + inner.style.display = 'inline'; + inner.style.zoom = '1'; + inner.appendChild(div); + + // Box not needed for rendering in IE8 standards + if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV') { + box.appendChild(inner); + abs.appendChild(box); + } else { + abs.appendChild(inner); + } + } else if (document.documentMode == 8 && !mxClient.IS_EM) { + box.appendChild(div); + abs.appendChild(box); + } else { + div.style.display = 'inline'; + abs.appendChild(div); + } + + // Inserts the node into the DOM + if (this.root.nodeName != 'DIV') { + // Rectangle to fix position in group + var rect = this.createVmlElement('rect'); + rect.stroked = 'false'; + rect.filled = 'false'; + + rect.appendChild(abs); + this.root.appendChild(rect); + } else { + this.root.appendChild(abs); + } + + if (clip) { + div.style.overflow = 'hidden'; + div.style.width = Math.round(w) + 'px'; + + if (!mxClient.IS_QUIRKS) { + div.style.maxHeight = Math.round(h) + 'px'; + } + } else if (overflow == 'fill') { + // KNOWN: Affects horizontal alignment in quirks + // but fill should only be used with align=left + div.style.overflow = 'hidden'; + div.style.width = Math.max(0, w) + 1 + 'px'; + div.style.height = Math.max(0, h) + 1 + 'px'; + } else if (overflow == 'width') { + // KNOWN: Affects horizontal alignment in quirks + // but fill should only be used with align=left + div.style.overflow = 'hidden'; + div.style.width = Math.max(0, w) + 1 + 'px'; + div.style.maxHeight = Math.max(0, h) + 1 + 'px'; + } + + if (this.rotateHtml && rot != 0) { + var rad = rot * (Math.PI / 180); + + // Precalculate cos and sin for the rotation + var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8)); + var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8)); + + rad %= 2 * Math.PI; + if (rad < 0) rad += 2 * Math.PI; + rad %= Math.PI; + if (rad > Math.PI / 2) rad = Math.PI - rad; + + var cos = Math.cos(rad); + var sin = Math.sin(rad); + + // Adds div to document to measure size + if (document.documentMode == 8 && !mxClient.IS_EM) { + div.style.display = 'inline-block'; + inner.style.display = 'inline-block'; + box.style.display = 'inline-block'; + } + + div.style.visibility = 'hidden'; + div.style.position = 'absolute'; + document.body.appendChild(div); + + var sizeDiv = div; + + if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') { + sizeDiv = sizeDiv.firstChild; + } + + var tmp = sizeDiv.offsetWidth + 3; + var oh = sizeDiv.offsetHeight; + + if (clip) { + w = Math.min(w, tmp); + oh = Math.min(oh, h); + } else { + w = tmp; + } + + // Handles words that are longer than the given wrapping width + if (wrap) { + div.style.width = w + 'px'; + } + + // Simulates max-height in quirks + if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h) { + oh = h; + + // Quirks does not support maxHeight + div.style.height = oh + 'px'; + } + + h = oh; + + var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5); + var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5); + + if (abs.nodeName == 'group' && this.root.nodeName == 'DIV') { + // Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards + var pos = this.createElement('div'); + pos.style.display = 'inline-block'; + pos.style.position = 'absolute'; + pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px'; + pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px'; + + abs.parentNode.appendChild(pos); + pos.appendChild(abs); + } else { + var sc = document.documentMode == 8 && !mxClient.IS_EM ? 1 : s.scale; + + abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px'; + abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px'; + } + + // KNOWN: Rotated text rendering quality is bad for IE9 quirks + inner.style.filter = + 'progid:DXImageTransform.Microsoft.Matrix(M11=' + + real_cos + + ', M12=' + + real_sin + + ', M21=' + + -real_sin + + ', M22=' + + real_cos + + ", sizingMethod='auto expand')"; + inner.style.backgroundColor = this.rotatedHtmlBackground; + + if (this.state.alpha < 1) { + inner.style.filter += 'alpha(opacity=' + this.state.alpha * 100 + ')'; + } + + // Restore parent node for DIV + inner.appendChild(div); + div.style.position = ''; + div.style.visibility = ''; + } else if (document.documentMode != 8 || mxClient.IS_EM) { + div.style.verticalAlign = 'top'; + + if (this.state.alpha < 1) { + abs.style.filter = 'alpha(opacity=' + this.state.alpha * 100 + ')'; + } + + // Adds div to document to measure size + var divParent = div.parentNode; + div.style.visibility = 'hidden'; + document.body.appendChild(div); + + w = div.offsetWidth; + var oh = div.offsetHeight; + + // Simulates max-height in quirks + if (mxClient.IS_QUIRKS && clip && oh > h) { + oh = h; + + // Quirks does not support maxHeight + div.style.height = oh + 'px'; + } + + h = oh; + + div.style.visibility = ''; + divParent.appendChild(div); + + abs.style.left = this.format(x + w * dx * this.state.scale) + 'px'; + abs.style.top = this.format(y + h * dy * this.state.scale) + 'px'; + } else { + if (this.state.alpha < 1) { + div.style.filter = 'alpha(opacity=' + this.state.alpha * 100 + ')'; + } + + // Faster rendering in IE8 without offsetWidth/Height + box.style.left = dx * 100 + '%'; + box.style.top = dy * 100 + '%'; + } + } else { + this.plainText( + x, + y, + w, + h, + mxUtils.htmlEntities(str, false), + align, + valign, + wrap, + format, + overflow, + clip, + rotation, + dir + ); + } + } +}; + +/** + * Function: plainText + * + * Paints the outline of the current path. + */ +mxVmlCanvas2D.prototype.plainText = function ( + x, + y, + w, + h, + str, + align, + valign, + wrap, + format, + overflow, + clip, + rotation, + dir +) { + // TextDirection is ignored since this code is not used (format is always HTML in the text function) + var s = this.state; + x = (x + s.dx) * s.scale; + y = (y + s.dy) * s.scale; + + var node = this.createVmlElement('shape'); + node.style.width = '1px'; + node.style.height = '1px'; + node.stroked = 'false'; + + var fill = this.createVmlElement('fill'); + fill.color = s.fontColor; + fill.opacity = s.alpha * 100 + '%'; + node.appendChild(fill); + + var path = this.createVmlElement('path'); + path.textpathok = 'true'; + path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0); + + node.appendChild(path); + + // KNOWN: Font family and text decoration ignored + var tp = this.createVmlElement('textpath'); + tp.style.cssText = 'v-text-align:' + align; + tp.style.align = align; + tp.style.fontFamily = s.fontFamily; + tp.string = str; + tp.on = 'true'; + + // Scale via fontsize instead of node.style.zoom for correct offsets in IE8 + var size = (s.fontSize * s.scale) / this.vmlScale; + tp.style.fontSize = size + 'px'; + + // Bold + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + tp.style.fontWeight = 'bold'; + } + + // Italic + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + tp.style.fontStyle = 'italic'; + } + + // Underline + if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + tp.style.textDecoration = 'underline'; + } + + var lines = str.split('\n'); + var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT; + var dx = 0; + var dy = 0; + + if (valign == mxConstants.ALIGN_BOTTOM) { + dy = -textHeight / 2; + } else if (valign != mxConstants.ALIGN_MIDDLE) { + // top + dy = textHeight / 2; + } + + if (rotation != null) { + node.style.rotation = rotation; + var rad = rotation * (Math.PI / 180); + dx = Math.sin(rad) * dy; + dy = Math.cos(rad) * dy; + } + + // FIXME: Clipping is relative to bounding box + /*if (clip) + { + node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)'; + }*/ + + node.appendChild(tp); + node.style.left = this.format(x - dx) + 'px'; + node.style.top = this.format(y + dy) + 'px'; + + this.root.appendChild(node); +}; + +/** + * Function: stroke + * + * Paints the outline of the current path. + */ +mxVmlCanvas2D.prototype.stroke = function () { + this.addNode(false, true); +}; + +/** + * Function: fill + * + * Fills the current path. + */ +mxVmlCanvas2D.prototype.fill = function () { + this.addNode(true, false); +}; + +/** + * Function: fillAndStroke + * + * Fills and paints the outline of the current path. + */ +mxVmlCanvas2D.prototype.fillAndStroke = function () { + this.addNode(true, true); +}; + +/** + * util/mxGuide.js + */ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxGuide + * + * Implements the alignment of selection cells to other cells in the graph. + * + * Constructor: mxGuide + * + * Constructs a new guide object. + */ +function mxGuide(graph, states) { + this.graph = graph; + this.setStates(states); +} + +/** + * Variable: graph + * + * Reference to the enclosing instance. + */ +mxGuide.prototype.graph = null; + +/** + * Variable: states + * + * Contains the that are used for alignment. + */ +mxGuide.prototype.states = null; + +/** + * Variable: horizontal + * + * Specifies if horizontal guides are enabled. Default is true. + */ +mxGuide.prototype.horizontal = true; + +/** + * Variable: vertical + * + * Specifies if vertical guides are enabled. Default is true. + */ +mxGuide.prototype.vertical = true; + +/** + * Variable: guideX + * + * Holds the for the horizontal guide. + */ +mxGuide.prototype.guideX = null; + +/** + * Variable: guideY + * + * Holds the for the vertical guide. + */ +mxGuide.prototype.guideY = null; + +/** + * Variable: rounded + * + * Specifies if rounded coordinates should be used. Default is false. + */ +mxGuide.prototype.rounded = false; + +/** + * Variable: tolerance + * + * Default tolerance in px if grid is disabled. Default is 2. + */ +mxGuide.prototype.tolerance = 2; + +/** + * Function: setStates + * + * Sets the that should be used for alignment. + */ +mxGuide.prototype.setStates = function (states) { + this.states = states; +}; + +/** + * Function: isEnabledForEvent + * + * Returns true if the guide should be enabled for the given native event. This + * implementation always returns true. + */ +mxGuide.prototype.isEnabledForEvent = function (evt) { + return true; +}; + +/** + * Function: getGuideTolerance + * + * Returns the tolerance for the guides. Default value is gridSize / 2. + */ +mxGuide.prototype.getGuideTolerance = function (gridEnabled) { + return gridEnabled && this.graph.gridEnabled ? this.graph.gridSize / 2 : this.tolerance; +}; + +/** + * Function: createGuideShape + * + * Returns the mxShape to be used for painting the respective guide. This + * implementation returns a new, dashed and crisp using + * and as the format. + * + * Parameters: + * + * horizontal - Boolean that specifies which guide should be created. + */ +mxGuide.prototype.createGuideShape = function (horizontal) { + var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); + guide.isDashed = true; + + return guide; +}; + +/** + * Function: isStateIgnored + * + * Returns true if the given state should be ignored. + */ +mxGuide.prototype.isStateIgnored = function (state) { + return false; +}; + +/** + * Function: move + * + * Moves the by the given and returnt the snapped point. + */ +mxGuide.prototype.move = function (bounds, delta, gridEnabled, clone) { + if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null) { + var scale = this.graph.getView().scale; + var tt = this.getGuideTolerance(gridEnabled) * scale; + var b = bounds.clone(); + b.x += delta.x; + b.y += delta.y; + var overrideX = false; + var stateX = null; + var valueX = null; + var overrideY = false; + var stateY = null; + var valueY = null; + var ttX = tt; + var ttY = tt; + var left = b.x; + var right = b.x + b.width; + var center = b.getCenterX(); + var top = b.y; + var bottom = b.y + b.height; + var middle = b.getCenterY(); + + // Snaps the left, center and right to the given x-coordinate + function snapX(x, state, centerAlign) { + var override = false; + + if (centerAlign && Math.abs(x - center) < ttX) { + delta.x = x - bounds.getCenterX(); + ttX = Math.abs(x - center); + override = true; + } else if (!centerAlign) { + if (Math.abs(x - left) < ttX) { + delta.x = x - bounds.x; + ttX = Math.abs(x - left); + override = true; + } else if (Math.abs(x - right) < ttX) { + delta.x = x - bounds.x - bounds.width; + ttX = Math.abs(x - right); + override = true; + } + } + + if (override) { + stateX = state; + valueX = x; + + if (this.guideX == null) { + this.guideX = this.createGuideShape(true); + + // Makes sure to use either VML or SVG shapes in order to implement + // event-transparency on the background area of the rectangle since + // HTML shapes do not let mouseevents through even when transparent + this.guideX.dialect = + this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.guideX.pointerEvents = false; + this.guideX.init(this.graph.getView().getOverlayPane()); + } + } + + overrideX = overrideX || override; + } + + // Snaps the top, middle or bottom to the given y-coordinate + function snapY(y, state, centerAlign) { + var override = false; + + if (centerAlign && Math.abs(y - middle) < ttY) { + delta.y = y - bounds.getCenterY(); + ttY = Math.abs(y - middle); + override = true; + } else if (!centerAlign) { + if (Math.abs(y - top) < ttY) { + delta.y = y - bounds.y; + ttY = Math.abs(y - top); + override = true; + } else if (Math.abs(y - bottom) < ttY) { + delta.y = y - bounds.y - bounds.height; + ttY = Math.abs(y - bottom); + override = true; + } + } + + if (override) { + stateY = state; + valueY = y; + + if (this.guideY == null) { + this.guideY = this.createGuideShape(false); + + // Makes sure to use either VML or SVG shapes in order to implement + // event-transparency on the background area of the rectangle since + // HTML shapes do not let mouseevents through even when transparent + this.guideY.dialect = + this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.guideY.pointerEvents = false; + this.guideY.init(this.graph.getView().getOverlayPane()); + } + } + + overrideY = overrideY || override; + } + + for (var i = 0; i < this.states.length; i++) { + var state = this.states[i]; + + if (state != null && !this.isStateIgnored(state)) { + // Align x + if (this.horizontal) { + snapX.call(this, state.getCenterX(), state, true); + snapX.call(this, state.x, state, false); + snapX.call(this, state.x + state.width, state, false); + + // Aligns left and right of shape to center of page + if (state.cell == null) { + snapX.call(this, state.getCenterX(), state, false); + } + } + + // Align y + if (this.vertical) { + snapY.call(this, state.getCenterY(), state, true); + snapY.call(this, state.y, state, false); + snapY.call(this, state.y + state.height, state, false); + + // Aligns left and right of shape to center of page + if (state.cell == null) { + snapY.call(this, state.getCenterY(), state, false); + } + } + } + } + + // Moves cells to the raster if not aligned + this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY); + delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y); + + // Redraws the guides + var c = this.graph.container; + + if (!overrideX && this.guideX != null) { + this.guideX.node.style.visibility = 'hidden'; + } else if (this.guideX != null) { + var minY = null; + var maxY = null; + + if (stateX != null && bounds != null) { + minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y); + maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.panDy, stateX.y + stateX.height); + } + + if (minY != null && maxY != null) { + this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)]; + } else { + this.guideX.points = [ + new mxPoint(valueX, -this.graph.panDy), + new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy), + ]; + } + + this.guideX.stroke = this.getGuideColor(stateX, true); + this.guideX.node.style.visibility = 'visible'; + this.guideX.redraw(); + } + + if (!overrideY && this.guideY != null) { + this.guideY.node.style.visibility = 'hidden'; + } else if (this.guideY != null) { + var minX = null; + var maxX = null; + + if (stateY != null && bounds != null) { + minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x); + maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.panDx, stateY.x + stateY.width); + } + + if (minX != null && maxX != null) { + this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)]; + } else { + this.guideY.points = [ + new mxPoint(-this.graph.panDx, valueY), + new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY), + ]; + } + + this.guideY.stroke = this.getGuideColor(stateY, false); + this.guideY.node.style.visibility = 'visible'; + this.guideY.redraw(); + } + } + + return delta; +}; + +/** + * Function: getDelta + * + * Rounds to pixels for virtual states (eg. page guides) + */ +mxGuide.prototype.getDelta = function (bounds, stateX, dx, stateY, dy) { + var s = this.graph.view.scale; + + if (this.rounded || (stateX != null && stateX.cell == null)) { + dx = Math.round((bounds.x + dx) / s) * s - bounds.x; + } + + if (this.rounded || (stateY != null && stateY.cell == null)) { + dy = Math.round((bounds.y + dy) / s) * s - bounds.y; + } + + return new mxPoint(dx, dy); +}; + +/** + * Function: getGuideColor + * + * Returns the color for the given state. + */ +mxGuide.prototype.getGuideColor = function (state, horizontal) { + return mxConstants.GUIDE_COLOR; +}; + +/** + * Function: hide + * + * Hides all current guides. + */ +mxGuide.prototype.hide = function () { + this.setVisible(false); +}; + +/** + * Function: setVisible + * + * Shows or hides the current guides. + */ +mxGuide.prototype.setVisible = function (visible) { + if (this.guideX != null) { + this.guideX.node.style.visibility = visible ? 'visible' : 'hidden'; + } + + if (this.guideY != null) { + this.guideY.node.style.visibility = visible ? 'visible' : 'hidden'; + } +}; + +/** + * Function: destroy + * + * Destroys all resources that this object uses. + */ +mxGuide.prototype.destroy = function () { + if (this.guideX != null) { + this.guideX.destroy(); + this.guideX = null; + } + + if (this.guideY != null) { + this.guideY.destroy(); + this.guideY = null; + } +}; + +/** + * shape/mxShape.js + */ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxShape + * + * Base class for all shapes. A shape in mxGraph is a + * separate implementation for SVG, VML and HTML. Which + * implementation to use is controlled by the + * property which is assigned from within the + * when the shape is created. The dialect must be assigned + * for a shape, and it does normally depend on the browser and + * the confiuration of the graph (see rendering hint). + * + * For each supported shape in SVG and VML, a corresponding + * shape exists in mxGraph, namely for text, image, rectangle, + * rhombus, ellipse and polyline. The other shapes are a + * combination of these shapes (eg. label and swimlane) + * or they consist of one or more (filled) path objects + * (eg. actor and cylinder). The HTML implementation is + * optional but may be required for a HTML-only view of + * the graph. + * + * Custom Shapes: + * + * To extend from this class, the basic code looks as follows. + * In the special case where the custom shape consists only of + * one filled region or one filled region and an additional stroke + * the and should be subclassed, + * respectively. + * + * (code) + * function CustomShape() { } + * + * CustomShape.prototype = new mxShape(); + * CustomShape.prototype.constructor = CustomShape; + * (end) + * + * To register a custom shape in an existing graph instance, + * one must register the shape under a new name in the graph's + * cell renderer as follows: + * + * (code) + * mxCellRenderer.registerShape('customShape', CustomShape); + * (end) + * + * The second argument is the name of the constructor. + * + * In order to use the shape you can refer to the given name above + * in a stylesheet. For example, to change the shape for the default + * vertex style, the following code is used: + * + * (code) + * var style = graph.getStylesheet().getDefaultVertexStyle(); + * style[mxConstants.STYLE_SHAPE] = 'customShape'; + * (end) + * + * Constructor: mxShape + * + * Constructs a new shape. + */ +function mxShape(stencil) { + this.stencil = stencil; + this.initStyles(); +} + +/** + * Variable: dialect + * + * Holds the dialect in which the shape is to be painted. + * This can be one of the DIALECT constants in . + */ +mxShape.prototype.dialect = null; + +/** + * Variable: scale + * + * Holds the scale in which the shape is being painted. + */ +mxShape.prototype.scale = 1; + +/** + * Variable: antiAlias + * + * Rendering hint for configuring the canvas. + */ +mxShape.prototype.antiAlias = true; + +/** + * Variable: minSvgStrokeWidth + * + * Minimum stroke width for SVG output. + */ +mxShape.prototype.minSvgStrokeWidth = 1; + +/** + * Variable: bounds + * + * Holds the that specifies the bounds of this shape. + */ +mxShape.prototype.bounds = null; + +/** + * Variable: points + * + * Holds the array of that specify the points of this shape. + */ +mxShape.prototype.points = null; + +/** + * Variable: node + * + * Holds the outermost DOM node that represents this shape. + */ +mxShape.prototype.node = null; + +/** + * Variable: state + * + * Optional reference to the corresponding . + */ +mxShape.prototype.state = null; + +/** + * Variable: style + * + * Optional reference to the style of the corresponding . + */ +mxShape.prototype.style = null; + +/** + * Variable: boundingBox + * + * Contains the bounding box of the shape, that is, the smallest rectangle + * that includes all pixels of the shape. + */ +mxShape.prototype.boundingBox = null; + +/** + * Variable: stencil + * + * Holds the that defines the shape. + */ +mxShape.prototype.stencil = null; + +/** + * Variable: svgStrokeTolerance + * + * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed + * to the canvas in if is true. + */ +mxShape.prototype.svgStrokeTolerance = 8; + +/** + * Variable: pointerEvents + * + * Specifies if pointer events should be handled. Default is true. + */ +mxShape.prototype.pointerEvents = true; + +/** + * Variable: svgPointerEvents + * + * Specifies if pointer events should be handled. Default is true. + */ +mxShape.prototype.svgPointerEvents = 'all'; + +/** + * Variable: shapePointerEvents + * + * Specifies if pointer events outside of shape should be handled. Default + * is false. + */ +mxShape.prototype.shapePointerEvents = false; + +/** + * Variable: stencilPointerEvents + * + * Specifies if pointer events outside of stencils should be handled. Default + * is false. Set this to true for backwards compatibility with the 1.x branch. + */ +mxShape.prototype.stencilPointerEvents = false; + +/** + * Variable: vmlScale + * + * Scale for improving the precision of VML rendering. Default is 1. + */ +mxShape.prototype.vmlScale = 1; + +/** + * Variable: outline + * + * Specifies if the shape should be drawn as an outline. This disables all + * fill colors and can be used to disable other drawing states that should + * not be painted for outlines. Default is false. This should be set before + * calling . + */ +mxShape.prototype.outline = false; + +/** + * Variable: visible + * + * Specifies if the shape is visible. Default is true. + */ +mxShape.prototype.visible = true; + +/** + * Variable: useSvgBoundingBox + * + * Allows to use the SVG bounding box in SVG. Default is false for performance + * reasons. + */ +mxShape.prototype.useSvgBoundingBox = false; + +/** + * Function: init + * + * Initializes the shape by creaing the DOM node using + * and adding it into the given container. + * + * Parameters: + * + * container - DOM node that will contain the shape. + */ +mxShape.prototype.init = function (container) { + if (this.node == null) { + this.node = this.create(container); + + if (container != null) { + container.appendChild(this.node); + } + } +}; + +/** + * Function: initStyles + * + * Sets the styles to their default values. + */ +mxShape.prototype.initStyles = function (container) { + this.strokewidth = 1; + this.rotation = 0; + this.opacity = 100; + this.fillOpacity = 100; + this.strokeOpacity = 100; + this.flipH = false; + this.flipV = false; +}; + +/** + * Function: isParseVml + * + * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This + * is only needed in IE8 and only if the shape contains VML markup. This method + * returns true. + */ +mxShape.prototype.isParseVml = function () { + return true; +}; + +/** + * Function: isHtmlAllowed + * + * Returns true if HTML is allowed for this shape. This implementation always + * returns false. + */ +mxShape.prototype.isHtmlAllowed = function () { + return false; +}; + +/** + * Function: getSvgScreenOffset + * + * Returns 0, or 0.5 if % 2 == 1. + */ +mxShape.prototype.getSvgScreenOffset = function () { + var sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth; + + return mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1 ? 0.5 : 0; +}; + +/** + * Function: create + * + * Creates and returns the DOM node(s) for the shape in + * the given container. This implementation invokes + * , or depending + * on the and style settings. + * + * Parameters: + * + * container - DOM node that will contain the shape. + */ +mxShape.prototype.create = function (container) { + var node = null; + + if (container != null && container.ownerSVGElement != null) { + node = this.createSvg(container); + } else if ( + document.documentMode == 8 || + !mxClient.IS_VML || + (this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed()) + ) { + node = this.createHtml(container); + } else { + node = this.createVml(container); + } + + return node; +}; + +/** + * Function: createSvg + * + * Creates and returns the SVG node(s) to represent this shape. + */ +mxShape.prototype.createSvg = function () { + return document.createElementNS(mxConstants.NS_SVG, 'g'); +}; + +/** + * Function: createVml + * + * Creates and returns the VML node to represent this shape. + */ +mxShape.prototype.createVml = function () { + var node = document.createElement(mxClient.VML_PREFIX + ':group'); + node.style.position = 'absolute'; + + return node; +}; + +/** + * Function: createHtml + * + * Creates and returns the HTML DOM node(s) to represent + * this shape. This implementation falls back to + * so that the HTML creation is optional. + */ +mxShape.prototype.createHtml = function () { + var node = document.createElement('div'); + node.style.position = 'absolute'; + + return node; +}; + +/** + * Function: reconfigure + * + * Reconfigures this shape. This will update the colors etc in + * addition to the bounds or points. + */ +mxShape.prototype.reconfigure = function () { + this.redraw(); +}; + +/** + * Function: redraw + * + * Creates and returns the SVG node(s) to represent this shape. + */ +mxShape.prototype.redraw = function () { + this.updateBoundsFromPoints(); + + if (this.visible && this.checkBounds()) { + this.node.style.visibility = 'visible'; + this.clear(); + + if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML)) { + this.redrawHtmlShape(); + } else { + this.redrawShape(); + } + + this.updateBoundingBox(); + } else { + this.node.style.visibility = 'hidden'; + this.boundingBox = null; + } +}; + +/** + * Function: clear + * + * Removes all child nodes and resets all CSS. + */ +mxShape.prototype.clear = function () { + if (this.node.ownerSVGElement != null) { + while (this.node.lastChild != null) { + this.node.removeChild(this.node.lastChild); + } + } else { + this.node.style.cssText = 'position:absolute;' + (this.cursor != null ? 'cursor:' + this.cursor + ';' : ''); + this.node.innerHTML = ''; + } +}; + +/** + * Function: updateBoundsFromPoints + * + * Updates the bounds based on the points. + */ +mxShape.prototype.updateBoundsFromPoints = function () { + var pts = this.points; + + if (pts != null && pts.length > 0 && pts[0] != null) { + this.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1); + + for (var i = 1; i < this.points.length; i++) { + if (pts[i] != null) { + this.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1)); + } + } + } +}; + +/** + * Function: getLabelBounds + * + * Returns the for the label bounds of this shape, based on the + * given scaled and translated bounds of the shape. This method should not + * change the rectangle in-place. This implementation returns the given rect. + */ +mxShape.prototype.getLabelBounds = function (rect) { + var d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST); + var bounds = rect; + + // Normalizes argument for getLabelMargins hook + if ( + d != mxConstants.DIRECTION_SOUTH && + d != mxConstants.DIRECTION_NORTH && + this.state != null && + this.state.text != null && + this.state.text.isPaintBoundsInverted() + ) { + bounds = bounds.clone(); + var tmp = bounds.width; + bounds.width = bounds.height; + bounds.height = tmp; + } + + var m = this.getLabelMargins(bounds); + + if (m != null) { + var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1'; + var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1'; + + // Handles special case for vertical labels + if (this.state != null && this.state.text != null && this.state.text.isPaintBoundsInverted()) { + var tmp = m.x; + m.x = m.height; + m.height = m.width; + m.width = m.y; + m.y = tmp; + + tmp = flipH; + flipH = flipV; + flipV = tmp; + } + + return mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV); + } + + return rect; +}; + +/** + * Function: getLabelMargins + * + * Returns the scaled top, left, bottom and right margin to be used for + * computing the label bounds as an , where the bottom and right + * margin are defined in the width and height of the rectangle, respectively. + */ +mxShape.prototype.getLabelMargins = function (rect) { + return null; +}; + +/** + * Function: checkBounds + * + * Returns true if the bounds are not null and all of its variables are numeric. + */ +mxShape.prototype.checkBounds = function () { + return ( + !isNaN(this.scale) && + isFinite(this.scale) && + this.scale > 0 && + this.bounds != null && + !isNaN(this.bounds.x) && + !isNaN(this.bounds.y) && + !isNaN(this.bounds.width) && + !isNaN(this.bounds.height) && + this.bounds.width > 0 && + this.bounds.height > 0 + ); +}; + +/** + * Function: createVmlGroup + * + * Returns the temporary element used for rendering in IE8 standards mode. + */ +mxShape.prototype.createVmlGroup = function () { + var node = document.createElement(mxClient.VML_PREFIX + ':group'); + node.style.position = 'absolute'; + node.style.width = this.node.style.width; + node.style.height = this.node.style.height; + + return node; +}; + +/** + * Function: redrawShape + * + * Updates the SVG or VML shape. + */ +mxShape.prototype.redrawShape = function () { + var canvas = this.createCanvas(); + + if (canvas != null) { + // Specifies if events should be handled + canvas.pointerEvents = this.pointerEvents; + + this.beforePaint(canvas); + this.paint(canvas); + this.afterPaint(canvas); + + if (this.node != canvas.root) { + // Forces parsing in IE8 standards mode - slow! avoid + this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML); + } + + if (this.node.nodeName == 'DIV' && document.documentMode == 8) { + // Makes DIV transparent to events for IE8 in IE8 standards + // mode (Note: Does not work for IE9 in IE8 standards mode + // and not for IE11 in enterprise mode) + this.node.style.filter = ''; + + // Adds event transparency in IE8 standards + mxUtils.addTransparentBackgroundFilter(this.node); + } + + this.destroyCanvas(canvas); + } +}; + +/** + * Function: createCanvas + * + * Creates a new canvas for drawing this shape. May return null. + */ +mxShape.prototype.createCanvas = function () { + var canvas = null; + + // LATER: Check if reusing existing DOM nodes improves performance + if (this.node.ownerSVGElement != null) { + canvas = this.createSvgCanvas(); + } else if (mxClient.IS_VML) { + this.updateVmlContainer(); + canvas = this.createVmlCanvas(); + } + + if (canvas != null && this.outline) { + canvas.setStrokeWidth(this.strokewidth); + canvas.setStrokeColor(this.stroke); + + if (this.isDashed != null) { + canvas.setDashed(this.isDashed); + } + + canvas.setStrokeWidth = function () {}; + canvas.setStrokeColor = function () {}; + canvas.setFillColor = function () {}; + canvas.setGradient = function () {}; + canvas.setDashed = function () {}; + canvas.text = function () {}; + } + + return canvas; +}; + +/** + * Function: createSvgCanvas + * + * Creates and returns an for rendering this shape. + */ +mxShape.prototype.createSvgCanvas = function () { + var canvas = new mxSvgCanvas2D(this.node, false); + canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0; + canvas.pointerEventsValue = this.svgPointerEvents; + var off = this.getSvgScreenOffset(); + + if (off != 0) { + this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')'); + } else { + this.node.removeAttribute('transform'); + } + + canvas.minStrokeWidth = this.minSvgStrokeWidth; + + if (!this.antiAlias) { + // Rounds all numbers in the SVG output to integers + canvas.format = function (value) { + return Math.round(parseFloat(value)); + }; + } + + return canvas; +}; + +/** + * Function: createVmlCanvas + * + * Creates and returns an for rendering this shape. + */ +mxShape.prototype.createVmlCanvas = function () { + // Workaround for VML rendering bug in IE8 standards mode + var node = document.documentMode == 8 && this.isParseVml() ? this.createVmlGroup() : this.node; + var canvas = new mxVmlCanvas2D(node, false); + + if (node.tagUrn != '') { + var w = Math.max(1, Math.round(this.bounds.width)); + var h = Math.max(1, Math.round(this.bounds.height)); + node.coordsize = w * this.vmlScale + ',' + h * this.vmlScale; + canvas.scale(this.vmlScale); + canvas.vmlScale = this.vmlScale; + } + + // Painting relative to top, left shape corner + var s = this.scale; + canvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s)); + + return canvas; +}; + +/** + * Function: updateVmlContainer + * + * Updates the bounds of the VML container. + */ +mxShape.prototype.updateVmlContainer = function () { + this.node.style.left = Math.round(this.bounds.x) + 'px'; + this.node.style.top = Math.round(this.bounds.y) + 'px'; + var w = Math.max(1, Math.round(this.bounds.width)); + var h = Math.max(1, Math.round(this.bounds.height)); + this.node.style.width = w + 'px'; + this.node.style.height = h + 'px'; + this.node.style.overflow = 'visible'; +}; + +/** + * Function: redrawHtml + * + * Allow optimization by replacing VML with HTML. + */ +mxShape.prototype.redrawHtmlShape = function () { + // LATER: Refactor methods + this.updateHtmlBounds(this.node); + this.updateHtmlFilters(this.node); + this.updateHtmlColors(this.node); +}; + +/** + * Function: updateHtmlFilters + * + * Allow optimization by replacing VML with HTML. + */ +mxShape.prototype.updateHtmlFilters = function (node) { + var f = ''; + + if (this.opacity < 100) { + f += 'alpha(opacity=' + this.opacity + ')'; + } + + if (this.isShadow) { + // FIXME: Cannot implement shadow transparency with filter + f += + 'progid:DXImageTransform.Microsoft.dropShadow (' + + "OffX='" + + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + + "', " + + "OffY='" + + Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) + + "', " + + "Color='" + + mxConstants.VML_SHADOWCOLOR + + "')"; + } + + if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) { + var start = this.fill; + var end = this.gradient; + var type = '0'; + + var lookup = { east: 0, south: 1, west: 2, north: 3 }; + var dir = this.direction != null ? lookup[this.direction] : 0; + + if (this.gradientDirection != null) { + dir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4); + } + + if (dir == 1) { + type = '1'; + var tmp = start; + start = end; + end = tmp; + } else if (dir == 2) { + var tmp = start; + start = end; + end = tmp; + } else if (dir == 3) { + type = '1'; + } + + f += + 'progid:DXImageTransform.Microsoft.gradient(' + + "startColorStr='" + + start + + "', endColorStr='" + + end + + "', gradientType='" + + type + + "')"; + } + + node.style.filter = f; +}; + +/** + * Function: updateHtmlColors + * + * Allow optimization by replacing VML with HTML. + */ +mxShape.prototype.updateHtmlColors = function (node) { + var color = this.stroke; + + if (color != null && color != mxConstants.NONE) { + node.style.borderColor = color; + + if (this.isDashed) { + node.style.borderStyle = 'dashed'; + } else if (this.strokewidth > 0) { + node.style.borderStyle = 'solid'; + } + + node.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px'; + } else { + node.style.borderWidth = '0px'; + } + + color = this.outline ? null : this.fill; + + if (color != null && color != mxConstants.NONE) { + node.style.backgroundColor = color; + node.style.backgroundImage = 'none'; + } else if (this.pointerEvents) { + node.style.backgroundColor = 'transparent'; + } else if (document.documentMode == 8) { + mxUtils.addTransparentBackgroundFilter(node); + } else { + this.setTransparentBackgroundImage(node); + } +}; + +/** + * Function: updateHtmlBounds + * + * Allow optimization by replacing VML with HTML. + */ +mxShape.prototype.updateHtmlBounds = function (node) { + var sw = document.documentMode >= 9 ? 0 : Math.ceil(this.strokewidth * this.scale); + node.style.borderWidth = Math.max(1, sw) + 'px'; + node.style.overflow = 'hidden'; + + node.style.left = Math.round(this.bounds.x - sw / 2) + 'px'; + node.style.top = Math.round(this.bounds.y - sw / 2) + 'px'; + + if (document.compatMode == 'CSS1Compat') { + sw = -sw; + } + + node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px'; + node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px'; +}; + +/** + * Function: destroyCanvas + * + * Destroys the given canvas which was used for drawing. This implementation + * increments the reference counts on all shared gradients used in the canvas. + */ +mxShape.prototype.destroyCanvas = function (canvas) { + // Manages reference counts + if (canvas instanceof mxSvgCanvas2D) { + // Increments ref counts + for (var key in canvas.gradients) { + var gradient = canvas.gradients[key]; + + if (gradient != null) { + gradient.mxRefCount = (gradient.mxRefCount || 0) + 1; + } + } + + this.releaseSvgGradients(this.oldGradients); + this.oldGradients = canvas.gradients; + } +}; + +/** + * Function: beforePaint + * + * Invoked before paint is called. + */ +mxShape.prototype.beforePaint = function (c) {}; + +/** + * Function: afterPaint + * + * Invokes after paint was called. + */ +mxShape.prototype.afterPaint = function (c) {}; + +/** + * Function: paint + * + * Generic rendering code. + */ +mxShape.prototype.paint = function (c) { + var strokeDrawn = false; + + if (c != null && this.outline) { + var stroke = c.stroke; + + c.stroke = function () { + strokeDrawn = true; + stroke.apply(this, arguments); + }; + + var fillAndStroke = c.fillAndStroke; + + c.fillAndStroke = function () { + strokeDrawn = true; + fillAndStroke.apply(this, arguments); + }; + } + + // Scale is passed-through to canvas + var s = this.scale; + var x = this.bounds.x / s; + var y = this.bounds.y / s; + var w = this.bounds.width / s; + var h = this.bounds.height / s; + + if (this.isPaintBoundsInverted()) { + var t = (w - h) / 2; + x += t; + y -= t; + var tmp = w; + w = h; + h = tmp; + } + + this.updateTransform(c, x, y, w, h); + this.configureCanvas(c, x, y, w, h); + + // Adds background rectangle to capture events + var bg = null; + + if ( + (this.stencil == null && this.points == null && this.shapePointerEvents) || + (this.stencil != null && this.stencilPointerEvents) + ) { + var bb = this.createBoundingBox(); + + if (this.dialect == mxConstants.DIALECT_SVG) { + bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height); + this.node.appendChild(bg); + } else { + var rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s); + rect.appendChild(c.createTransparentFill()); + rect.stroked = 'false'; + c.root.appendChild(rect); + } + } + + if (this.stencil != null) { + this.stencil.drawShape(c, this, x, y, w, h); + } else { + // Stencils have separate strokewidth + c.setStrokeWidth(this.strokewidth); + + if (this.points != null) { + // Paints edge shape + var pts = []; + + for (var i = 0; i < this.points.length; i++) { + if (this.points[i] != null) { + pts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s)); + } + } + + this.paintEdgeShape(c, pts); + } else { + // Paints vertex shape + this.paintVertexShape(c, x, y, w, h); + } + } + + if (bg != null && c.state != null && c.state.transform != null) { + bg.setAttribute('transform', c.state.transform); + } + + // Draws highlight rectangle if no stroke was used + if (c != null && this.outline && !strokeDrawn) { + c.rect(x, y, w, h); + c.stroke(); + } +}; + +/** + * Function: configureCanvas + * + * Sets the state of the canvas for drawing the shape. + */ +mxShape.prototype.configureCanvas = function (c, x, y, w, h) { + var dash = null; + + if (this.style != null) { + dash = this.style['dashPattern']; + } + + c.setAlpha(this.opacity / 100); + c.setFillAlpha(this.fillOpacity / 100); + c.setStrokeAlpha(this.strokeOpacity / 100); + + // Sets alpha, colors and gradients + if (this.isShadow != null) { + c.setShadow(this.isShadow); + } + + // Dash pattern + if (this.isDashed != null) { + c.setDashed( + this.isDashed, + this.style != null ? mxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false + ); + } + + if (dash != null) { + c.setDashPattern(dash); + } + + if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) { + var b = this.getGradientBounds(c, x, y, w, h); + c.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection); + } else { + c.setFillColor(this.fill); + } + + c.setStrokeColor(this.stroke); +}; + +/** + * Function: getGradientBounds + * + * Returns the bounding box for the gradient box for this shape. + */ +mxShape.prototype.getGradientBounds = function (c, x, y, w, h) { + return new mxRectangle(x, y, w, h); +}; + +/** + * Function: updateTransform + * + * Sets the scale and rotation on the given canvas. + */ +mxShape.prototype.updateTransform = function (c, x, y, w, h) { + // NOTE: Currently, scale is implemented in state and canvas. This will + // move to canvas in a later version, so that the states are unscaled + // and untranslated and do not need an update after zooming or panning. + c.scale(this.scale); + c.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2); +}; + +/** + * Function: paintVertexShape + * + * Paints the vertex shape. + */ +mxShape.prototype.paintVertexShape = function (c, x, y, w, h) { + this.paintBackground(c, x, y, w, h); + + if ( + !this.outline || + this.style == null || + mxUtils.getValue(this.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0 + ) { + c.setShadow(false); + this.paintForeground(c, x, y, w, h); + } +}; + +/** + * Function: paintBackground + * + * Hook for subclassers. This implementation is empty. + */ +mxShape.prototype.paintBackground = function (c, x, y, w, h) {}; + +/** + * Function: paintForeground + * + * Hook for subclassers. This implementation is empty. + */ +mxShape.prototype.paintForeground = function (c, x, y, w, h) {}; + +/** + * Function: paintEdgeShape + * + * Hook for subclassers. This implementation is empty. + */ +mxShape.prototype.paintEdgeShape = function (c, pts) {}; + +/** + * Function: getArcSize + * + * Returns the arc size for the given dimension. + */ +mxShape.prototype.getArcSize = function (w, h) { + var r = 0; + + if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') { + r = Math.min( + w / 2, + Math.min(h / 2, mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2) + ); + } else { + var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100; + r = Math.min(w * f, h * f); + } + + return r; +}; + +/** + * Function: paintGlassEffect + * + * Paints the glass gradient effect. + */ +mxShape.prototype.paintGlassEffect = function (c, x, y, w, h, arc) { + var sw = Math.ceil(this.strokewidth / 2); + var size = 0.4; + + c.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1); + c.begin(); + arc += 2 * sw; + + if (this.isRounded) { + c.moveTo(x - sw + arc, y - sw); + c.quadTo(x - sw, y - sw, x - sw, y - sw + arc); + c.lineTo(x - sw, y + h * size); + c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size); + c.lineTo(x + w + sw, y - sw + arc); + c.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw); + } else { + c.moveTo(x - sw, y - sw); + c.lineTo(x - sw, y + h * size); + c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size); + c.lineTo(x + w + sw, y - sw); + } + + c.close(); + c.fill(); +}; + +/** + * Function: addPoints + * + * Paints the given points with rounded corners. + */ +mxShape.prototype.addPoints = function (c, pts, rounded, arcSize, close, exclude, initialMove) { + if (pts != null && pts.length > 0) { + initialMove = initialMove != null ? initialMove : true; + var pe = pts[pts.length - 1]; + + // Adds virtual waypoint in the center between start and end point + if (close && rounded) { + pts = pts.slice(); + var p0 = pts[0]; + var wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2); + pts.splice(0, 0, wp); + } + + var pt = pts[0]; + var i = 1; + + // Draws the line segments + if (initialMove) { + c.moveTo(pt.x, pt.y); + } else { + c.lineTo(pt.x, pt.y); + } + + while (i < (close ? pts.length : pts.length - 1)) { + var tmp = pts[mxUtils.mod(i, pts.length)]; + var dx = pt.x - tmp.x; + var dy = pt.y - tmp.y; + + if (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, i - 1) < 0)) { + // Draws a line from the last point to the current + // point with a spacing of size off the current point + // into direction of the last point + var dist = Math.sqrt(dx * dx + dy * dy); + var nx1 = (dx * Math.min(arcSize, dist / 2)) / dist; + var ny1 = (dy * Math.min(arcSize, dist / 2)) / dist; + + var x1 = tmp.x + nx1; + var y1 = tmp.y + ny1; + c.lineTo(x1, y1); + + // Draws a curve from the last point to the current + // point with a spacing of size off the current point + // into direction of the next point + var next = pts[mxUtils.mod(i + 1, pts.length)]; + + // Uses next non-overlapping point + while (i < pts.length - 2 && Math.round(next.x - tmp.x) == 0 && Math.round(next.y - tmp.y) == 0) { + next = pts[mxUtils.mod(i + 2, pts.length)]; + i++; + } + + dx = next.x - tmp.x; + dy = next.y - tmp.y; + + dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); + var nx2 = (dx * Math.min(arcSize, dist / 2)) / dist; + var ny2 = (dy * Math.min(arcSize, dist / 2)) / dist; + + var x2 = tmp.x + nx2; + var y2 = tmp.y + ny2; + + c.quadTo(tmp.x, tmp.y, x2, y2); + tmp = new mxPoint(x2, y2); + } else { + c.lineTo(tmp.x, tmp.y); + } + + pt = tmp; + i++; + } + + if (close) { + c.close(); + } else { + c.lineTo(pe.x, pe.y); + } + } +}; + +/** + * Function: resetStyles + * + * Resets all styles. + */ +mxShape.prototype.resetStyles = function () { + this.initStyles(); + + this.spacing = 0; + + delete this.fill; + delete this.gradient; + delete this.gradientDirection; + delete this.stroke; + delete this.startSize; + delete this.endSize; + delete this.startArrow; + delete this.endArrow; + delete this.direction; + delete this.isShadow; + delete this.isDashed; + delete this.isRounded; + delete this.glass; +}; + +/** + * Function: apply + * + * Applies the style of the given to the shape. This + * implementation assigns the following styles to local fields: + * + * - => fill + * - => gradient + * - => gradientDirection + * - => opacity + * - => fillOpacity + * - => strokeOpacity + * - => stroke + * - => strokewidth + * - => isShadow + * - => isDashed + * - => spacing + * - => startSize + * - => endSize + * - => isRounded + * - => startArrow + * - => endArrow + * - => rotation + * - => direction + * - => glass + * + * This keeps a reference to the '); + * }; + * (end) + * + * Headers: + * + * Apart from setting the title argument in the mxPrintPreview constructor you + * can override as follows to add a header to any page: + * + * (code) + * var oldRenderPage = mxPrintPreview.prototype.renderPage; + * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber) + * { + * var div = oldRenderPage.apply(this, arguments); + * + * var header = document.createElement('div'); + * header.style.position = 'absolute'; + * header.style.top = '0px'; + * header.style.width = '100%'; + * header.style.textAlign = 'right'; + * mxUtils.write(header, 'Your header here'); + * div.firstChild.appendChild(header); + * + * return div; + * }; + * (end) + * + * The pageNumber argument contains the number of the current page, starting at + * 1. To display a header on the first page only, check pageNumber and add a + * vertical offset in the constructor call for the height of the header. + * + * Page Format: + * + * For landscape printing, use as + * the pageFormat in and . + * Keep in mind that one can not set the defaults for the print dialog + * of the operating system from JavaScript so the user must manually choose + * a page format that matches this setting. + * + * You can try passing the following CSS directive to to set the + * page format in the print dialog to landscape. However, this CSS + * directive seems to be ignored in most major browsers, including IE. + * + * (code) + * @page { + * size: landscape; + * } + * (end) + * + * Note that the print preview behaves differently in IE when used from the + * filesystem or via HTTP so printing should always be tested via HTTP. + * + * If you are using a DOCTYPE in the source page you can override + * and provide the same DOCTYPE for the print preview if required. Here is + * an example for IE8 standards mode. + * + * (code) + * var preview = new mxPrintPreview(graph); + * preview.getDoctype = function() + * { + * return ''; + * }; + * preview.open(); + * (end) + * + * Constructor: mxPrintPreview + * + * Constructs a new print preview for the given parameters. + * + * Parameters: + * + * graph - to be previewed. + * scale - Optional scale of the output. Default is 1 / . + * pageFormat - that specifies the page format (in pixels). + * border - Border in pixels along each side of every page. Note that the + * actual print function in the browser will add another border for + * printing. + * This should match the page format of the printer. Default uses the + * of the given graph. + * x0 - Optional left offset of the output. Default is 0. + * y0 - Optional top offset of the output. Default is 0. + * borderColor - Optional color of the page border. Default is no border. + * Note that a border is sometimes useful to highlight the printed page + * border in the print preview of the browser. + * title - Optional string that is used for the window title. Default + * is 'Printer-friendly version'. + * pageSelector - Optional boolean that specifies if the page selector + * should appear in the window with the print preview. Default is true. + */ +function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector) { + this.graph = graph; + this.scale = scale != null ? scale : 1 / graph.pageScale; + this.border = border != null ? border : 0; + this.pageFormat = mxRectangle.fromRectangle(pageFormat != null ? pageFormat : graph.pageFormat); + this.title = title != null ? title : 'Printer-friendly version'; + this.x0 = x0 != null ? x0 : 0; + this.y0 = y0 != null ? y0 : 0; + this.borderColor = borderColor; + this.pageSelector = pageSelector != null ? pageSelector : true; +} + +/** + * Variable: graph + * + * Reference to the that should be previewed. + */ +mxPrintPreview.prototype.graph = null; + +/** + * Variable: pageFormat + * + * Holds the that defines the page format. + */ +mxPrintPreview.prototype.pageFormat = null; + +/** + * Variable: scale + * + * Holds the scale of the print preview. + */ +mxPrintPreview.prototype.scale = null; + +/** + * Variable: border + * + * The border inset around each side of every page in the preview. This is set + * to 0 if autoOrigin is false. + */ +mxPrintPreview.prototype.border = 0; + +/** + * Variable: marginTop + * + * The margin at the top of the page (number). Default is 0. + */ +mxPrintPreview.prototype.marginTop = 0; + +/** + * Variable: marginBottom + * + * The margin at the bottom of the page (number). Default is 0. + */ +mxPrintPreview.prototype.marginBottom = 0; + +/** + * Variable: x0 + * + * Holds the horizontal offset of the output. + */ +mxPrintPreview.prototype.x0 = 0; + +/** + * Variable: y0 + * + * Holds the vertical offset of the output. + */ +mxPrintPreview.prototype.y0 = 0; + +/** + * Variable: autoOrigin + * + * Specifies if the origin should be automatically computed based on the top, + * left corner of the actual diagram contents. The required offset will be added + * to and in . Default is true. + */ +mxPrintPreview.prototype.autoOrigin = true; + +/** + * Variable: printOverlays + * + * Specifies if overlays should be printed. Default is false. + */ +mxPrintPreview.prototype.printOverlays = false; + +/** + * Variable: printControls + * + * Specifies if controls (such as folding icons) should be printed. Default is + * false. + */ +mxPrintPreview.prototype.printControls = false; + +/** + * Variable: printBackgroundImage + * + * Specifies if the background image should be printed. Default is false. + */ +mxPrintPreview.prototype.printBackgroundImage = false; + +/** + * Variable: backgroundColor + * + * Holds the color value for the page background color. Default is #ffffff. + */ +mxPrintPreview.prototype.backgroundColor = '#ffffff'; + +/** + * Variable: borderColor + * + * Holds the color value for the page border. + */ +mxPrintPreview.prototype.borderColor = null; + +/** + * Variable: title + * + * Holds the title of the preview window. + */ +mxPrintPreview.prototype.title = null; + +/** + * Variable: pageSelector + * + * Boolean that specifies if the page selector should be + * displayed. Default is true. + */ +mxPrintPreview.prototype.pageSelector = null; + +/** + * Variable: wnd + * + * Reference to the preview window. + */ +mxPrintPreview.prototype.wnd = null; + +/** + * Variable: targetWindow + * + * Assign any window here to redirect the rendering in . + */ +mxPrintPreview.prototype.targetWindow = null; + +/** + * Variable: pageCount + * + * Holds the actual number of pages in the preview. + */ +mxPrintPreview.prototype.pageCount = 0; + +/** + * Variable: clipping + * + * Specifies is clipping should be used to avoid creating too many cell states + * in large diagrams. The bounding box of the cells in the original diagram is + * used if this is enabled. Default is true. + */ +mxPrintPreview.prototype.clipping = true; + +/** + * Function: getWindow + * + * Returns . + */ +mxPrintPreview.prototype.getWindow = function () { + return this.wnd; +}; + +/** + * Function: getDocType + * + * Returns the string that should go before the HTML tag in the print preview + * page. This implementation returns an X-UA meta tag for IE5 in quirks mode, + * IE8 in IE8 standards mode and edge in IE9 standards mode. + */ +mxPrintPreview.prototype.getDoctype = function () { + var dt = ''; + + if (document.documentMode == 5) { + dt = ''; + } else if (document.documentMode == 8) { + dt = ''; + } else if (document.documentMode > 8) { + // Comment needed to make standards doctype apply in IE + dt = ''; + } + + return dt; +}; + +/** + * Function: appendGraph + * + * Adds the given graph to the existing print preview. + * + * Parameters: + * + * css - Optional CSS string to be used in the head section. + * targetWindow - Optional window that should be used for rendering. If + * this is specified then no HEAD tag, CSS and BODY tag will be written. + */ +mxPrintPreview.prototype.appendGraph = function (graph, scale, x0, y0, forcePageBreaks, keepOpen) { + this.graph = graph; + this.scale = scale != null ? scale : 1 / graph.pageScale; + this.x0 = x0; + this.y0 = y0; + this.open(null, null, forcePageBreaks, keepOpen); +}; + +/** + * Function: open + * + * Shows the print preview window. The window is created here if it does + * not exist. + * + * Parameters: + * + * css - Optional CSS string to be used in the head section. + * targetWindow - Optional window that should be used for rendering. If + * this is specified then no HEAD tag, CSS and BODY tag will be written. + */ +mxPrintPreview.prototype.open = function (css, targetWindow, forcePageBreaks, keepOpen) { + // Closing the window while the page is being rendered may cause an + // exception in IE. This and any other exceptions are simply ignored. + var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay; + var div = null; + + try { + // Temporarily overrides the method to redirect rendering of overlays + // to the draw pane so that they are visible in the printout + if (this.printOverlays) { + this.graph.cellRenderer.initializeOverlay = function (state, overlay) { + overlay.init(state.view.getDrawPane()); + }; + } + + if (this.printControls) { + this.graph.cellRenderer.initControl = function (state, control, handleEvents, clickHandler) { + control.dialect = state.view.graph.dialect; + control.init(state.view.getDrawPane()); + }; + } + + this.wnd = targetWindow != null ? targetWindow : this.wnd; + var isNewWindow = false; + + if (this.wnd == null) { + isNewWindow = true; + this.wnd = window.open(); + } + + var doc = this.wnd.document; + + if (isNewWindow) { + var dt = this.getDoctype(); + + if (dt != null && dt.length > 0) { + doc.writeln(dt); + } + + if (mxClient.IS_VML) { + doc.writeln(''); + } else { + if (document.compatMode === 'CSS1Compat') { + doc.writeln(''); + } + + doc.writeln(''); + } + + doc.writeln(''); + this.writeHead(doc, css); + doc.writeln(''); + doc.writeln(''); + } + + // Computes the horizontal and vertical page count + var bounds = this.graph.getGraphBounds().clone(); + var currentScale = this.graph.getView().getScale(); + var sc = currentScale / this.scale; + var tr = this.graph.getView().getTranslate(); + + // Uses the absolute origin with no offset for all printing + if (!this.autoOrigin) { + this.x0 -= tr.x * this.scale; + this.y0 -= tr.y * this.scale; + bounds.width += bounds.x; + bounds.height += bounds.y; + bounds.x = 0; + bounds.y = 0; + this.border = 0; + } + + // Store the available page area + var availableWidth = this.pageFormat.width - this.border * 2; + var availableHeight = this.pageFormat.height - this.border * 2; + + // Adds margins to page format + this.pageFormat.height += this.marginTop + this.marginBottom; + + // Compute the unscaled, untranslated bounds to find + // the number of vertical and horizontal pages + bounds.width /= sc; + bounds.height /= sc; + + var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth)); + var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight)); + this.pageCount = hpages * vpages; + + var writePageSelector = mxUtils.bind(this, function () { + if (this.pageSelector && (vpages > 1 || hpages > 1)) { + var table = this.createPageSelector(vpages, hpages); + doc.body.appendChild(table); + + // Implements position: fixed in IE quirks mode + if ( + (mxClient.IS_IE && doc.documentMode == null) || + doc.documentMode == 5 || + doc.documentMode == 8 || + doc.documentMode == 7 + ) { + table.style.position = 'absolute'; + + var update = function () { + table.style.top = (doc.body.scrollTop || doc.documentElement.scrollTop) + 10 + 'px'; + }; + + mxEvent.addListener(this.wnd, 'scroll', function (evt) { + update(); + }); + + mxEvent.addListener(this.wnd, 'resize', function (evt) { + update(); + }); + } + } + }); + + var addPage = mxUtils.bind(this, function (div, addBreak) { + // Border of the DIV (aka page) inside the document + if (this.borderColor != null) { + div.style.borderColor = this.borderColor; + div.style.borderStyle = 'solid'; + div.style.borderWidth = '1px'; + } + + // Needs to be assigned directly because IE doesn't support + // child selectors, eg. body > div { background: white; } + div.style.background = this.backgroundColor; + + if (forcePageBreaks || addBreak) { + div.style.pageBreakAfter = 'always'; + } + + // NOTE: We are dealing with cross-window DOM here, which + // is a problem in IE, so we copy the HTML markup instead. + // The underlying problem is that the graph display markup + // creation (in mxShape, mxGraphView) is hardwired to using + // document.createElement and hence we must use this document + // to create the complete page and then copy it over to the + // new window.document. This can be fixed later by using the + // ownerDocument of the container in mxShape and mxGraphView. + if (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE)) { + // For some obscure reason, removing the DIV from the + // parent before fetching its outerHTML has missing + // fillcolor properties and fill children, so the div + // must be removed afterwards to keep the fillcolors. + doc.writeln(div.outerHTML); + div.parentNode.removeChild(div); + } else if (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE) { + var clone = doc.createElement('div'); + clone.innerHTML = div.outerHTML; + clone = clone.getElementsByTagName('div')[0]; + doc.body.appendChild(clone); + div.parentNode.removeChild(div); + } else { + div.parentNode.removeChild(div); + doc.body.appendChild(div); + } + + if (forcePageBreaks || addBreak) { + this.addPageBreak(doc); + } + }); + + var cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height); + + if (cov != null) { + for (var i = 0; i < cov.length; i++) { + addPage(cov[i], true); + } + } + + var apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height); + + // Appends each page to the page output for printing, making + // sure there will be a page break after each page (ie. div) + for (var i = 0; i < vpages; i++) { + var dy = + (i * availableHeight) / this.scale - this.y0 / this.scale + (bounds.y - tr.y * currentScale) / currentScale; + + for (var j = 0; j < hpages; j++) { + if (this.wnd == null) { + return null; + } + + var dx = + (j * availableWidth) / this.scale - this.x0 / this.scale + (bounds.x - tr.x * currentScale) / currentScale; + var pageNum = i * hpages + j + 1; + var clip = new mxRectangle(dx, dy, availableWidth, availableHeight); + div = this.renderPage( + this.pageFormat.width, + this.pageFormat.height, + 0, + 0, + mxUtils.bind(this, function (div) { + this.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip); + + if (this.printBackgroundImage) { + this.insertBackgroundImage(div, -dx, -dy); + } + }), + pageNum + ); + + // Gives the page a unique ID for later accessing the page + div.setAttribute('id', 'mxPage-' + pageNum); + + addPage(div, apx != null || i < vpages - 1 || j < hpages - 1); + } + } + + if (apx != null) { + for (var i = 0; i < apx.length; i++) { + addPage(apx[i], i < apx.length - 1); + } + } + + if (isNewWindow && !keepOpen) { + this.closeDocument(); + writePageSelector(); + } + + this.wnd.focus(); + } catch (e) { + // Removes the DIV from the document in case of an error + if (div != null && div.parentNode != null) { + div.parentNode.removeChild(div); + } + } finally { + this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay; + } + + return this.wnd; +}; + +/** + * Function: addPageBreak + * + * Adds a page break to the given document. + */ +mxPrintPreview.prototype.addPageBreak = function (doc) { + var hr = doc.createElement('hr'); + hr.className = 'mxPageBreak'; + doc.body.appendChild(hr); +}; + +/** + * Function: closeDocument + * + * Writes the closing tags for body and page after calling . + */ +mxPrintPreview.prototype.closeDocument = function () { + try { + if (this.wnd != null && this.wnd.document != null) { + var doc = this.wnd.document; + + this.writePostfix(doc); + doc.writeln(''); + doc.writeln(''); + doc.close(); + + // Removes all event handlers in the print output + mxEvent.release(doc.body); + } + } catch (e) { + // ignore any errors resulting from wnd no longer being available + } +}; + +/** + * Function: writeHead + * + * Writes the HEAD section into the given document, without the opening + * and closing HEAD tags. + */ +mxPrintPreview.prototype.writeHead = function (doc, css) { + if (this.title != null) { + doc.writeln('' + this.title + ''); + } + + // Adds required namespaces + if (mxClient.IS_VML) { + doc.writeln(''); + } + + // Adds all required stylesheets + // mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc); + + // Removes horizontal rules and page selector from print output + doc.writeln(''); +}; + +/** + * Function: writePostfix + * + * Called before closing the body of the page. This implementation is empty. + */ +mxPrintPreview.prototype.writePostfix = function (doc) { + // empty +}; + +/** + * Function: createPageSelector + * + * Creates the page selector table. + */ +mxPrintPreview.prototype.createPageSelector = function (vpages, hpages) { + var doc = this.wnd.document; + var table = doc.createElement('table'); + table.className = 'mxPageSelector'; + table.setAttribute('border', '0'); + + var tbody = doc.createElement('tbody'); + + for (var i = 0; i < vpages; i++) { + var row = doc.createElement('tr'); + + for (var j = 0; j < hpages; j++) { + var pageNum = i * hpages + j + 1; + var cell = doc.createElement('td'); + var a = doc.createElement('a'); + a.setAttribute('href', '#mxPage-' + pageNum); + + // Workaround for FF where the anchor is appended to the URL of the original document + if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC) { + var js = + "var page = document.getElementById('mxPage-" + + pageNum + + "');page.scrollIntoView(true);event.preventDefault();"; + a.setAttribute('onclick', js); + } + + mxUtils.write(a, pageNum, doc); + cell.appendChild(a); + row.appendChild(cell); + } + + tbody.appendChild(row); + } + + table.appendChild(tbody); + + return table; +}; + +/** + * Function: renderPage + * + * Creates a DIV that prints a single page of the given + * graph using the given scale and returns the DIV that + * represents the page. + * + * Parameters: + * + * w - Width of the page in pixels. + * h - Height of the page in pixels. + * dx - Optional horizontal page offset in pixels (used internally). + * dy - Optional vertical page offset in pixels (used internally). + * content - Callback that adds the HTML content to the inner div of a page. + * Takes the inner div as the argument. + * pageNumber - Integer representing the page number. + */ +mxPrintPreview.prototype.renderPage = function (w, h, dx, dy, content, pageNumber) { + var doc = this.wnd.document; + var div = document.createElement('div'); + var arg = null; + + try { + // Workaround for ignored clipping in IE 9 standards + // when printing with page breaks and HTML labels. + if (dx != 0 || dy != 0) { + div.style.position = 'relative'; + div.style.width = w + 'px'; + div.style.height = h + 'px'; + div.style.pageBreakInside = 'avoid'; + + var innerDiv = document.createElement('div'); + innerDiv.style.position = 'relative'; + innerDiv.style.top = this.border + 'px'; + innerDiv.style.left = this.border + 'px'; + innerDiv.style.width = w - 2 * this.border + 'px'; + innerDiv.style.height = h - 2 * this.border + 'px'; + innerDiv.style.overflow = 'hidden'; + + var viewport = document.createElement('div'); + viewport.style.position = 'relative'; + viewport.style.marginLeft = dx + 'px'; + viewport.style.marginTop = dy + 'px'; + + // FIXME: IE8 standards output problems + if (doc.documentMode == 8) { + innerDiv.style.position = 'absolute'; + viewport.style.position = 'absolute'; + } + + if (doc.documentMode == 10) { + viewport.style.width = '100%'; + viewport.style.height = '100%'; + } + + innerDiv.appendChild(viewport); + div.appendChild(innerDiv); + document.body.appendChild(div); + arg = viewport; + } + // FIXME: IE10/11 too many pages + else { + div.style.width = w + 'px'; + div.style.height = h + 'px'; + div.style.overflow = 'hidden'; + div.style.pageBreakInside = 'avoid'; + + // IE8 uses above branch currently + if (doc.documentMode == 8) { + div.style.position = 'relative'; + } + + var innerDiv = document.createElement('div'); + innerDiv.style.width = w - 2 * this.border + 'px'; + innerDiv.style.height = h - 2 * this.border + 'px'; + innerDiv.style.overflow = 'hidden'; + + if ( + mxClient.IS_IE && + (doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7) + ) { + innerDiv.style.marginTop = this.border + 'px'; + innerDiv.style.marginLeft = this.border + 'px'; + } else { + innerDiv.style.top = this.border + 'px'; + innerDiv.style.left = this.border + 'px'; + } + + if (this.graph.dialect == mxConstants.DIALECT_VML) { + innerDiv.style.position = 'absolute'; + } + + div.appendChild(innerDiv); + document.body.appendChild(div); + arg = innerDiv; + } + } catch (e) { + div.parentNode.removeChild(div); + div = null; + + throw e; + } + + content(arg); + + return div; +}; + +/** + * Function: getRoot + * + * Returns the root cell for painting the graph. + */ +mxPrintPreview.prototype.getRoot = function () { + var root = this.graph.view.currentRoot; + + if (root == null) { + root = this.graph.getModel().getRoot(); + } + + return root; +}; + +/** + * Function: useCssTransforms + * + * Returns true if CSS transforms should be used for scaling content. + * This returns true if foreignObject is supported and we're not in Safari + * as it has clipping bugs for transformed CSS content with foreignObjects. + */ +mxPrintPreview.prototype.useCssTransforms = function () { + return !mxClient.NO_FO && !mxClient.IS_SF; +}; + +/** + * Function: addGraphFragment + * + * Adds a graph fragment to the given div. + * + * Parameters: + * + * dx - Horizontal translation for the diagram. + * dy - Vertical translation for the diagram. + * scale - Scale for the diagram. + * pageNumber - Number of the page to be rendered. + * div - Div that contains the output. + * clip - Contains the clipping rectangle as an . + */ +mxPrintPreview.prototype.addGraphFragment = function (dx, dy, scale, pageNumber, div, clip) { + var view = this.graph.getView(); + var previousContainer = this.graph.container; + this.graph.container = div; + + var canvas = view.getCanvas(); + var backgroundPane = view.getBackgroundPane(); + var drawPane = view.getDrawPane(); + var overlayPane = view.getOverlayPane(); + var realScale = scale; + + if (this.graph.dialect == mxConstants.DIALECT_SVG) { + view.createSvg(); + + // Uses CSS transform for scaling + if (this.useCssTransforms()) { + var g = view.getDrawPane().parentNode; + var prev = g.getAttribute('transform'); + g.setAttribute('transformOrigin', '0 0'); + g.setAttribute('transform', 'scale(' + scale + ',' + scale + ')' + 'translate(' + dx + ',' + dy + ')'); + + scale = 1; + dx = 0; + dy = 0; + } + } else if (this.graph.dialect == mxConstants.DIALECT_VML) { + view.createVml(); + } else { + view.createHtml(); + } + + // Disables events on the view + var eventsEnabled = view.isEventsEnabled(); + view.setEventsEnabled(false); + + // Disables the graph to avoid cursors + var graphEnabled = this.graph.isEnabled(); + this.graph.setEnabled(false); + + // Resets the translation + var translate = view.getTranslate(); + view.translate = new mxPoint(dx, dy); + + // Redraws only states that intersect the clip + var redraw = this.graph.cellRenderer.redraw; + var states = view.states; + var s = view.scale; + + // Gets the transformed clip for intersection check below + if (this.clipping) { + var tempClip = new mxRectangle( + (clip.x + translate.x) * s, + (clip.y + translate.y) * s, + (clip.width * s) / realScale, + (clip.height * s) / realScale + ); + + // Checks clipping rectangle for speedup + // Must create terminal states for edge clipping even if terminal outside of clip + this.graph.cellRenderer.redraw = function (state, force, rendering) { + if (state != null) { + // Gets original state from graph to find bounding box + var orig = states.get(state.cell); + + if (orig != null) { + var bbox = view.getBoundingBox(orig, false); + + // Stops rendering if outside clip for speedup but ignores + // edge labels where width and height is set to 0 + if (bbox != null && bbox.width > 0 && bbox.height > 0 && !mxUtils.intersects(tempClip, bbox)) { + return; + } + } + } + + redraw.apply(this, arguments); + }; + } + + var temp = null; + + try { + // Creates the temporary cell states in the view and + // draws them onto the temporary DOM nodes in the view + var cells = [this.getRoot()]; + temp = new mxTemporaryCellStates( + view, + scale, + cells, + null, + mxUtils.bind(this, function (state) { + return this.getLinkForCellState(state); + }) + ); + } finally { + // Removes overlay pane with selection handles + // controls and icons from the print output + if (mxClient.IS_IE) { + view.overlayPane.innerHTML = ''; + view.canvas.style.overflow = 'hidden'; + view.canvas.style.position = 'relative'; + view.canvas.style.top = this.marginTop + 'px'; + view.canvas.style.width = clip.width + 'px'; + view.canvas.style.height = clip.height + 'px'; + } else { + // Removes everything but the SVG node + var tmp = div.firstChild; + + while (tmp != null) { + var next = tmp.nextSibling; + var name = tmp.nodeName.toLowerCase(); + + // Note: Width and height are required in FF 11 + if (name == 'svg') { + tmp.style.overflow = 'hidden'; + tmp.style.position = 'relative'; + tmp.style.top = this.marginTop + 'px'; + tmp.setAttribute('width', clip.width); + tmp.setAttribute('height', clip.height); + tmp.style.width = ''; + tmp.style.height = ''; + } + // Tries to fetch all text labels and only text labels + else if (tmp.style.cursor != 'default' && name != 'div') { + tmp.parentNode.removeChild(tmp); + } + + tmp = next; + } + } + + // Puts background image behind SVG output + if (this.printBackgroundImage) { + var svgs = div.getElementsByTagName('svg'); + + if (svgs.length > 0) { + svgs[0].style.position = 'absolute'; + } + } + + // Completely removes the overlay pane to remove more handles + view.overlayPane.parentNode.removeChild(view.overlayPane); + + // Restores the state of the view + this.graph.setEnabled(graphEnabled); + this.graph.container = previousContainer; + this.graph.cellRenderer.redraw = redraw; + view.canvas = canvas; + view.backgroundPane = backgroundPane; + view.drawPane = drawPane; + view.overlayPane = overlayPane; + view.translate = translate; + temp.destroy(); + view.setEventsEnabled(eventsEnabled); + } +}; + +/** + * Function: getLinkForCellState + * + * Returns the link for the given cell state. This returns null. + */ +mxPrintPreview.prototype.getLinkForCellState = function (state) { + return this.graph.getLinkForCell(state.cell); +}; + +/** + * Function: insertBackgroundImage + * + * Inserts the background image into the given div. + */ +mxPrintPreview.prototype.insertBackgroundImage = function (div, dx, dy) { + var bg = this.graph.backgroundImage; + + if (bg != null) { + var img = document.createElement('img'); + img.style.position = 'absolute'; + img.style.marginLeft = Math.round(dx * this.scale) + 'px'; + img.style.marginTop = Math.round(dy * this.scale) + 'px'; + img.setAttribute('width', Math.round(this.scale * bg.width)); + img.setAttribute('height', Math.round(this.scale * bg.height)); + img.src = bg.src; + + div.insertBefore(img, div.firstChild); + } +}; + +/** + * Function: getCoverPages + * + * Returns the pages to be added before the print output. This returns null. + */ +mxPrintPreview.prototype.getCoverPages = function () { + return null; +}; + +/** + * Function: getAppendices + * + * Returns the pages to be added after the print output. This returns null. + */ +mxPrintPreview.prototype.getAppendices = function () { + return null; +}; + +/** + * Function: print + * + * Opens the print preview and shows the print dialog. + * + * Parameters: + * + * css - Optional CSS string to be used in the head section. + */ +mxPrintPreview.prototype.print = function (css) { + var wnd = this.open(css); + + if (wnd != null) { + wnd.print(); + } +}; + +/** + * Function: close + * + * Closes the print preview window. + */ +mxPrintPreview.prototype.close = function () { + if (this.wnd != null) { + this.wnd.close(); + this.wnd = null; + } +}; + +/** + * view/mxStylesheet.js + */ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxStylesheet + * + * Defines the appearance of the cells in a graph. See for an + * example of creating a new cell style. It is recommended to use objects, not + * arrays for holding cell styles. Existing styles can be cloned using + * and turned into a string for debugging using + * . + * + * Default Styles: + * + * The stylesheet contains two built-in styles, which are used if no style is + * defined for a cell: + * + * defaultVertex - Default style for vertices + * defaultEdge - Default style for edges + * + * Example: + * + * (code) + * var vertexStyle = stylesheet.getDefaultVertexStyle(); + * vertexStyle[mxConstants.STYLE_ROUNDED] = true; + * var edgeStyle = stylesheet.getDefaultEdgeStyle(); + * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation; + * (end) + * + * Modifies the built-in default styles. + * + * To avoid the default style for a cell, add a leading semicolon + * to the style definition, eg. + * + * (code) + * ;shadow=1 + * (end) + * + * Removing keys: + * + * For removing a key in a cell style of the form [stylename;|key=value;] the + * special value none can be used, eg. highlight;fillColor=none + * + * See also the helper methods in mxUtils to modify strings of this format, + * namely , , + * , , + * and . + * + * Constructor: mxStylesheet + * + * Constructs a new stylesheet and assigns default styles. + */ +function mxStylesheet() { + this.styles = new Object(); + + this.putDefaultVertexStyle(this.createDefaultVertexStyle()); + this.putDefaultEdgeStyle(this.createDefaultEdgeStyle()); +} + +/** + * Function: styles + * + * Maps from names to cell styles. Each cell style is a map of key, + * value pairs. + */ +mxStylesheet.prototype.styles; + +/** + * Function: createDefaultVertexStyle + * + * Creates and returns the default vertex style. + */ +mxStylesheet.prototype.createDefaultVertexStyle = function () { + var style = new Object(); + + style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; + style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; + style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE; + style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; + style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF'; + style[mxConstants.STYLE_STROKECOLOR] = '#6482B9'; + style[mxConstants.STYLE_FONTCOLOR] = '#774400'; + + return style; +}; + +/** + * Function: createDefaultEdgeStyle + * + * Creates and returns the default edge style. + */ +mxStylesheet.prototype.createDefaultEdgeStyle = function () { + var style = new Object(); + + style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR; + style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC; + style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE; + style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; + style[mxConstants.STYLE_STROKECOLOR] = '#6482B9'; + style[mxConstants.STYLE_FONTCOLOR] = '#446299'; + + return style; +}; + +/** + * Function: putDefaultVertexStyle + * + * Sets the default style for vertices using defaultVertex as the + * stylename. + * + * Parameters: + * style - Key, value pairs that define the style. + */ +mxStylesheet.prototype.putDefaultVertexStyle = function (style) { + this.putCellStyle('defaultVertex', style); +}; + +/** + * Function: putDefaultEdgeStyle + * + * Sets the default style for edges using defaultEdge as the stylename. + */ +mxStylesheet.prototype.putDefaultEdgeStyle = function (style) { + this.putCellStyle('defaultEdge', style); +}; + +/** + * Function: getDefaultVertexStyle + * + * Returns the default style for vertices. + */ +mxStylesheet.prototype.getDefaultVertexStyle = function () { + return this.styles['defaultVertex']; +}; + +/** + * Function: getDefaultEdgeStyle + * + * Sets the default style for edges. + */ +mxStylesheet.prototype.getDefaultEdgeStyle = function () { + return this.styles['defaultEdge']; +}; + +/** + * Function: putCellStyle + * + * Stores the given map of key, value pairs under the given name in + * . + * + * Example: + * + * The following example adds a new style called 'rounded' into an + * existing stylesheet: + * + * (code) + * var style = new Object(); + * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; + * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; + * style[mxConstants.STYLE_ROUNDED] = true; + * graph.getStylesheet().putCellStyle('rounded', style); + * (end) + * + * In the above example, the new style is an object. The possible keys of + * the object are all the constants in that start with STYLE + * and the values are either JavaScript objects, such as + * (which is in fact a function) + * or expressions, such as true. Note that not all keys will be + * interpreted by all shapes (eg. the line shape ignores the fill color). + * The final call to this method associates the style with a name in the + * stylesheet. The style is used in a cell with the following code: + * + * (code) + * model.setStyle(cell, 'rounded'); + * (end) + * + * Parameters: + * + * name - Name for the style to be stored. + * style - Key, value pairs that define the style. + */ +mxStylesheet.prototype.putCellStyle = function (name, style) { + this.styles[name] = style; +}; + +/** + * Function: getCellStyle + * + * Returns the cell style for the specified stylename or the given + * defaultStyle if no style can be found for the given stylename. + * + * Parameters: + * + * name - String of the form [(stylename|key=value);] that represents the + * style. + * defaultStyle - Default style to be returned if no style can be found. + */ +mxStylesheet.prototype.getCellStyle = function (name, defaultStyle) { + var style = defaultStyle; + + if (name != null && name.length > 0) { + var pairs = name.split(';'); + + if (style != null && name.charAt(0) != ';') { + style = mxUtils.clone(style); + } else { + style = new Object(); + } + + // Parses each key, value pair into the existing style + for (var i = 0; i < pairs.length; i++) { + var tmp = pairs[i]; + var pos = tmp.indexOf('='); + + if (pos >= 0) { + var key = tmp.substring(0, pos); + var value = tmp.substring(pos + 1); + + if (value == mxConstants.NONE) { + delete style[key]; + } else if (mxUtils.isNumeric(value)) { + style[key] = parseFloat(value); + } else { + style[key] = value; + } + } else { + // Merges the entries from a named style + var tmpStyle = this.styles[tmp]; + + if (tmpStyle != null) { + for (var key in tmpStyle) { + style[key] = tmpStyle[key]; + } + } + } + } + } + + return style; +}; + +/** + * view/mxCellState.js + */ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxCellState + * + * Represents the current state of a cell in a given . + * + * For edges, the edge label position is stored in . + * + * The size for oversize labels can be retrieved using the boundingBox property + * of the field as shown below. + * + * (code) + * var bbox = (state.text != null) ? state.text.boundingBox : null; + * (end) + * + * Constructor: mxCellState + * + * Constructs a new object that represents the current state of the given + * cell in the specified view. + * + * Parameters: + * + * view - that contains the state. + * cell - that this state represents. + * style - Array of key, value pairs that constitute the style. + */ +function mxCellState(view, cell, style) { + this.view = view; + this.cell = cell; + this.style = style != null ? style : {}; + + this.origin = new mxPoint(); + this.absoluteOffset = new mxPoint(); +} + +/** + * Extends mxRectangle. + */ +mxCellState.prototype = new mxRectangle(); +mxCellState.prototype.constructor = mxCellState; + +/** + * Variable: view + * + * Reference to the enclosing . + */ +mxCellState.prototype.view = null; + +/** + * Variable: cell + * + * Reference to the that is represented by this state. + */ +mxCellState.prototype.cell = null; + +/** + * Variable: style + * + * Contains an array of key, value pairs that represent the style of the + * cell. + */ +mxCellState.prototype.style = null; + +/** + * Variable: invalidStyle + * + * Specifies if the style is invalid. Default is false. + */ +mxCellState.prototype.invalidStyle = false; + +/** + * Variable: invalid + * + * Specifies if the state is invalid. Default is true. + */ +mxCellState.prototype.invalid = true; + +/** + * Variable: origin + * + * that holds the origin for all child cells. Default is a new + * empty . + */ +mxCellState.prototype.origin = null; + +/** + * Variable: absolutePoints + * + * Holds an array of that represent the absolute points of an + * edge. + */ +mxCellState.prototype.absolutePoints = null; + +/** + * Variable: absoluteOffset + * + * that holds the absolute offset. For edges, this is the + * absolute coordinates of the label position. For vertices, this is the + * offset of the label relative to the top, left corner of the vertex. + */ +mxCellState.prototype.absoluteOffset = null; + +/** + * Variable: visibleSourceState + * + * Caches the visible source terminal state. + */ +mxCellState.prototype.visibleSourceState = null; + +/** + * Variable: visibleTargetState + * + * Caches the visible target terminal state. + */ +mxCellState.prototype.visibleTargetState = null; + +/** + * Variable: terminalDistance + * + * Caches the distance between the end points for an edge. + */ +mxCellState.prototype.terminalDistance = 0; + +/** + * Variable: length + * + * Caches the length of an edge. + */ +mxCellState.prototype.length = 0; + +/** + * Variable: segments + * + * Array of numbers that represent the cached length of each segment of the + * edge. + */ +mxCellState.prototype.segments = null; + +/** + * Variable: shape + * + * Holds the that represents the cell graphically. + */ +mxCellState.prototype.shape = null; + +/** + * Variable: text + * + * Holds the that represents the label of the cell. Thi smay be + * null if the cell has no label. + */ +mxCellState.prototype.text = null; + +/** + * Variable: unscaledWidth + * + * Holds the unscaled width of the state. + */ +mxCellState.prototype.unscaledWidth = null; + +/** + * Variable: unscaledHeight + * + * Holds the unscaled height of the state. + */ +mxCellState.prototype.unscaledHeight = null; + +/** + * Function: getPerimeterBounds + * + * Returns the that should be used as the perimeter of the + * cell. + * + * Parameters: + * + * border - Optional border to be added around the perimeter bounds. + * bounds - Optional to be used as the initial bounds. + */ +mxCellState.prototype.getPerimeterBounds = function (border, bounds) { + border = border || 0; + bounds = bounds != null ? bounds : new mxRectangle(this.x, this.y, this.width, this.height); + + if (this.shape != null && this.shape.stencil != null && this.shape.stencil.aspect == 'fixed') { + var aspect = this.shape.stencil.computeAspect(this.style, bounds.x, bounds.y, bounds.width, bounds.height); + + bounds.x = aspect.x; + bounds.y = aspect.y; + bounds.width = this.shape.stencil.w0 * aspect.width; + bounds.height = this.shape.stencil.h0 * aspect.height; + } + + if (border != 0) { + bounds.grow(border); + } + + return bounds; +}; + +/** + * Function: setAbsoluteTerminalPoint + * + * Sets the first or last point in depending on isSource. + * + * Parameters: + * + * point - that represents the terminal point. + * isSource - Boolean that specifies if the first or last point should + * be assigned. + */ +mxCellState.prototype.setAbsoluteTerminalPoint = function (point, isSource) { + if (isSource) { + if (this.absolutePoints == null) { + this.absolutePoints = []; + } + + if (this.absolutePoints.length == 0) { + this.absolutePoints.push(point); + } else { + this.absolutePoints[0] = point; + } + } else { + if (this.absolutePoints == null) { + this.absolutePoints = []; + this.absolutePoints.push(null); + this.absolutePoints.push(point); + } else if (this.absolutePoints.length == 1) { + this.absolutePoints.push(point); + } else { + this.absolutePoints[this.absolutePoints.length - 1] = point; + } + } +}; + +/** + * Function: setCursor + * + * Sets the given cursor on the shape and text shape. + */ +mxCellState.prototype.setCursor = function (cursor) { + if (this.shape != null) { + this.shape.setCursor(cursor); + } + + if (this.text != null) { + this.text.setCursor(cursor); + } +}; + +/** + * Function: getVisibleTerminal + * + * Returns the visible source or target terminal cell. + * + * Parameters: + * + * source - Boolean that specifies if the source or target cell should be + * returned. + */ +mxCellState.prototype.getVisibleTerminal = function (source) { + var tmp = this.getVisibleTerminalState(source); + + return tmp != null ? tmp.cell : null; +}; + +/** + * Function: getVisibleTerminalState + * + * Returns the visible source or target terminal state. + * + * Parameters: + * + * source - Boolean that specifies if the source or target state should be + * returned. + */ +mxCellState.prototype.getVisibleTerminalState = function (source) { + return source ? this.visibleSourceState : this.visibleTargetState; +}; + +/** + * Function: setVisibleTerminalState + * + * Sets the visible source or target terminal state. + * + * Parameters: + * + * terminalState - that represents the terminal. + * source - Boolean that specifies if the source or target state should be set. + */ +mxCellState.prototype.setVisibleTerminalState = function (terminalState, source) { + if (source) { + this.visibleSourceState = terminalState; + } else { + this.visibleTargetState = terminalState; + } +}; + +/** + * Function: getCellBounds + * + * Returns the unscaled, untranslated bounds. + */ +mxCellState.prototype.getCellBounds = function () { + return this.cellBounds; +}; + +/** + * Function: getPaintBounds + * + * Returns the unscaled, untranslated paint bounds. This is the same as + * but with a 90 degree rotation if the shape's + * isPaintBoundsInverted returns true. + */ +mxCellState.prototype.getPaintBounds = function () { + return this.paintBounds; +}; + +/** + * Function: updateCachedBounds + * + * Updates the cellBounds and paintBounds. + */ +mxCellState.prototype.updateCachedBounds = function () { + var tr = this.view.translate; + var s = this.view.scale; + this.cellBounds = new mxRectangle(this.x / s - tr.x, this.y / s - tr.y, this.width / s, this.height / s); + this.paintBounds = mxRectangle.fromRectangle(this.cellBounds); + + if (this.shape != null && this.shape.isPaintBoundsInverted()) { + this.paintBounds.rotate90(); + } +}; + +/** + * Destructor: setState + * + * Copies all fields from the given state to this state. + */ +mxCellState.prototype.setState = function (state) { + this.view = state.view; + this.cell = state.cell; + this.style = state.style; + this.absolutePoints = state.absolutePoints; + this.origin = state.origin; + this.absoluteOffset = state.absoluteOffset; + this.boundingBox = state.boundingBox; + this.terminalDistance = state.terminalDistance; + this.segments = state.segments; + this.length = state.length; + this.x = state.x; + this.y = state.y; + this.width = state.width; + this.height = state.height; + this.unscaledWidth = state.unscaledWidth; + this.unscaledHeight = state.unscaledHeight; +}; + +/** + * Function: clone + * + * Returns a clone of this . + */ +mxCellState.prototype.clone = function () { + var clone = new mxCellState(this.view, this.cell, this.style); + + // Clones the absolute points + if (this.absolutePoints != null) { + clone.absolutePoints = []; + + for (var i = 0; i < this.absolutePoints.length; i++) { + clone.absolutePoints[i] = this.absolutePoints[i].clone(); + } + } + + if (this.origin != null) { + clone.origin = this.origin.clone(); + } + + if (this.absoluteOffset != null) { + clone.absoluteOffset = this.absoluteOffset.clone(); + } + + if (this.boundingBox != null) { + clone.boundingBox = this.boundingBox.clone(); + } + + clone.terminalDistance = this.terminalDistance; + clone.segments = this.segments; + clone.length = this.length; + clone.x = this.x; + clone.y = this.y; + clone.width = this.width; + clone.height = this.height; + clone.unscaledWidth = this.unscaledWidth; + clone.unscaledHeight = this.unscaledHeight; + + return clone; +}; + +/** + * Destructor: destroy + * + * Destroys the state and all associated resources. + */ +mxCellState.prototype.destroy = function () { + this.view.graph.cellRenderer.destroy(this); +}; + +/** + * view/mxGraphSelectionModel.js + */ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxGraphSelectionModel + * + * Implements the selection model for a graph. Here is a listener that handles + * all removed selection cells. + * + * (code) + * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt) + * { + * var cells = evt.getProperty('added'); + * + * for (var i = 0; i < cells.length; i++) + * { + * // Handle cells[i]... + * } + * }); + * (end) + * + * Event: mxEvent.UNDO + * + * Fires after the selection was changed in . The + * edit property contains the which contains the + * . + * + * Event: mxEvent.CHANGE + * + * Fires after the selection changes by executing an . The + * added and removed properties contain arrays of + * cells that have been added to or removed from the selection, respectively. + * The names are inverted due to historic reasons. This cannot be changed. + * + * Constructor: mxGraphSelectionModel + * + * Constructs a new graph selection model for the given . + * + * Parameters: + * + * graph - Reference to the enclosing . + */ +function mxGraphSelectionModel(graph) { + this.graph = graph; + this.cells = []; +} + +/** + * Extends mxEventSource. + */ +mxGraphSelectionModel.prototype = new mxEventSource(); +mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel; + +/** + * Variable: doneResource + * + * Specifies the resource key for the status message after a long operation. + * If the resource for this key does not exist then the value is used as + * the status message. Default is 'done'. + */ +mxGraphSelectionModel.prototype.doneResource = mxClient.language != 'none' ? 'done' : ''; + +/** + * Variable: updatingSelectionResource + * + * Specifies the resource key for the status message while the selection is + * being updated. If the resource for this key does not exist then the + * value is used as the status message. Default is 'updatingSelection'. + */ +mxGraphSelectionModel.prototype.updatingSelectionResource = mxClient.language != 'none' ? 'updatingSelection' : ''; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxGraphSelectionModel.prototype.graph = null; + +/** + * Variable: singleSelection + * + * Specifies if only one selected item at a time is allowed. + * Default is false. + */ +mxGraphSelectionModel.prototype.singleSelection = false; + +/** + * Function: isSingleSelection + * + * Returns as a boolean. + */ +mxGraphSelectionModel.prototype.isSingleSelection = function () { + return this.singleSelection; +}; + +/** + * Function: setSingleSelection + * + * Sets the flag. + * + * Parameters: + * + * singleSelection - Boolean that specifies the new value for + * . + */ +mxGraphSelectionModel.prototype.setSingleSelection = function (singleSelection) { + this.singleSelection = singleSelection; +}; + +/** + * Function: isSelected + * + * Returns true if the given is selected. + */ +mxGraphSelectionModel.prototype.isSelected = function (cell) { + if (cell != null) { + return mxUtils.indexOf(this.cells, cell) >= 0; + } + + return false; +}; + +/** + * Function: isEmpty + * + * Returns true if no cells are currently selected. + */ +mxGraphSelectionModel.prototype.isEmpty = function () { + return this.cells.length == 0; +}; + +/** + * Function: clear + * + * Clears the selection and fires a event if the selection was not + * empty. + */ +mxGraphSelectionModel.prototype.clear = function () { + this.changeSelection(null, this.cells); +}; + +/** + * Function: setCell + * + * Selects the specified using . + * + * Parameters: + * + * cell - to be selected. + */ +mxGraphSelectionModel.prototype.setCell = function (cell) { + if (cell != null) { + this.setCells([cell]); + } +}; + +/** + * Function: setCells + * + * Selects the given array of and fires a event. + * + * Parameters: + * + * cells - Array of to be selected. + */ +mxGraphSelectionModel.prototype.setCells = function (cells) { + if (cells != null) { + if (this.singleSelection) { + cells = [this.getFirstSelectableCell(cells)]; + } + + var tmp = []; + + for (var i = 0; i < cells.length; i++) { + if (this.graph.isCellSelectable(cells[i])) { + tmp.push(cells[i]); + } + } + + this.changeSelection(tmp, this.cells); + } +}; + +/** + * Function: getFirstSelectableCell + * + * Returns the first selectable cell in the given array of cells. + */ +mxGraphSelectionModel.prototype.getFirstSelectableCell = function (cells) { + if (cells != null) { + for (var i = 0; i < cells.length; i++) { + if (this.graph.isCellSelectable(cells[i])) { + return cells[i]; + } + } + } + + return null; +}; + +/** + * Function: addCell + * + * Adds the given to the selection and fires a + * event. + * + * Parameters: + * + * cells - Array of to add to the selection. + */ +mxGraphSelectionModel.prototype.addCells = function (cells) { + if (cells != null) { + var remove = null; + + if (this.singleSelection) { + remove = this.cells; + cells = [this.getFirstSelectableCell(cells)]; + } + + var tmp = []; + + for (var i = 0; i < cells.length; i++) { + if (!this.isSelected(cells[i]) && this.graph.isCellSelectable(cells[i])) { + tmp.push(cells[i]); + } + } + + this.changeSelection(tmp, remove); + } +}; + +/** + * Function: removeCell + * + * Removes the specified from the selection and fires a