1/**
2 * Copyright (c) 2006-2017, JGraph Ltd
3 * Copyright (c) 2006-2017, Gaudenz Alder
4 */
5(function()
6{
7	if (typeof html4 !== 'undefined')
8	{
9		/**
10		 * Enables paste from Lucidchart
11		 */
12		html4.ATTRIBS['span::data-lucid-content'] = 0;
13		html4.ATTRIBS['span::data-lucid-type'] = 0;
14
15		/**
16		 * Enables custom fonts in labels.
17		 */
18		html4.ATTRIBS['font::data-font-src'] = 0;
19	}
20
21	/**
22	 * Specifies the app name. Default is document.title.
23	 */
24	Editor.prototype.appName = 'diagrams.net';
25
26	/**
27	 * Known file types.
28	 */
29	Editor.prototype.diagramFileTypes = [
30		{description: 'diagramXmlDesc', extension: 'drawio', mimeType: 'text/xml'},
31		{description: 'diagramPngDesc', extension: 'png', mimeType: 'image/png'},
32		{description: 'diagramSvgDesc', extension: 'svg', mimeType: 'image/svg'},
33		{description: 'diagramHtmlDesc', extension: 'html', mimeType: 'text/html'},
34		{description: 'diagramXmlDesc', extension: 'xml', mimeType: 'text/xml'}];
35
36	/**
37	 * Known file types.
38	 */
39	Editor.prototype.libraryFileTypes = [{description: 'Library (.drawiolib, .xml)', extensions: ['drawiolib', 'xml']}];
40
41	/**
42	 * Additional help text for special file extensions.
43	 */
44	Editor.prototype.fileExtensions = [
45		{ext: 'html', title: 'filetypeHtml'},
46		{ext: 'png', title: 'filetypePng'},
47		{ext: 'svg', title: 'filetypeSvg'}];
48
49	/**
50	 *
51	 */
52	Editor.styles = [{},
53		{commonStyle: {fontColor: '#5C5C5C', strokeColor: '#006658', fillColor: '#21C0A5'}},
54		{commonStyle: {fontColor: '#095C86', strokeColor: '#AF45ED', fillColor: '#F694C1'},
55			edgeStyle: {strokeColor: '#60E696'}},
56		{commonStyle: {fontColor: '#46495D', strokeColor: '#788AA3', fillColor: '#B2C9AB'}},
57		{commonStyle: {fontColor: '#5AA9E6', strokeColor: '#FF6392', fillColor: '#FFE45E'}},
58		{commonStyle: {fontColor: '#1D3557', strokeColor: '#457B9D', fillColor: '#A8DADC'},
59			graph: {background: '#F1FAEE'}},
60		{commonStyle: {fontColor: '#393C56', strokeColor: '#E07A5F', fillColor: '#F2CC8F'},
61			graph: {background: '#F4F1DE', gridColor: '#D4D0C0'}},
62		{commonStyle: {fontColor: '#143642', strokeColor: '#0F8B8D', fillColor: '#FAE5C7'},
63			edgeStyle: {strokeColor: '#A8201A'},
64			graph: {background: '#DAD2D8', gridColor: '#ABA4A9'}},
65		{commonStyle: {fontColor: '#FEFAE0', strokeColor: '#DDA15E', fillColor: '#BC6C25'},
66			graph: {background: '#283618', gridColor: '#48632C'}},
67		{commonStyle: {fontColor: '#E4FDE1', strokeColor: '#028090', fillColor: '#F45B69'},
68			graph: {background: '#114B5F', gridColor: '#0B3240'}},
69		{},
70		{vertexStyle: {strokeColor: '#D0CEE2', fillColor: '#FAD9D5'},
71			edgeStyle: {strokeColor: '#09555B'},
72			commonStyle: {fontColor: '#1A1A1A'}},
73		{vertexStyle: {strokeColor: '#BAC8D3', fillColor: '#09555B', fontColor: '#EEEEEE'},
74			edgeStyle: {strokeColor: '#0B4D6A'}},
75		{vertexStyle: {strokeColor: '#D0CEE2', fillColor: '#5D7F99'},
76			edgeStyle: {strokeColor: '#736CA8'},
77			commonStyle: {fontColor: '#1A1A1A'}},
78		{vertexStyle: {strokeColor: '#FFFFFF', fillColor: '#182E3E', fontColor: '#FFFFFF'},
79			edgeStyle: {strokeColor: '#23445D'},
80			graph: {background: '#FCE7CD', gridColor: '#CFBDA8'}},
81		{vertexStyle: {strokeColor: '#FFFFFF', fillColor: '#F08E81'},
82			edgeStyle: {strokeColor: '#182E3E'},
83			commonStyle: {fontColor: '#1A1A1A'},
84			graph: {background: '#B0E3E6', gridColor: '#87AEB0'}},
85		{vertexStyle: {strokeColor: '#909090', fillColor: '#F5AB50'},
86			edgeStyle: {strokeColor: '#182E3E'},
87			commonStyle: {fontColor: '#1A1A1A'},
88			graph: {background: '#EEEEEE'}},
89		{vertexStyle: {strokeColor: '#EEEEEE', fillColor: '#56517E', fontColor: '#FFFFFF'},
90			edgeStyle: {strokeColor: '#182E3E'},
91			graph: {background: '#FAD9D5', gridColor: '#BFA6A3'}},
92		{vertexStyle: {strokeColor: '#BAC8D3', fillColor: '#B1DDF0', fontColor: '#182E3E'},
93			edgeStyle: {strokeColor: '#EEEEEE', fontColor: '#FFFFFF'},
94			graph: {background: '#09555B', gridColor: '#13B4C2'}},
95		{vertexStyle: {fillColor: '#EEEEEE', fontColor: '#1A1A1A'},
96			edgeStyle: {fontColor: '#FFFFFF'},
97			commonStyle: {strokeColor: '#FFFFFF'},
98			graph: {background: '#182E3E', gridColor: '#4D94C7'}}
99	];
100
101	/**
102	 *
103	 */
104	Editor.saveImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iYmxhY2siIHdpZHRoPSIxOHB4IiBoZWlnaHQ9IjE4cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTkgMTJ2N0g1di03SDN2N2MwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0ydi03aC0yem0tNiAuNjdsMi41OS0yLjU4TDE3IDExLjVsLTUgNS01LTUgMS40MS0xLjQxTDExIDEyLjY3VjNoMnoiLz48L3N2Zz4=';
105
106	/**
107	 *
108	 */
109	Editor.smallPlusImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/plus.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDdCMTdENjVCOEM4MTFFNDlCRjVBNDdCODU5NjNBNUMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDdCMTdENjZCOEM4MTFFNDlCRjVBNDdCODU5NjNBNUMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowN0IxN0Q2M0I4QzgxMUU0OUJGNUE0N0I4NTk2M0E1QyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowN0IxN0Q2NEI4QzgxMUU0OUJGNUE0N0I4NTk2M0E1QyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtjrjmgAAAAtSURBVHjaYvz//z8DMigvLwcLdHZ2MiKLMzEQCaivkLGsrOw/dU0cAr4GCDAARQsQbTFrv10AAAAASUVORK5CYII=';
110
111	/**
112	 *
113	 */
114	Editor.spinImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/spin.gif' : 'data:image/gif;base64,R0lGODlhDAAMAPUxAEVriVp7lmCAmmGBm2OCnGmHn3OPpneSqYKbr4OcsIScsI2kto6kt46lt5KnuZmtvpquvpuvv56ywaCzwqK1xKu7yay9yq+/zLHAzbfF0bjG0bzJ1LzK1MDN18jT28nT3M3X3tHa4dTc49Xd5Njf5dng5t3k6d/l6uDm6uru8e7x8/Dz9fT29/b4+Pj5+fj5+vr6+v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkKADEAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAADAAMAAAGR8CYcEgsOgYAIax4CCQuQldrCBEsiK8VS2hoFGOrlJDA+cZQwkLnqyoJFZKviSS0ICrE0ec0jDAwIiUeGyBFGhMPFBkhZo1BACH5BAkKAC4ALAAAAAAMAAwAhVB0kFR3k1V4k2CAmmWEnW6Lo3KOpXeSqH2XrIOcsISdsImhtIqhtJCmuJGnuZuwv52wwJ+ywZ+ywqm6yLHBzbLCzrXEz7fF0LnH0rrI0r7L1b/M1sXR2cfT28rV3czW3s/Z4Nfe5Nvi6ODm6uLn6+Ln7OLo7OXq7efs7+zw8u/y9PDy9PX3+Pr7+////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZDQJdwSCxGDAIAoVFkFBwYSyIwGE4OkCJxIdG6WkJEx8sSKj7elfBB0a5SQg1EQ0SVVMPKhDM6iUIkRR4ZFxsgJl6JQQAh+QQJCgAxACwAAAAADAAMAIVGa4lcfZdjgpxkg51nhp5ui6N3kqh5lKqFnbGHn7KIoLOQp7iRp7mSqLmTqbqarr6br7+fssGitcOitcSuvsuuv8uwwMyzw861xNC5x9K6x9K/zNbDztjE0NnG0drJ1NzQ2eDS2+LT2+LV3ePZ4Oba4ebb4ufc4+jm6+7t8PLt8PPt8fPx8/Xx9PX09vf19/j3+Pn///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ8CYcEgsUhQFggFSjCQmnE1jcBhqGBXiIuAQSi7FGEIgfIzCFoCXFCZiPO0hKBMiwl7ET6eUYqlWLkUnISImKC1xbUEAIfkECQoAMgAsAAAAAAwADACFTnKPT3KPVHaTYoKcb4yjcY6leZSpf5mtgZuvh5+yiqG0i6K1jqW3kae5nrHBnrLBn7LCoLPCobTDqbrIqrvIs8LOtMPPtcPPtcTPuMbRucfSvcrUvsvVwMzWxdHaydTcytXdzNbezdff0drh2ODl2+Ln3eTp4Obq4ujs5Ont5uvu6O3w6u7w6u7x7/L09vj5+vr7+vv7////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkdAmXBILHIcicOCUqxELKKPxKAYgiYd4oMAEWo8RVmjIMScwhmBcJMKXwLCECmMGAhPI1QRwBiaSixCMDFhLSorLi8wYYxCQQAh+QQJCgAxACwAAAAADAAMAIVZepVggJphgZtnhp5vjKN2kah3kqmBmq+KobSLorWNpLaRp7mWq7ybr7+gs8KitcSktsWnuManucexwM2ywc63xtG6yNO9ytS+ytW/zNbDz9jH0tvL1d3N197S2+LU3OPU3ePV3eTX3+Xa4efb4ufd5Onl6u7r7vHs7/Lt8PLw8/Xy9Pby9fb09ff2+Pn3+Pn6+vr///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGSMCYcEgseiwSR+RS7GA4JFGF8RiWNiEiJTERgkjFGAQh/KTCGoJwpApnBkITKrwoCFWnFlEhaAxXLC9CBwAGRS4wQgELYY1CQQAh+QQJCgAzACwAAAAADAAMAIVMcI5SdZFhgZtti6JwjaR4k6mAma6Cm6+KobSLorWLo7WNo7aPpredsMCescGitMOitcSmuMaqu8ixwc2zws63xdC4xtG5x9K9ytXAzdfCztjF0NnF0drK1d3M1t7P2N/P2eDT2+LX3+Xe5Onh5+vi5+vj6Ozk6e3n7O/o7O/q7vHs7/Lt8PPu8fPx8/X3+Pn6+vv7+/v8/Pz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRcCZcEgsmkIbTOZTLIlGqZNnchm2SCgiJ6IRqljFmQUiXIVnoITQde4chC9Y+LEQxmTFRkFSNFAqDAMIRQoCAAEEDmeLQQAh+QQJCgAwACwAAAAADAAMAIVXeZRefplff5lhgZtph59yjqV2kaeAmq6FnbGFnrGLorWNpLaQp7mRqLmYrb2essGgs8Klt8apusitvcquv8u2xNC7yNO8ydS8ytTAzdfBzdfM1t7N197Q2eDU3OPX3+XZ4ObZ4ebc4+jf5erg5erg5uvp7fDu8fPv8vTz9fb09vf19/j3+Pn4+fn5+vr6+/v///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRUCYcEgspkwjEKhUVJ1QsBNp0xm2VixiSOMRvlxFGAcTJook5eEHIhQcwpWIkAFQECkNy9AQWFwyEAkPRQ4FAwQIE2llQQAh+QQJCgAvACwAAAAADAAMAIVNcY5SdZFigptph6BvjKN0kKd8lquAmq+EnbGGn7KHn7ONpLaOpbearr+csMCdscCescGhtMOnuMauvsuzws60w862xdC9ytW/y9a/zNbCztjG0drH0tvK1N3M1t7N19/U3ePb4uff5urj6Ozk6e3l6u7m6u7o7PDq7vDt8PPv8vTw8vTw8/X19vf6+vv///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ8CXcEgsvlytVUplJLJIpSEDUESFTELBwSgCCQEV42kjDFiMo4uQsDB2MkLHoEHUTD7DRAHC8VAiZ0QSCgYIDxhNiUEAOw==';
115
116	/**
117	 *
118	 */
119	Editor.globeImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTEuOTkgMkM2LjQ3IDIgMiA2LjQ4IDIgMTJzNC40NyAxMCA5Ljk5IDEwQzE3LjUyIDIyIDIyIDE3LjUyIDIyIDEyUzE3LjUyIDIgMTEuOTkgMnptNi45MyA2aC0yLjk1Yy0uMzItMS4yNS0uNzgtMi40NS0xLjM4LTMuNTYgMS44NC42MyAzLjM3IDEuOTEgNC4zMyAzLjU2ek0xMiA0LjA0Yy44MyAxLjIgMS40OCAyLjUzIDEuOTEgMy45NmgtMy44MmMuNDMtMS40MyAxLjA4LTIuNzYgMS45MS0zLjk2ek00LjI2IDE0QzQuMSAxMy4zNiA0IDEyLjY5IDQgMTJzLjEtMS4zNi4yNi0yaDMuMzhjLS4wOC42Ni0uMTQgMS4zMi0uMTQgMiAwIC42OC4wNiAxLjM0LjE0IDJINC4yNnptLjgyIDJoMi45NWMuMzIgMS4yNS43OCAyLjQ1IDEuMzggMy41Ni0xLjg0LS42My0zLjM3LTEuOS00LjMzLTMuNTZ6bTIuOTUtOEg1LjA4Yy45Ni0xLjY2IDIuNDktMi45MyA0LjMzLTMuNTZDOC44MSA1LjU1IDguMzUgNi43NSA4LjAzIDh6TTEyIDE5Ljk2Yy0uODMtMS4yLTEuNDgtMi41My0xLjkxLTMuOTZoMy44MmMtLjQzIDEuNDMtMS4wOCAyLjc2LTEuOTEgMy45NnpNMTQuMzQgMTRIOS42NmMtLjA5LS42Ni0uMTYtMS4zMi0uMTYtMiAwLS42OC4wNy0xLjM1LjE2LTJoNC42OGMuMDkuNjUuMTYgMS4zMi4xNiAyIDAgLjY4LS4wNyAxLjM0LS4xNiAyem0uMjUgNS41NmMuNi0xLjExIDEuMDYtMi4zMSAxLjM4LTMuNTZoMi45NWMtLjk2IDEuNjUtMi40OSAyLjkzLTQuMzMgMy41NnpNMTYuMzYgMTRjLjA4LS42Ni4xNC0xLjMyLjE0LTIgMC0uNjgtLjA2LTEuMzQtLjE0LTJoMy4zOGMuMTYuNjQuMjYgMS4zMS4yNiAycy0uMSAxLjM2LS4yNiAyaC0zLjM4eiIvPjwvc3ZnPg==';
120
121	/**
122	 *
123	 */
124	Editor.commentImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMjEuOTkgNGMwLTEuMS0uODktMi0xLjk5LTJINGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxNGw0IDQtLjAxLTE4ek0xOCAxNEg2di0yaDEydjJ6bTAtM0g2VjloMTJ2MnptMC0zSDZWNmgxMnYyeiIvPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48L3N2Zz4=';
125
126	/**
127	 *
128	 */
129	Editor.userImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgMTJjMi4yMSAwIDQtMS43OSA0LTRzLTEuNzktNC00LTQtNCAxLjc5LTQgNCAxLjc5IDQgNCA0em0wIDJjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6Ii8+PC9zdmc+';
130
131	/**
132	 *
133	 */
134	Editor.shareImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTggMTYuMDhjLS43NiAwLTEuNDQuMy0xLjk2Ljc3TDguOTEgMTIuN2MuMDUtLjIzLjA5LS40Ni4wOS0uN3MtLjA0LS40Ny0uMDktLjdsNy4wNS00LjExYy41NC41IDEuMjUuODEgMi4wNC44MSAxLjY2IDAgMy0xLjM0IDMtM3MtMS4zNC0zLTMtMy0zIDEuMzQtMyAzYzAgLjI0LjA0LjQ3LjA5LjdMOC4wNCA5LjgxQzcuNSA5LjMxIDYuNzkgOSA2IDljLTEuNjYgMC0zIDEuMzQtMyAzczEuMzQgMyAzIDNjLjc5IDAgMS41LS4zMSAyLjA0LS44MWw3LjEyIDQuMTZjLS4wNS4yMS0uMDguNDMtLjA4LjY1IDAgMS42MSAxLjMxIDIuOTIgMi45MiAyLjkyIDEuNjEgMCAyLjkyLTEuMzEgMi45Mi0yLjkycy0xLjMxLTIuOTItMi45Mi0yLjkyeiIvPjwvc3ZnPg==';
135
136	/**
137	 *
138	 */
139	Editor.syncImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgNFYxTDggNWw0IDRWNmMzLjMxIDAgNiAyLjY5IDYgNiAwIDEuMDEtLjI1IDEuOTctLjcgMi44bDEuNDYgMS40NkMxOS41NCAxNS4wMyAyMCAxMy41NyAyMCAxMmMwLTQuNDItMy41OC04LTgtOHptMCAxNGMtMy4zMSAwLTYtMi42OS02LTYgMC0xLjAxLjI1LTEuOTcuNy0yLjhMNS4yNCA3Ljc0QzQuNDYgOC45NyA0IDEwLjQzIDQgMTJjMCA0LjQyIDMuNTggOCA4IDh2M2w0LTQtNC00djN6Ii8+PC9zdmc+';
140
141	/**
142	 *
143	 */
144	Editor.syncDisabledImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTAgNi4zNVY0LjI2Yy0uOC4yMS0xLjU1LjU0LTIuMjMuOTZsMS40NiAxLjQ2Yy4yNS0uMTIuNS0uMjQuNzctLjMzem0tNy4xNC0uOTRsMi4zNiAyLjM2QzQuNDUgOC45OSA0IDEwLjQ0IDQgMTJjMCAyLjIxLjkxIDQuMiAyLjM2IDUuNjRMNCAyMGg2di02bC0yLjI0IDIuMjRDNi42OCAxNS4xNSA2IDEzLjY2IDYgMTJjMC0xIC4yNS0xLjk0LjY4LTIuNzdsOC4wOCA4LjA4Yy0uMjUuMTMtLjUuMjUtLjc3LjM0djIuMDljLjgtLjIxIDEuNTUtLjU0IDIuMjMtLjk2bDIuMzYgMi4zNiAxLjI3LTEuMjdMNC4xNCA0LjE0IDIuODYgNS40MXpNMjAgNGgtNnY2bDIuMjQtMi4yNEMxNy4zMiA4Ljg1IDE4IDEwLjM0IDE4IDEyYzAgMS0uMjUgMS45NC0uNjggMi43N2wxLjQ2IDEuNDZDMTkuNTUgMTUuMDEgMjAgMTMuNTYgMjAgMTJjMC0yLjIxLS45MS00LjItMi4zNi01LjY0TDIwIDR6Ii8+PC9zdmc+';
145
146	/**
147	 *
148	 */
149	Editor.syncProblemImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMyAxMmMwIDIuMjEuOTEgNC4yIDIuMzYgNS42NEwzIDIwaDZ2LTZsLTIuMjQgMi4yNEM1LjY4IDE1LjE1IDUgMTMuNjYgNSAxMmMwLTIuNjEgMS42Ny00LjgzIDQtNS42NVY0LjI2QzUuNTUgNS4xNSAzIDguMjcgMyAxMnptOCA1aDJ2LTJoLTJ2MnpNMjEgNGgtNnY2bDIuMjQtMi4yNEMxOC4zMiA4Ljg1IDE5IDEwLjM0IDE5IDEyYzAgMi42MS0xLjY3IDQuODMtNCA1LjY1djIuMDljMy40NS0uODkgNi00LjAxIDYtNy43NCAwLTIuMjEtLjkxLTQuMi0yLjM2LTUuNjRMMjEgNHptLTEwIDloMlY3aC0ydjZ6Ii8+PC9zdmc+';
150
151	/**
152	 *
153	 */
154	Editor.drawLogoImage = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJFYmVuZV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKCSB2aWV3Qm94PSIwIDAgMjUwIDI1MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjUwIDI1MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MntmaWxsOiMwMDA7fQo8L3N0eWxlPgo8cGF0aCBjbGFzcz0ic3QyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLDE1KSIgZD0iTTE5Ny4xLDEzOC4zaC0yMy43bC0yNS00Mi43YzUuNy0xLjIsOS44LTYuMiw5LjctMTJWNTEuNWMwLTYuOC01LjQtMTIuMy0xMi4yLTEyLjNjMCwwLTAuMSwwLTAuMSwwaC00MS43CgljLTYuOCwwLTEyLjMsNS40LTEyLjMsMTIuMmMwLDAsMCwwLjEsMCwwLjF2MzIuMWMwLDUuOCw0LDEwLjgsOS43LDEybC0yNSw0Mi43SDUyLjljLTYuOCwwLTEyLjMsNS40LTEyLjMsMTIuMmMwLDAsMCwwLjEsMCwwLjEKCXYzMi4xYzAsNi44LDUuNCwxMi4zLDEyLjIsMTIuM2MwLDAsMC4xLDAsMC4xLDBoNDEuN2M2LjgsMCwxMi4zLTUuNCwxMi4zLTEyLjJjMCwwLDAtMC4xLDAtMC4xdi0zMi4xYzAtNi44LTUuNC0xMi4zLTEyLjItMTIuMwoJYzAsMC0wLjEsMC0wLjEsMGgtNGwyNC44LTQyLjRoMTkuM2wyNC45LDQyLjRoLTQuMWMtNi44LDAtMTIuMyw1LjQtMTIuMywxMi4yYzAsMCwwLDAuMSwwLDAuMXYzMi4xYzAsNi44LDUuNCwxMi4zLDEyLjIsMTIuMwoJYzAsMCwwLjEsMCwwLjEsMGg0MS43YzYuOCwwLDEyLjMtNS40LDEyLjMtMTIuMmMwLDAsMC0wLjEsMC0wLjF2LTMyLjFjMC02LjgtNS40LTEyLjMtMTIuMi0xMi4zCglDMTk3LjIsMTM4LjMsMTk3LjIsMTM4LjMsMTk3LjEsMTM4LjN6Ii8+Cjwvc3ZnPgo=';
155
156	/**
157	 *
158	 */
159	Editor.tailSpin = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9Ii0yIC0yIDQ0IDQ0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogICAgPGRlZnM+CiAgICAgICAgPGxpbmVhckdyYWRpZW50IHgxPSI4LjA0MiUiIHkxPSIwJSIgeDI9IjY1LjY4MiUiIHkyPSIyMy44NjUlIiBpZD0iYSI+CiAgICAgICAgICAgIDxzdG9wIHN0b3AtY29sb3I9IiM4MDgwODAiIHN0b3Atb3BhY2l0eT0iMCIgb2Zmc2V0PSIwJSIvPgogICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjODA4MDgwIiBzdG9wLW9wYWNpdHk9Ii42MzEiIG9mZnNldD0iNjMuMTQ2JSIvPgogICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjODA4MDgwIiBvZmZzZXQ9IjEwMCUiLz4KICAgICAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPC9kZWZzPgogICAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxIDEpIj4KICAgICAgICAgICAgPHBhdGggZD0iTTM2IDE4YzAtOS45NC04LjA2LTE4LTE4LTE4IiBzdHJva2U9InVybCgjYSkiIHN0cm9rZS13aWR0aD0iNiI+CiAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybQogICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIKICAgICAgICAgICAgICAgICAgICB0eXBlPSJyb3RhdGUiCiAgICAgICAgICAgICAgICAgICAgZnJvbT0iMCAxOCAxOCIKICAgICAgICAgICAgICAgICAgICB0bz0iMzYwIDE4IDE4IgogICAgICAgICAgICAgICAgICAgIGR1cj0iMC45cyIKICAgICAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICAgICAgICAgICAgPC9wYXRoPgogICAgICAgICAgICA8Y2lyY2xlIGZpbGw9IiM4MDgwODAiIGN4PSIzNiIgY3k9IjE4IiByPSIxIj4KICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtCiAgICAgICAgICAgICAgICAgICAgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIgogICAgICAgICAgICAgICAgICAgIHR5cGU9InJvdGF0ZSIKICAgICAgICAgICAgICAgICAgICBmcm9tPSIwIDE4IDE4IgogICAgICAgICAgICAgICAgICAgIHRvPSIzNjAgMTggMTgiCiAgICAgICAgICAgICAgICAgICAgZHVyPSIwLjlzIgogICAgICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogICAgICAgICAgICA8L2NpcmNsZT4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo=';
160
161	/**
162	 * Used in the GraphViewer lightbox.
163	 */
164	Editor.tweetImage = IMAGE_PATH + '/tweet.png';
165
166	/**
167	 * Used in the GraphViewer lightbox.
168	 */
169	Editor.facebookImage = IMAGE_PATH + '/facebook.png';
170
171	/**
172	 *
173	 */
174	Editor.blankImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==';
175
176	/**
177	 *
178	 */
179	Editor.hiResImage = (mxClient.IS_SVG) ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA+CAMAAACLMWy1AAAAh1BMVEUAAABMTExERERBQUFBQUFFRUVAQEBCQkJAQEA6OjpDQ0NKSkpBQUFBQUFERERERERBQUFCQkJCQkJCQkJJSUlBQUFCQkJDQ0NDQ0NCQkJDQ0NBQUFBQUFCQkJBQUFCQkJCQkJDQ0NCQkJHR0dBQUFCQkJCQkJAQEBCQkJDQ0NAQEBERERCQkIk1hS2AAAAKnRSTlMAAjj96BL7PgQFRwfu3TYazKuVjRXl1V1DPCn1uLGjnWNVIgy9hU40eGqPkM38AAACG0lEQVRYw+2X63KbMBCFzwZblgGDceN74muatpLe//m6MHV3gHGFAv2RjM94MAbxzdnVsQbBDKwH8AH8MDAyafzjqYeyOG04XE7RS8nIRDXg6BlT+rA0nmtAPh+NQRDxIASIMG44rAMrGunBgHwy3uUldxggIStGKp2f+DQc2O4h4eQsX3O2IFB/oEbsjOKbStnjAEA+zJ0ylZTbgvoDn8xNyn6Dbj5Kd4GsNpABa6duQPfSdEj88TgMAhKuCWjAkgmFXPLnsD0pWd3OFGdrMugQII/eOMPEiGOzqPMIeWrcSoMCg71W1pXBPvCP+gS/OdXqQ3uW23+93XGWLl/OaBb805bNcBPoEIcVJsnHzcxpZH86u5KZ9gDby5dQCcnKqdbke4ItI4Tzd7IW9hZQt4EO6GG9b9sYuuK9Wwn8TIr2xKbF2+3Nhr+qxChJ/AI6pIfCu4z4Zowp4ZUNihz79vewzctnHDwTvQO/hCdFBzrUGDOPn2Y/F8YKT4oOATLvlhOznzmBSdFBJWtc58y7r+UVFOCQczy3wpN6pegDqHtsCPTGvH9JuTO0Dyg8icldYPk+RB6g8Aofj4m2EKBvtTmUPD9xDd1pPcSReV2U5iD/ik2yrngtvvqBfPzOvKiDTKTsCdoHZJ7pLLffgTwlJ5vJdtJV2/jiAYaLvLGhMAEDO5QcDg2M/jOw/8Zn+K3ZwJvHT7ZffgC/NvA3zcybTeIfE4EAAAAASUVORK5CYII=' : IMAGE_PATH + '/img-hi-res.png';
180
181	/**
182	 *
183	 */
184	Editor.loResImage = (mxClient.IS_SVG) ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA+CAMAAACLMWy1AAAAS1BMVEVAQEAAAAA1NTVBQUFDQ0NDQ0NFRUVERERBQUFBQUFBQUFAQEBBQUFBQUFCQkJCQkJCQkJBQUFCQkJDQ0NDQ0NCQkJCQkJCQkJGRkb5/XqTAAAAGXRSTlP+AAWODlASCsesX+Lc2LyWe3pwa1tCPjohjSJfoAAAAI1JREFUWMPt1MkKhTAMRuG0anvneXr/J71nUypKcdqI/N8yhLMKMZE1CahnClDQzMPB44ED3EgeCubgDWnWQMHpwTtKwTe+UHD4sJ94wbUEHHFGhILlYDeSnsQeabeCgsPBgB0MOZZ9oGA5GJFiJSfUULAfjLjARrhCwX7wh2YCDwVbwZkUBKqFFJRN+wOcwSgR2sREcgAAAABJRU5ErkJggg==' : IMAGE_PATH + '/img-lo-res.png';
185
186	/**
187	 *
188	 */
189	Editor.cameraImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE0LjEyIDRsMS44MyAySDIwdjEySDRWNmg0LjA1bDEuODMtMmg0LjI0TTE1IDJIOUw3LjE3IDRINGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY2YzAtMS4xLS45LTItMi0yaC0zLjE3TDE1IDJ6bS0zIDdjMS42NSAwIDMgMS4zNSAzIDNzLTEuMzUgMy0zIDMtMy0xLjM1LTMtMyAxLjM1LTMgMy0zbTAtMmMtMi43NiAwLTUgMi4yNC01IDVzMi4yNCA1IDUgNSA1LTIuMjQgNS01LTIuMjQtNS01LTV6Ii8+PC9zdmc+';
190
191	/**
192	 *
193	 */
194	Editor.tagsImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjE4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE4cHgiIGZpbGw9IiMwMDAwMDAiPjxnPjxwYXRoIGQ9Ik0wLDBoMjR2MjRIMFYweiIgZmlsbD0ibm9uZSIvPjwvZz48Zz48Zz48cGF0aCBkPSJNMjEuNDEsMTEuNDFsLTguODMtOC44M0MxMi4yMSwyLjIxLDExLjcsMiwxMS4xNywySDRDMi45LDIsMiwyLjksMiw0djcuMTdjMCwwLjUzLDAuMjEsMS4wNCwwLjU5LDEuNDFsOC44Myw4LjgzIGMwLjc4LDAuNzgsMi4wNSwwLjc4LDIuODMsMGw3LjE3LTcuMTdDMjIuMiwxMy40NiwyMi4yLDEyLjIsMjEuNDEsMTEuNDF6IE0xMi44MywyMEw0LDExLjE3VjRoNy4xN0wyMCwxMi44M0wxMi44MywyMHoiLz48Y2lyY2xlIGN4PSI2LjUiIGN5PSI2LjUiIHI9IjEuNSIvPjwvZz48L2c+PC9zdmc+';
195
196	/**
197	 * Broken image symbol for offline SVG.
198	 */
199	Editor.svgBrokenImage = Graph.createSvgImage(10, 10, '<rect x="0" y="0" width="10" height="10" stroke="#000" fill="transparent"/><path d="m 0 0 L 10 10 L 0 10 L 10 0" stroke="#000" fill="transparent"/>');
200
201	/**
202	 * Error image for not found images
203	 */
204	Editor.errorImage = 'data:image/gif;base64,R0lGODlhEAAQAPcAAADGAIQAAISEhP8AAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEAAAAALAAAAAAQABAAAAhoAAEIFBigYMGBCAkGGMCQ4cGECxtKHBAAYUQCEzFSHLiQgMeGHjEGEAAg4oCQJz86LCkxpEqHAkwyRClxpEyXGmGaREmTIsmOL1GO/DkzI0yOE2sKIMlRJsWhCQHENDiUaVSpS5cmDAgAOw==';
205
206	/**
207	 * Error image for not found images
208	 */
209	Editor.configurationKey = '.configuration';
210
211	/**
212	 * Error image for not found images
213	 */
214	Editor.settingsKey = '.drawio-config';
215
216	/**
217	 * Default value for custom libraries in mxSettings.
218	 */
219	Editor.defaultCustomLibraries = [];
220
221	/**
222	 * Default value for custom libraries in mxSettings.
223	 */
224	Editor.enableCustomLibraries = true;
225
226	/**
227	 * Specifies if custom properties should be enabled.
228	 */
229	Editor.enableCustomProperties = true;
230
231	/**
232	 * Sets the default value for including a copy of the diagram.
233	 * Default is true.
234	 */
235	Editor.defaultIncludeDiagram = true;
236
237	/**
238	 * Specifies if custom properties should be enabled.
239	 */
240	Editor.enableServiceWorker = urlParams['pwa'] != '0' &&
241		'serviceWorker' in navigator && (urlParams['offline'] == '1' ||
242		/.*\.diagrams\.net$/.test(window.location.hostname) ||
243		/.*\.draw\.io$/.test(window.location.hostname));
244
245	/**
246	 * Specifies if web fonts are enabled.
247	 */
248	Editor.enableWebFonts = urlParams['safe-style-src'] != '1';
249
250	/**
251	 * Disables the shadow option in the format panel.
252	 */
253	Editor.enableShadowOption = !mxClient.IS_SF;
254
255	/**
256	 * Disables the export URL function.
257	 */
258	Editor.enableExportUrl = true;
259
260	/**
261	 * Specifies if XML files should be compressed. Default is true.
262	 */
263	Editor.compressXml = true;
264
265	/**
266	 * Specifies if XML files should be compressed. Default is true.
267	 */
268	Editor.oneDriveInlinePicker = (window.urlParams != null && window.urlParams['inlinePicker'] == '0') ? false : true;
269
270	/**
271	 * Specifies global variables.
272	 */
273	Editor.globalVars = null;
274
275	/**
276	 * Reference to the config object passed to <configure>.
277	 */
278	Editor.config = null;
279
280	/**
281	 * Reference to the version of the last config object in
282	 * <configure>. If this is different to the last version in
283	 * mxSettings.parse, then the settings are reset.
284	 */
285	Editor.configVersion = null;
286
287	/**
288	 * Default border for image export (to allow for sketch style).
289	 */
290	Editor.defaultBorder = 5;
291
292	/**
293	 * Common properties for all edges.
294	 */
295	Editor.commonProperties = [
296        {name: 'comic', dispName: 'Comic', type: 'bool', defVal: false, isVisible: function(state, format)
297        {
298        	return mxUtils.getValue(state.style, 'sketch', '0') != '1';
299        }},
300        {name: 'jiggle', dispName: 'Jiggle', type: 'float', min: 0, defVal: 1, isVisible: function(state, format)
301        {
302        	return mxUtils.getValue(state.style, 'comic', '0') == '1' ||
303        		mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
304        }},
305        {name: 'fillWeight', dispName: 'Fill Weight', type: 'int', defVal: -1, isVisible: function(state, format)
306        {
307        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
308        }},
309        {name: 'hachureGap', dispName: 'Hachure Gap', type: 'int', defVal: -1, isVisible: function(state, format)
310        {
311        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
312        }},
313        {name: 'hachureAngle', dispName: 'Hachure Angle', type: 'int', defVal: -41, isVisible: function(state, format)
314        {
315        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
316        }},
317        {name: 'curveFitting', dispName: 'Curve Fitting', type: 'float', defVal: 0.95, isVisible: function(state, format)
318        {
319        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
320        }},
321        {name: 'simplification', dispName: 'Simplification', type: 'float', defVal: 0, min: 0, max: 1, isVisible: function(state, format)
322        {
323        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
324        }},
325        {name: 'disableMultiStroke', dispName: 'Disable Multi Stroke', type: 'bool', defVal: false, isVisible: function(state, format)
326        {
327        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
328        }},
329        {name: 'disableMultiStrokeFill', dispName: 'Disable Multi Stroke Fill', type: 'bool', defVal: false, isVisible: function(state, format)
330        {
331        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
332        }},
333        {name: 'dashOffset', dispName: 'Dash Offset', type: 'int', defVal: -1, isVisible: function(state, format)
334        {
335        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
336        }},
337        {name: 'dashGap', dispName: 'Dash Gap', type: 'int', defVal: -1, isVisible: function(state, format)
338        {
339        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
340        }},
341        {name: 'zigzagOffset', dispName: 'ZigZag Offset', type: 'int', defVal: -1, isVisible: function(state, format)
342        {
343        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
344        }},
345        {name: 'jiggle', dispName: 'Jiggle', type: 'float', min: 0, defVal: 1, isVisible: function(state, format)
346        {
347        	return mxUtils.getValue(state.style, 'comic', '0') == '1' ||
348        		mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
349        }},
350        {name: 'sketchStyle', dispName: 'Sketch Style', type: 'enum', defVal: 'rough',
351        	enumList: [{val: 'rough', dispName: 'Rough'}, {val: 'comic', dispName: 'Comic'}],
352        	isVisible: function(state, format)
353        {
354        	return mxUtils.getValue(state.style, 'sketch', (urlParams['rough'] == '1') ? '1' : '0') == '1';
355        }}
356	];
357
358	/**
359	 * Common properties for all edges.
360	 */
361	Editor.commonEdgeProperties = [
362        {type: 'separator'},
363        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
364        {name: 'sourcePortConstraint', dispName: 'Source Constraint', type: 'enum', defVal: 'none',
365        	enumList: [{val: 'none', dispName: 'None'}, {val: 'north', dispName: 'North'}, {val: 'east', dispName: 'East'}, {val: 'south', dispName: 'South'}, {val: 'west', dispName: 'West'}]
366        },
367        {name: 'targetPortConstraint', dispName: 'Target Constraint', type: 'enum', defVal: 'none',
368        	enumList: [{val: 'none', dispName: 'None'}, {val: 'north', dispName: 'North'}, {val: 'east', dispName: 'East'}, {val: 'south', dispName: 'South'}, {val: 'west', dispName: 'West'}]
369        },
370        {name: 'jettySize', dispName: 'Jetty Size', type: 'int', min: 0, defVal: 'auto', allowAuto: true, isVisible: function(state)
371        {
372    		return mxUtils.getValue(state.style, mxConstants.STYLE_EDGE, null) == 'orthogonalEdgeStyle';
373        }},
374        {name: 'fillOpacity', dispName: 'Fill Opacity', type: 'int', min: 0, max: 100, defVal: 100},
375        {name: 'strokeOpacity', dispName: 'Stroke Opacity', type: 'int', min: 0, max: 100, defVal: 100},
376        {name: 'startFill', dispName: 'Start Fill', type: 'bool', defVal: true},
377        {name: 'endFill', dispName: 'End Fill', type: 'bool', defVal: true},
378        {name: 'perimeterSpacing', dispName: 'Terminal Spacing', type: 'float', defVal: 0},
379        {name: 'anchorPointDirection', dispName: 'Anchor Direction', type: 'bool', defVal: true},
380        {name: 'snapToPoint', dispName: 'Snap to Point', type: 'bool', defVal: false},
381        {name: 'fixDash', dispName: 'Fixed Dash', type: 'bool', defVal: false},
382        {name: 'editable', dispName: 'Editable', type: 'bool', defVal: true},
383        {name: 'metaEdit', dispName: 'Edit Dialog', type: 'bool', defVal: false},
384        {name: 'backgroundOutline', dispName: 'Background Outline', type: 'bool', defVal: false},
385        {name: 'bendable', dispName: 'Bendable', type: 'bool', defVal: true},
386        {name: 'movable', dispName: 'Movable', type: 'bool', defVal: true},
387        {name: 'cloneable', dispName: 'Cloneable', type: 'bool', defVal: true},
388        {name: 'deletable', dispName: 'Deletable', type: 'bool', defVal: true},
389        {name: 'noJump', dispName: 'No Jumps', type: 'bool', defVal: false},
390        {name: 'flowAnimation', dispName: 'Flow Animation', type: 'bool', defVal: false},
391		{name: 'ignoreEdge', dispName: 'Ignore Edge', type: 'bool', defVal: false},
392        {name: 'orthogonalLoop', dispName: 'Loop Routing', type: 'bool', defVal: false},
393		{name: 'orthogonal', dispName: 'Orthogonal', type: 'bool', defVal: false}
394	].concat(Editor.commonProperties);
395
396	/**
397	 * Common properties for all vertices.
398	 */
399	Editor.commonVertexProperties = [
400        {name: 'colspan', dispName: 'Colspan', type: 'int', min: 1, defVal: 1, isVisible: function(state, format)
401        {
402        	var graph = format.editorUi.editor.graph;
403
404    		return urlParams['test'] == '1' && state.vertices.length == 1 &&
405				state.edges.length == 0 && graph.isTableCell(state.vertices[0]);
406        }},
407        {name: 'rowspan', dispName: 'Rowspan', type: 'int', min: 1, defVal: 1, isVisible: function(state, format)
408        {
409        	var graph = format.editorUi.editor.graph;
410
411    		return urlParams['test'] == '1' && state.vertices.length == 1 &&
412				state.edges.length == 0 && graph.isTableCell(state.vertices[0]);
413        }},
414        {type: 'separator'},
415        {name: 'resizeLastRow', dispName: 'Resize Last Row', type: 'bool', getDefaultValue: function(state, format)
416        {
417        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
418        	var graph = format.editorUi.editor.graph;
419        	var style = graph.getCellStyle(cell);
420
421        	return mxUtils.getValue(style, 'resizeLastRow', '0') == '1';
422        }, isVisible: function(state, format)
423        {
424        	var graph = format.editorUi.editor.graph;
425
426    		return state.vertices.length == 1 && state.edges.length == 0 &&
427    			graph.isTable(state.vertices[0]);
428        }},
429        {name: 'resizeLast', dispName: 'Resize Last Column', type: 'bool', getDefaultValue: function(state, format)
430        {
431        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
432        	var graph = format.editorUi.editor.graph;
433        	var style = graph.getCellStyle(cell);
434
435        	return mxUtils.getValue(style, 'resizeLast', '0') == '1';
436        }, isVisible: function(state, format)
437        {
438        	var graph = format.editorUi.editor.graph;
439
440    		return state.vertices.length == 1 && state.edges.length == 0 &&
441    			graph.isTable(state.vertices[0]);
442        }},
443        {name: 'fillOpacity', dispName: 'Fill Opacity', type: 'int', min: 0, max: 100, defVal: 100},
444        {name: 'strokeOpacity', dispName: 'Stroke Opacity', type: 'int', min: 0, max: 100, defVal: 100},
445        {name: 'overflow', dispName: 'Text Overflow', defVal: 'visible', type: 'enum',
446        	enumList: [{val: 'visible', dispName: 'Visible'}, {val: 'hidden', dispName: 'Hidden'}, {val: 'block', dispName: 'Block'},
447        		{val: 'fill', dispName: 'Fill'}, {val: 'width', dispName: 'Width'}]
448        },
449        {name: 'noLabel', dispName: 'Hide Label', type: 'bool', defVal: false},
450        {name: 'labelPadding', dispName: 'Label Padding', type: 'float', defVal: 0},
451        {name: 'direction', dispName: 'Direction', type: 'enum', defVal: 'east',
452        	enumList: [{val: 'north', dispName: 'North'}, {val: 'east', dispName: 'East'}, {val: 'south', dispName: 'South'}, {val: 'west', dispName: 'West'}]
453        },
454        {name: 'portConstraint', dispName: 'Constraint', type: 'enum', defVal: 'none',
455        	enumList: [{val: 'none', dispName: 'None'}, {val: 'north', dispName: 'North'}, {val: 'east', dispName: 'East'}, {val: 'south', dispName: 'South'}, {val: 'west', dispName: 'West'}]
456        },
457        {name: 'portConstraintRotation', dispName: 'Rotate Constraint', type: 'bool', defVal: false},
458        {name: 'connectable', dispName: 'Connectable', type: 'bool', getDefaultValue: function(state, format)
459        {
460        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
461        	var graph = format.editorUi.editor.graph;
462
463        	return graph.isCellConnectable(cell);
464        }, isVisible: function(state, format)
465        {
466    		return state.vertices.length == 1 && state.edges.length == 0;
467        }},
468        {name: 'allowArrows', dispName: 'Allow Arrows', type: 'bool', defVal: true},
469        {name: 'snapToPoint', dispName: 'Snap to Point', type: 'bool', defVal: false},
470        {name: 'perimeter', dispName: 'Perimeter', defVal: 'none', type: 'enum',
471        	enumList: [{val: 'none', dispName: 'None'},
472        			{val: 'rectanglePerimeter', dispName: 'Rectangle'}, {val: 'ellipsePerimeter', dispName: 'Ellipse'},
473        			{val: 'rhombusPerimeter', dispName: 'Rhombus'}, {val: 'trianglePerimeter', dispName: 'Triangle'},
474        			{val: 'hexagonPerimeter2', dispName: 'Hexagon'}, {val: 'lifelinePerimeter', dispName: 'Lifeline'},
475        			{val: 'orthogonalPerimeter', dispName: 'Orthogonal'}, {val: 'backbonePerimeter', dispName: 'Backbone'},
476        			{val: 'calloutPerimeter', dispName: 'Callout'}, {val: 'parallelogramPerimeter', dispName: 'Parallelogram'},
477        			{val: 'trapezoidPerimeter', dispName: 'Trapezoid'}, {val: 'stepPerimeter', dispName: 'Step'},
478        			{val: 'centerPerimeter', dispName: 'Center'}]
479        },
480        {name: 'fixDash', dispName: 'Fixed Dash', type: 'bool', defVal: false},
481        {name: 'autosize', dispName: 'Autosize', type: 'bool', defVal: false},
482        {name: 'container', dispName: 'Container', type: 'bool', defVal: false, isVisible: function(state, format)
483        {
484    		return state.vertices.length == 1 && state.edges.length == 0;
485        }},
486        {name: 'dropTarget', dispName: 'Drop Target', type: 'bool', getDefaultValue: function(state, format)
487        {
488        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
489        	var graph = format.editorUi.editor.graph;
490
491        	return cell != null && (graph.isSwimlane(cell) || graph.model.getChildCount(cell) > 0);
492        }, isVisible: function(state, format)
493        {
494    		return state.vertices.length == 1 && state.edges.length == 0;
495        }},
496        {name: 'collapsible', dispName: 'Collapsible', type: 'bool', getDefaultValue: function(state, format)
497        {
498        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
499        	var graph = format.editorUi.editor.graph;
500
501        	return cell != null && ((graph.isContainer(cell) && state.style['collapsible'] != '0') ||
502        		(!graph.isContainer(cell) && state.style['collapsible'] == '1'));
503        }, isVisible: function(state, format)
504        {
505    		return state.vertices.length == 1 && state.edges.length == 0;
506        }},
507        {name: 'recursiveResize', dispName: 'Resize Children', type: 'bool', defVal: true, isVisible: function(state, format)
508        {
509    		return state.vertices.length == 1 && state.edges.length == 0 &&
510    			!format.editorUi.editor.graph.isSwimlane(state.vertices[0]) &&
511    			mxUtils.getValue(state.style, 'childLayout', null) == null;
512        }},
513        {name: 'expand', dispName: 'Expand', type: 'bool', defVal: true},
514        {name: 'part', dispName: 'Part', type: 'bool', defVal: false, isVisible: function(state, format)
515        {
516        	var model = format.editorUi.editor.graph.model;
517
518        	return (state.vertices.length > 0) ? model.isVertex(model.getParent(state.vertices[0])) : false;
519        }},
520        {name: 'editable', dispName: 'Editable', type: 'bool', defVal: true},
521        {name: 'metaEdit', dispName: 'Edit Dialog', type: 'bool', defVal: false},
522        {name: 'backgroundOutline', dispName: 'Background Outline', type: 'bool', defVal: false},
523        {name: 'movable', dispName: 'Movable', type: 'bool', defVal: true},
524        {name: 'movableLabel', dispName: 'Movable Label', type: 'bool', defVal: false, isVisible: function(state, format)
525        {
526    		var geo = (state.vertices.length > 0) ? format.editorUi.editor.graph.getCellGeometry(state.vertices[0]) : null;
527
528    		return geo != null && !geo.relative;
529        }},
530        {name: 'resizable', dispName: 'Resizable', type: 'bool', defVal: true},
531        {name: 'resizeWidth', dispName: 'Resize Width', type: 'bool', defVal: false},
532        {name: 'resizeHeight', dispName: 'Resize Height', type: 'bool', defVal: false},
533        {name: 'rotatable', dispName: 'Rotatable', type: 'bool', defVal: true},
534        {name: 'cloneable', dispName: 'Cloneable', type: 'bool', defVal: true},
535        {name: 'deletable', dispName: 'Deletable', type: 'bool', defVal: true},
536        {name: 'treeFolding', dispName: 'Tree Folding', type: 'bool', defVal: false},
537        {name: 'treeMoving', dispName: 'Tree Moving', type: 'bool', defVal: false},
538        {name: 'pointerEvents', dispName: 'Pointer Events', type: 'bool', defVal: true, isVisible: function(state, format)
539        {
540        	var fillColor = mxUtils.getValue(state.style, mxConstants.STYLE_FILLCOLOR, null);
541
542        	return format.editorUi.editor.graph.isSwimlane(state.vertices[0]) ||
543        		fillColor == null || fillColor == mxConstants.NONE ||
544				state.style['pointerEvents'] != null;
545        }},
546        {name: 'moveCells', dispName: 'Move Cells on Fold', type: 'bool', defVal: false, isVisible: function(state, format)
547        {
548        	return state.vertices.length > 0 && format.editorUi.editor.graph.isContainer(state.vertices[0]);
549        }}
550	].concat(Editor.commonProperties);
551
552	/**
553	 * Default value for the CSV import dialog.
554	 */
555	Editor.defaultCsvValue = '##\n' +
556		'## Example CSV import. Use ## for comments and # for configuration. Paste CSV below.\n' +
557		'## The following names are reserved and should not be used (or ignored):\n' +
558		'## id, tooltip, placeholder(s), link and label (see below)\n' +
559		'##\n' +
560		'#\n' +
561		'## Node label with placeholders and HTML.\n' +
562		'## Default is \'%name_of_first_column%\'.\n' +
563		'#\n' +
564		'# label: %name%<br><i style="color:gray;">%position%</i><br><a href="mailto:%email%">Email</a>\n' +
565		'#\n' +
566		'## Node style (placeholders are replaced once).\n' +
567		'## Default is the current style for nodes.\n' +
568		'#\n' +
569		'# style: label;image=%image%;whiteSpace=wrap;html=1;rounded=1;fillColor=%fill%;strokeColor=%stroke%;\n' +
570		'#\n' +
571		'## Parent style for nodes with child nodes (placeholders are replaced once).\n' +
572		'#\n' +
573		'# parentstyle: swimlane;whiteSpace=wrap;html=1;childLayout=stackLayout;horizontal=1;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;\n' +
574		'#\n' +
575		'## Optional column name that contains a reference to a named style in styles.\n' +
576		'## Default is the current style for nodes.\n' +
577		'#\n' +
578		'# stylename: -\n' +
579		'#\n' +
580		'## JSON for named styles of the form {"name": "style", "name": "style"} where style is a cell style with\n' +
581		'## placeholders that are replaced once.\n' +
582		'#\n' +
583		'# styles: -\n' +
584		'#\n' +
585		'## JSON for variables in styles of the form {"name": "value", "name": "value"} where name is a string\n' +
586		'## that will replace a placeholder in a style.\n' +
587		'#\n' +
588		'# vars: -\n' +
589		'#\n' +
590		'## Optional column name that contains a reference to a named label in labels.\n' +
591		'## Default is the current label.\n' +
592		'#\n' +
593		'# labelname: -\n' +
594		'#\n' +
595		'## JSON for named labels of the form {"name": "label", "name": "label"} where label is a cell label with\n' +
596		'## placeholders.\n' +
597		'#\n' +
598		'# labels: -\n' +
599		'#\n' +
600		'## Uses the given column name as the identity for cells (updates existing cells).\n' +
601		'## Default is no identity (empty value or -).\n' +
602		'#\n' +
603		'# identity: -\n' +
604		'#\n' +
605		'## Uses the given column name as the parent reference for cells. Default is no parent (empty or -).\n' +
606		'## The identity above is used for resolving the reference so it must be specified.\n' +
607		'#\n' +
608		'# parent: -\n' +
609		'#\n' +
610		'## Adds a prefix to the identity of cells to make sure they do not collide with existing cells (whose\n' +
611		'## IDs are numbers from 0..n, sometimes with a GUID prefix in the context of realtime collaboration).\n' +
612		'## Default is csvimport-.\n' +
613		'#\n' +
614		'# namespace: csvimport-\n' +
615		'#\n' +
616		'## Connections between rows ("from": source colum, "to": target column).\n' +
617		'## Label, style and invert are optional. Defaults are \'\', current style and false.\n' +
618		'## If placeholders are used in the style, they are replaced with data from the source.\n' +
619		'## An optional placeholders can be set to target to use data from the target instead.\n' +
620		'## In addition to label, an optional fromlabel and tolabel can be used to name the column\n' +
621		'## that contains the text for the label in the edges source or target (invert ignored).\n' +
622		'## In addition to those, an optional source and targetlabel can be used to specify a label\n' +
623		'## that contains placeholders referencing the respective columns in the source or target row.\n' +
624		'## The label is created in the form fromlabel + sourcelabel + label + tolabel + targetlabel.\n' +
625		'## Additional labels can be added by using an optional labels array with entries of the\n' +
626		'## form {"label": string, "x": number, "y": number, "dx": number, "dy": number} where\n' +
627		'## x is from -1 to 1 along the edge, y is orthogonal, and dx/dy are offsets in pixels.\n' +
628		'## An optional placeholders with the string value "source" or "target" can be specified\n' +
629		'## to replace placeholders in the additional label with data from the source or target.\n' +
630		'## The target column may contain a comma-separated list of values.\n' +
631		'## Multiple connect entries are allowed.\n' +
632		'#\n' +
633		'# connect: {"from": "manager", "to": "name", "invert": true, "label": "manages", \\\n' +
634		'#          "style": "curved=1;endArrow=blockThin;endFill=1;fontSize=11;"}\n' +
635		'# connect: {"from": "refs", "to": "id", "style": "curved=1;fontSize=11;"}\n' +
636		'#\n' +
637		'## Node x-coordinate. Possible value is a column name. Default is empty. Layouts will\n' +
638		'## override this value.\n' +
639		'#\n' +
640		'# left: \n' +
641		'#\n' +
642		'## Node y-coordinate. Possible value is a column name. Default is empty. Layouts will\n' +
643		'## override this value.\n' +
644		'#\n' +
645		'# top: \n' +
646		'#\n' +
647		'## Node width. Possible value is a number (in px), auto or an @ sign followed by a column\n' +
648		'## name that contains the value for the width. Default is auto.\n' +
649		'#\n' +
650		'# width: auto\n' +
651		'#\n' +
652		'## Node height. Possible value is a number (in px), auto or an @ sign followed by a column\n' +
653		'## name that contains the value for the height. Default is auto.\n' +
654		'#\n' +
655		'# height: auto\n' +
656		'#\n' +
657		'## Padding for autosize. Default is 0.\n' +
658		'#\n' +
659		'# padding: -12\n' +
660		'#\n' +
661		'## Comma-separated list of ignored columns for metadata. (These can be\n' +
662		'## used for connections and styles but will not be added as metadata.)\n' +
663		'#\n' +
664		'# ignore: id,image,fill,stroke,refs,manager\n' +
665		'#\n' +
666		'## Column to be renamed to link attribute (used as link).\n' +
667		'#\n' +
668		'# link: url\n' +
669		'#\n' +
670		'## Spacing between nodes. Default is 40.\n' +
671		'#\n' +
672		'# nodespacing: 40\n' +
673		'#\n' +
674		'## Spacing between levels of hierarchical layouts. Default is 100.\n' +
675		'#\n' +
676		'# levelspacing: 100\n' +
677		'#\n' +
678		'## Spacing between parallel edges. Default is 40. Use 0 to disable.\n' +
679		'#\n' +
680		'# edgespacing: 40\n' +
681		'#\n' +
682		'## Name or JSON of layout. Possible values are auto, none, verticaltree, horizontaltree,\n' +
683		'## verticalflow, horizontalflow, organic, circle or a JSON string as used in Layout, Apply.\n' +
684		'## Default is auto.\n' +
685		'#\n' +
686		'# layout: auto\n' +
687		'#\n' +
688		'## ---- CSV below this line. First line are column names. ----\n' +
689		'name,position,id,location,manager,email,fill,stroke,refs,url,image\n' +
690		'Tessa Miller,CFO,emi,Office 1,,me@example.com,#dae8fc,#6c8ebf,,https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-3-128.png\n' +
691		'Edward Morrison,Brand Manager,emo,Office 2,Tessa Miller,me@example.com,#d5e8d4,#82b366,,https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-10-3-128.png\n' +
692		'Alison Donovan,System Admin,rdo,Office 3,Tessa Miller,me@example.com,#d5e8d4,#82b366,"emo,tva",https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-2-128.png\n' +
693		'Evan Valet,HR Director,tva,Office 4,Tessa Miller,me@example.com,#d5e8d4,#82b366,,https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-9-2-128.png\n';
694
695	/**
696	 * Compresses the given string.
697	 */
698	Editor.createRoughCanvas = function(c)
699	{
700		var rc = rough.canvas(
701		{
702			// Provides expected function but return value is not used
703			getContext: function()
704			{
705				return c;
706			}
707		});
708
709		rc.draw = function(drawable)
710		{
711			var sets = drawable.sets || [];
712			var o = drawable.options || this.getDefaultOptions();
713
714			for (var i = 0; i < sets.length; i++)
715			{
716				var drawing = sets[i];
717
718				switch (drawing.type)
719				{
720					case 'path':
721						if (o.stroke != null)
722						{
723							this._drawToContext(c, drawing, o);
724						}
725						break;
726					case 'fillPath':
727						this._drawToContext(c, drawing, o);
728						break;
729					case 'fillSketch':
730						this.fillSketch(c, drawing, o);
731						break;
732				}
733			}
734		};
735
736		rc.fillSketch = function(ctx, drawing, o)
737		{
738			var strokeColor = c.state.strokeColor;
739			var strokeWidth = c.state.strokeWidth;
740			var strokeAlpha = c.state.strokeAlpha;
741			var dashed = c.state.dashed;
742
743			var fweight = o.fillWeight;
744			if (fweight < 0)
745			{
746				fweight = o.strokeWidth / 2;
747			}
748
749			c.setStrokeAlpha(c.state.fillAlpha);
750			c.setStrokeColor(o.fill || '');
751			c.setStrokeWidth(fweight);
752			c.setDashed(false);
753
754			this._drawToContext(ctx, drawing, o);
755
756			c.setDashed(dashed);
757			c.setStrokeWidth(strokeWidth);
758			c.setStrokeColor(strokeColor);
759			c.setStrokeAlpha(strokeAlpha);
760		};
761
762		rc._drawToContext = function(ctx, drawing, o)
763		{
764			ctx.begin();
765
766			for (var i = 0; i < drawing.ops.length; i++)
767			{
768				var item = drawing.ops[i];
769				var data = item.data;
770
771				switch (item.op)
772				{
773					case 'move':
774						ctx.moveTo(data[0], data[1]);
775						break;
776					case 'bcurveTo':
777						ctx.curveTo(data[0], data[1], data[2], data[3], data[4], data[5]);
778						break;
779					case 'lineTo':
780						ctx.lineTo(data[0], data[1]);
781						break;
782				}
783			};
784
785			ctx.end();
786
787			if (drawing.type === 'fillPath' && o.filled)
788			{
789				ctx.fill();
790			}
791			else
792			{
793				ctx.stroke();
794			}
795		};
796
797		return rc;
798	};
799
800	/**
801	 * Uses RoughJs for drawing comic shapes.
802	 */
803	(function()
804	{
805		/**
806		 * Adds handJiggle style (jiggle=n sets jiggle)
807		 */
808		function RoughCanvas(canvas, rc, shape)
809		{
810			this.canvas = canvas;
811			this.rc = rc;
812			this.shape = shape;
813
814			// Avoids "spikes" in the output
815			this.canvas.setLineJoin('round');
816			this.canvas.setLineCap('round');
817
818			this.originalBegin = this.canvas.begin;
819			this.canvas.begin = mxUtils.bind(this, RoughCanvas.prototype.begin);
820
821			this.originalEnd = this.canvas.end;
822			this.canvas.end = mxUtils.bind(this, RoughCanvas.prototype.end);
823
824			this.originalRect = this.canvas.rect;
825			this.canvas.rect = mxUtils.bind(this, RoughCanvas.prototype.rect);
826
827			this.originalRoundrect = this.canvas.roundrect;
828			this.canvas.roundrect = mxUtils.bind(this, RoughCanvas.prototype.roundrect);
829
830			this.originalEllipse = this.canvas.ellipse;
831			this.canvas.ellipse = mxUtils.bind(this, RoughCanvas.prototype.ellipse);
832
833			this.originalLineTo = this.canvas.lineTo;
834			this.canvas.lineTo = mxUtils.bind(this, RoughCanvas.prototype.lineTo);
835
836			this.originalMoveTo = this.canvas.moveTo;
837			this.canvas.moveTo = mxUtils.bind(this, RoughCanvas.prototype.moveTo);
838
839			this.originalQuadTo = this.canvas.quadTo;
840			this.canvas.quadTo = mxUtils.bind(this, RoughCanvas.prototype.quadTo);
841
842			this.originalCurveTo = this.canvas.curveTo;
843			this.canvas.curveTo = mxUtils.bind(this, RoughCanvas.prototype.curveTo);
844
845			this.originalArcTo = this.canvas.arcTo;
846			this.canvas.arcTo = mxUtils.bind(this, RoughCanvas.prototype.arcTo);
847
848			this.originalClose = this.canvas.close;
849			this.canvas.close = mxUtils.bind(this, RoughCanvas.prototype.close);
850
851			this.originalFill = this.canvas.fill;
852			this.canvas.fill = mxUtils.bind(this, RoughCanvas.prototype.fill);
853
854			this.originalStroke = this.canvas.stroke;
855			this.canvas.stroke = mxUtils.bind(this, RoughCanvas.prototype.stroke);
856
857			this.originalFillAndStroke = this.canvas.fillAndStroke;
858			this.canvas.fillAndStroke = mxUtils.bind(this, RoughCanvas.prototype.fillAndStroke);
859
860			this.path = [];
861			this.passThrough = false;
862		};
863
864		RoughCanvas.prototype.moveOp = 'M';
865		RoughCanvas.prototype.lineOp = 'L';
866		RoughCanvas.prototype.quadOp = 'Q';
867		RoughCanvas.prototype.curveOp = 'C';
868		RoughCanvas.prototype.closeOp = 'Z';
869
870		RoughCanvas.prototype.getStyle = function(stroke, fill)
871		{
872			// Random seed created from cell ID
873			var seed = 1;
874
875			if (this.shape.state != null)
876			{
877				var str = this.shape.state.cell.id;
878
879				if (str != null)
880				{
881					for (var i = 0; i < str.length; i++)
882					{
883				    	seed = ((seed << 5) - seed + str.charCodeAt(i)) << 0;
884					}
885				}
886			}
887
888			var style = {strokeWidth: this.canvas.state.strokeWidth, seed: seed, preserveVertices: true};
889			var defs = this.rc.getDefaultOptions();
890
891			if (stroke)
892			{
893				style.stroke = this.canvas.state.strokeColor === 'none' ? 'transparent' : this.canvas.state.strokeColor;
894			}
895			else
896			{
897				delete style.stroke;
898			}
899
900			var gradient = null;
901			style.filled = fill;
902
903			if (fill)
904			{
905				style.fill = this.canvas.state.fillColor === 'none' ? '' : this.canvas.state.fillColor;
906				gradient = this.canvas.state.gradientColor === 'none' ? null : this.canvas.state.gradientColor;
907			}
908			else
909			{
910				style.fill = '';
911			}
912
913			// Applies cell style
914			style['bowing'] = mxUtils.getValue(this.shape.style, 'bowing', defs['bowing']);
915			style['hachureAngle'] = mxUtils.getValue(this.shape.style, 'hachureAngle', defs['hachureAngle']);
916			style['curveFitting'] = mxUtils.getValue(this.shape.style, 'curveFitting', defs['curveFitting']);
917			style['roughness'] = mxUtils.getValue(this.shape.style, 'jiggle', defs['roughness']);
918			style['simplification'] = mxUtils.getValue(this.shape.style, 'simplification', defs['simplification']);
919			style['disableMultiStroke'] = mxUtils.getValue(this.shape.style, 'disableMultiStroke', defs['disableMultiStroke']);
920			style['disableMultiStrokeFill'] = mxUtils.getValue(this.shape.style, 'disableMultiStrokeFill', defs['disableMultiStrokeFill']);
921
922			var hachureGap = mxUtils.getValue(this.shape.style, 'hachureGap', -1);
923			style['hachureGap'] = (hachureGap == 'auto') ? -1 : hachureGap;
924			style['dashGap'] = mxUtils.getValue(this.shape.style, 'dashGap', hachureGap);
925			style['dashOffset'] = mxUtils.getValue(this.shape.style, 'dashOffset', hachureGap);
926			style['zigzagOffset'] = mxUtils.getValue(this.shape.style, 'zigzagOffset', hachureGap);
927
928			var fillWeight = mxUtils.getValue(this.shape.style, 'fillWeight', -1);
929			style['fillWeight'] = (fillWeight == 'auto') ? -1 : fillWeight;
930
931			var fillStyle = mxUtils.getValue(this.shape.style, 'fillStyle', 'auto');
932
933			if (fillStyle == 'auto')
934			{
935				var bg = mxUtils.hex2rgba((this.shape.state != null) ?
936					this.shape.state.view.graph.shapeBackgroundColor :
937					(Editor.isDarkMode() ? Editor.darkColor : '#ffffff'));
938				fillStyle = (style.fill != null && (gradient != null || (bg != null &&
939					style.fill == bg))) ? 'solid' : defs['fillStyle'];
940			}
941
942			style['fillStyle'] = fillStyle;
943
944			return style;
945		};
946
947		RoughCanvas.prototype.begin = function()
948		{
949			if (this.passThrough)
950			{
951				this.originalBegin.apply(this.canvas, arguments);
952			}
953			else
954			{
955				this.path = [];
956			}
957		};
958
959		RoughCanvas.prototype.end = function()
960		{
961			if (this.passThrough)
962			{
963				this.originalEnd.apply(this.canvas, arguments);
964			}
965			else
966			{
967				// do nothing
968			}
969		};
970
971		RoughCanvas.prototype.addOp = function()
972		{
973			if (this.path != null)
974			{
975				this.path.push(arguments[0]);
976
977				if (arguments.length > 2)
978				{
979					var s = this.canvas.state;
980
981					for (var i = 2; i < arguments.length; i += 2)
982					{
983						this.lastX = arguments[i - 1];
984						this.lastY = arguments[i];
985
986						this.path.push(this.canvas.format((this.lastX)));
987						this.path.push(this.canvas.format((this.lastY)));
988					}
989				}
990			}
991		};
992
993		RoughCanvas.prototype.lineTo = function(endX, endY)
994		{
995			if (this.passThrough)
996			{
997				this.originalLineTo.apply(this.canvas, arguments);
998			}
999			else
1000			{
1001				this.addOp(this.lineOp, endX, endY);
1002				this.lastX = endX;
1003				this.lastY = endY;
1004			}
1005		};
1006
1007		RoughCanvas.prototype.moveTo = function(endX, endY)
1008		{
1009			if (this.passThrough)
1010			{
1011				this.originalMoveTo.apply(this.canvas, arguments);
1012			}
1013			else
1014			{
1015				this.addOp(this.moveOp, endX, endY);
1016				this.lastX = endX;
1017				this.lastY = endY;
1018				this.firstX = endX;
1019				this.firstY = endY;
1020			}
1021		};
1022
1023		RoughCanvas.prototype.close = function()
1024		{
1025			if (this.passThrough)
1026			{
1027				this.originalClose.apply(this.canvas, arguments);
1028			}
1029			else
1030			{
1031				this.addOp(this.closeOp);
1032			}
1033		};
1034
1035		RoughCanvas.prototype.quadTo = function(x1, y1, x2, y2)
1036		{
1037			if (this.passThrough)
1038			{
1039				this.originalQuadTo.apply(this.canvas, arguments);
1040			}
1041			else
1042			{
1043				this.addOp(this.quadOp, x1, y1, x2, y2);
1044				this.lastX = x2;
1045				this.lastY = y2;
1046			}
1047		};
1048
1049		RoughCanvas.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
1050		{
1051			if (this.passThrough)
1052			{
1053				this.originalCurveTo.apply(this.canvas, arguments);
1054			}
1055			else
1056			{
1057				this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);
1058				this.lastX = x3;
1059				this.lastY = y3;
1060			}
1061		};
1062
1063		RoughCanvas.prototype.arcTo = function(rx, ry, angle, largeArcFlag, sweepFlag, x, y)
1064		{
1065			if (this.passThrough)
1066			{
1067				this.originalArcTo.apply(this.canvas, arguments);
1068			}
1069			else
1070			{
1071				var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);
1072
1073				if (curves != null)
1074				{
1075					for (var i = 0; i < curves.length; i += 6)
1076					{
1077						this.curveTo(curves[i], curves[i + 1], curves[i + 2],
1078							curves[i + 3], curves[i + 4], curves[i + 5]);
1079					}
1080				}
1081
1082				this.lastX = x;
1083				this.lastY = y;
1084			}
1085		};
1086
1087		RoughCanvas.prototype.rect = function(x, y, w, h)
1088		{
1089			if (this.passThrough)
1090			{
1091				this.originalRect.apply(this.canvas, arguments);
1092			}
1093			else
1094			{
1095				this.path = [];
1096				this.nextShape = this.rc.generator.rectangle(x, y, w, h, this.getStyle(true, true));
1097			}
1098		};
1099
1100		RoughCanvas.prototype.ellipse = function(x, y, w, h)
1101		{
1102			if (this.passThrough)
1103			{
1104				this.originalEllipse.apply(this.canvas, arguments);
1105			}
1106			else
1107			{
1108				this.path = [];
1109				this.nextShape = this.rc.generator.ellipse(x + w / 2, y + h / 2, w, h, this.getStyle(true, true));
1110			}
1111		};
1112
1113		RoughCanvas.prototype.roundrect = function(x, y, w, h, dx, dy)
1114		{
1115			if (this.passThrough)
1116			{
1117				this.originalRoundrect.apply(this.canvas, arguments);
1118			}
1119			else
1120			{
1121				this.begin();
1122				this.moveTo(x + dx, y);
1123				this.lineTo(x + w - dx, y);
1124				this.quadTo(x + w, y, x + w, y + dy);
1125				this.lineTo(x + w, y + h - dy);
1126				this.quadTo(x + w, y + h, x + w - dx, y + h);
1127				this.lineTo(x + dx, y + h);
1128				this.quadTo(x, y + h, x, y + h - dy);
1129				this.lineTo(x, y + dy);
1130				this.quadTo(x, y, x + dx, y);
1131			}
1132		};
1133
1134		RoughCanvas.prototype.drawPath = function(style)
1135		{
1136			if (this.path.length > 0)
1137			{
1138				this.passThrough = true;
1139				try
1140				{
1141					this.rc.path(this.path.join(' '), style);
1142				}
1143				catch (e)
1144				{
1145					// ignore
1146				}
1147				this.passThrough = false;
1148			}
1149			else if (this.nextShape != null)
1150			{
1151				for (var key in style)
1152				{
1153					this.nextShape.options[key] = style[key];
1154				}
1155
1156				if (style['stroke'] == null)
1157				{
1158					delete this.nextShape.options['stroke'];
1159				}
1160
1161				if (!style.filled)
1162				{
1163					delete this.nextShape.options['fill'];
1164				}
1165
1166				this.passThrough = true;
1167				this.rc.draw(this.nextShape);
1168				this.passThrough = false;
1169			}
1170		};
1171
1172		RoughCanvas.prototype.stroke = function()
1173		{
1174			if (this.passThrough)
1175			{
1176				this.originalStroke.apply(this.canvas, arguments);
1177			}
1178			else
1179			{
1180				this.drawPath(this.getStyle(true, false));
1181			}
1182		};
1183
1184		RoughCanvas.prototype.fill = function()
1185		{
1186			if (this.passThrough)
1187			{
1188				this.originalFill.apply(this.canvas, arguments);
1189			}
1190			else
1191			{
1192				this.drawPath(this.getStyle(false, true));
1193			}
1194		};
1195
1196		RoughCanvas.prototype.fillAndStroke = function()
1197		{
1198			if (this.passThrough)
1199			{
1200				this.originalFillAndStroke.apply(this.canvas, arguments);
1201			}
1202			else
1203			{
1204				this.drawPath(this.getStyle(true, true));
1205			}
1206		};
1207
1208		RoughCanvas.prototype.destroy = function()
1209		{
1210			 this.canvas.lineTo = this.originalLineTo;
1211			 this.canvas.moveTo = this.originalMoveTo;
1212			 this.canvas.close = this.originalClose;
1213			 this.canvas.quadTo = this.originalQuadTo;
1214			 this.canvas.curveTo = this.originalCurveTo;
1215			 this.canvas.arcTo = this.originalArcTo;
1216			 this.canvas.close = this.originalClose;
1217			 this.canvas.fill = this.originalFill;
1218			 this.canvas.stroke = this.originalStroke;
1219			 this.canvas.fillAndStroke = this.originalFillAndStroke;
1220			 this.canvas.begin = this.originalBegin;
1221			 this.canvas.end = this.originalEnd;
1222			 this.canvas.rect = this.originalRect;
1223			 this.canvas.ellipse = this.originalEllipse;
1224			 this.canvas.roundrect = this.originalRoundrect;
1225		};
1226
1227		// Returns a new HandJiggle canvas
1228		mxShape.prototype.createRoughCanvas = function(c)
1229		{
1230			return new RoughCanvas(c, Editor.createRoughCanvas(c), this);
1231		};
1232
1233		// Overrides to include sketch style
1234		var shapeCreateHandJiggle = mxShape.prototype.createHandJiggle;
1235		mxShape.prototype.createHandJiggle = function(c)
1236		{
1237			if (!this.outline && this.style != null && mxUtils.getValue(this.style,
1238					'sketch', /*(urlParams['sketch'] != '1' && urlParams['rough'] == '1') ?
1239						'1' : */'0') != '0')
1240			{
1241				if (mxUtils.getValue(this.style, 'sketchStyle', 'rough') == 'comic')
1242				{
1243					return this.createComicCanvas(c);
1244				}
1245				else
1246				{
1247					return this.createRoughCanvas(c);
1248				}
1249			}
1250			else
1251			{
1252				return shapeCreateHandJiggle.apply(this, arguments);
1253			}
1254		};
1255
1256		// Overrides for event handling on transparent background for sketch style
1257		var shapePaint = mxShape.prototype.paint;
1258		mxShape.prototype.paint = function(c)
1259		{
1260			var addTolerance = c.addTolerance;
1261			var events = true;
1262
1263			if (this.style != null)
1264			{
1265				events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
1266			}
1267
1268			if (c.handJiggle != null && c.handJiggle.constructor == RoughCanvas && !this.outline)
1269			{
1270				// Save needed for possible transforms applied during paint
1271				c.save();
1272				var fill = this.fill;
1273				var stroke = this.stroke;
1274				this.fill = null;
1275				this.stroke = null;
1276
1277				var configurePointerEvents = this.configurePointerEvents;
1278
1279				// Ignores color changes during paint
1280				var setStrokeColor = c.setStrokeColor;
1281
1282				c.setStrokeColor = function()
1283				{
1284					// ignore
1285				};
1286
1287				var setFillColor = c.setFillColor;
1288
1289				c.setFillColor = function()
1290				{
1291					// ignore
1292				};
1293
1294				// Adds stroke tolerance for plain rendering if filled
1295				if (!events && fill != null)
1296				{
1297					this.configurePointerEvents = function()
1298					{
1299						// ignore
1300					};
1301				}
1302
1303				c.handJiggle.passThrough = true;
1304
1305				shapePaint.apply(this, arguments);
1306
1307				c.handJiggle.passThrough = false;
1308				c.setFillColor = setFillColor;
1309				c.setStrokeColor = setStrokeColor;
1310				this.configurePointerEvents = configurePointerEvents;
1311				this.stroke = stroke;
1312				this.fill = fill;
1313				c.restore();
1314
1315				// Bypasses stroke tolerance for sketched rendering if filled
1316				if (events && fill != null)
1317				{
1318					c.addTolerance = function()
1319					{
1320						// ignore
1321					};
1322				}
1323			}
1324
1325			shapePaint.apply(this, arguments);
1326			c.addTolerance = addTolerance;
1327		};
1328
1329		// Overrides glass effect to disable sketch style
1330		var shapePaintGlassEffect = mxShape.prototype.paintGlassEffect;
1331		mxShape.prototype.paintGlassEffect = function(c, x, y, w, h, arc)
1332		{
1333			if (c.handJiggle != null && c.handJiggle.constructor == RoughCanvas)
1334			{
1335				c.handJiggle.passThrough = true;
1336				shapePaintGlassEffect.apply(this, arguments);
1337				c.handJiggle.passThrough = false;
1338			}
1339			else
1340			{
1341				shapePaintGlassEffect.apply(this, arguments);
1342			}
1343		};
1344	})();
1345
1346	/**
1347	 * Compresses the given string.
1348	 */
1349	Editor.fastCompress = function(data)
1350	{
1351		if (data == null || data.length == 0 || typeof(pako) === 'undefined')
1352		{
1353			return data;
1354		}
1355		else
1356		{
1357			return Graph.arrayBufferToString(pako.deflateRaw(data));
1358		}
1359	};
1360
1361	/**
1362	 * Decompresses the given string.
1363	 */
1364	Editor.fastDecompress = function(data)
1365	{
1366	   	if (data == null || data.length == 0 || typeof(pako) === 'undefined')
1367		{
1368			return data;
1369		}
1370		else
1371		{
1372			return pako.inflateRaw(Graph.stringToArrayBuffer(atob(data)), {to: 'string'});
1373		}
1374	};
1375
1376	/**
1377	 * Helper function to extract the graph model XML node.
1378	 */
1379	Editor.extractGraphModel = function(node, allowMxFile, checked)
1380	{
1381		if (node != null && typeof(pako) !== 'undefined')
1382		{
1383			var tmp = node.ownerDocument.getElementsByTagName('div');
1384			var divs = [];
1385
1386			if (tmp != null && tmp.length > 0)
1387			{
1388				for (var i = 0; i < tmp.length; i++)
1389				{
1390					if (tmp[i].getAttribute('class') == 'mxgraph')
1391					{
1392						divs.push(tmp[i]);
1393						break;
1394					}
1395				}
1396			}
1397
1398			if (divs.length > 0)
1399			{
1400				var data = divs[0].getAttribute('data-mxgraph');
1401
1402				if (data != null)
1403				{
1404					var config = JSON.parse(data);
1405
1406					if (config != null && config.xml != null)
1407					{
1408						var doc2 = mxUtils.parseXml(config.xml);
1409						node = doc2.documentElement;
1410					}
1411				}
1412				else
1413				{
1414					var divs2 = divs[0].getElementsByTagName('div');
1415
1416					if (divs2.length > 0)
1417					{
1418						var data = mxUtils.getTextContent(divs2[0]);
1419		        		data = Graph.decompress(data, null, checked);
1420
1421		        		if (data.length > 0)
1422		        		{
1423		        			var doc2 = mxUtils.parseXml(data);
1424		        			node = doc2.documentElement;
1425		        		}
1426					}
1427				}
1428			}
1429		}
1430
1431		if (node != null && node.nodeName == 'svg')
1432		{
1433			var tmp = node.getAttribute('content');
1434
1435			if (tmp != null && tmp.charAt(0) != '<' && tmp.charAt(0) != '%')
1436			{
1437				tmp = unescape((window.atob) ? atob(tmp) : Base64.decode(cont, tmp));
1438			}
1439
1440			if (tmp != null && tmp.charAt(0) == '%')
1441			{
1442				tmp = decodeURIComponent(tmp);
1443			}
1444
1445			if (tmp != null && tmp.length > 0)
1446			{
1447				node = mxUtils.parseXml(tmp).documentElement;
1448			}
1449			else
1450			{
1451				throw {message: mxResources.get('notADiagramFile')};
1452			}
1453		}
1454
1455		if (node != null && !allowMxFile)
1456		{
1457			var diagramNode = null;
1458
1459			if (node.nodeName == 'diagram')
1460			{
1461				diagramNode = node;
1462			}
1463			else if (node.nodeName == 'mxfile')
1464			{
1465				var diagrams = node.getElementsByTagName('diagram');
1466
1467				if (diagrams.length > 0)
1468				{
1469					diagramNode = diagrams[Math.max(0, Math.min(diagrams.length - 1, urlParams['page'] || 0))];
1470				}
1471			}
1472
1473			if (diagramNode != null)
1474			{
1475				node = Editor.parseDiagramNode(diagramNode, checked);
1476			}
1477		}
1478
1479		if (node != null && node.nodeName != 'mxGraphModel' && (!allowMxFile || node.nodeName != 'mxfile'))
1480		{
1481			node = null;
1482		}
1483
1484		return node;
1485	};
1486
1487	/**
1488	 * Extracts the XML from the compressed or non-compressed text chunk.
1489	 */
1490	Editor.parseDiagramNode = function(diagramNode, checked)
1491	{
1492		var text = mxUtils.trim(mxUtils.getTextContent(diagramNode));
1493		var node = null;
1494
1495		if (text.length > 0)
1496		{
1497			var tmp = Graph.decompress(text, null, checked);
1498
1499			if (tmp != null && tmp.length > 0)
1500			{
1501				node = mxUtils.parseXml(tmp).documentElement;
1502			}
1503		}
1504		else
1505		{
1506			var temp = mxUtils.getChildNodes(diagramNode);
1507
1508			if (temp.length > 0)
1509			{
1510				// Creates new document for unique IDs within mxGraphModel
1511				var doc = mxUtils.createXmlDocument();
1512				doc.appendChild(doc.importNode(temp[0], true));
1513				node = doc.documentElement;
1514			}
1515		}
1516
1517		return node;
1518	};
1519
1520	/**
1521	 * Extracts the XML from the compressed or non-compressed text chunk.
1522	 */
1523	Editor.getDiagramNodeXml = function(diagramNode)
1524	{
1525		var text = mxUtils.getTextContent(diagramNode);
1526		var xml = null;
1527
1528		if (text.length > 0)
1529		{
1530			xml = Graph.decompress(text);
1531		}
1532		else if (diagramNode.firstChild != null)
1533		{
1534			xml = mxUtils.getXml(diagramNode.firstChild);
1535		}
1536
1537		return xml;
1538	};
1539
1540	/**
1541	 * Static method for parsing PDF files.
1542	 */
1543	Editor.extractGraphModelFromPdf = function(base64)
1544	{
1545		base64 = base64.substring(base64.indexOf(',') + 1);
1546
1547		// Workaround for invalid character error in Safari
1548		var f = (window.atob && !mxClient.IS_SF) ? atob(base64) : Base64.decode(base64, true);
1549
1550		//The new format of embedding diagram XML as embedded file (attachment) is in PDF 1.7
1551		if (f.substring(0, 8) == '%PDF-1.7')
1552		{
1553			var blockStart = f.indexOf('EmbeddedFile');
1554
1555			if (blockStart > -1)
1556			{
1557				var streamStart = f.indexOf('stream', blockStart) + 9; //the start of the stream [skipping header check]
1558				var fileInfo = f.substring(blockStart, streamStart);
1559
1560				if (fileInfo.indexOf('application#2Fvnd.jgraph.mxfile') > 0)
1561				{
1562					var streamEnd = f.indexOf('endstream', streamStart - 1);
1563
1564					return pako.inflateRaw(Graph.stringToArrayBuffer(f.substring(streamStart, streamEnd)), {to: 'string'});
1565				}
1566			}
1567
1568			//Not found
1569			return null;
1570		}
1571
1572		var check = '/Subject (%3Cmxfile';
1573		var result = null;
1574		var curline = '';
1575		var checked = 0;
1576		var pos = 0;
1577		var obj = [];
1578		var buf = null;
1579		var nr = null;
1580
1581		while (pos < f.length)
1582		{
1583			var b = f.charCodeAt(pos);
1584			pos += 1;
1585
1586			if (b != 10)
1587			{
1588				curline += String.fromCharCode(b);
1589			}
1590
1591			if (b == check.charCodeAt(checked))
1592			{
1593				checked++;
1594			}
1595			else
1596			{
1597				checked = 0;
1598			}
1599
1600			if (checked == check.length)
1601			{
1602				var end = f.indexOf('%3C%2Fmxfile%3E)', pos) + 15; //15 is the length of encoded </mxfile>
1603				pos -= 9; //9 is the length of encoded <mxfile
1604
1605				// Default case is XML inlined in Subject metadata
1606				if (end > pos)
1607				{
1608					result = f.substring(pos, end);
1609
1610					break;
1611				}
1612			}
1613
1614			// Creates table for lookup if no inline data is found
1615			if (b == 10)
1616			{
1617				if (curline == 'endobj')
1618				{
1619					buf = null;
1620				}
1621				else if (curline.substring(curline.length - 3, curline.length) == 'obj' ||
1622					curline == 'xref' || curline == 'trailer')
1623				{
1624					buf = [];
1625					obj[curline.split(' ')[0]] = buf;
1626				}
1627				else if (buf != null)
1628				{
1629					buf.push(curline);
1630				}
1631
1632				curline = '';
1633			}
1634		}
1635
1636		// Extract XML via references
1637		if (result == null)
1638		{
1639			result = Editor.extractGraphModelFromXref(obj);
1640		}
1641
1642		if (result != null)
1643		{
1644			result = decodeURIComponent(result.
1645					replace(/\\\(/g, "(").
1646					replace(/\\\)/g, ")"));
1647		}
1648
1649		return result;
1650	};
1651
1652	/**
1653	 * Static method for extracting Subject via references of the form
1654	 *
1655	 * << /Size 33 /Root 20 0 R /Info 1 0 R and 1 0 obj << /Subject 22 0 R
1656	 *
1657	 * Where Info is the metadata block and Subject is the data block.
1658	 */
1659	Editor.extractGraphModelFromXref = function(obj)
1660	{
1661		var trailer = obj['trailer'];
1662		var result = null;
1663
1664		// Gets Info object
1665		if (trailer != null)
1666		{
1667			var arr = /.* \/Info (\d+) (\d+) R/g.exec(trailer.join('\n'));
1668
1669			if (arr != null && arr.length > 0)
1670			{
1671				var info = obj[arr[1]];
1672
1673				if (info != null)
1674				{
1675					arr = /.* \/Subject (\d+) (\d+) R/g.exec(info.join('\n'));
1676
1677					if (arr != null && arr.length > 0)
1678					{
1679						var subj = obj[arr[1]];
1680
1681						if (subj != null)
1682						{
1683							subj = subj.join('\n');
1684							result = subj.substring(1, subj.length - 1);
1685						}
1686					}
1687				}
1688			}
1689		}
1690
1691		return result;
1692	};
1693
1694	/**
1695	 * Extracts the XML from the compressed or non-compressed text chunk.
1696	 */
1697	Editor.extractGraphModelFromPng = function(data)
1698	{
1699		var result = null;
1700
1701		try
1702		{
1703			var base64 = data.substring(data.indexOf(',') + 1);
1704
1705			// Workaround for invalid character error in Safari
1706			var binary = (window.atob && !mxClient.IS_SF) ? atob(base64) : Base64.decode(base64, true);
1707
1708			EditorUi.parsePng(binary, mxUtils.bind(this, function(pos, type, length)
1709			{
1710				var value = binary.substring(pos + 8, pos + 8 + length);
1711
1712				if (type == 'zTXt')
1713				{
1714					var idx = value.indexOf(String.fromCharCode(0));
1715
1716					if (value.substring(0, idx) == 'mxGraphModel')
1717					{
1718						// Workaround for Java URL Encoder using + for spaces, which isn't compatible with JS
1719						var xmlData = pako.inflateRaw(Graph.stringToArrayBuffer(
1720							value.substring(idx + 2)), {to: 'string'}).replace(/\+/g,' ');
1721
1722						if (xmlData != null && xmlData.length > 0)
1723						{
1724							result = xmlData;
1725						}
1726					}
1727				}
1728				// Uncompressed section is normally not used
1729				else if (type == 'tEXt')
1730				{
1731					var vals = value.split(String.fromCharCode(0));
1732
1733					if (vals.length > 1 && (vals[0] == 'mxGraphModel' ||
1734						vals[0] == 'mxfile'))
1735					{
1736						result = vals[1];
1737					}
1738				}
1739
1740				if (result != null || type == 'IDAT')
1741				{
1742					// Stops processing the file as our text chunks
1743					// are always placed before the data section
1744					return true;
1745				}
1746			}));
1747		}
1748		catch (e)
1749		{
1750			// ignores decoding errors
1751		}
1752
1753		if (result != null && result.charAt(0) == '%')
1754		{
1755			result = decodeURIComponent(result);
1756		}
1757
1758		// Workaround for double encoded content
1759		if (result != null && result.charAt(0) == '%')
1760		{
1761			result = decodeURIComponent(result);
1762		}
1763
1764		return result;
1765	};
1766
1767	/**
1768	 * Extracts any parsers errors in the given XML.
1769	 */
1770	Editor.extractParserError = function(node, defaultCause)
1771	{
1772		var cause = null;
1773		var errors = (node != null) ? node.getElementsByTagName('parsererror') : null;
1774
1775		if (errors != null && errors.length > 0)
1776		{
1777			cause = defaultCause || mxResources.get('invalidChars');
1778			var divs = errors[0].getElementsByTagName('div');
1779
1780			if (divs.length > 0)
1781			{
1782				cause = mxUtils.getTextContent(divs[0]);
1783			}
1784		}
1785
1786		return (cause != null) ? mxUtils.trim(cause) : cause;
1787	};
1788
1789	/**
1790	 * Adds the given retry function to the given error.
1791	 */
1792	Editor.addRetryToError = function(err, retry)
1793	{
1794		if (err != null)
1795		{
1796			var e = (err.error != null) ? err.error : err;
1797
1798			if (e.retry == null)
1799			{
1800				e.retry = retry;
1801			}
1802		}
1803	};
1804
1805	/**
1806	 * Global configuration of the Editor
1807	 * see https://www.diagrams.net/doc/faq/configure-diagram-editor
1808	 *
1809	 * For defaultVertexStyle, defaultEdgeStyle and defaultLibraries, this must be called before
1810	 * mxSettings.load via global config variable window.mxLoadSettings = false.
1811	 */
1812	Editor.configure = function(config, untrusted)
1813	{
1814		if (config != null)
1815		{
1816			Editor.config = config;
1817			Editor.configVersion = config.version;
1818			Menus.prototype.defaultFonts = config.defaultFonts || Menus.prototype.defaultFonts;
1819			ColorDialog.prototype.presetColors = config.presetColors || ColorDialog.prototype.presetColors;
1820			ColorDialog.prototype.defaultColors = config.defaultColors || ColorDialog.prototype.defaultColors;
1821			ColorDialog.prototype.colorNames = config.colorNames || ColorDialog.prototype.colorNames;
1822			StyleFormatPanel.prototype.defaultColorSchemes = config.defaultColorSchemes || StyleFormatPanel.prototype.defaultColorSchemes;
1823			Graph.prototype.defaultEdgeLength = config.defaultEdgeLength || Graph.prototype.defaultEdgeLength;
1824			DrawioFile.prototype.autosaveDelay = config.autosaveDelay || DrawioFile.prototype.autosaveDelay;
1825
1826			if (config.templateFile != null)
1827			{
1828				EditorUi.templateFile = config.templateFile;
1829			}
1830
1831			if (config.styles != null)
1832			{
1833				Editor.styles = config.styles;
1834			}
1835
1836			if (config.globalVars != null)
1837			{
1838				Editor.globalVars = config.globalVars;
1839			}
1840
1841			if (config.compressXml != null)
1842			{
1843				Editor.compressXml = config.compressXml;
1844			}
1845
1846			if (config.includeDiagram != null)
1847			{
1848				Editor.defaultIncludeDiagram = config.includeDiagram;
1849			}
1850
1851			if (config.simpleLabels != null)
1852			{
1853				Editor.simpleLabels = config.simpleLabels;
1854			}
1855
1856			if (config.oneDriveInlinePicker != null)
1857			{
1858				Editor.oneDriveInlinePicker = config.oneDriveInlinePicker;
1859			}
1860
1861			if (config.darkColor != null)
1862			{
1863				Editor.darkColor = config.darkColor;
1864			}
1865
1866			if (config.lightColor != null)
1867			{
1868				Editor.lightColor = config.lightColor;
1869			}
1870
1871			if (config.settingsName != null)
1872			{
1873				Editor.configurationKey = '.' + config.settingsName + '-configuration';
1874				Editor.settingsKey = '.' + config.settingsName + '-config';
1875				mxSettings.key = Editor.settingsKey;
1876			}
1877
1878			if (config.customFonts)
1879			{
1880				Menus.prototype.defaultFonts = config.customFonts.
1881					concat(Menus.prototype.defaultFonts);
1882			}
1883
1884			if (config.customPresetColors)
1885			{
1886				ColorDialog.prototype.presetColors = config.customPresetColors.
1887					concat(ColorDialog.prototype.presetColors);
1888			}
1889
1890			if (config.customColorSchemes != null)
1891			{
1892				StyleFormatPanel.prototype.defaultColorSchemes = config.customColorSchemes.
1893					concat(StyleFormatPanel.prototype.defaultColorSchemes);
1894			}
1895
1896			// Custom CSS injected directly into the page
1897			if (config.css != null)
1898			{
1899				var s = document.createElement('style');
1900				s.setAttribute('type', 'text/css');
1901				s.appendChild(document.createTextNode(config.css));
1902
1903				var t = document.getElementsByTagName('script')[0];
1904			  	t.parentNode.insertBefore(s, t);
1905			}
1906
1907			// Configures the custom libraries
1908			if (config.libraries != null)
1909			{
1910				Sidebar.prototype.customEntries = config.libraries;
1911			}
1912
1913			// Defines the enabled built-in libraries.
1914			if (config.enabledLibraries != null)
1915			{
1916				Sidebar.prototype.enabledLibraries = config.enabledLibraries;
1917			}
1918
1919			// Overrides default libraries
1920			if (config.defaultLibraries != null)
1921			{
1922				Sidebar.prototype.defaultEntries = config.defaultLibraries;
1923			}
1924
1925			// Overrides default custom libraries
1926			if (config.defaultCustomLibraries != null)
1927			{
1928				Editor.defaultCustomLibraries = config.defaultCustomLibraries;
1929			}
1930
1931			// Disables custom libraries
1932			if (config.enableCustomLibraries != null)
1933			{
1934				Editor.enableCustomLibraries = config.enableCustomLibraries;
1935			}
1936
1937			// Overrides default vertex style
1938			if (config.defaultVertexStyle != null)
1939			{
1940				Graph.prototype.defaultVertexStyle = config.defaultVertexStyle;
1941			}
1942
1943			// Overrides default edge style
1944			if (config.defaultEdgeStyle != null)
1945			{
1946				Graph.prototype.defaultEdgeStyle = config.defaultEdgeStyle;
1947			}
1948
1949			// Overrides default page visible
1950			if (config.defaultPageVisible != null)
1951			{
1952				Graph.prototype.defaultPageVisible = config.defaultPageVisible;
1953			}
1954
1955			// Overrides default grid enabled
1956			if (config.defaultGridEnabled != null)
1957			{
1958				Graph.prototype.defaultGridEnabled = config.defaultGridEnabled;
1959			}
1960
1961			// Overrides mouse wheel function
1962			if (config.zoomWheel != null)
1963			{
1964				Graph.zoomWheel = config.zoomWheel;
1965			}
1966
1967			// Overrides zoom factor
1968			if (config.zoomFactor != null)
1969			{
1970				var val = parseFloat(config.zoomFactor);
1971
1972				if (!isNaN(val) && val > 1)
1973				{
1974					Graph.prototype.zoomFactor = val;
1975				}
1976				else
1977				{
1978					EditorUi.debug('Invalid zoomFactor: value must be float > 1');
1979				}
1980			}
1981
1982			// Overrides grid steps
1983			if (config.gridSteps != null)
1984			{
1985				var val = parseInt(config.gridSteps);
1986
1987				if (!isNaN(val) && val > 0)
1988				{
1989					mxGraphView.prototype.gridSteps = val;
1990				}
1991				else
1992				{
1993					EditorUi.debug('Invalid gridSteps: value must be int > 0');
1994				}
1995			}
1996
1997			if (config.pageFormat != null)
1998			{
1999				var w = parseInt(config.pageFormat.width);
2000				var h = parseInt(config.pageFormat.height);
2001
2002				if (!isNaN(w) && w > 0 && !isNaN(h) && h > 0)
2003				{
2004					mxGraph.prototype.defaultPageFormat = new mxRectangle(0, 0, w, h);
2005					mxGraph.prototype.pageFormat = mxGraph.prototype.defaultPageFormat;
2006				}
2007				else
2008				{
2009					EditorUi.debug('Invalid pageFormat: value must be {width: int, height: int}');
2010				}
2011			}
2012
2013			if (config.thumbWidth)
2014			{
2015				Sidebar.prototype.thumbWidth = config.thumbWidth;
2016			}
2017
2018			if (config.thumbHeight)
2019			{
2020				Sidebar.prototype.thumbHeight = config.thumbHeight;
2021			}
2022
2023			if (config.emptyLibraryXml)
2024			{
2025				EditorUi.prototype.emptyLibraryXml = config.emptyLibraryXml;
2026			}
2027
2028			if (config.emptyDiagramXml)
2029			{
2030				EditorUi.prototype.emptyDiagramXml = config.emptyDiagramXml;
2031			}
2032
2033			if (config.sidebarWidth)
2034			{
2035				EditorUi.prototype.hsplitPosition = config.sidebarWidth;
2036			}
2037
2038			if (config.sidebarTitles)
2039			{
2040				Sidebar.prototype.sidebarTitles = config.sidebarTitles;
2041			}
2042
2043			if (config.sidebarTitleSize)
2044			{
2045				var val = parseInt(config.sidebarTitleSize);
2046
2047				if (!isNaN(val) && val > 0)
2048				{
2049					Sidebar.prototype.sidebarTitleSize = val;
2050				}
2051				else
2052				{
2053					EditorUi.debug('Invalid sidebarTitleSize: value must be int > 0');
2054				}
2055			}
2056
2057			if (config.fontCss)
2058			{
2059				if (typeof config.fontCss === 'string')
2060				{
2061					Editor.configureFontCss(config.fontCss);
2062				}
2063				else
2064				{
2065					EditorUi.debug('Invalid fontCss: value must be string');
2066				}
2067			}
2068
2069			if (config.autosaveDelay != null)
2070			{
2071				var val = parseInt(config.autosaveDelay);
2072
2073				if (!isNaN(val) && val > 0)
2074				{
2075					DrawioFile.prototype.autosaveDelay = val;
2076				}
2077				else
2078				{
2079					EditorUi.debug('Invalid autosaveDelay: value must be int > 0');
2080				}
2081			}
2082
2083			if (config.plugins != null && !untrusted)
2084			{
2085				// Required for callback
2086				App.initPluginCallback();
2087
2088				for (var i = 0; i < config.plugins.length; i++)
2089				{
2090					mxscript(config.plugins[i]);
2091				}
2092			}
2093
2094			if(config.maxImageBytes != null)
2095			{
2096				EditorUi.prototype.maxImageBytes = config.maxImageBytes;
2097			}
2098
2099			if(config.maxImageSize != null)
2100			{
2101				EditorUi.prototype.maxImageSize = config.maxImageSize;
2102			}
2103		}
2104	};
2105
2106	/**
2107	 * Adds the global fontCss configuration.
2108	 */
2109	Editor.configureFontCss = function(fontCss)
2110	{
2111		if (fontCss != null)
2112		{
2113			Editor.prototype.fontCss = fontCss;
2114			var t = document.getElementsByTagName('script')[0];
2115
2116			if (t != null && t.parentNode != null)
2117			{
2118				var s = document.createElement('style');
2119				s.setAttribute('type', 'text/css');
2120				s.appendChild(document.createTextNode(fontCss));
2121				t.parentNode.insertBefore(s, t);
2122
2123				// Preloads fonts where supported
2124				var parts = fontCss.split('url(');
2125
2126				for (var i = 1; i < parts.length; i++)
2127				{
2128				    var idx = parts[i].indexOf(')');
2129				    var url = Editor.trimCssUrl(parts[i].substring(0, idx));
2130
2131				    var l = document.createElement('link');
2132					l.setAttribute('rel', 'preload');
2133					l.setAttribute('href', url);
2134					l.setAttribute('as', 'font');
2135					l.setAttribute('crossorigin', '');
2136
2137				  	t.parentNode.insertBefore(l, t);
2138				}
2139			}
2140		}
2141	};
2142
2143	/**
2144	 * Strips leading and trailing quotes and spaces
2145	 */
2146    Editor.trimCssUrl = function(str)
2147    {
2148    	return str.replace(new RegExp("^[\\s\"']+", "g"), "").replace(new RegExp("[\\s\"']+$", "g"), "");
2149    }
2150
2151    /**
2152     * Prefix for URLs that reference Google fonts.
2153     */
2154	Editor.GOOGLE_FONTS = 'https://fonts.googleapis.com/css?family=';
2155
2156	/**
2157	 * Alphabet for global unique IDs.
2158	 */
2159	Editor.GUID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_';
2160
2161	/**
2162	 * Default length for global unique IDs.
2163	 */
2164	Editor.GUID_LENGTH = 20;
2165
2166	/**
2167	 * Default length for global unique IDs.
2168	 */
2169	Editor.guid = function(length)
2170	{
2171		var len = (length != null) ? length : Editor.GUID_LENGTH;
2172		var rtn = [];
2173
2174		for (var i = 0; i < len; i++)
2175		{
2176			rtn.push(Editor.GUID_ALPHABET.charAt(Math.floor(Math.random() * Editor.GUID_ALPHABET.length)));
2177		}
2178
2179		return rtn.join('');
2180	};
2181
2182	/**
2183	 * General timeout is 25 seconds.
2184	 */
2185	Editor.prototype.timeout = 25000;
2186
2187	/**
2188	 * Mathjax output ignores CSS transforms in Safari (lightbox and normal mode).
2189	 * Check the following test case on page 2 before enabling this in production:
2190	 * https://devhost.jgraph.com/git/drawio/etc/embed/sf-math-fo-clipping.html?dev=1
2191	 * UPDATE: Fixed via position:static CSS override in initMath.
2192	 */
2193	Editor.prototype.useForeignObjectForMath = true;
2194
2195	/**
2196	 * Executes the first step for connecting to Google Drive.
2197	 */
2198	Editor.prototype.editButtonLink = (urlParams['edit'] != null) ? decodeURIComponent(urlParams['edit']) : null;
2199
2200	/**
2201	 * Specifies if img.crossOrigin is supported. This is true for all browsers except IE10 and earlier.
2202	 */
2203	Editor.prototype.crossOriginImages = !mxClient.IS_IE;
2204
2205	/**
2206	 * Adds support for old stylesheets and compressed files
2207	 */
2208	var editorSetGraphXml = Editor.prototype.setGraphXml;
2209	Editor.prototype.setGraphXml = function(node)
2210	{
2211		node = (node != null && node.nodeName != 'mxlibrary') ? this.extractGraphModel(node) : null;
2212
2213		if (node != null)
2214		{
2215			// Checks input for parser errors
2216			var errs = node.getElementsByTagName('parsererror');
2217
2218			if (errs != null && errs.length > 0)
2219			{
2220				var elt = errs[0];
2221				var divs = elt.getElementsByTagName('div');
2222
2223				if (divs != null && divs.length > 0)
2224				{
2225					elt = divs[0];
2226				}
2227
2228				throw {message: mxUtils.getTextContent(elt)};
2229			}
2230			else if (node.nodeName == 'mxGraphModel')
2231			{
2232				var style = node.getAttribute('style') || 'default-style2';
2233
2234				// Decodes the style if required
2235				if (urlParams['embed'] != '1' && (style == null || style == ''))
2236				{
2237					var node2 = (this.graph.themes != null) ?
2238						this.graph.themes['default-old'] :
2239						mxUtils.load(STYLE_PATH + '/default-old.xml').getDocumentElement();
2240
2241				    if (node2 != null)
2242				    {
2243				    	var dec2 = new mxCodec(node2.ownerDocument);
2244				    	dec2.decode(node2, this.graph.getStylesheet());
2245				    }
2246				}
2247				else if (style != this.graph.currentStyle)
2248				{
2249				    var node2 = (this.graph.themes != null) ?
2250						this.graph.themes[style] :
2251						mxUtils.load(STYLE_PATH + '/' + style + '.xml').getDocumentElement()
2252
2253				    if (node2 != null)
2254				    {
2255				    	var dec2 = new mxCodec(node2.ownerDocument);
2256				    	dec2.decode(node2, this.graph.getStylesheet());
2257				    }
2258				}
2259
2260				this.graph.currentStyle = style;
2261				this.graph.mathEnabled = (urlParams['math'] == '1' || node.getAttribute('math') == '1');
2262
2263				var bgImg = node.getAttribute('backgroundImage');
2264
2265				if (bgImg != null)
2266				{
2267					this.graph.setBackgroundImage(this.graph.parseBackgroundImage(bgImg));
2268				}
2269				else
2270				{
2271					this.graph.setBackgroundImage(null);
2272				}
2273
2274				mxClient.NO_FO = ((this.graph.mathEnabled && !this.useForeignObjectForMath)) ?
2275					true : this.originalNoForeignObject;
2276
2277				this.graph.useCssTransforms = !mxClient.NO_FO &&
2278					this.isChromelessView() &&
2279					this.graph.isCssTransformsSupported();
2280				this.graph.updateCssTransform();
2281
2282				this.graph.setShadowVisible(node.getAttribute('shadow') == '1', false);
2283
2284				var extFonts = node.getAttribute('extFonts');
2285
2286				if (extFonts)
2287				{
2288					try
2289					{
2290						extFonts = extFonts.split('|').map(function(ef)
2291						{
2292							var parts = ef.split('^');
2293							return {name: parts[0], url: parts[1]};
2294						});
2295
2296						for (var i = 0; i < extFonts.length; i++)
2297						{
2298							this.graph.addExtFont(extFonts[i].name, extFonts[i].url);
2299						}
2300					}
2301					catch(e)
2302					{
2303						console.log('ExtFonts format error: ' + e.message);
2304					}
2305				}
2306				else if (this.graph.extFonts != null && this.graph.extFonts.length > 0)
2307				{
2308					this.graph.extFonts = [];
2309				}
2310			}
2311
2312			// Calls updateGraphComponents
2313			editorSetGraphXml.apply(this, arguments);
2314		}
2315		else
2316		{
2317			throw {
2318			    message: mxResources.get('notADiagramFile') || 'Invalid data',
2319			    toString: function() { return this.message; }
2320			};
2321		}
2322	};
2323
2324	/**
2325	 * Adds persistent style to file
2326	 */
2327	var editorGetGraphXml = Editor.prototype.getGraphXml;
2328	Editor.prototype.getGraphXml = function(ignoreSelection, resolveReferences)
2329	{
2330		ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
2331		var node = editorGetGraphXml.apply(this, arguments);
2332
2333		// Adds the current style
2334		if (this.graph.currentStyle != null && this.graph.currentStyle != 'default-style2')
2335		{
2336			node.setAttribute('style', this.graph.currentStyle);
2337		}
2338
2339		var bgImg = this.graph.getBackgroundImageObject(
2340			this.graph.backgroundImage,
2341			resolveReferences);
2342
2343		// Adds the background image
2344		if (bgImg != null)
2345		{
2346			node.setAttribute('backgroundImage', JSON.stringify(bgImg));
2347		}
2348
2349		node.setAttribute('math', (this.graph.mathEnabled) ? '1' : '0');
2350		node.setAttribute('shadow', (this.graph.shadowVisible) ? '1' : '0');
2351
2352		if (this.graph.extFonts != null && this.graph.extFonts.length > 0)
2353		{
2354			var strExtFonts = this.graph.extFonts.map(function(ef)
2355			{
2356				return ef.name + '^' + ef.url;
2357			});
2358
2359			node.setAttribute('extFonts', strExtFonts.join('|'));
2360		}
2361
2362		return node;
2363	};
2364
2365	/**
2366	 * Helper function to extract the graph model XML node.
2367	 */
2368	Editor.prototype.isDataSvg = function(svg)
2369	{
2370		try
2371		{
2372			var svgRoot = mxUtils.parseXml(svg).documentElement;
2373			var tmp = svgRoot.getAttribute('content');
2374
2375			if (tmp != null)
2376			{
2377				if (tmp != null && tmp.charAt(0) != '<' && tmp.charAt(0) != '%')
2378				{
2379					tmp = unescape((window.atob) ? atob(tmp) : Base64.decode(cont, tmp));
2380				}
2381
2382				if (tmp != null && tmp.charAt(0) == '%')
2383				{
2384					tmp = decodeURIComponent(tmp);
2385				}
2386
2387				if (tmp != null && tmp.length > 0)
2388				{
2389					var node = mxUtils.parseXml(tmp).documentElement;
2390
2391
2392					return node.nodeName == 'mxfile' || node.nodeName == 'mxGraphModel';
2393				}
2394			}
2395		}
2396		catch (e)
2397		{
2398			// ignore
2399		}
2400
2401		return false;
2402	};
2403
2404	/**
2405	 * Helper function to extract the graph model XML node.
2406	 */
2407	Editor.prototype.extractGraphModel = function(node, allowMxFile, checked)
2408	{
2409		return Editor.extractGraphModel.apply(this, arguments);
2410	};
2411
2412	/**
2413	 * Overrides reset graph.
2414	 */
2415	var editorResetGraph = Editor.prototype.resetGraph;
2416	Editor.prototype.resetGraph = function()
2417	{
2418		this.graph.mathEnabled = (urlParams['math'] == '1');
2419		this.graph.view.x0 = null;
2420		this.graph.view.y0 = null;
2421		mxClient.NO_FO = ((this.graph.mathEnabled && !this.useForeignObjectForMath)) ?
2422			true : this.originalNoForeignObject;
2423
2424		this.graph.useCssTransforms = !mxClient.NO_FO &&
2425			this.isChromelessView() &&
2426			this.graph.isCssTransformsSupported();
2427		this.graph.updateCssTransform();
2428
2429		editorResetGraph.apply(this, arguments);
2430	};
2431
2432	/**
2433	 * Math support.
2434	 */
2435	var editorUpdateGraphComponents = Editor.prototype.updateGraphComponents;
2436	Editor.prototype.updateGraphComponents = function()
2437	{
2438		editorUpdateGraphComponents.apply(this, arguments);
2439		mxClient.NO_FO = ((this.graph.mathEnabled && !this.useForeignObjectForMath) &&
2440			Editor.MathJaxRender != null) ? true : this.originalNoForeignObject;
2441
2442		this.graph.useCssTransforms = !mxClient.NO_FO &&
2443			this.isChromelessView() &&
2444			this.graph.isCssTransformsSupported();
2445		this.graph.updateCssTransform();
2446	};
2447
2448	/**
2449	 * Overrides relative position to fix clipping bug in Webkit.
2450	 */
2451	Editor.mathJaxWebkitCss = 'div.MathJax_SVG_Display { position: static; }\n' +
2452		'span.MathJax_SVG { position: static !important; }';
2453
2454	/**
2455	 * Initializes math typesetting and loads respective code.
2456	 */
2457	Editor.initMath = function(src, config)
2458	{
2459		if (typeof(window.MathJax) === 'undefined')
2460		{
2461			src = ((src != null) ? src : DRAW_MATH_URL + '/MathJax.js') + '?config=TeX-MML-AM_' +
2462				((urlParams['math-output'] == 'html') ? 'HTMLorMML' : 'SVG') + '-full';
2463			Editor.mathJaxQueue = [];
2464
2465			Editor.doMathJaxRender = function(container)
2466			{
2467				window.setTimeout(function()
2468				{
2469					if (container.style.visibility != 'hidden')
2470					{
2471						MathJax.Hub.Queue(['Typeset', MathJax.Hub, container]);
2472					}
2473				}, 0);
2474			};
2475
2476			var font = (urlParams['math-font'] != null) ?
2477				decodeURIComponent(urlParams['math-font']) : 'TeX';
2478
2479			config = (config != null) ? config :
2480			{
2481				'HTML-CSS': {
2482					availableFonts: [font],
2483					imageFont: null
2484				},
2485				SVG: {
2486					font: font,
2487					// Needed for client-side export to work
2488					useFontCache: false
2489				},
2490				// Ignores math in in-place editor
2491				tex2jax: {
2492					ignoreClass: 'mxCellEditor'
2493				},
2494				asciimath2jax: {
2495					ignoreClass: 'mxCellEditor'
2496				}
2497			};
2498
2499			// Disables global typesetting and messages on startup, adds queue for
2500			// asynchronous rendering while MathJax is loading
2501			window.MathJax =
2502			{
2503				skipStartupTypeset: true,
2504				showMathMenu: false,
2505				messageStyle: 'none',
2506				AuthorInit: function ()
2507				{
2508					MathJax.Hub.Config(config);
2509
2510					MathJax.Hub.Register.StartupHook('Begin', function()
2511					{
2512						for (var i = 0; i < Editor.mathJaxQueue.length; i++)
2513						{
2514							Editor.doMathJaxRender(Editor.mathJaxQueue[i]);
2515						}
2516					});
2517				}
2518			};
2519
2520			// Adds global enqueue method for async rendering
2521			Editor.MathJaxRender = function(container)
2522			{
2523				// Initial rendering when MathJax finished loading
2524				if (typeof(MathJax) !== 'undefined' && typeof(MathJax.Hub) !== 'undefined')
2525				{
2526					Editor.doMathJaxRender(container);
2527				}
2528				else
2529				{
2530					Editor.mathJaxQueue.push(container);
2531				}
2532			};
2533
2534			// Adds global clear queue method
2535			Editor.MathJaxClear = function()
2536			{
2537				Editor.mathJaxQueue = [];
2538			};
2539
2540			// Updates math typesetting after changes
2541			var editorInit = Editor.prototype.init;
2542
2543			Editor.prototype.init = function()
2544			{
2545				editorInit.apply(this, arguments);
2546
2547				this.graph.addListener(mxEvent.SIZE, mxUtils.bind(this, function(sender, evt)
2548				{
2549					if (this.graph.container != null && this.graph.mathEnabled && !this.graph.blockMathRender)
2550					{
2551						Editor.MathJaxRender(this.graph.container);
2552					}
2553				}));
2554			};
2555
2556			var tags = document.getElementsByTagName('script');
2557
2558			if (tags != null && tags.length > 0)
2559			{
2560				var s = document.createElement('script');
2561				s.setAttribute('type', 'text/javascript');
2562				s.setAttribute('src', src);
2563				tags[0].parentNode.appendChild(s);
2564			}
2565
2566			// Workaround for zoomed math clipping in Webkit
2567			try
2568			{
2569				if (mxClient.IS_GC || mxClient.IS_SF)
2570				{
2571					var style = document.createElement('style')
2572					style.type = 'text/css';
2573					style.innerHTML = Editor.mathJaxWebkitCss;
2574					document.getElementsByTagName('head')[0].appendChild(style);
2575				}
2576			}
2577			catch (e)
2578			{
2579				// ignore
2580			}
2581		}
2582	};
2583
2584	/**
2585	 * Return array of string values, or NULL if CSV string not well formed.
2586	 */
2587	Editor.prototype.csvToArray = function(text)
2588	{
2589	    var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
2590	    var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
2591	    // Return NULL if input string is not well formed CSV string.
2592	    if (!re_valid.test(text)) return null;
2593	    var a = [];                     // Initialize array to receive values.
2594	    text.replace(re_value, // "Walk" the string using replace with callback.
2595	        function(m0, m1, m2, m3) {
2596	            // Remove backslash from \' in single quoted values.
2597	            if      (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));
2598	            // Remove backslash from \" in double quoted values.
2599	            else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
2600	            else if (m3 !== undefined) a.push(m3);
2601	            return ''; // Return empty string.
2602	        });
2603	    // Handle special case of empty last value.
2604	    if (/,\s*$/.test(text)) a.push('');
2605	    return a;
2606	};
2607
2608	/**
2609	 * Returns true if the given URL is known to have CORS headers and is
2610	 * allowed by CSP.
2611	 */
2612	Editor.prototype.isCorsEnabledForUrl = function(url)
2613	{
2614		// Disables proxy for desktop and chrome app as it is served locally
2615		if (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp)
2616		{
2617			return true;
2618		}
2619
2620		// Does not use proxy for same domain
2621		if (url.substring(0, window.location.origin.length) == window.location.origin)
2622		{
2623			return true;
2624		}
2625
2626		// Blocked by CSP in production but allowed for hosted deployment
2627		if (urlParams['cors'] != null && this.corsRegExp == null)
2628		{
2629			this.corsRegExp = new RegExp(decodeURIComponent(urlParams['cors']));
2630		}
2631
2632		// No access-control-allow-origin for some Iconfinder images, add this when fixed:
2633		// /^https?:\/\/[^\/]*\.iconfinder.com\//.test(url) ||
2634		return (this.corsRegExp != null && this.corsRegExp.test(url)) ||
2635			url.substring(0, 34) === 'https://raw.githubusercontent.com/';
2636	};
2637
2638
2639	/**
2640	 * Converts all images in the SVG output to data URIs for immediate rendering
2641	 */
2642	Editor.prototype.createImageUrlConverter = function()
2643	{
2644		var converter = new mxUrlConverter();
2645		converter.updateBaseUrl();
2646
2647		// Extends convert to avoid CORS using an image proxy server where needed
2648		var convert = converter.convert;
2649		var self = this;
2650
2651		converter.convert = function(src)
2652		{
2653			if (src != null)
2654			{
2655				var remote = src.substring(0, 7) == 'http://' || src.substring(0, 8) == 'https://';
2656
2657				if (remote && !navigator.onLine)
2658				{
2659					src = Editor.svgBrokenImage.src;
2660				}
2661				else if (remote && src.substring(0, converter.baseUrl.length) != converter.baseUrl &&
2662						(!self.crossOriginImages || !self.isCorsEnabledForUrl(src)))
2663				{
2664					src = PROXY_URL + '?url=' + encodeURIComponent(src);
2665				}
2666				else if (src.substring(0, 19) != 'chrome-extension://' && !mxClient.IS_CHROMEAPP)
2667				{
2668					src = convert.apply(this, arguments);
2669				}
2670			}
2671
2672			return src;
2673		};
2674
2675		return converter;
2676	};
2677
2678	/**
2679	 *
2680	 */
2681	Editor.createSvgDataUri = function(svg)
2682	{
2683		return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
2684	};
2685
2686	/**
2687	 *
2688	 */
2689	Editor.prototype.convertImageToDataUri = function(url, callback)
2690	{
2691		try
2692		{
2693			var acceptResponse = true;
2694
2695			var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
2696			{
2697				acceptResponse = false;
2698				callback(Editor.svgBrokenImage.src);
2699			}), this.timeout);
2700
2701			if (/(\.svg)$/i.test(url))
2702			{
2703				mxUtils.get(url, mxUtils.bind(this, function(req)
2704				{
2705			    	window.clearTimeout(timeoutThread);
2706
2707					if (acceptResponse)
2708					{
2709						callback(Editor.createSvgDataUri(req.getText()));
2710					}
2711				}),
2712				function()
2713				{
2714			    	window.clearTimeout(timeoutThread);
2715
2716					if (acceptResponse)
2717					{
2718						callback(Editor.svgBrokenImage.src);
2719					}
2720				});
2721			}
2722			else
2723			{
2724			    var img = new Image();
2725
2726			    if (this.crossOriginImages)
2727		    	{
2728				    img.crossOrigin = 'anonymous';
2729			    }
2730
2731			    img.onload = function()
2732			    {
2733			    	window.clearTimeout(timeoutThread);
2734
2735					if (acceptResponse)
2736					{
2737				        try
2738				        {
2739					        var canvas = document.createElement('canvas');
2740					        var ctx = canvas.getContext('2d');
2741					        canvas.height = img.height;
2742					        canvas.width = img.width;
2743					        ctx.drawImage(img, 0, 0);
2744
2745				        	callback(canvas.toDataURL());
2746				        }
2747				        catch (e)
2748				        {
2749			        		callback(Editor.svgBrokenImage.src);
2750				        }
2751					}
2752			    };
2753
2754			    img.onerror = function()
2755			    {
2756			    	window.clearTimeout(timeoutThread);
2757
2758					if (acceptResponse)
2759					{
2760						callback(Editor.svgBrokenImage.src);
2761					}
2762			    };
2763
2764			    img.src = url;
2765			}
2766		}
2767		catch (e)
2768		{
2769			callback(Editor.svgBrokenImage.src);
2770		}
2771	};
2772
2773
2774	/**
2775	 * Converts all images in the SVG output to data URIs for immediate rendering
2776	 */
2777	Editor.prototype.convertImages = function(svgRoot, callback, imageCache, converter)
2778	{
2779		// Converts images to data URLs for immediate painting
2780		if (converter == null)
2781		{
2782			converter = this.createImageUrlConverter();
2783		}
2784
2785		// Barrier for asynchronous image loading
2786		var counter = 0;
2787
2788		function inc()
2789		{
2790			counter++;
2791		};
2792
2793		function dec()
2794		{
2795			counter--;
2796
2797			if (counter == 0)
2798			{
2799				callback(svgRoot);
2800			}
2801		};
2802
2803		var cache = imageCache || new Object();
2804
2805		var convertImages = mxUtils.bind(this, function(tagName, srcAttr)
2806		{
2807			var images = svgRoot.getElementsByTagName(tagName);
2808
2809			for (var i = 0; i < images.length; i++)
2810			{
2811				(mxUtils.bind(this, function(img)
2812				{
2813					try
2814					{
2815						if (img != null)
2816						{
2817							var src = converter.convert(img.getAttribute(srcAttr));
2818
2819							// Data URIs are pass-through
2820							if (src != null && src.substring(0, 5) != 'data:')
2821							{
2822								var tmp = cache[src];
2823
2824								if (tmp == null)
2825								{
2826									inc();
2827
2828									this.convertImageToDataUri(src, function(uri)
2829									{
2830										if (uri != null)
2831										{
2832											cache[src] = uri;
2833											img.setAttribute(srcAttr, uri);
2834										}
2835
2836										dec();
2837									});
2838								}
2839								else
2840								{
2841									img.setAttribute(srcAttr, tmp);
2842								}
2843							}
2844							else if (src != null)
2845							{
2846								img.setAttribute(srcAttr, src);
2847							}
2848						}
2849					}
2850					catch (e)
2851					{
2852						// ignore
2853					}
2854				}))(images[i]);
2855			}
2856		});
2857
2858		// Converts all known image tags in output
2859		// LATER: Add support for images in CSS
2860		convertImages('image', 'xlink:href');
2861		convertImages('img', 'src');
2862
2863		// All from cache or no images
2864		if (counter == 0)
2865		{
2866			callback(svgRoot);
2867		}
2868	};
2869
2870	/**
2871	 * Base64 encodes the given string. This method seems to be more
2872	 * robust for encoding PNG from binary AJAX responses.
2873	 */
2874	Editor.base64Encode = function(str)
2875	{
2876	    var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2877	    var out = "", i = 0, len = str.length, c1, c2, c3;
2878
2879	    while (i < len)
2880	    {
2881	        c1 = str.charCodeAt(i++) & 0xff;
2882
2883	        if (i == len)
2884	        {
2885	            out += CHARS.charAt(c1 >> 2);
2886	            out += CHARS.charAt((c1 & 0x3) << 4);
2887	            out += "==";
2888	            break;
2889	        }
2890
2891	        c2 = str.charCodeAt(i++);
2892
2893	        if (i == len)
2894	        {
2895	            out += CHARS.charAt(c1 >> 2);
2896	            out += CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
2897	            out += CHARS.charAt((c2 & 0xF) << 2);
2898	            out += "=";
2899	            break;
2900	        }
2901
2902	        c3 = str.charCodeAt(i++);
2903	        out += CHARS.charAt(c1 >> 2);
2904	        out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
2905	        out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
2906	        out += CHARS.charAt(c3 & 0x3F);
2907	    }
2908
2909	    return out;
2910	};
2911
2912	/**
2913	 * Checks if the client is authorized and calls the next step.
2914	 */
2915	Editor.prototype.loadUrl = function(url, success, error, forceBinary, retry, dataUriPrefix, noBinary, headers)
2916	{
2917		try
2918		{
2919			var binary = !noBinary && (forceBinary || /(\.png)($|\?)/i.test(url) ||
2920				/(\.jpe?g)($|\?)/i.test(url) || /(\.gif)($|\?)/i.test(url) ||
2921				/(\.pdf)($|\?)/i.test(url));
2922			retry = (retry != null) ? retry : true;
2923
2924			var fn = mxUtils.bind(this, function()
2925			{
2926				mxUtils.get(url, mxUtils.bind(this, function(req)
2927				{
2928					if (req.getStatus() >= 200 && req.getStatus() <= 299)
2929					{
2930				    	if (success != null)
2931				    	{
2932					    	var data = req.getText();
2933
2934				    		// Returns PNG as base64 encoded data URI
2935							if (binary)
2936							{
2937								// NOTE: This requires BinaryToArray VB script in the page
2938								if ((document.documentMode == 9 || document.documentMode == 10) &&
2939									typeof window.mxUtilsBinaryToArray !== 'undefined')
2940								{
2941									var bin = mxUtilsBinaryToArray(req.request.responseBody).toArray();
2942									var tmp = new Array(bin.length);
2943
2944									for (var i = 0; i < bin.length; i++)
2945									{
2946										tmp[i] = String.fromCharCode(bin[i]);
2947									}
2948
2949									data = tmp.join('');
2950								}
2951
2952								// LATER: Could be JPG but modern browsers
2953								// ignore the mime type in the data URI
2954								dataUriPrefix = (dataUriPrefix != null) ? dataUriPrefix : 'data:image/png;base64,';
2955								data = dataUriPrefix + Editor.base64Encode(data);
2956							}
2957
2958				    		success(data);
2959				    	}
2960					}
2961					else if (error != null)
2962			    	{
2963						if (req.getStatus() == 0)
2964						{
2965							// Handles CORS errors
2966							error({message: mxResources.get('accessDenied')}, req);
2967						}
2968						else
2969						{
2970							error({message: mxResources.get('error') + ' ' + req.getStatus()}, req);
2971						}
2972			    	}
2973				}), function(req)
2974				{
2975			    	if (error != null)
2976			    	{
2977			    		error({message: mxResources.get('error') + ' ' + req.getStatus()});
2978			    	}
2979				}, binary, this.timeout, function()
2980			    {
2981				    if (retry && error != null)
2982					{
2983						error({code: App.ERROR_TIMEOUT, retry: fn});
2984					}
2985			    }, headers);
2986			});
2987
2988			fn();
2989		}
2990		catch (e)
2991		{
2992			if (error != null)
2993			{
2994				error(e);
2995			}
2996		}
2997	};
2998
2999	/**
3000	 * Makes all relative font URLs absolute in the given font CSS.
3001	 */
3002    Editor.prototype.absoluteCssFonts = function(fontCss)
3003    {
3004    	var result = null;
3005
3006    	if (fontCss != null)
3007    	{
3008    		var parts = fontCss.split('url(');
3009
3010    		if (parts.length > 0)
3011    		{
3012    			result = [parts[0]];
3013
3014    			// Gets path for URL
3015    			var path = window.location.pathname;
3016    			var idx = (path != null) ? path.lastIndexOf('/') : -1;
3017
3018    			if (idx >= 0)
3019    			{
3020    				path = path.substring(0, idx + 1);
3021    			}
3022
3023    			// Gets base tag from head
3024    			var temp = document.getElementsByTagName('base');
3025    			var base = null;
3026
3027    			if (temp != null && temp.length > 0)
3028    			{
3029    				base = temp[0].getAttribute('href');
3030    			}
3031
3032    			for (var i = 1; i < parts.length; i++)
3033    			{
3034    				var idx = parts[i].indexOf(')');
3035
3036    				if (idx > 0)
3037    				{
3038	    				var url = Editor.trimCssUrl(parts[i].substring(0, idx));
3039
3040	    				if (this.graph.isRelativeUrl(url))
3041	    				{
3042	                        url = (base != null) ? base + url : (window.location.protocol + '//' + window.location.hostname +
3043	                        	((url.charAt(0) == '/') ? '' : path) + url);
3044	                    }
3045
3046	    				result.push('url("' + url + '"' + parts[i].substring(idx));
3047    				}
3048    				else
3049    				{
3050    					result.push(parts[i]);
3051    				}
3052    			}
3053    		}
3054    		else
3055    		{
3056    			result = [fontCss]
3057    		}
3058    	}
3059
3060    	return (result != null) ? result.join('') : null;
3061	};
3062
3063	/**
3064	 * For the fonts in CSS to be applied when rendering images on canvas, the actual
3065	 * font data must be made available via a data URI encoding of the file.
3066	 */
3067    Editor.prototype.embedCssFonts = function(fontCss, then)
3068    {
3069        var parts = fontCss.split('url(');
3070        var waiting = 0;
3071
3072        if (this.cachedFonts == null)
3073        {
3074        	this.cachedFonts = {};
3075        }
3076
3077        var finish = mxUtils.bind(this, function()
3078        {
3079            if (waiting == 0)
3080            {
3081                // Constructs string
3082                var result = [parts[0]];
3083
3084                for (var j = 1; j < parts.length; j++)
3085                {
3086                    var idx = parts[j].indexOf(')');
3087                    result.push('url("');
3088                    result.push(this.cachedFonts[Editor.trimCssUrl(parts[j].substring(0, idx))]);
3089                    result.push('"' + parts[j].substring(idx));
3090                }
3091
3092                then(result.join(''));
3093            }
3094        });
3095
3096        if (parts.length > 0)
3097        {
3098            for (var i = 1; i < parts.length; i++)
3099            {
3100                var idx = parts[i].indexOf(')');
3101                var format = null;
3102
3103                // Checks if there is a format directive
3104                var fmtIdx = parts[i].indexOf('format(', idx);
3105
3106                if (fmtIdx > 0)
3107                {
3108                    format = Editor.trimCssUrl(parts[i].substring(fmtIdx + 7, parts[i].indexOf(')', fmtIdx)));
3109                }
3110
3111                (mxUtils.bind(this, function(url)
3112                {
3113                    if (this.cachedFonts[url] == null)
3114                    {
3115                        // Mark font as being fetched and fetch it
3116                    	this.cachedFonts[url] = url;
3117                        waiting++;
3118
3119                        var mime = 'application/x-font-ttf';
3120
3121                        // See https://stackoverflow.com/questions/2871655/proper-mime-type-for-fonts
3122                        if (format == 'svg' || /(\.svg)($|\?)/i.test(url))
3123                        {
3124                            mime = 'image/svg+xml';
3125                        }
3126                        else if (format == 'otf' || format == 'embedded-opentype' || /(\.otf)($|\?)/i.test(url))
3127                        {
3128                            mime = 'application/x-font-opentype';
3129                        }
3130                        else if (format == 'woff' || /(\.woff)($|\?)/i.test(url))
3131                        {
3132                            mime = 'application/font-woff';
3133                        }
3134                        else if (format == 'woff2' || /(\.woff2)($|\?)/i.test(url))
3135                        {
3136                            mime = 'application/font-woff2';
3137                        }
3138                        else if (format == 'eot' || /(\.eot)($|\?)/i.test(url))
3139                        {
3140                            mime = 'application/vnd.ms-fontobject';
3141                        }
3142                        else if (format == 'sfnt' || /(\.sfnt)($|\?)/i.test(url))
3143                        {
3144                            mime = 'application/font-sfnt';
3145                        }
3146
3147                        var realUrl = url;
3148
3149                        if ((/^https?:\/\//.test(realUrl)) && !this.isCorsEnabledForUrl(realUrl))
3150                        {
3151                            realUrl = PROXY_URL + '?url=' + encodeURIComponent(url);
3152                        }
3153
3154                        // LATER: Remove cache-control header
3155                        this.loadUrl(realUrl, mxUtils.bind(this, function(uri)
3156                        {
3157                        	this.cachedFonts[url] = uri;
3158                            waiting--;
3159                            finish();
3160                        }), mxUtils.bind(this, function(err)
3161                        {
3162                            // LATER: handle error
3163                            waiting--;
3164                            finish();
3165                        }), true, null, 'data:' + mime + ';charset=utf-8;base64,');
3166                    }
3167                }))(Editor.trimCssUrl(parts[i].substring(0, idx)), format);
3168            }
3169
3170            //In case all fonts are cached
3171            finish();
3172        }
3173        else
3174    	{
3175        	//No font urls found
3176        	then(fontCss);
3177    	}
3178    };
3179
3180	/**
3181	 * For the fontCSS to be applied when rendering images on canvas, the actual
3182	 * font data must be made available via a data URI encoding of the file.
3183	 */
3184    Editor.prototype.loadFonts = function(then)
3185    {
3186        if (this.fontCss != null && this.resolvedFontCss == null)
3187        {
3188        	this.embedCssFonts(this.fontCss, mxUtils.bind(this, function(resolvedFontCss)
3189			{
3190        		this.resolvedFontCss = resolvedFontCss;
3191
3192				if (then != null)
3193				{
3194        			then();
3195				}
3196			}));
3197        }
3198        else if (then != null)
3199        {
3200            then();
3201        }
3202    };
3203
3204    /**
3205     * Embeds external fonts
3206     */
3207    Editor.prototype.embedExtFonts = function(callback)
3208    {
3209    	var extFonts = this.graph.getCustomFonts();
3210
3211		if (extFonts.length > 0)
3212		{
3213			var styleCnt = '', waiting = 0;
3214
3215			if (this.cachedGoogleFonts == null)
3216			{
3217				this.cachedGoogleFonts = {};
3218			}
3219
3220			var googleCssDone = mxUtils.bind(this, function()
3221			{
3222				if (waiting == 0)
3223	            {
3224					this.embedCssFonts(styleCnt, callback);
3225	            }
3226			});
3227
3228			for (var i = 0; i < extFonts.length; i++)
3229			{
3230				(mxUtils.bind(this, function(fontName, fontUrl)
3231				{
3232					if (Graph.isCssFontUrl(fontUrl))
3233					{
3234						if (this.cachedGoogleFonts[fontUrl] == null)
3235						{
3236							waiting++;
3237
3238							this.loadUrl(fontUrl, mxUtils.bind(this, function(css)
3239		                    {
3240								this.cachedGoogleFonts[fontUrl] = css;
3241								styleCnt += css;
3242		                        waiting--;
3243		                        googleCssDone();
3244		                    }), mxUtils.bind(this, function(err)
3245		                    {
3246		                        // LATER: handle error
3247		                        waiting--;
3248		                        styleCnt += '@import url(' + fontUrl + ');';
3249		                        googleCssDone();
3250		                    }));
3251						}
3252						else
3253						{
3254							styleCnt += this.cachedGoogleFonts[fontUrl];
3255						}
3256					}
3257					else
3258					{
3259						styleCnt += '@font-face {' +
3260				            'font-family: "' + fontName + '";' +
3261				            'src: url("' + fontUrl + '")}';
3262					}
3263				}))(extFonts[i].name, extFonts[i].url);
3264			}
3265
3266			googleCssDone();
3267		}
3268		else
3269		{
3270			callback();
3271		}
3272    };
3273
3274	/**
3275	 * Copies MathJax CSS into the SVG output.
3276	 */
3277	Editor.prototype.addMathCss = function(svgRoot)
3278	{
3279		var defs = svgRoot.getElementsByTagName('defs');
3280
3281		if (defs != null && defs.length > 0)
3282		{
3283			var styles = document.getElementsByTagName('style');
3284
3285			for (var i = 0; i < styles.length; i++)
3286			{
3287				// Ignores style elements with no MathJax CSS
3288				if (mxUtils.getTextContent(styles[i]).indexOf('MathJax') > 0)
3289				{
3290					defs[0].appendChild(styles[i].cloneNode(true));
3291				}
3292			}
3293		}
3294	};
3295
3296	/**
3297	 * Adds the global fontCss configuration.
3298	 */
3299	Editor.prototype.addFontCss = function(svgRoot, fontCss)
3300	{
3301		fontCss = (fontCss != null) ? fontCss : this.absoluteCssFonts(this.fontCss);
3302
3303		// Creates defs element if not available
3304		if (fontCss != null)
3305		{
3306			var defs = svgRoot.getElementsByTagName('defs');
3307			var svgDoc = svgRoot.ownerDocument;
3308			var defsElt = null;
3309
3310			if (defs.length == 0)
3311			{
3312				defsElt = (svgDoc.createElementNS != null) ?
3313					svgDoc.createElementNS(mxConstants.NS_SVG, 'defs') :
3314					svgDoc.createElement('defs');
3315
3316				if (svgRoot.firstChild != null)
3317				{
3318					svgRoot.insertBefore(defsElt, svgRoot.firstChild);
3319				}
3320				else
3321				{
3322					svgRoot.appendChild(defsElt);
3323				}
3324			}
3325			else
3326			{
3327				defsElt = defs[0];
3328			}
3329
3330			var style = (svgDoc.createElementNS != null) ?
3331				svgDoc.createElementNS(mxConstants.NS_SVG, 'style') :
3332				svgDoc.createElement('style');
3333			style.setAttribute('type', 'text/css');
3334			mxUtils.setTextContent(style, fontCss);
3335			defsElt.appendChild(style);
3336		}
3337	};
3338
3339	/**
3340	 * Disables client-side image export if math is enabled.
3341	 */
3342	Editor.prototype.isExportToCanvas = function()
3343	{
3344		return mxClient.IS_CHROMEAPP || this.useCanvasForExport;
3345	};
3346
3347	/**
3348	 * Returns the maximum possible scale for the given canvas dimension and scale.
3349	 * This will return the given scale or the maximum scale that can be used to
3350	 * generate a valid image in the current browser.
3351	 *
3352	 * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas
3353	 */
3354	Editor.prototype.getMaxCanvasScale = function(w, h, scale)
3355	{
3356		var max = (mxClient.IS_FF) ? 8192 : 16384;
3357
3358		return Math.min(scale, Math.min(max / w, max / h));
3359	};
3360
3361	/**
3362	 *
3363	 */
3364	Editor.prototype.exportToCanvas = function(callback, width, imageCache, background, error, limitHeight,
3365		ignoreSelection, scale, transparentBackground, addShadow, converter, graph, border, noCrop, grid,
3366		keepTheme, exportType, cells)
3367	{
3368		try
3369		{
3370			limitHeight = (limitHeight != null) ? limitHeight : true;
3371			ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
3372			graph = (graph != null) ? graph : this.graph;
3373			border = (border != null) ? border : 0;
3374
3375			var bg = (transparentBackground) ? null : graph.background;
3376
3377			if (bg == mxConstants.NONE)
3378			{
3379				bg = null;
3380			}
3381
3382			if (bg == null)
3383			{
3384				bg = background;
3385			}
3386
3387			// Handles special case where background is null but transparent is false
3388			if (bg == null && transparentBackground == false)
3389			{
3390				bg = (keepTheme) ? this.graph.defaultPageBackgroundColor : '#ffffff';
3391			}
3392
3393			this.convertImages(graph.getSvg(null, null, border, noCrop, null, ignoreSelection,
3394				null, null, null, addShadow, null, keepTheme, exportType, cells),
3395				mxUtils.bind(this, function(svgRoot)
3396			{
3397				try
3398				{
3399					var img = new Image();
3400
3401					img.onload = mxUtils.bind(this, function()
3402					{
3403				   		try
3404				   		{
3405				   			var canvas = document.createElement('canvas');
3406							var w = parseInt(svgRoot.getAttribute('width'));
3407							var h = parseInt(svgRoot.getAttribute('height'));
3408							scale = (scale != null) ? scale : 1;
3409
3410							if (width != null)
3411							{
3412								scale = (!limitHeight) ? width / w : Math.min(1, Math.min((width * 3) / (h * 4), width / w));
3413							}
3414
3415							scale = this.getMaxCanvasScale(w, h, scale);
3416							w = Math.ceil(scale * w);
3417							h = Math.ceil(scale * h);
3418
3419							canvas.setAttribute('width', w);
3420					   		canvas.setAttribute('height', h);
3421					   		var ctx = canvas.getContext('2d');
3422
3423					   		if (bg != null)
3424					   		{
3425					   			ctx.beginPath();
3426								ctx.rect(0, 0, w, h);
3427								ctx.fillStyle = bg;
3428								ctx.fill();
3429					   		}
3430
3431					   		if (scale != 1)
3432					   		{
3433					   			ctx.scale(scale, scale);
3434					   		}
3435
3436						    function drawImage()
3437						    {
3438						    	// Workaround for broken data URI images in Safari on first export
3439						   		if (mxClient.IS_SF)
3440						   		{
3441									window.setTimeout(function()
3442									{
3443										ctx.drawImage(img, 0, 0);
3444										callback(canvas, svgRoot);
3445									}, 0);
3446						   		}
3447						   		else
3448						   		{
3449						   			ctx.drawImage(img, 0, 0);
3450						   			callback(canvas, svgRoot);
3451						   		}
3452						    };
3453
3454						    if (grid)
3455						    {
3456							    var view = graph.view;
3457							    var curViewScale = view.scale;
3458							    view.scale = 1; //Reset the scale temporary to generate unscaled grid image which is then scaled
3459								var gridImage = btoa(unescape(encodeURIComponent(view.createSvgGrid(view.gridColor))));
3460								view.scale = curViewScale;
3461								gridImage = 'data:image/svg+xml;base64,' + gridImage;
3462				                var phase = graph.gridSize * view.gridSteps * scale;
3463
3464				                var b = graph.getGraphBounds();
3465								var tx = view.translate.x * curViewScale;
3466								var ty = view.translate.y * curViewScale;
3467								var x0 = tx + (b.x - tx) / curViewScale - border;
3468								var y0 = ty + (b.y - ty) / curViewScale - border;
3469
3470								var background = new Image();
3471
3472								background.onload = function()
3473								{
3474									try
3475									{
3476										var x = -Math.round(phase - mxUtils.mod((tx - x0) * scale, phase));
3477										var y = -Math.round(phase - mxUtils.mod((ty - y0) * scale, phase));
3478
3479										for (var i = x; i < w; i += phase)
3480										{
3481											for (var j = y; j < h; j += phase)
3482											{
3483												ctx.drawImage(background, i / scale, j / scale);
3484											}
3485										}
3486
3487										drawImage();
3488									}
3489							   		catch (e)
3490							   		{
3491							   			if (error != null)
3492										{
3493											error(e);
3494										}
3495							   		}
3496								};
3497
3498								background.onerror = function(e)
3499								{
3500									if (error != null)
3501									{
3502										error(e);
3503									}
3504								};
3505
3506								background.src = gridImage;
3507						    }
3508						    else
3509					    	{
3510						    	drawImage();
3511					    	}
3512				   		}
3513				   		catch (e)
3514				   		{
3515				   			if (error != null)
3516							{
3517								error(e);
3518							}
3519				   		}
3520					});
3521
3522					img.onerror = function(e)
3523					{
3524						//console.log('img', e, img.src);
3525
3526						if (error != null)
3527						{
3528							error(e);
3529						}
3530					};
3531
3532					if (addShadow)
3533					{
3534						this.graph.addSvgShadow(svgRoot);
3535					}
3536
3537					if (this.graph.mathEnabled)
3538					{
3539						this.addMathCss(svgRoot);
3540					}
3541
3542					var done = mxUtils.bind(this, function()
3543					{
3544						try
3545						{
3546							if (this.resolvedFontCss != null)
3547							{
3548								this.addFontCss(svgRoot, this.resolvedFontCss);
3549							}
3550
3551							img.src = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
3552						}
3553						catch (e)
3554						{
3555							if (error != null)
3556							{
3557								error(e);
3558							}
3559						}
3560					});
3561
3562					this.embedExtFonts(mxUtils.bind(this, function(extFontsEmbeddedCss)
3563					{
3564						try
3565						{
3566							if (extFontsEmbeddedCss != null)
3567							{
3568								this.addFontCss(svgRoot, extFontsEmbeddedCss);
3569							}
3570
3571							this.loadFonts(done);
3572						}
3573						catch (e)
3574						{
3575							if (error != null)
3576							{
3577								error(e);
3578							}
3579						}
3580					}));
3581				}
3582				catch (e)
3583				{
3584					//console.log('src', e, img.src);
3585
3586					if (error != null)
3587					{
3588						error(e);
3589					}
3590				}
3591			}), imageCache, converter);
3592		}
3593		catch (e)
3594		{
3595			if (error != null)
3596			{
3597				error(e);
3598			}
3599		}
3600	};
3601
3602	Editor.crcTable = [];
3603
3604	for (var n = 0; n < 256; n++)
3605	{
3606		var c = n;
3607
3608		for (var k = 0; k < 8; k++)
3609		{
3610			if ((c & 1) == 1)
3611			{
3612				c = 0xedb88320 ^ (c >>> 1);
3613			}
3614			else
3615			{
3616				c >>>= 1;
3617			}
3618
3619			Editor.crcTable[n] = c;
3620		}
3621	}
3622
3623	Editor.updateCRC = function(crc, data, off, len)
3624	{
3625		var c = crc;
3626
3627		for (var n = 0; n < len; n++)
3628		{
3629			c = Editor.crcTable[(c ^ data.charCodeAt(off + n)) & 0xff] ^ (c >>> 8);
3630		}
3631
3632		return c;
3633	};
3634
3635	Editor.crc32 = function(str)
3636	{
3637	    var crc = 0 ^ (-1);
3638
3639	    for (var i = 0; i < str.length; i++ )
3640	    {
3641	        crc = (crc >>> 8) ^ Editor.crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
3642	    }
3643
3644	    return (crc ^ (-1)) >>> 0;
3645	};
3646
3647	/**
3648	 * Adds the given text to the compressed or non-compressed text chunk.
3649	 */
3650	Editor.writeGraphModelToPng = function(data, type, key, value, error)
3651	{
3652		var base64 = data.substring(data.indexOf(',') + 1);
3653		var f = (window.atob) ? atob(base64) : Base64.decode(base64, true);
3654		var pos = 0;
3655
3656		function fread(d, count)
3657		{
3658			var start = pos;
3659			pos += count;
3660
3661			return d.substring(start, pos);
3662		};
3663
3664		// Reads unsigned long 32 bit big endian
3665		function _freadint(d)
3666		{
3667			var bytes = fread(d, 4);
3668
3669			return bytes.charCodeAt(3) + (bytes.charCodeAt(2) << 8) +
3670				(bytes.charCodeAt(1) << 16) + (bytes.charCodeAt(0) << 24);
3671		};
3672
3673		function writeInt(num)
3674		{
3675			return String.fromCharCode((num >> 24) & 0x000000ff, (num >> 16) & 0x000000ff,
3676				(num >> 8) & 0x000000ff, num & 0x000000ff);
3677		};
3678
3679		// Checks signature
3680		if (fread(f,8) != String.fromCharCode(137) + 'PNG' + String.fromCharCode(13, 10, 26, 10))
3681		{
3682			if (error != null)
3683			{
3684				error();
3685			}
3686
3687			return;
3688		}
3689
3690		// Reads header chunk
3691		fread(f,4);
3692
3693		if (fread(f,4) != 'IHDR')
3694		{
3695			if (error != null)
3696			{
3697				error();
3698			}
3699
3700			return;
3701		}
3702
3703		fread(f, 17);
3704		var result = f.substring(0, pos);
3705
3706		do
3707		{
3708			var n = _freadint(f);
3709			var chunk = fread(f,4);
3710
3711			if (chunk == 'IDAT')
3712			{
3713				result = f.substring(0, pos - 8);
3714
3715				if (type == 'pHYs' && key == 'dpi')
3716				{
3717					var dpm = Math.round(value / 0.0254); //One inch is equal to exactly 0.0254 meters.
3718					var chunkData = writeInt(dpm) + writeInt(dpm) + String.fromCharCode(1);
3719				}
3720				else
3721				{
3722					var chunkData = key + String.fromCharCode(0) +
3723						((type == 'zTXt') ? String.fromCharCode(0) : '') +
3724						value;
3725				}
3726
3727				var crc = 0xffffffff;
3728				crc = Editor.updateCRC(crc, type, 0, 4);
3729				crc = Editor.updateCRC(crc, chunkData, 0, chunkData.length);
3730
3731				result += writeInt(chunkData.length) + type + chunkData + writeInt(crc ^ 0xffffffff);
3732				result += f.substring(pos - 8, f.length);
3733
3734				break;
3735			}
3736
3737			result += f.substring(pos - 8, pos - 4 + n);
3738			fread(f,n);
3739			fread(f,4);
3740		}
3741		while (n);
3742
3743		return 'data:image/png;base64,' + ((window.btoa) ? btoa(result) : Base64.encode(result, true));
3744	};
3745
3746	/**
3747	 * Adds persistence for recent colors
3748	 */
3749	if (window.ColorDialog)
3750	{
3751		FilenameDialog.filenameHelpLink = 'https://www.diagrams.net/doc/faq/save-file-formats';
3752
3753		var colorDialogAddRecentColor = ColorDialog.addRecentColor;
3754
3755		ColorDialog.addRecentColor = function(color, max)
3756		{
3757			colorDialogAddRecentColor.apply(this, arguments);
3758
3759			mxSettings.setRecentColors(ColorDialog.recentColors);
3760			mxSettings.save();
3761		};
3762
3763		var colorDialogResetRecentColors = ColorDialog.resetRecentColors;
3764
3765		ColorDialog.resetRecentColors = function()
3766		{
3767			colorDialogResetRecentColors.apply(this, arguments);
3768
3769			mxSettings.setRecentColors(ColorDialog.recentColors);
3770			mxSettings.save();
3771		};
3772	}
3773
3774	// Overrides ID for pages
3775	if (window.EditDataDialog)
3776	{
3777		EditDataDialog.getDisplayIdForCell = function(ui, cell)
3778		{
3779			var id = null;
3780
3781			if (ui.editor.graph.getModel().getParent(cell) != null)
3782			{
3783				id = cell.getId();
3784			}
3785			else if (ui.currentPage != null)
3786			{
3787				id = ui.currentPage.getId();
3788			}
3789
3790			return id;
3791		};
3792	}
3793
3794	var AddCustomPropertyDialog = function(editorUi, callback)
3795	{
3796		var row, td;
3797
3798		var table = document.createElement('table');
3799		var tbody = document.createElement('tbody');
3800		table.setAttribute('cellpadding', (mxClient.IS_SF) ? '0' : '2');
3801
3802		row = document.createElement('tr');
3803
3804		td = document.createElement('td');
3805		td.style.fontSize = '10pt';
3806		td.style.width = '100px';
3807		mxUtils.write(td, mxResources.get('name', null, 'Name') + ':');
3808
3809		row.appendChild(td);
3810
3811		var nameInput = document.createElement('input');
3812		nameInput.style.width = '180px';
3813
3814		td = document.createElement('td');
3815		td.appendChild(nameInput);
3816		row.appendChild(td);
3817
3818		tbody.appendChild(row);
3819
3820		row = document.createElement('tr');
3821
3822		td = document.createElement('td');
3823		td.style.fontSize = '10pt';
3824		mxUtils.write(td, mxResources.get('type', null, 'Type') + ':');
3825
3826		row.appendChild(td);
3827
3828		var typeSelect = document.createElement('select');
3829		typeSelect.style.width = '180px';
3830
3831		var boolOption = document.createElement('option');
3832		boolOption.setAttribute('value', 'bool');
3833		mxUtils.write(boolOption, mxResources.get('bool', null, 'Boolean'));
3834		typeSelect.appendChild(boolOption);
3835
3836		var clrOption = document.createElement('option');
3837		clrOption.setAttribute('value', 'color');
3838		mxUtils.write(clrOption, mxResources.get('color', null, 'Color'));
3839		typeSelect.appendChild(clrOption);
3840
3841		var enumOption = document.createElement('option');
3842		enumOption.setAttribute('value', 'enum');
3843		mxUtils.write(enumOption, mxResources.get('enum', null, 'Enumeration'));
3844		typeSelect.appendChild(enumOption);
3845
3846		var floatOption = document.createElement('option');
3847		floatOption.setAttribute('value', 'float');
3848		mxUtils.write(floatOption, mxResources.get('float', null, 'Float'));
3849		typeSelect.appendChild(floatOption);
3850
3851		var intOption = document.createElement('option');
3852		intOption.setAttribute('value', 'int');
3853		mxUtils.write(intOption, mxResources.get('int', null, 'Int'));
3854		typeSelect.appendChild(intOption);
3855
3856		var strOption = document.createElement('option');
3857		strOption.setAttribute('value', 'string');
3858		mxUtils.write(strOption, mxResources.get('string', null, 'String'));
3859		typeSelect.appendChild(strOption);
3860
3861		td = document.createElement('td');
3862		td.appendChild(typeSelect);
3863		row.appendChild(td);
3864
3865		tbody.appendChild(row);
3866
3867		row = document.createElement('tr');
3868
3869		td = document.createElement('td');
3870		td.style.fontSize = '10pt';
3871		mxUtils.write(td, mxResources.get('dispName', null, 'Display Name') + ':');
3872
3873		row.appendChild(td);
3874
3875		var dispNameInput = document.createElement('input');
3876		dispNameInput.style.width = '180px';
3877
3878		td = document.createElement('td');
3879		td.appendChild(dispNameInput);
3880		row.appendChild(td);
3881
3882		tbody.appendChild(row);
3883
3884		var listRow = document.createElement('tr');
3885
3886		td = document.createElement('td');
3887		td.style.fontSize = '10pt';
3888		mxUtils.write(td, mxResources.get('enumList', null, 'Enum List') + ' (csv):');
3889
3890		listRow.appendChild(td);
3891
3892		var enumListInput = document.createElement('input');
3893		enumListInput.style.width = '180px';
3894
3895		td = document.createElement('td');
3896		td.appendChild(enumListInput);
3897		listRow.appendChild(td);
3898
3899		listRow.style.display = 'none';
3900		tbody.appendChild(listRow);
3901
3902		table.appendChild(tbody);
3903
3904		function typeChanged()
3905		{
3906			if (typeSelect.value === 'enum')
3907			{
3908				listRow.style.display = '';
3909				this.container.parentNode.style.height = "150px";
3910
3911			}
3912			else
3913			{
3914				listRow.style.display = 'none';
3915				this.container.parentNode.style.height = "130px";
3916			}
3917		};
3918
3919		mxEvent.addListener(typeSelect, 'change', mxUtils.bind(this, typeChanged));
3920
3921		row = document.createElement('tr');
3922		td = document.createElement('td');
3923		td.setAttribute('align', 'right');
3924		td.style.paddingTop = '22px';
3925		td.colSpan = 2;
3926
3927		var addBtn = mxUtils.button(mxResources.get('add', null, 'Add'), mxUtils.bind(this, function()
3928		{
3929	    	var name = nameInput.value;
3930
3931	    	if (name == "")
3932    		{
3933	    		nameInput.style.border = "1px solid red";
3934	    		return;
3935    		}
3936
3937			var type = typeSelect.value;
3938	    	var dispName = dispNameInput.value;
3939
3940	    	if (dispName == "")
3941    		{
3942	    		dispNameInput.style.border = "1px solid red";
3943	    		return;
3944    		}
3945
3946	    	var enumList = enumListInput.value;
3947
3948	    	if (enumList == "" && type == "enum")
3949    		{
3950	    		enumListInput.style.border = "1px solid red";
3951	    		return;
3952
3953    		}
3954
3955			if (enumList != null)
3956			{
3957				enumList = enumList.split(',');
3958
3959				for (var i = 0; i < enumList.length; i++)
3960				{
3961					enumList[i] = enumList[i].trim();
3962				}
3963			}
3964
3965			if (callback)
3966			{
3967				callback(editorUi, name, type, dispName, enumList);
3968				editorUi.hideDialog();
3969			}
3970		}));
3971		addBtn.className = 'geBtn gePrimaryBtn';
3972
3973		var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
3974		{
3975			editorUi.hideDialog();
3976		});
3977		cancelBtn.className = 'geBtn';
3978
3979		if (editorUi.editor.cancelFirst)
3980		{
3981			td.appendChild(cancelBtn);
3982			td.appendChild(addBtn);
3983		}
3984		else
3985		{
3986			td.appendChild(addBtn);
3987			td.appendChild(cancelBtn);
3988		}
3989
3990		row.appendChild(td);
3991		tbody.appendChild(row);
3992		table.appendChild(tbody);
3993		this.container = table;
3994	};
3995
3996	if (window.StyleFormatPanel != null)
3997	{
3998		var formatInit = Format.prototype.init;
3999
4000		Format.prototype.init = function()
4001		{
4002			formatInit.apply(this, arguments);
4003
4004			this.editorUi.editor.addListener('fileLoaded', this.update);
4005		};
4006
4007		var formatRefresh = Format.prototype.refresh;
4008
4009		Format.prototype.refresh = function()
4010		{
4011			if (this.editorUi.getCurrentFile() != null || urlParams['embed'] == '1' ||
4012				this.editorUi.editor.chromeless)
4013			{
4014				formatRefresh.apply(this, arguments);
4015			}
4016			else
4017			{
4018				this.clear();
4019			}
4020		};
4021
4022		/**
4023		 * Hook for subclassers.
4024		 */
4025		DiagramFormatPanel.prototype.isShadowOptionVisible = function()
4026		{
4027			var file = this.editorUi.getCurrentFile();
4028
4029			return urlParams['embed'] == '1' || (file != null && file.isEditable());
4030		};
4031
4032		/**
4033		 * Option is not visible in default theme.
4034		 */
4035	    DiagramFormatPanel.prototype.isMathOptionVisible = function(div)
4036	    {
4037	        return false;
4038	    };
4039
4040		/**
4041		 * Add global shadow option.
4042		 */
4043		var diagramFormatPanelAddView = DiagramFormatPanel.prototype.addView;
4044
4045		DiagramFormatPanel.prototype.addView = function(div)
4046		{
4047			var div = diagramFormatPanelAddView.apply(this, arguments);
4048			var file = this.editorUi.getCurrentFile();
4049
4050			if (mxClient.IS_SVG && this.isShadowOptionVisible())
4051			{
4052				var ui = this.editorUi;
4053				var editor = ui.editor;
4054				var graph = editor.graph;
4055
4056				var option = this.createOption(mxResources.get('shadow'), function()
4057				{
4058					return graph.shadowVisible;
4059				}, function(checked)
4060				{
4061					var change = new ChangePageSetup(ui);
4062					change.ignoreColor = true;
4063					change.ignoreImage = true;
4064					change.shadowVisible = checked;
4065
4066					graph.model.execute(change);
4067				},
4068				{
4069					install: function(apply)
4070					{
4071						this.listener = function()
4072						{
4073							apply(graph.shadowVisible);
4074						};
4075
4076						ui.addListener('shadowVisibleChanged', this.listener);
4077					},
4078					destroy: function()
4079					{
4080						ui.removeListener(this.listener);
4081					}
4082				});
4083
4084				if (!Editor.enableShadowOption)
4085				{
4086					option.getElementsByTagName('input')[0].setAttribute('disabled', 'disabled');
4087					mxUtils.setOpacity(option, 60);
4088				}
4089
4090				div.appendChild(option);
4091			}
4092
4093			return div;
4094		};
4095
4096		/**
4097		 * Adds autosave and math typesetting options.
4098		 */
4099		var diagramFormatPanelAddOptions = DiagramFormatPanel.prototype.addOptions;
4100		DiagramFormatPanel.prototype.addOptions = function(div)
4101		{
4102			div = diagramFormatPanelAddOptions.apply(this, arguments);
4103
4104			var ui = this.editorUi;
4105			var editor = ui.editor;
4106			var graph = editor.graph;
4107
4108			if (graph.isEnabled())
4109			{
4110				var file = ui.getCurrentFile();
4111
4112				if (file != null && file.isAutosaveOptional())
4113				{
4114					var opt = this.createOption(mxResources.get('autosave'), function()
4115					{
4116						return ui.editor.autosave;
4117					}, function(checked)
4118					{
4119						ui.editor.setAutosave(checked);
4120
4121						if (ui.editor.autosave && file.isModified())
4122						{
4123							file.fileChanged();
4124						}
4125					},
4126					{
4127						install: function(apply)
4128						{
4129							this.listener = function()
4130							{
4131								apply(ui.editor.autosave);
4132							};
4133
4134							ui.editor.addListener('autosaveChanged', this.listener);
4135						},
4136						destroy: function()
4137						{
4138							ui.editor.removeListener(this.listener);
4139						}
4140					});
4141
4142					div.appendChild(opt);
4143				}
4144			}
4145
4146	        if (this.isMathOptionVisible() && graph.isEnabled() && typeof(MathJax) !== 'undefined')
4147	        {
4148	            // Math
4149	            var option = this.createOption(mxResources.get('mathematicalTypesetting'), function()
4150	            {
4151	                return graph.mathEnabled;
4152	            }, function(checked)
4153	            {
4154	                ui.actions.get('mathematicalTypesetting').funct();
4155	            },
4156	            {
4157	                install: function(apply)
4158	                {
4159	                    this.listener = function()
4160	                    {
4161	                        apply(graph.mathEnabled);
4162	                    };
4163
4164	                    ui.addListener('mathEnabledChanged', this.listener);
4165	                },
4166	                destroy: function()
4167	                {
4168	                    ui.removeListener(this.listener);
4169	                }
4170	            });
4171
4172	            option.style.paddingTop = '5px';
4173	            div.appendChild(option);
4174
4175	            var help = ui.menus.createHelpLink('https://www.diagrams.net/doc/faq/math-typesetting');
4176	            help.style.position = 'relative';
4177	            help.style.marginLeft = '6px';
4178	            help.style.top = '2px';
4179	            option.appendChild(help);
4180	        }
4181
4182			return div;
4183		};
4184
4185		mxCellRenderer.prototype.defaultVertexShape.prototype.customProperties = [
4186	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
4187	        {name: 'absoluteArcSize', dispName: 'Abs. Arc Size', type: 'bool', defVal: false}
4188	      ];
4189
4190		mxCellRenderer.defaultShapes['link'].prototype.customProperties = [
4191	        {name: 'width', dispName: 'Width', type: 'float', min:0, defVal: 4}
4192		];
4193
4194		mxCellRenderer.defaultShapes['flexArrow'].prototype.customProperties = [
4195	        {name: 'width', dispName: 'Width', type: 'float', min:0, defVal: 10},
4196	        {name: 'startWidth', dispName: 'Start Width', type: 'float', min:0, defVal: 20},
4197	        {name: 'endWidth', dispName: 'End Width', type: 'float', min:0, defVal: 20}
4198		];
4199
4200		mxCellRenderer.defaultShapes['process'].prototype.customProperties = [
4201			{name: 'size', dispName: 'Indent', type: 'float', min: 0, max: 0.5, defVal: 0.1}
4202		];
4203
4204		mxCellRenderer.defaultShapes['rhombus'].prototype.customProperties = [
4205	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, max: 50, defVal: mxConstants.LINE_ARCSIZE},
4206	        {name: 'double', dispName: 'Double', type: 'bool', defVal: false}
4207		];
4208
4209		mxCellRenderer.defaultShapes['partialRectangle'].prototype.customProperties = [
4210	        {name: 'top', dispName: 'Top Line', type: 'bool', defVal: true},
4211	        {name: 'bottom', dispName: 'Bottom Line', type: 'bool', defVal: true},
4212	        {name: 'left', dispName: 'Left Line', type: 'bool', defVal: true},
4213	        {name: 'right', dispName: 'Right Line', type: 'bool', defVal: true}
4214        ];
4215
4216		mxCellRenderer.defaultShapes['parallelogram'].prototype.customProperties = [
4217	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
4218	        {name: 'size', dispName: 'Slope Angle', type: 'float', min:0, max: 1, defVal: 0.2}
4219		];
4220
4221		mxCellRenderer.defaultShapes['hexagon'].prototype.customProperties = [
4222	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
4223	        {name: 'size', dispName: 'Slope Angle', type: 'float', min:0, max: 1, defVal: 0.25}
4224		];
4225
4226		mxCellRenderer.defaultShapes['triangle'].prototype.customProperties = [
4227	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE}
4228		];
4229
4230		mxCellRenderer.defaultShapes['document'].prototype.customProperties = [
4231	        {name: 'size', dispName: 'Size', type: 'float', defVal: 0.3, min:0, max:1}
4232		];
4233
4234		mxCellRenderer.defaultShapes['internalStorage'].prototype.customProperties = [
4235	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
4236	        {name: 'dx', dispName: 'Left Line', type: 'float', min:0, defVal: 20},
4237	        {name: 'dy', dispName: 'Top Line', type: 'float', min:0, defVal: 20}
4238		];
4239
4240		mxCellRenderer.defaultShapes['cube'].prototype.customProperties = [
4241	        {name: 'size', dispName: 'Size', type: 'float', min:0, defVal:20 },
4242	        {name: 'darkOpacity', dispName: 'Dark Opacity', type: 'float', min:-1, max:1, defVal:0 },
4243	        {name: 'darkOpacity2', dispName: 'Dark Opacity 2', type: 'float', min:-1, max:1, defVal:0 }
4244		];
4245
4246		mxCellRenderer.defaultShapes['step'].prototype.customProperties = [
4247	        {name: 'size', dispName: 'Notch Size', type: 'float', min:0, defVal:20},
4248	        {name: 'fixedSize', dispName: 'Fixed Size', type: 'bool', defVal:true}
4249		];
4250
4251		mxCellRenderer.defaultShapes['trapezoid'].prototype.customProperties = [
4252	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
4253	        {name: 'size', dispName: 'Slope Angle', type: 'float', min:0, max: 1, defVal: 0.2}
4254		];
4255
4256		mxCellRenderer.defaultShapes['tape'].prototype.customProperties = [
4257	        {name: 'size', dispName: 'Size', type: 'float', min:0, max:1, defVal:0.4 }
4258		];
4259
4260		mxCellRenderer.defaultShapes['note'].prototype.customProperties = [
4261	        {name: 'size', dispName: 'Fold Size', type: 'float', min:0, defVal: 30},
4262	        {name: 'darkOpacity', dispName: 'Dark Opacity', type: 'float', min:-1, max:1, defVal:0 },
4263	    ];
4264
4265		mxCellRenderer.defaultShapes['card'].prototype.customProperties = [
4266	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
4267	        {name: 'size', dispName: 'Cutoff Size', type: 'float', min:0, defVal: 30}
4268	    ];
4269
4270		mxCellRenderer.defaultShapes['callout'].prototype.customProperties = [
4271	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: mxConstants.LINE_ARCSIZE},
4272	        {name: 'base', dispName: 'Callout Width', type: 'float', min:0, defVal: 20},
4273	        {name: 'size', dispName: 'Callout Length', type: 'float', min:0, defVal: 30},
4274	        {name: 'position', dispName: 'Callout Position', type: 'float', min:0, max:1, defVal: 0.5},
4275	        {name: 'position2', dispName: 'Callout Tip Position', type: 'float', min:0, max:1, defVal: 0.5}
4276	    ];
4277
4278		mxCellRenderer.defaultShapes['folder'].prototype.customProperties = [
4279	        {name: 'tabWidth', dispName: 'Tab Width', type: 'float'},
4280	        {name: 'tabHeight', dispName: 'Tab Height', type: 'float'},
4281	        {name: 'tabPosition', dispName: 'Tap Position', type: 'enum',
4282	        	enumList: [{val: 'left', dispName: 'Left'}, {val: 'right', dispName: 'Right'}]
4283	        }
4284	    ];
4285
4286		mxCellRenderer.defaultShapes['swimlane'].prototype.customProperties = [
4287	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: 15},
4288	        {name: 'startSize', dispName: 'Header Size', type: 'float'},
4289	        {name: 'horizontal', dispName: 'Horizontal', type: 'bool', defVal: true},
4290	        {name: 'separatorColor', dispName: 'Separator Color', type: 'color', defVal: null},
4291	    ];
4292
4293		mxCellRenderer.defaultShapes['table'].prototype.customProperties = [
4294			{name: 'rowLines', dispName: 'Row Lines', type: 'bool', defVal: true},
4295			{name: 'columnLines', dispName: 'Column Lines', type: 'bool', defVal: true},
4296			{name: 'fixedRows', dispName: 'Fixed Rows', type: 'bool', defVal: false},
4297			{name: 'resizeLast', dispName: 'Resize Last Column', type: 'bool', defVal: false},
4298			{name: 'resizeLastRow', dispName: 'Resize Last Row', type: 'bool', defVal: false}].
4299			concat(mxCellRenderer.defaultShapes['swimlane'].prototype.customProperties);
4300
4301		mxCellRenderer.defaultShapes['doubleEllipse'].prototype.customProperties = [
4302	        {name: 'margin', dispName: 'Indent', type: 'float', min:0, defVal:4}
4303	    ];
4304
4305		mxCellRenderer.defaultShapes['ext'].prototype.customProperties = [
4306	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: 15},
4307			{name: 'double', dispName: 'Double', type: 'bool', defVal: false},
4308	        {name: 'margin', dispName: 'Indent', type: 'float', min: 0, defVal:0}
4309	    ];
4310
4311		mxCellRenderer.defaultShapes['curlyBracket'].prototype.customProperties = [
4312			{name: 'rounded', dispName: 'Rounded', type: 'bool', defVal: true},
4313	        {name: 'size', dispName: 'Size', type: 'float', min:0, max: 1, defVal: 0.5}
4314	    ];
4315
4316		mxCellRenderer.defaultShapes['image'].prototype.customProperties = [
4317			{name: 'imageAspect', dispName: 'Fixed Image Aspect', type: 'bool', defVal:true}
4318	    ];
4319
4320		mxCellRenderer.defaultShapes['label'].prototype.customProperties = [
4321			{name: 'imageAspect', dispName: 'Fixed Image Aspect', type: 'bool', defVal:true},
4322			{name: 'imageAlign', dispName: 'Image Align', type: 'enum',
4323				enumList: [{val: 'left', dispName: 'Left'},
4324						   {val: 'center', dispName: 'Center'},
4325						   {val: 'right', dispName: 'Right'}], defVal: 'left'},
4326			{name: 'imageVerticalAlign', dispName: 'Image Vertical Align', type: 'enum',
4327				enumList: [{val: 'top', dispName: 'Top'},
4328					       {val: 'middle', dispName: 'Middle'},
4329					       {val: 'bottom', dispName: 'Bottom'}], defVal: 'middle'},
4330	        {name: 'imageWidth', dispName: 'Image Width', type: 'float', min:0, defVal: 24},
4331	        {name: 'imageHeight', dispName: 'Image Height', type: 'float', min:0, defVal: 24},
4332	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: 12},
4333	        {name: 'absoluteArcSize', dispName: 'Abs. Arc Size', type: 'bool', defVal: false}
4334	    ];
4335
4336		mxCellRenderer.defaultShapes['dataStorage'].prototype.customProperties = [
4337	        {name: 'size', dispName: 'Size', type: 'float', min:0, max:1, defVal:0.1 }
4338		];
4339
4340		mxCellRenderer.defaultShapes['manualInput'].prototype.customProperties = [
4341	        {name: 'size', dispName: 'Size', type: 'float', min:0, defVal:30 },
4342	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: 20}
4343		];
4344
4345		mxCellRenderer.defaultShapes['loopLimit'].prototype.customProperties = [
4346	        {name: 'size', dispName: 'Size', type: 'float', min:0, defVal:20 },
4347	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: 20}
4348		];
4349
4350		mxCellRenderer.defaultShapes['offPageConnector'].prototype.customProperties = [
4351	        {name: 'size', dispName: 'Size', type: 'float', min:0, defVal:38 },
4352	        {name: 'arcSize', dispName: 'Arc Size', type: 'float', min:0, defVal: 20}
4353		];
4354
4355		mxCellRenderer.defaultShapes['display'].prototype.customProperties = [
4356	        {name: 'size', dispName: 'Size', type: 'float', min: 0, max: 1, defVal: 0.25 }
4357		];
4358
4359		mxCellRenderer.defaultShapes['singleArrow'].prototype.customProperties = [
4360	        {name: 'arrowWidth', dispName: 'Arrow Width', type: 'float', min: 0, max: 1, defVal: 0.3 },
4361	        {name: 'arrowSize', dispName: 'Arrowhead Length', type: 'float', min: 0, max: 1, defVal: 0.2 }
4362		];
4363
4364		mxCellRenderer.defaultShapes['doubleArrow'].prototype.customProperties = [
4365	        {name: 'arrowWidth', dispName: 'Arrow Width', type: 'float', min: 0, max: 1, defVal: 0.3 },
4366	        {name: 'arrowSize', dispName: 'Arrowhead Length', type: 'float', min: 0, max: 1, defVal: 0.2 }
4367		];
4368
4369		mxCellRenderer.defaultShapes['cross'].prototype.customProperties = [
4370	        {name: 'size', dispName: 'Size', type: 'float', min: 0, max: 1, defVal: 0.2 }
4371		];
4372
4373		mxCellRenderer.defaultShapes['corner'].prototype.customProperties = [
4374	        {name: 'dx', dispName: 'Width1', type: 'float', min: 0, defVal: 20 },
4375	        {name: 'dy', dispName: 'Width2', type: 'float', min: 0, defVal: 20 }
4376		];
4377
4378		mxCellRenderer.defaultShapes['tee'].prototype.customProperties = [
4379	        {name: 'dx', dispName: 'Width1', type: 'float', min: 0, defVal: 20 },
4380	        {name: 'dy', dispName: 'Width2', type: 'float', min: 0, defVal: 20 }
4381		];
4382
4383		mxCellRenderer.defaultShapes['umlLifeline'].prototype.customProperties = [
4384			{name: 'participant', dispName:'Participant', type:'enum', defVal:'none', enumList:[
4385				{val:'none', dispName: 'Default'},
4386				{val:'umlActor', dispName: 'Actor'},
4387				{val:'umlBoundary', dispName: 'Boundary'},
4388				{val:'umlEntity', dispName: 'Entity'},
4389				{val:'umlControl', dispName: 'Control'},
4390				]},
4391			{name: 'size', dispName:'Height', type:'float', defVal:40, min:0}
4392		];
4393
4394		mxCellRenderer.defaultShapes['umlFrame'].prototype.customProperties = [
4395			{name: 'width', dispName:'Title Width', type:'float', defVal:60, min:0},
4396			{name: 'height', dispName:'Title Height', type:'float', defVal:30, min:0}
4397		];
4398
4399		/**
4400		 * Configures global color schemes.
4401		 */
4402		StyleFormatPanel.prototype.defaultColorSchemes = [[{fill: '', stroke: ''}, {fill: '#f5f5f5', stroke: '#666666', font: '#333333'},
4403			{fill: '#dae8fc', stroke: '#6c8ebf'}, {fill: '#d5e8d4', stroke: '#82b366'},
4404			{fill: '#ffe6cc', stroke: '#d79b00'}, {fill: '#fff2cc', stroke: '#d6b656'},
4405			{fill: '#f8cecc', stroke: '#b85450'}, {fill: '#e1d5e7', stroke: '#9673a6'}],
4406			[{fill: '', stroke: ''}, {fill: '#60a917', stroke: '#2D7600', font: '#ffffff'},
4407			{fill: '#008a00', stroke: '#005700', font: '#ffffff'}, {fill: '#1ba1e2', stroke: '#006EAF', font: '#ffffff'},
4408			{fill: '#0050ef', stroke: '#001DBC', font: '#ffffff'}, {fill: '#6a00ff', stroke: '#3700CC', font: '#ffffff'},
4409			//{fill: '#aa00ff', stroke: '#7700CC', font: '#ffffff'},
4410			{fill: '#d80073', stroke: '#A50040', font: '#ffffff'}, {fill: '#a20025', stroke: '#6F0000', font: '#ffffff'}],
4411			[{fill: '#e51400', stroke: '#B20000', font: '#ffffff'}, {fill: '#fa6800', stroke: '#C73500', font: '#000000'},
4412			{fill: '#f0a30a', stroke: '#BD7000', font: '#000000'}, {fill: '#e3c800', stroke: '#B09500', font: '#000000'},
4413			{fill: '#6d8764', stroke: '#3A5431', font: '#ffffff'}, {fill: '#647687', stroke: '#314354', font: '#ffffff'},
4414			{fill: '#76608a', stroke: '#432D57', font: '#ffffff'}, {fill: '#a0522d', stroke: '#6D1F00', font: '#ffffff'}],
4415			[{fill: '', stroke: ''}, {fill: mxConstants.NONE, stroke: ''},
4416			{fill: '#fad7ac', stroke: '#b46504'}, {fill: '#fad9d5', stroke: '#ae4132'},
4417			{fill: '#b0e3e6', stroke: '#0e8088'}, {fill: '#b1ddf0', stroke: '#10739e'},
4418			{fill: '#d0cee2', stroke: '#56517e'}, {fill: '#bac8d3', stroke: '#23445d'}],
4419		    [{fill: '', stroke: ''},
4420			{fill: '#f5f5f5', stroke: '#666666', gradient: '#b3b3b3'},
4421			{fill: '#dae8fc', stroke: '#6c8ebf', gradient: '#7ea6e0'},
4422			{fill: '#d5e8d4', stroke: '#82b366', gradient: '#97d077'},
4423			{fill: '#ffcd28', stroke: '#d79b00', gradient: '#ffa500'},
4424			{fill: '#fff2cc', stroke: '#d6b656', gradient: '#ffd966'},
4425			{fill: '#f8cecc', stroke: '#b85450', gradient: '#ea6b66'},
4426			{fill: '#e6d0de', stroke: '#996185', gradient: '#d5739d'}],
4427			[{fill: '', stroke: ''}, {fill: '#eeeeee', stroke: '#36393d'},
4428			{fill: '#f9f7ed', stroke: '#36393d'}, {fill: '#ffcc99', stroke: '#36393d'},
4429			{fill: '#cce5ff', stroke: '#36393d'}, {fill: '#ffff88', stroke: '#36393d'},
4430			{fill: '#cdeb8b', stroke: '#36393d'}, {fill: '#ffcccc', stroke: '#36393d'}]];
4431
4432		/**
4433		 * Configures custom color schemes.
4434		 */
4435		StyleFormatPanel.prototype.customColorSchemes = null;
4436
4437		StyleFormatPanel.prototype.findCommonProperties = function(cell, properties, addAll)
4438		{
4439			if (properties == null) return;
4440
4441			var handleCustomProp = function(custProperties)
4442			{
4443				if (custProperties != null)
4444				{
4445					if (addAll)
4446					{
4447						for (var i = 0; i < custProperties.length; i++)
4448						{
4449							properties[custProperties[i].name] = custProperties[i];
4450						}
4451					}
4452					else
4453					{
4454						for (var key in properties)
4455						{
4456							var found = false;
4457
4458							for (var i = 0; i < custProperties.length; i++)
4459							{
4460								if (custProperties[i].name == key && custProperties[i].type == properties[key].type)
4461								{
4462									found = true;
4463									break;
4464								}
4465							}
4466
4467							if (!found)
4468							{
4469								delete properties[key];
4470							}
4471						}
4472					}
4473				}
4474			};
4475
4476			var view = this.editorUi.editor.graph.view;
4477			var state = view.getState(cell);
4478
4479			if (state != null && state.shape != null)
4480			{
4481				//Add common properties to all shapes
4482				if (!state.shape.commonCustomPropAdded)
4483				{
4484					state.shape.commonCustomPropAdded = true;
4485					state.shape.customProperties = state.shape.customProperties || [];
4486
4487					if (state.cell.vertex)
4488					{
4489						Array.prototype.push.apply(state.shape.customProperties, Editor.commonVertexProperties);
4490					}
4491					else
4492					{
4493						Array.prototype.push.apply(state.shape.customProperties, Editor.commonEdgeProperties);
4494					}
4495				}
4496
4497				handleCustomProp(state.shape.customProperties);
4498			}
4499
4500			//This currently is not needed but let's keep it in case we needed in the future
4501			var userCustomProp = cell.getAttribute('customProperties');
4502
4503			if (userCustomProp != null)
4504			{
4505				try
4506				{
4507					handleCustomProp(JSON.parse(userCustomProp));
4508				}
4509				catch(e){}
4510			}
4511		};
4512
4513		/**
4514		 * Adds predefiend styles.
4515		 */
4516		var styleFormatPanelInit = StyleFormatPanel.prototype.init;
4517
4518		StyleFormatPanel.prototype.init = function()
4519		{
4520			// TODO: Update sstate in Format
4521			var sstate = this.format.getSelectionState();
4522
4523			if (sstate.style.shape != 'image' && !sstate.containsLabel && sstate.cells.length > 0)
4524			{
4525				this.container.appendChild(this.addStyles(this.createPanel()));
4526			}
4527
4528			styleFormatPanelInit.apply(this, arguments);
4529
4530			if (Editor.enableCustomProperties)
4531			{
4532				var properties = {};
4533				var vertices = sstate.vertices;
4534				var edges = sstate.edges;
4535
4536				for (var i = 0; i < vertices.length; i++)
4537				{
4538					this.findCommonProperties(vertices[i], properties, i == 0);
4539				}
4540
4541				for (var i = 0; i < edges.length; i++)
4542				{
4543					this.findCommonProperties(edges[i], properties, vertices.length == 0 && i == 0);
4544				}
4545
4546				if (Object.getOwnPropertyNames != null && Object.getOwnPropertyNames(properties).length > 0)
4547				{
4548					this.container.appendChild(this.addProperties(this.createPanel(), properties, sstate));
4549				}
4550			}
4551		};
4552
4553		/**
4554		 * Overridden to add copy and paste style.
4555		 */
4556		var styleFormatPanelAddStyleOps = StyleFormatPanel.prototype.addStyleOps;
4557
4558		StyleFormatPanel.prototype.addStyleOps = function(div)
4559		{
4560			var ss = this.format.getSelectionState();
4561			var graph = this.editorUi.editor.graph;
4562
4563			var btn = mxUtils.button(mxResources.get('copyStyle'), mxUtils.bind(this, function(evt)
4564			{
4565				this.editorUi.actions.get('copyStyle').funct();
4566			}));
4567
4568			btn.setAttribute('title', mxResources.get('copyStyle') + ' (' + this.editorUi.actions.get('copyStyle').shortcut + ')');
4569			btn.style.marginBottom = '2px';
4570			btn.style.width = '104px';
4571			btn.style.marginRight = '2px';
4572
4573			div.appendChild(btn);
4574
4575			if (ss.cells.length > 0)
4576			{
4577				var btn = mxUtils.button(mxResources.get('pasteStyle'), mxUtils.bind(this, function(evt)
4578				{
4579					this.editorUi.actions.get('pasteStyle').funct();
4580				}));
4581
4582				btn.setAttribute('title', mxResources.get('pasteStyle') + ' (' + this.editorUi.actions.get('pasteStyle').shortcut + ')');
4583				btn.style.marginBottom = '2px';
4584				btn.style.width = '104px';
4585
4586				div.appendChild(btn);
4587			}
4588			else
4589			{
4590				btn.style.width = '210px';
4591			}
4592
4593			mxUtils.br(div);
4594
4595			return styleFormatPanelAddStyleOps.apply(this, arguments);
4596		};
4597
4598		/**
4599		 * Initial collapsed state of the properties panel.
4600		 */
4601		EditorUi.prototype.propertiesCollapsed = true;
4602
4603		/**
4604		 * Create Properties Panel
4605		 */
4606		StyleFormatPanel.prototype.addProperties = function(div, properties, state)
4607		{
4608			var that = this;
4609			var graph = this.editorUi.editor.graph;
4610			var secondLevel = [];
4611
4612			function insertAfter(newElem, curElem)
4613			{
4614				curElem.parentNode.insertBefore(newElem, curElem.nextSibling);
4615			};
4616
4617			function applyStyleVal(pName, newVal, prop, delIndex)
4618			{
4619				graph.getModel().beginUpdate();
4620				try
4621				{
4622					var changedProps = [];
4623					var changedVals = [];
4624
4625					if (prop.index != null)
4626					{
4627						var allVals = [];
4628						var curVal = prop.parentRow.nextSibling;
4629
4630						while(curVal && curVal.getAttribute('data-pName') == pName)
4631						{
4632							allVals.push(curVal.getAttribute('data-pValue'));
4633							curVal = curVal.nextSibling;
4634						}
4635
4636						if (prop.index < allVals.length)
4637						{
4638							if (delIndex != null)
4639							{
4640								allVals.splice(delIndex, 1);
4641							}
4642							else
4643							{
4644								allVals[prop.index] = newVal;
4645							}
4646						}
4647						else
4648						{
4649							allVals.push(newVal);
4650						}
4651
4652						if (prop.size != null && allVals.length > prop.size) //trim the array to the specifies size
4653						{
4654							allVals = allVals.slice(0, prop.size);
4655						}
4656
4657						newVal = allVals.join(',');
4658
4659						if (prop.countProperty != null)
4660						{
4661							graph.setCellStyles(prop.countProperty, allVals.length, graph.getSelectionCells());
4662
4663							changedProps.push(prop.countProperty);
4664							changedVals.push(allVals.length);
4665						}
4666					}
4667
4668					graph.setCellStyles(pName, newVal, graph.getSelectionCells());
4669					changedProps.push(pName);
4670					changedVals.push(newVal);
4671
4672					if (prop.dependentProps != null)
4673					{
4674						for (var i = 0; i < prop.dependentProps.length; i++)
4675						{
4676							var defVal = prop.dependentPropsDefVal[i];
4677							var vals = prop.dependentPropsVals[i];
4678
4679							if (vals.length > newVal)
4680							{
4681								vals = vals.slice(0, newVal);
4682							}
4683							else
4684							{
4685								for (var j = vals.length; j < newVal; j++)
4686								{
4687									vals.push(defVal);
4688								}
4689							}
4690
4691							vals = vals.join(',');
4692							graph.setCellStyles(prop.dependentProps[i], vals, graph.getSelectionCells());
4693							changedProps.push(prop.dependentProps[i]);
4694							changedVals.push(vals);
4695						}
4696					}
4697
4698					if (typeof(prop.onChange) == 'function')
4699					{
4700						prop.onChange(graph, newVal);
4701					}
4702
4703					that.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', changedProps,
4704						'values', changedVals, 'cells', graph.getSelectionCells()));
4705				}
4706				finally
4707				{
4708					graph.getModel().endUpdate();
4709				}
4710			}
4711
4712			function setElementPos(td, elem, adjustHeight)
4713			{
4714				var divPos = mxUtils.getOffset(div, true);
4715				var pos = mxUtils.getOffset(td, true);
4716				elem.style.position = 'absolute';
4717				elem.style.left = (pos.x - divPos.x) + 'px';
4718				elem.style.top = (pos.y - divPos.y) + 'px';
4719				elem.style.width = td.offsetWidth + 'px';
4720				elem.style.height = (td.offsetHeight - (adjustHeight? 4 : 0)) + 'px';
4721				elem.style.zIndex = 5;
4722			};
4723
4724			function createColorBtn(pName, pValue, prop)
4725			{
4726				var clrDiv = document.createElement('div');
4727				clrDiv.style.width = '32px';
4728				clrDiv.style.height = '4px';
4729				clrDiv.style.margin = '2px';
4730				clrDiv.style.border = '1px solid black';
4731				clrDiv.style.background = !pValue || pValue == 'none'? 'url(\'' + Dialog.prototype.noColorImage + '\')' : pValue;
4732
4733				btn = mxUtils.button('', mxUtils.bind(that, function(evt)
4734				{
4735					this.editorUi.pickColor(pValue, function(color)
4736					{
4737						clrDiv.style.background = color == 'none'? 'url(\'' + Dialog.prototype.noColorImage + '\')' : color;
4738						applyStyleVal(pName, color, prop);
4739					});
4740					mxEvent.consume(evt);
4741				}));
4742
4743				btn.style.height = '12px';
4744				btn.style.width = '40px';
4745				btn.className = 'geColorBtn';
4746
4747				btn.appendChild(clrDiv);
4748				return btn;
4749			};
4750
4751			function createDynArrList(pName, pValue, subType, defVal, countProperty, myRow, flipBkg)
4752			{
4753				if (pValue != null)
4754				{
4755					var vals = pValue.split(',');
4756					secondLevel.push({name: pName, values: vals, type: subType, defVal: defVal, countProperty: countProperty, parentRow: myRow, isDeletable: true, flipBkg: flipBkg});
4757				}
4758
4759				btn = mxUtils.button('+', mxUtils.bind(that, function(evt)
4760				{
4761					var beforeElem = myRow;
4762					var index = 0;
4763
4764					while (beforeElem.nextSibling != null)
4765					{
4766						var cur = beforeElem.nextSibling;
4767						var elemPName = cur.getAttribute('data-pName');
4768
4769						if (elemPName == pName)
4770						{
4771							beforeElem = beforeElem.nextSibling;
4772							index++;
4773						}
4774						else
4775						{
4776							break;
4777						}
4778					}
4779
4780					var newProp = {type: subType, parentRow: myRow, index: index, isDeletable: true, defVal: defVal, countProperty: countProperty};
4781					var arrItem = createPropertyRow(pName, '', newProp, index % 2 == 0, flipBkg);
4782					applyStyleVal(pName, defVal, newProp);
4783					insertAfter(arrItem, beforeElem);
4784
4785					mxEvent.consume(evt);
4786				}));
4787
4788				btn.style.height = '16px';
4789				btn.style.width = '25px';
4790				btn.className = 'geColorBtn';
4791
4792				return btn;
4793			};
4794
4795			function createStaticArrList(pName, pValue, subType, defVal, size, myRow, flipBkg)
4796			{
4797				if (size > 0)
4798				{
4799					var vals = new Array(size);
4800
4801					var curVals = pValue != null? pValue.split(',') : [];
4802
4803					for (var i = 0; i < size; i++)
4804					{
4805						vals[i] = curVals[i] != null? curVals[i] : (defVal != null? defVal : '');
4806					}
4807
4808					secondLevel.push({name: pName, values: vals, type: subType, defVal: defVal, parentRow: myRow, flipBkg: flipBkg, size: size});
4809				}
4810
4811				return document.createElement('div'); //empty cell
4812			};
4813
4814			function createCheckbox(pName, pValue, prop)
4815			{
4816				var input = document.createElement('input');
4817				input.type = 'checkbox';
4818				input.checked = pValue == '1';
4819
4820				mxEvent.addListener(input, 'change', function()
4821				{
4822					applyStyleVal(pName, input.checked? '1' : '0', prop);
4823				});
4824				return input;
4825			};
4826
4827			function createPropertyRow(pName, pValue, prop, isOdd, flipBkg)
4828			{
4829				var pDiplayName = prop.dispName;
4830				var pType = prop.type;
4831				var row = document.createElement('tr');
4832				row.className = 'gePropRow' + (flipBkg? 'Dark' : '') + (isOdd? 'Alt' : '') + ' gePropNonHeaderRow';
4833				row.setAttribute('data-pName', pName);
4834				row.setAttribute('data-pValue', pValue);
4835				var rightAlig = false;
4836
4837				if (prop.index != null)
4838				{
4839					row.setAttribute('data-index', prop.index);
4840					pDiplayName = (pDiplayName != null? pDiplayName : '') + '[' + prop.index + ']';
4841					rightAlig = true;
4842				}
4843
4844				var td = document.createElement('td');
4845				td.className = 'gePropRowCell';
4846				var label = mxResources.get(pDiplayName, null, pDiplayName);
4847				mxUtils.write(td, label);
4848				td.setAttribute('title', label);
4849
4850				if (rightAlig)
4851				{
4852					td.style.textAlign = 'right';
4853				}
4854
4855				row.appendChild(td);
4856				td = document.createElement('td');
4857				td.className = 'gePropRowCell';
4858
4859				if (pType == 'color')
4860				{
4861					td.appendChild(createColorBtn(pName, pValue, prop));
4862				}
4863				else if (pType == 'bool' || pType == 'boolean')
4864				{
4865					td.appendChild(createCheckbox(pName, pValue, prop));
4866				}
4867				else if (pType == 'enum')
4868				{
4869					var pEnumList = prop.enumList;
4870
4871					for (var i = 0; i < pEnumList.length; i++)
4872					{
4873						var op = pEnumList[i];
4874
4875						if (op.val == pValue)
4876						{
4877							mxUtils.write(td, mxResources.get(op.dispName, null, op.dispName));
4878							break;
4879						}
4880					}
4881
4882					mxEvent.addListener(td, 'click', mxUtils.bind(that, function()
4883					{
4884						var select = document.createElement('select');
4885						setElementPos(td, select);
4886
4887						for (var i = 0; i < pEnumList.length; i++)
4888						{
4889							var op = pEnumList[i];
4890							var opElem = document.createElement('option');
4891							opElem.value = mxUtils.htmlEntities(op.val);
4892							mxUtils.write(opElem, mxResources.get(op.dispName, null, op.dispName));
4893							select.appendChild(opElem);
4894						}
4895
4896						select.value = pValue;
4897
4898						div.appendChild(select);
4899
4900						mxEvent.addListener(select, 'change', function()
4901						{
4902							var newVal = mxUtils.htmlEntities(select.value);
4903							applyStyleVal(pName, newVal, prop);
4904							//set value triggers a redraw of the panel which removes the select and updates the row
4905						});
4906
4907						select.focus();
4908
4909						//FF calls blur on focus! so set the event after focusing (not with selects but to be safe)
4910						mxEvent.addListener(select, 'blur', function()
4911						{
4912							div.removeChild(select);
4913						});
4914					}));
4915				}
4916				else if (pType == 'dynamicArr')
4917				{
4918					td.appendChild(createDynArrList(pName, pValue, prop.subType, prop.subDefVal, prop.countProperty, row, flipBkg));
4919				}
4920				else if (pType == 'staticArr')
4921				{
4922					td.appendChild(createStaticArrList(pName, pValue, prop.subType, prop.subDefVal, prop.size, row, flipBkg));
4923				}
4924				else if (pType == 'readOnly')
4925				{
4926					var inp = document.createElement('input');
4927					inp.setAttribute('readonly', '');
4928					inp.value = pValue;
4929					inp.style.width = '96px';
4930					inp.style.borderWidth = '0px';
4931					td.appendChild(inp);
4932				}
4933				else
4934				{
4935					td.innerHTML = pValue;
4936
4937					mxEvent.addListener(td, 'click', mxUtils.bind(that, function()
4938					{
4939						var input = document.createElement('input');
4940						setElementPos(td, input, true);
4941						input.value = pValue;
4942						input.className = 'gePropEditor';
4943
4944						if ((pType == 'int' || pType == 'float') && !prop.allowAuto)
4945						{
4946							input.type = 'number';
4947							input.step = pType == 'int'? '1' : 'any';
4948
4949							if (prop.min != null)
4950							{
4951								input.min = parseFloat(prop.min);
4952							}
4953
4954							if (prop.max != null)
4955							{
4956								input.max = parseFloat(prop.max);
4957							}
4958						}
4959
4960						div.appendChild(input);
4961
4962						function setInputVal()
4963						{
4964							var inputVal = input.value;
4965							inputVal = inputVal.length == 0 && pType != 'string'? 0 : inputVal;
4966
4967							if (prop.allowAuto)
4968							{
4969								if (inputVal.trim != null && inputVal.trim().toLowerCase() == 'auto')
4970								{
4971									inputVal = 'auto';
4972									pType = 'string';
4973								}
4974								else
4975								{
4976									inputVal = parseFloat(inputVal);
4977									inputVal = isNaN(inputVal)? 0 : inputVal;
4978								}
4979							}
4980
4981							if (prop.min != null && inputVal < prop.min)
4982							{
4983								inputVal = prop.min;
4984							}
4985							else if (prop.max != null && inputVal > prop.max)
4986							{
4987								inputVal = prop.max;
4988							}
4989
4990							var newVal = mxUtils.htmlEntities((pType == 'int'? parseInt(inputVal) : inputVal) + '');
4991
4992							applyStyleVal(pName, newVal, prop);
4993						}
4994
4995						mxEvent.addListener(input, 'keypress', function(e)
4996						{
4997							if (e.keyCode == 13)
4998							{
4999								setInputVal();
5000								//set value triggers a redraw of the panel which removes the input
5001							}
5002						});
5003
5004						input.focus();
5005
5006						//FF calls blur on focus! so set the event after focusing
5007						mxEvent.addListener(input, 'blur', function()
5008						{
5009							setInputVal();
5010						});
5011					}));
5012				}
5013
5014				if (prop.isDeletable)
5015				{
5016					var delBtn = mxUtils.button('-', mxUtils.bind(that, function(evt)
5017					{
5018						//delete the node by refreshing the properties
5019						applyStyleVal(pName, '', prop, prop.index);
5020
5021						mxEvent.consume(evt);
5022					}));
5023
5024					delBtn.style.height = '16px';
5025					delBtn.style.width = '25px';
5026					delBtn.style.float = 'right';
5027					delBtn.className = 'geColorBtn';
5028					td.appendChild(delBtn);
5029				}
5030
5031				row.appendChild(td);
5032				return row;
5033			};
5034
5035			div.style.position = 'relative';
5036			div.style.padding = '0';
5037			var grid = document.createElement('table');
5038			grid.className = 'geProperties';
5039			grid.style.whiteSpace = 'nowrap';
5040			grid.style.width = '100%';
5041			//create header row
5042			var hrow = document.createElement('tr');
5043			hrow.className = 'gePropHeader';
5044			var th = document.createElement('th');
5045			th.className = 'gePropHeaderCell';
5046			var collapseImg = document.createElement('img');
5047			collapseImg.src = Sidebar.prototype.expandedImage;
5048			collapseImg.style.verticalAlign = 'middle';
5049			th.appendChild(collapseImg);
5050			mxUtils.write(th, mxResources.get('property'));
5051			hrow.style.cursor = 'pointer';
5052
5053			var onFold = function()
5054			{
5055				var rows = grid.querySelectorAll('.gePropNonHeaderRow');
5056				var display;
5057
5058				if (!that.editorUi.propertiesCollapsed)
5059				{
5060					collapseImg.src = Sidebar.prototype.expandedImage;
5061					display = '';
5062				}
5063				else
5064				{
5065					collapseImg.src = Sidebar.prototype.collapsedImage;
5066					display = 'none';
5067
5068					for (var e = div.childNodes.length - 1; e >= 0 ; e--)
5069					{
5070						//Blur can be executed concurrently with this method and the element is removed before removing it here
5071						try
5072						{
5073							var child = div.childNodes[e];
5074							var nodeName = child.nodeName.toUpperCase();
5075
5076							if (nodeName == 'INPUT' || nodeName == 'SELECT')
5077							{
5078								div.removeChild(child);
5079							}
5080						}
5081						catch(ex){}
5082					}
5083				}
5084
5085				for (var r = 0; r < rows.length; r++)
5086				{
5087					rows[r].style.display = display;
5088				}
5089			};
5090
5091			mxEvent.addListener(hrow, 'click', function()
5092			{
5093				that.editorUi.propertiesCollapsed = !that.editorUi.propertiesCollapsed;
5094				onFold();
5095			});
5096			hrow.appendChild(th);
5097			th = document.createElement('th');
5098			th.className = 'gePropHeaderCell';
5099			th.innerHTML = mxResources.get('value');
5100			hrow.appendChild(th);
5101			grid.appendChild(hrow);
5102
5103			var isOdd = false;
5104			var flipBkg = false;
5105
5106			var cellId = null;
5107
5108			if (state.vertices.length == 1 && state.edges.length == 0)
5109			{
5110				cellId = state.vertices[0].id;
5111			}
5112			else if (state.vertices.length == 0 && state.edges.length == 1)
5113			{
5114				cellId = state.edges[0].id;
5115			}
5116
5117			//Add it to top (always)
5118			if (cellId != null)
5119			{
5120				grid.appendChild(createPropertyRow('id', mxUtils.htmlEntities(cellId), {dispName: 'ID', type: 'readOnly'}, true, false));
5121			}
5122
5123			for (var key in properties)
5124			{
5125				var prop = properties[key];
5126
5127				if (typeof(prop.isVisible) == 'function')
5128				{
5129					if (!prop.isVisible(state, this)) continue;
5130				}
5131
5132				var pValue = state.style[key] != null? mxUtils.htmlEntities(state.style[key] + '') :
5133					((prop.getDefaultValue != null) ? prop.getDefaultValue(state, this) : prop.defVal); //or undefined if defVal is undefined
5134
5135				if (prop.type == 'separator')
5136				{
5137					flipBkg = !flipBkg;
5138					continue;
5139				}
5140				else if (prop.type == 'staticArr') //if dynamic values are needed, a more elegant technique is needed to replace such values
5141				{
5142					prop.size = parseInt(state.style[prop.sizeProperty] || properties[prop.sizeProperty].defVal) || 0;
5143				}
5144				else if (prop.dependentProps != null)
5145				{
5146					var dependentProps = prop.dependentProps;
5147					var dependentPropsVals = [];
5148					var dependentPropsDefVal = [];
5149
5150					for (var i = 0; i < dependentProps.length; i++)
5151					{
5152						var propVal = state.style[dependentProps[i]];
5153						dependentPropsDefVal.push(properties[dependentProps[i]].subDefVal);
5154						dependentPropsVals.push(propVal != null? propVal.split(',') : []);
5155					}
5156
5157					prop.dependentPropsDefVal = dependentPropsDefVal;
5158					prop.dependentPropsVals = dependentPropsVals;
5159				}
5160
5161				grid.appendChild(createPropertyRow(key, pValue, prop, isOdd, flipBkg));
5162
5163				isOdd = !isOdd;
5164			}
5165
5166			for (var i = 0; i < secondLevel.length; i++)
5167			{
5168				var prop = secondLevel[i];
5169				var insertElem = prop.parentRow;
5170
5171				for (var j = 0; j < prop.values.length; j++)
5172				{
5173					//mxUtils.clone failed because of the HTM element, so manual cloning is used
5174					var iProp = {type: prop.type, parentRow: prop.parentRow, isDeletable: prop.isDeletable, index: j, defVal: prop.defVal, countProperty: prop.countProperty, size: prop.size};
5175					var arrItem = createPropertyRow(prop.name, prop.values[j], iProp, j % 2 == 0, prop.flipBkg);
5176					insertAfter(arrItem, insertElem);
5177					insertElem = arrItem;
5178				}
5179			}
5180
5181			div.appendChild(grid);
5182			onFold();
5183
5184			return div;
5185		};
5186
5187		/**
5188		 * Creates the buttons for the predefined styles.
5189		 */
5190		StyleFormatPanel.prototype.addStyles = function(div)
5191		{
5192			var ui = this.editorUi;
5193			var graph = ui.editor.graph;
5194			var picker = document.createElement('div');
5195			picker.style.whiteSpace = 'nowrap';
5196			picker.style.paddingLeft = '24px';
5197			picker.style.paddingRight = '20px';
5198			div.style.paddingLeft = '16px';
5199			div.style.paddingBottom = '6px';
5200			div.style.position = 'relative';
5201			div.appendChild(picker);
5202
5203			var stylenames = ['plain-gray', 'plain-blue', 'plain-green', 'plain-turquoise',
5204				'plain-orange', 'plain-yellow', 'plain-red', 'plain-pink', 'plain-purple', 'gray',
5205				'blue', 'green', 'turquoise', 'orange', 'yellow', 'red', 'pink', 'purple'];
5206
5207			// Maximum palettes to switch the switcher
5208			var maxEntries = 10;
5209
5210			// Selector
5211			var switcher = document.createElement('div');
5212			switcher.style.whiteSpace = 'nowrap';
5213			switcher.style.position = 'relative';
5214			switcher.style.textAlign = 'center';
5215			switcher.style.width = '210px';
5216
5217			var dots = [];
5218
5219			for (var i = 0; i < this.defaultColorSchemes.length; i++)
5220			{
5221				var dot = document.createElement('div');
5222				dot.style.display = 'inline-block';
5223				dot.style.width = '6px';
5224				dot.style.height = '6px';
5225				dot.style.marginLeft = '4px';
5226				dot.style.marginRight = '3px';
5227				dot.style.borderRadius = '3px';
5228				dot.style.cursor = 'pointer';
5229				dot.style.background = 'transparent';
5230				dot.style.border = '1px solid #b5b6b7';
5231
5232				(mxUtils.bind(this, function(index)
5233				{
5234					mxEvent.addListener(dot, 'click', mxUtils.bind(this, function()
5235					{
5236						setScheme(index);
5237					}));
5238				}))(i);
5239
5240				dots.push(dot);
5241				switcher.appendChild(dot);
5242			}
5243
5244			var setScheme = mxUtils.bind(this, function(index)
5245			{
5246				if (dots[index] != null)
5247				{
5248					if (this.format.currentScheme != null && dots[this.format.currentScheme] != null)
5249					{
5250						dots[this.format.currentScheme].style.background = 'transparent';
5251					}
5252
5253					this.format.currentScheme = index;
5254					updateScheme(this.defaultColorSchemes[this.format.currentScheme]);
5255					dots[this.format.currentScheme].style.background = '#84d7ff';
5256				}
5257			});
5258
5259			var updateScheme = mxUtils.bind(this, function(colorsets)
5260			{
5261				var addButton = mxUtils.bind(this, function(colorset)
5262				{
5263					var btn = mxUtils.button('', mxUtils.bind(this, function(evt)
5264					{
5265						graph.getModel().beginUpdate();
5266						try
5267						{
5268							var cells = this.format.getSelectionState().cells;
5269
5270							for (var i = 0; i < cells.length; i++)
5271							{
5272								var style = graph.getModel().getStyle(cells[i]);
5273
5274								for (var j = 0; j < stylenames.length; j++)
5275								{
5276									style = mxUtils.removeStylename(style, stylenames[j]);
5277								}
5278
5279								var defaults = (graph.getModel().isVertex(cells[i])) ? graph.defaultVertexStyle : graph.defaultEdgeStyle;
5280
5281								if (colorset != null)
5282								{
5283									if (!mxEvent.isShiftDown(evt))
5284									{
5285										if (colorset['fill'] == '')
5286										{
5287											style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR, null);
5288										}
5289										else
5290										{
5291											style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR, colorset['fill'] ||
5292												mxUtils.getValue(defaults, mxConstants.STYLE_FILLCOLOR, null));
5293										}
5294
5295										style = mxUtils.setStyle(style, mxConstants.STYLE_GRADIENTCOLOR, colorset['gradient'] ||
5296											mxUtils.getValue(defaults, mxConstants.STYLE_GRADIENTCOLOR, null));
5297
5298										if (!mxEvent.isControlDown(evt) && (!mxClient.IS_MAC || !mxEvent.isMetaDown(evt)) &&
5299											graph.getModel().isVertex(cells[i]))
5300										{
5301											style = mxUtils.setStyle(style, mxConstants.STYLE_FONTCOLOR, colorset['font'] ||
5302												mxUtils.getValue(defaults, mxConstants.STYLE_FONTCOLOR, null));
5303										}
5304									}
5305
5306									if (!mxEvent.isAltDown(evt))
5307									{
5308										if (colorset['stroke'] == '')
5309										{
5310											style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR, null);
5311										}
5312										else
5313										{
5314											style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR, colorset['stroke'] ||
5315												mxUtils.getValue(defaults, mxConstants.STYLE_STROKECOLOR, null));
5316										}
5317									}
5318								}
5319								else
5320								{
5321									style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR,
5322										mxUtils.getValue(defaults, mxConstants.STYLE_FILLCOLOR, '#ffffff'));
5323									style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR,
5324										mxUtils.getValue(defaults, mxConstants.STYLE_STROKECOLOR, '#000000'));
5325									style = mxUtils.setStyle(style, mxConstants.STYLE_GRADIENTCOLOR,
5326										mxUtils.getValue(defaults, mxConstants.STYLE_GRADIENTCOLOR, null));
5327
5328									if (graph.getModel().isVertex(cells[i]))
5329									{
5330										style = mxUtils.setStyle(style, mxConstants.STYLE_FONTCOLOR,
5331											mxUtils.getValue(defaults, mxConstants.STYLE_FONTCOLOR, null));
5332									}
5333								}
5334
5335								graph.getModel().setStyle(cells[i], style);
5336							}
5337						}
5338						finally
5339						{
5340							graph.getModel().endUpdate();
5341						}
5342					}));
5343
5344					btn.className = 'geStyleButton';
5345					btn.style.width = '36px';
5346					btn.style.height = (this.defaultColorSchemes.length <= maxEntries) ? '24px' : '30px';
5347					btn.style.margin = '0px 6px 6px 0px';
5348
5349					if (colorset != null)
5350					{
5351						var b = (urlParams['sketch'] == '1') ? '2px solid' : '1px solid';
5352
5353						if (colorset['gradient'] != null)
5354						{
5355							if (mxClient.IS_IE && (document.documentMode < 10))
5356							{
5357						    	btn.style.filter = 'progid:DXImageTransform.Microsoft.Gradient('+
5358				                	'StartColorStr=\'' + colorset['fill'] +
5359				                	'\', EndColorStr=\'' + colorset['gradient'] + '\', GradientType=0)';
5360							}
5361							else
5362							{
5363								btn.style.backgroundImage = 'linear-gradient(' + colorset['fill'] + ' 0px,' +
5364									colorset['gradient'] + ' 100%)';
5365							}
5366						}
5367						else if (colorset['fill'] == mxConstants.NONE)
5368						{
5369							btn.style.background = 'url(\'' + Dialog.prototype.noColorImage + '\')';
5370						}
5371						else if (colorset['fill'] == '')
5372						{
5373							btn.style.backgroundColor = mxUtils.getValue(graph.defaultVertexStyle,
5374								mxConstants.STYLE_FILLCOLOR, (Editor.isDarkMode()) ? Editor.darkColor : '#ffffff');
5375						}
5376						else
5377						{
5378							btn.style.backgroundColor = colorset['fill'] || mxUtils.getValue(graph.defaultVertexStyle,
5379								mxConstants.STYLE_FILLCOLOR, (Editor.isDarkMode()) ? Editor.darkColor : '#ffffff');
5380						}
5381
5382						if (colorset['stroke'] == mxConstants.NONE)
5383						{
5384							btn.style.border = b + ' transparent';
5385						}
5386						else if (colorset['stroke'] == '')
5387						{
5388							btn.style.border = b + ' ' + mxUtils.getValue(graph.defaultVertexStyle,
5389								mxConstants.STYLE_STROKECOLOR, (!Editor.isDarkMode()) ? Editor.darkColor : '#ffffff');
5390						}
5391						else
5392						{
5393							btn.style.border = b + ' ' + (colorset['stroke'] || mxUtils.getValue(graph.defaultVertexStyle,
5394									mxConstants.STYLE_STROKECOLOR, (!Editor.isDarkMode()) ? Editor.darkColor : '#ffffff'));
5395						}
5396
5397						if (colorset['title'] != null)
5398						{
5399							btn.setAttribute('title', colorset['title']);
5400						}
5401					}
5402					else
5403					{
5404						var bg = mxUtils.getValue(graph.defaultVertexStyle, mxConstants.STYLE_FILLCOLOR, '#ffffff');
5405						var bd = mxUtils.getValue(graph.defaultVertexStyle, mxConstants.STYLE_STROKECOLOR, '#000000');
5406
5407						btn.style.backgroundColor = bg;
5408						btn.style.border = '1px solid ' + bd;
5409					}
5410
5411					btn.style.borderRadius = '0';
5412
5413					picker.appendChild(btn);
5414				});
5415
5416				picker.innerHTML = '';
5417
5418				for (var i = 0; i < colorsets.length; i++)
5419				{
5420					if (i > 0 && mxUtils.mod(i, 4) == 0)
5421					{
5422						mxUtils.br(picker);
5423					}
5424
5425					addButton(colorsets[i]);
5426				}
5427			});
5428
5429			if (this.format.currentScheme == null)
5430			{
5431				setScheme(Editor.isDarkMode() ? 1 : (urlParams['sketch'] == '1' ? 5 : 0));
5432			}
5433			else
5434			{
5435				setScheme(this.format.currentScheme);
5436			}
5437
5438			var bottom = (this.defaultColorSchemes.length <= maxEntries) ? 28 : 8;
5439
5440			var left = document.createElement('div');
5441			left.style.cssText = 'position:absolute;left:10px;top:8px;bottom:' + bottom + 'px;width:20px;margin:4px;opacity:0.5;' +
5442				'background-repeat:no-repeat;background-position:center center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAAB2dnZ4eHh3d3d1dXVxcXF2dnZ2dnZ2dnZxcXF2dnYmb3w1AAAACnRSTlMAfCTkhhvb7cQSPH2JPgAAADRJREFUCNdjwACMAmBKaiGYs2oJmLPKAZ3DabU8AMRTXpUKopislqFyVzCAuUZgikkBZjoAcMYLnp53P/UAAAAASUVORK5CYII=);';
5443
5444			mxEvent.addListener(left, 'click', mxUtils.bind(this, function()
5445			{
5446				setScheme(mxUtils.mod(this.format.currentScheme - 1, this.defaultColorSchemes.length));
5447			}));
5448
5449			var right = document.createElement('div');
5450			right.style.cssText = 'position:absolute;left:202px;top:8px;bottom:' + bottom + 'px;width:20px;margin:4px;opacity:0.5;' +
5451				'background-repeat:no-repeat;background-position:center center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnYBuwCcAAAACnRSTlMAfCTkhhvb7cQSPH2JPgAAADZJREFUCNdjQAOMAmBKaiGY8loF5rKswsZlrVo8AUiFrTICcbIWK8A5DF1gDoMymMPApIAwHwCS0Qx/U7qCBQAAAABJRU5ErkJggg==);';
5452
5453			if (this.defaultColorSchemes.length > 1)
5454			{
5455				div.appendChild(left);
5456				div.appendChild(right);
5457			}
5458
5459			mxEvent.addListener(right, 'click', mxUtils.bind(this, function()
5460			{
5461				setScheme(mxUtils.mod(this.format.currentScheme + 1, this.defaultColorSchemes.length));
5462			}));
5463
5464			// Hover state
5465			function addHoverState(elt)
5466			{
5467				mxEvent.addListener(elt, 'mouseenter', function()
5468				{
5469					elt.style.opacity = '1';
5470				});
5471				mxEvent.addListener(elt, 'mouseleave', function()
5472				{
5473					elt.style.opacity = '0.5';
5474				});
5475			};
5476
5477			addHoverState(left);
5478			addHoverState(right);
5479
5480			updateScheme(this.defaultColorSchemes[this.format.currentScheme]);
5481
5482			if (this.defaultColorSchemes.length <= maxEntries)
5483			{
5484				div.appendChild(switcher);
5485			}
5486
5487			return div;
5488		};
5489
5490		StyleFormatPanel.prototype.addEditOps = function(div)
5491		{
5492			var ss = this.format.getSelectionState();
5493			var btn = null;
5494
5495			if (ss.cells.length == 1)
5496			{
5497				btn = mxUtils.button(mxResources.get('editStyle'), mxUtils.bind(this, function(evt)
5498				{
5499					this.editorUi.actions.get('editStyle').funct();
5500				}));
5501
5502				btn.setAttribute('title', mxResources.get('editStyle') + ' (' + this.editorUi.actions.get('editStyle').shortcut + ')');
5503				btn.style.width = '210px';
5504				btn.style.marginBottom = '2px';
5505
5506				div.appendChild(btn);
5507			}
5508
5509			var graph = this.editorUi.editor.graph;
5510			var state = (ss.cells.length == 1) ? graph.view.getState(ss.cells[0]) : null;
5511
5512			if (state != null && state.shape != null && state.shape.stencil != null)
5513			{
5514				var btn2 = mxUtils.button(mxResources.get('editShape'), mxUtils.bind(this, function(evt)
5515				{
5516					this.editorUi.actions.get('editShape').funct();
5517				}));
5518
5519				btn2.setAttribute('title', mxResources.get('editShape'));
5520				btn2.style.marginBottom = '2px';
5521
5522				if (btn == null)
5523				{
5524					btn2.style.width = '210px';
5525				}
5526				else
5527				{
5528					btn.style.width = '104px';
5529					btn2.style.width = '104px';
5530					btn2.style.marginLeft = '2px';
5531				}
5532
5533				div.appendChild(btn2);
5534			}
5535			else if (ss.image && ss.cells.length > 0)
5536			{
5537				var btn2 = mxUtils.button(mxResources.get('editImage'), mxUtils.bind(this, function(evt)
5538				{
5539					this.editorUi.actions.get('image').funct();
5540				}));
5541
5542				btn2.setAttribute('title', mxResources.get('editImage'));
5543				btn2.style.marginBottom = '2px';
5544
5545				if (btn == null)
5546				{
5547					btn2.style.width = '210px';
5548				}
5549				else
5550				{
5551					btn.style.width = '104px';
5552					btn2.style.width = '104px';
5553					btn2.style.marginLeft = '2px';
5554				}
5555
5556				div.appendChild(btn2);
5557			}
5558
5559			return div;
5560		};
5561	}
5562
5563	/**
5564	 * Lookup table for mapping from font URL and name to elements in the DOM.
5565	 */
5566	Graph.customFontElements = {};
5567
5568	/**
5569	 * Lookup table for recent custom fonts.
5570	 */
5571	Graph.recentCustomFonts = {};
5572
5573	/**
5574	 * Returns true if the given font URL references a Google font.
5575	 */
5576	Graph.isGoogleFontUrl = function(url)
5577	{
5578		return url.substring(0, Editor.GOOGLE_FONTS.length) == Editor.GOOGLE_FONTS;
5579	};
5580
5581	/**
5582	 * Returns true if the given font URL is a CSS file.
5583	 */
5584	Graph.isCssFontUrl = function(url)
5585	{
5586		return Graph.isGoogleFontUrl(url);
5587	};
5588
5589	/**
5590	 * Creates the DOM node for the custom font.
5591	 */
5592	Graph.createFontElement = function(name, url)
5593	{
5594		var elt = null;
5595
5596		if (Graph.isCssFontUrl(url))
5597		{
5598			elt = document.createElement('link');
5599			elt.setAttribute('rel', 'stylesheet');
5600			elt.setAttribute('type', 'text/css');
5601			elt.setAttribute('charset', 'UTF-8');
5602			elt.setAttribute('href', url);
5603		}
5604		else
5605		{
5606			elt = document.createElement('style');
5607			mxUtils.write(elt, '@font-face {\n' +
5608				'font-family: "' + name + '";\n' +
5609				'src: url("' + url + '");\n}');
5610		}
5611
5612		return elt;
5613	};
5614
5615	/**
5616	 * Adds a font to the document.
5617	 */
5618	Graph.addFont = function(name, url, callback)
5619	{
5620		if (name != null && name.length > 0 && url != null && url.length > 0)
5621		{
5622			var key = name.toLowerCase();
5623
5624			// Blocks UI font from being overwritten
5625			if (key != 'helvetica' && name != 'arial' && key != 'sans-serif')
5626			{
5627				var entry = Graph.customFontElements[key];
5628
5629				// Replaces element if URL has changed
5630				if (entry != null && entry.url != url)
5631				{
5632					entry.elt.parentNode.removeChild(entry.elt);
5633					entry = null;
5634				}
5635
5636				if (entry == null)
5637				{
5638					var realUrl = url;
5639
5640					// Fixes possible mixed content by using proxy
5641					if (url.substring(0, 5) == 'http:')
5642					{
5643						realUrl = PROXY_URL + '?url=' + encodeURIComponent(url);
5644					}
5645
5646					entry = {name: name, url: url, elt: Graph.createFontElement(name, realUrl)};
5647					Graph.customFontElements[key] = entry;
5648					Graph.recentCustomFonts[key] = entry;
5649					var head = document.getElementsByTagName('head')[0];
5650
5651					if (callback != null)
5652					{
5653						if (entry.elt.nodeName.toLowerCase() == 'link')
5654						{
5655							entry.elt.onload = callback;
5656							entry.elt.onerror = callback;
5657						}
5658						else
5659						{
5660							callback();
5661						}
5662					}
5663
5664					if (head != null)
5665					{
5666						head.appendChild(entry.elt);
5667					}
5668				}
5669				else if (callback != null)
5670				{
5671					callback();
5672				}
5673			}
5674			else if (callback != null)
5675			{
5676				callback();
5677			}
5678		}
5679		else if (callback != null)
5680		{
5681			callback();
5682		}
5683
5684		return name;
5685	};
5686
5687	/**
5688	 * Returns the URL for the given font name if it exists in the document.
5689	 * Otherwise it returns the given URL.
5690	 */
5691	Graph.getFontUrl = function(name, url)
5692	{
5693		var font = Graph.customFontElements[name.toLowerCase()];
5694
5695		if (font != null)
5696		{
5697			url = font.url;
5698		}
5699
5700		return url;
5701	};
5702
5703	/**
5704	 * Processes the fonts in the given element and its descendants.
5705	 */
5706	Graph.processFontAttributes = function(elt)
5707	{
5708		var elts = elt.getElementsByTagName('*');
5709
5710		for (var i = 0; i < elts.length; i++)
5711		{
5712			var url = elts[i].getAttribute('data-font-src');
5713
5714			if (url != null)
5715			{
5716				var name = (elts[i].nodeName == 'FONT') ?
5717					elts[i].getAttribute('face') :
5718					elts[i].style.fontFamily;
5719
5720				if (name != null)
5721				{
5722					Graph.addFont(name, url);
5723				}
5724			}
5725		}
5726	};
5727
5728	/**
5729	 * Processes the font in the given cell style.
5730	 */
5731	Graph.processFontStyle = function(style)
5732	{
5733		if (style != null)
5734		{
5735			var url = mxUtils.getValue(style, 'fontSource', null);
5736
5737			if (url != null)
5738			{
5739				var name = mxUtils.getValue(style, mxConstants.STYLE_FONTFAMILY, null);
5740
5741				if (name != null)
5742				{
5743					Graph.addFont(name, decodeURIComponent(url));
5744				}
5745			}
5746		}
5747
5748		return style;
5749	};
5750
5751	/**
5752	 * Changes the default stylename so that it matches the old named style
5753	 * if one was specified in the XML.
5754	 */
5755	Graph.prototype.defaultThemeName = 'default-style2';
5756
5757	/**
5758	 * Contains the last XML that was pasted.
5759	 */
5760	Graph.prototype.lastPasteXml = null;
5761
5762	/**
5763	 * Contains the number of times the last XML was pasted.
5764	 */
5765	Graph.prototype.pasteCounter = 0;
5766
5767	/**
5768	 * Graph Overrides
5769	 */
5770	Graph.prototype.defaultScrollbars = urlParams['sb'] != '0';
5771
5772	/**
5773	 * Specifies if the page should be visible for new files. Default is true.
5774	 */
5775	Graph.prototype.defaultPageVisible = urlParams['pv'] != '0';
5776
5777	/**
5778	 * Specifies if the page should be visible for new files. Default is true.
5779	 */
5780	Graph.prototype.shadowId = 'dropShadow';
5781
5782	/**
5783	 * Properties for the SVG shadow effect.
5784	 */
5785	Graph.prototype.svgShadowColor = '#3D4574';
5786
5787	/**
5788	 * Properties for the SVG shadow effect.
5789	 */
5790	Graph.prototype.svgShadowOpacity = '0.4';
5791
5792	/**
5793	 * Properties for the SVG shadow effect.
5794	 */
5795	Graph.prototype.svgShadowBlur = '1.7';
5796
5797	/**
5798	 * Properties for the SVG shadow effect.
5799	 */
5800	Graph.prototype.svgShadowSize = '3';
5801
5802	/**
5803	 * Enables move of bends/segments without selecting.
5804	 */
5805	Graph.prototype.edgeMode = urlParams['edge'] != 'move';
5806
5807	/**
5808	 * Enables move of bends/segments without selecting.
5809	 */
5810	Graph.prototype.hiddenTags = null;
5811
5812	/**
5813	 * Enables move of bends/segments without selecting.
5814	 */
5815	Graph.prototype.defaultMathEnabled = false;
5816
5817	/**
5818	 * Adds rack child layout style.
5819	 */
5820	var graphInit = Graph.prototype.init;
5821
5822	Graph.prototype.init = function()
5823	{
5824		graphInit.apply(this, arguments);
5825
5826		// Array of hidden tags used in isCellVisible override
5827		this.hiddenTags = [];
5828
5829		//TODO initialize Freehand in the correct location!
5830		if (window.mxFreehand)
5831		{
5832			this.freehand = new mxFreehand(this);
5833		}
5834
5835		// Override insert location for current mouse point
5836		var mouseEvent = null;
5837
5838		function setMouseEvent(evt)
5839		{
5840			mouseEvent = evt;
5841		};
5842
5843		mxEvent.addListener(this.container, 'mouseenter', setMouseEvent);
5844		mxEvent.addListener(this.container, 'mousemove', setMouseEvent);
5845		mxEvent.addListener(this.container, 'mouseleave', function(evt)
5846		{
5847			mouseEvent = null;
5848		});
5849
5850		// Extends getInsertPoint to use the current mouse location
5851		this.isMouseInsertPoint = function()
5852		{
5853			return mouseEvent != null;
5854		};
5855
5856		var getInsertPoint = this.getInsertPoint;
5857
5858		this.getInsertPoint = function()
5859		{
5860			if (mouseEvent != null)
5861			{
5862				return this.getPointForEvent(mouseEvent);
5863			}
5864
5865			return getInsertPoint.apply(this, arguments);
5866		};
5867
5868		var layoutManagerGetLayout = this.layoutManager.getLayout;
5869
5870		this.layoutManager.getLayout = function(cell)
5871		{
5872			// Workaround for possible invalid style after change and before view validation
5873			var style = this.graph.getCellStyle(cell);
5874
5875			// mxRackContainer may be undefined as it is dynamically loaded at render time
5876			if (style != null)
5877			{
5878				if (style['childLayout'] == 'rack')
5879				{
5880					var rackLayout = new mxStackLayout(this.graph, false);
5881					var unitSize = 20;
5882
5883					if (style['rackUnitSize'] != null)
5884					{
5885						rackLayout.gridSize = parseFloat(style['rackUnitSize']);
5886					}
5887					else
5888					{
5889						rackLayout.gridSize = (typeof mxRackContainer !== 'undefined') ? mxRackContainer.unitSize : unitSize;
5890					}
5891
5892					rackLayout.marginLeft = style['marginLeft'] || 0;
5893					rackLayout.marginRight = style['marginRight'] || 0;
5894					rackLayout.marginTop = style['marginTop'] || 0;
5895					rackLayout.marginBottom = style['marginBottom'] || 0;
5896					rackLayout.allowGaps = style['allowGaps'] || 0;
5897					rackLayout.horizontal = mxUtils.getValue(style, 'horizontalRack', '0') == '1';
5898					rackLayout.resizeParent = false;
5899					rackLayout.fill = true;
5900
5901					return rackLayout;
5902				}
5903			}
5904
5905			return layoutManagerGetLayout.apply(this, arguments);
5906		}
5907
5908		this.updateGlobalUrlVariables();
5909	};
5910
5911	/**
5912	 * Adds support for custom fonts in cell styles.
5913	 */
5914	var graphPostProcessCellStyle = Graph.prototype.postProcessCellStyle;
5915	Graph.prototype.postProcessCellStyle = function(style)
5916	{
5917		this.replaceDefaultColors(style);
5918
5919		return Graph.processFontStyle(graphPostProcessCellStyle.apply(this, arguments));
5920	};
5921
5922	/**
5923	 * Replaces default colors.
5924	 */
5925	Graph.prototype.replaceDefaultColors = function(style)
5926	{
5927		if (style != null)
5928		{
5929			var bg = mxUtils.hex2rgba(this.shapeBackgroundColor);
5930			var fg = mxUtils.hex2rgba(this.shapeForegroundColor);
5931
5932			this.replaceDefaultColor(style, mxConstants.STYLE_FONTCOLOR, fg);
5933			this.replaceDefaultColor(style, mxConstants.STYLE_FILLCOLOR, bg);
5934			this.replaceDefaultColor(style, mxConstants.STYLE_STROKECOLOR, fg);
5935			this.replaceDefaultColor(style, mxConstants.STYLE_IMAGE_BORDER, fg);
5936			this.replaceDefaultColor(style, mxConstants.STYLE_IMAGE_BACKGROUND, bg);
5937			this.replaceDefaultColor(style, mxConstants.STYLE_LABEL_BORDERCOLOR, fg);
5938			this.replaceDefaultColor(style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, bg);
5939			this.replaceDefaultColor(style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, bg);
5940		}
5941	};
5942
5943	/**
5944	 * Replaces the colors for the given key.
5945	 */
5946	Graph.prototype.replaceDefaultColor = function(style, key, value)
5947	{
5948		if (style != null && style[key] == 'default' && value != null)
5949		{
5950			style[key] = value;
5951		}
5952	};
5953
5954	/**
5955	 * Handles custom fonts in labels.
5956	 */
5957	var mxSvgCanvas2DUpdateTextNodes = mxSvgCanvas2D.prototype.updateTextNodes;
5958	mxSvgCanvas2D.prototype.updateTextNodes = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, g)
5959	{
5960		mxSvgCanvas2DUpdateTextNodes.apply(this, arguments);
5961		Graph.processFontAttributes(g);
5962	};
5963
5964	/**
5965	 * Handles custom fonts in labels.
5966	 */
5967	var mxTextRedraw = mxText.prototype.redraw;
5968	mxText.prototype.redraw = function()
5969	{
5970		mxTextRedraw.apply(this, arguments);
5971
5972		// Handles label rendered without foreign object
5973		if (this.node != null && this.node.nodeName == 'DIV')
5974		{
5975			Graph.processFontAttributes(this.node);
5976		}
5977	};
5978
5979	Graph.prototype.createTagsDialog = function(isEnabled, invert, addFn)
5980	{
5981		var graph = this;
5982		var allTags = graph.hiddenTags.slice();
5983
5984		var div = document.createElement('div');
5985		div.style.userSelect = 'none';
5986		div.style.overflow = 'hidden';
5987		div.style.padding = '10px';
5988		div.style.height = '100%';
5989
5990		var tagCloud = document.createElement('div');
5991		tagCloud.style.boxSizing = 'border-box';
5992		tagCloud.style.borderRadius = '4px';
5993		tagCloud.style.userSelect = 'none';
5994		tagCloud.style.overflow = 'auto';
5995		tagCloud.style.position = 'absolute';
5996		tagCloud.style.left = '10px';
5997		tagCloud.style.right = '10px';
5998		tagCloud.style.top = '10px';
5999		tagCloud.style.border = (graph.isEnabled()) ? '1px solid #808080' : 'none';
6000		tagCloud.style.bottom = (graph.isEnabled()) ? '48px' : '10px';
6001
6002		div.appendChild(tagCloud);
6003
6004		function removeInvisibleSelectionCells()
6005		{
6006			var cells = graph.getSelectionCells();
6007			var visible = [];
6008
6009			for (var i = 0; i < cells.length; i++)
6010			{
6011				if (graph.isCellVisible(cells[i]))
6012				{
6013					visible.push(cells[i]);
6014				}
6015			}
6016
6017			graph.setSelectionCells(visible);
6018		};
6019
6020		function setAllVisible(visible)
6021		{
6022			if (visible)
6023			{
6024				graph.hiddenTags = [];
6025			}
6026			else
6027			{
6028				graph.hiddenTags = allTags.slice();
6029			}
6030
6031			removeInvisibleSelectionCells();
6032			graph.refresh();
6033		};
6034
6035		var resetBtn = mxUtils.button(mxResources.get('reset'), function(evt)
6036		{
6037			graph.hiddenTags = [];
6038
6039			if (!mxEvent.isShiftDown(evt))
6040			{
6041				allTags = graph.hiddenTags.slice();
6042			}
6043
6044			removeInvisibleSelectionCells();
6045			graph.refresh();
6046		});
6047
6048		resetBtn.setAttribute('title', mxResources.get('reset'));
6049		resetBtn.className = 'geBtn';
6050		resetBtn.style.margin = '0 4px 0 0';
6051
6052		var addBtn = mxUtils.button(mxResources.get('add'), function()
6053		{
6054			if (addFn != null)
6055			{
6056				// Takes all tags and callback to update all tags
6057				addFn(allTags, function(newAllTags)
6058				{
6059					allTags = newAllTags;
6060					refreshUi();
6061				});
6062			}
6063		});
6064		addBtn.setAttribute('title', mxResources.get('add'));
6065		addBtn.className = 'geBtn';
6066		addBtn.style.margin = '0';
6067
6068		graph.addListener(mxEvent.ROOT, function()
6069		{
6070			allTags = graph.hiddenTags.slice();
6071		});
6072
6073		function refreshTags(tags, selected)
6074		{
6075			tagCloud.innerHTML = '';
6076
6077			if (tags.length > 0)
6078			{
6079				var table = document.createElement('table');
6080				table.setAttribute('cellpadding', '2');
6081				table.style.boxSizing = 'border-box';
6082				table.style.tableLayout = 'fixed';
6083				table.style.width = '100%';
6084
6085				var tbody = document.createElement('tbody');
6086
6087				if (tags != null && tags.length > 0)
6088				{
6089					for (var i = 0; i < tags.length; i++)
6090					{
6091						(function(tag)
6092						{
6093							function setTagVisible()
6094							{
6095								var temp = allTags.slice();
6096								var index = mxUtils.indexOf(temp, tag);
6097								temp.splice(index, 1);
6098								graph.hiddenTags = temp;
6099								removeInvisibleSelectionCells();
6100								graph.refresh();
6101							};
6102
6103							function selectCells()
6104							{
6105								var cells = graph.getCellsForTags(
6106									[tag], null, null, true);
6107
6108								if (graph.isEnabled())
6109								{
6110									graph.setSelectionCells(cells);
6111								}
6112								else
6113								{
6114									graph.highlightCells(cells);
6115								}
6116							};
6117
6118							var visible = mxUtils.indexOf(graph.hiddenTags, tag) < 0;
6119							var row = document.createElement('tr');
6120							var td = document.createElement('td');
6121							td.style.align = 'center';
6122							td.style.width = '16px';
6123
6124							var img = document.createElement('img');
6125							img.setAttribute('src', visible ? Editor.visibleImage : Editor.hiddenImage);
6126							img.setAttribute('title', mxResources.get(visible ? 'hideIt' : 'show', [tag]));
6127							mxUtils.setOpacity(img, visible ? 75 : 25);
6128							img.style.verticalAlign = 'middle';
6129							img.style.cursor = 'pointer';
6130							img.style.width = '16px';
6131
6132							if (invert || Editor.isDarkMode())
6133							{
6134								img.style.filter = 'invert(100%)';
6135							}
6136
6137							td.appendChild(img);
6138
6139							mxEvent.addListener(img, 'click', function(evt)
6140							{
6141								var idx = mxUtils.indexOf(graph.hiddenTags, tag);
6142
6143								if (mxEvent.isShiftDown(evt))
6144								{
6145									setAllVisible(mxUtils.indexOf(graph.hiddenTags, tag) >= 0);
6146								}
6147								else
6148								{
6149									if (idx < 0)
6150									{
6151										graph.hiddenTags.push(tag);
6152									}
6153									else if (idx >= 0)
6154									{
6155										graph.hiddenTags.splice(idx, 1);
6156									}
6157
6158									removeInvisibleSelectionCells();
6159									graph.refresh();
6160								}
6161
6162								mxEvent.consume(evt);
6163							});
6164
6165							row.appendChild(td);
6166
6167							td = document.createElement('td');
6168							td.style.overflow = 'hidden';
6169							td.style.whiteSpace = 'nowrap';
6170							td.style.textOverflow = 'ellipsis';
6171							td.style.verticalAlign = 'middle';
6172							td.style.cursor = 'pointer';
6173							td.setAttribute('title', tag);
6174
6175							a = document.createElement('a');
6176							mxUtils.write(a, tag);
6177							a.style.textOverflow = 'ellipsis';
6178							a.style.position = 'relative';
6179							mxUtils.setOpacity(a, visible ? 100 : 40);
6180							td.appendChild(a);
6181
6182							mxEvent.addListener(td, 'click', (function(evt)
6183							{
6184								if (mxEvent.isShiftDown(evt))
6185								{
6186									setAllVisible(true);
6187									selectCells();
6188								}
6189								else
6190								{
6191									if (visible && graph.hiddenTags.length > 0)
6192									{
6193										setAllVisible(true);
6194									}
6195									else
6196									{
6197										setTagVisible();
6198									}
6199								}
6200
6201								mxEvent.consume(evt);
6202							}));
6203
6204							row.appendChild(td);
6205
6206							if (graph.isEnabled())
6207							{
6208								td = document.createElement('td');
6209								td.style.verticalAlign = 'middle';
6210								td.style.textAlign = 'center';
6211								td.style.width = '18px';
6212
6213								if (selected == null)
6214								{
6215									td.style.align = 'center';
6216									td.style.width = '16px';
6217
6218									var img = document.createElement('img');
6219									img.setAttribute('src', Editor.crossImage);
6220									img.setAttribute('title', mxResources.get('removeIt', [tag]));
6221									mxUtils.setOpacity(img, visible ? 75 : 25);
6222									img.style.verticalAlign = 'middle';
6223									img.style.cursor = 'pointer';
6224									img.style.width = '16px';
6225
6226									if (invert || Editor.isDarkMode())
6227									{
6228										img.style.filter = 'invert(100%)';
6229									}
6230
6231									mxEvent.addListener(img, 'click', function(evt)
6232									{
6233										var idx = mxUtils.indexOf(allTags, tag);
6234
6235										if (idx >= 0)
6236										{
6237											allTags.splice(idx, 1);
6238										}
6239
6240										graph.removeTagsForCells(
6241											graph.model.getDescendants(
6242											graph.model.getRoot()), [tag]);
6243										graph.refresh();
6244
6245										mxEvent.consume(evt);
6246									});
6247
6248									td.appendChild(img);
6249								}
6250								else
6251								{
6252									var cb2 = document.createElement('input');
6253									cb2.setAttribute('type', 'checkbox');
6254									cb2.style.margin = '0px';
6255
6256									cb2.defaultChecked = (selected != null &&
6257										mxUtils.indexOf(selected, tag) >= 0);
6258									cb2.checked = cb2.defaultChecked;
6259									cb2.style.background = 'transparent';
6260									cb2.setAttribute('title', mxResources.get(
6261										cb2.defaultChecked ?
6262										'removeIt' : 'add', [tag]));
6263
6264									mxEvent.addListener(cb2, 'change', function(evt)
6265									{
6266										if (cb2.checked)
6267										{
6268											graph.addTagsForCells(graph.getSelectionCells(), [tag]);
6269										}
6270										else
6271										{
6272											graph.removeTagsForCells(graph.getSelectionCells(), [tag]);
6273										}
6274
6275										mxEvent.consume(evt);
6276									});
6277
6278									td.appendChild(cb2);
6279								}
6280
6281								row.appendChild(td);
6282							}
6283
6284							tbody.appendChild(row);
6285						})(tags[i]);
6286					}
6287				}
6288
6289				table.appendChild(tbody);
6290				tagCloud.appendChild(table);
6291			}
6292		};
6293
6294		var refreshUi = mxUtils.bind(this, function(sender, evt)
6295		{
6296			if (isEnabled())
6297			{
6298				var tags = graph.getAllTags();
6299
6300				for (var i = 0; i < tags.length; i++)
6301				{
6302					if (mxUtils.indexOf(allTags, tags[i]) < 0)
6303					{
6304						allTags.push(tags[i]);
6305					}
6306				}
6307
6308				allTags.sort();
6309
6310				if (graph.isSelectionEmpty())
6311				{
6312					refreshTags(allTags);
6313				}
6314				else
6315				{
6316					refreshTags(allTags, graph.getCommonTagsForCells(
6317						graph.getSelectionCells()));
6318				}
6319			}
6320		});
6321
6322		graph.selectionModel.addListener(mxEvent.CHANGE, refreshUi);
6323		graph.model.addListener(mxEvent.CHANGE, refreshUi);
6324		graph.addListener(mxEvent.REFRESH, refreshUi);
6325
6326		var footer = document.createElement('div');
6327		footer.style.boxSizing = 'border-box';
6328		footer.style.whiteSpace = 'nowrap';
6329		footer.style.position = 'absolute';
6330		footer.style.overflow = 'hidden';
6331		footer.style.bottom = '0px';
6332		footer.style.height = '42px';
6333		footer.style.right = '10px';
6334		footer.style.left = '10px';
6335
6336		if (graph.isEnabled())
6337		{
6338			footer.appendChild(resetBtn);
6339			footer.appendChild(addBtn);
6340			div.appendChild(footer);
6341		}
6342
6343		return {div: div, refresh: refreshUi};
6344	};
6345
6346	/**
6347	 * Returns all custom fonts (old and new).
6348	 */
6349	Graph.prototype.getCustomFonts = function()
6350	{
6351		var fonts = this.extFonts;
6352
6353		if (fonts != null)
6354		{
6355			fonts = fonts.slice();
6356		}
6357		else
6358		{
6359			fonts = [];
6360		}
6361
6362		for (var key in Graph.customFontElements)
6363		{
6364			var font = Graph.customFontElements[key];
6365			fonts.push({name: font.name, url: font.url});
6366		}
6367
6368		return fonts;
6369	};
6370
6371	/**
6372	 * Assigns the given custom font to the selected text.
6373	 */
6374	Graph.prototype.setFont = function(name, url)
6375	{
6376		// Adds the font element to the document
6377		Graph.addFont(name, url);
6378
6379		// Only valid known fonts are allowed as parameters so we set
6380		// the real font name and the data-source-face in the element
6381		// which is used as the face attribute when editing stops
6382		// KNOWN: Undo for the DOM change is not working
6383		document.execCommand('fontname', false, name);
6384
6385		// Finds element with new font name and checks its data-font-src attribute
6386		if (url != null)
6387		{
6388			var fonts = this.cellEditor.textarea.getElementsByTagName('font');
6389
6390			// Enforces consistent font naming
6391			url = Graph.getFontUrl(name, url);
6392
6393			for (var i = 0; i < fonts.length; i++)
6394			{
6395				if (fonts[i].getAttribute('face') == name)
6396				{
6397					if (fonts[i].getAttribute('data-font-src') != url)
6398					{
6399						fonts[i].setAttribute('data-font-src', url);
6400					}
6401				}
6402			}
6403		}
6404	};
6405
6406	/**
6407	 * Disables fast zoom with shadow in lightbox for Safari
6408	 * to work around blank output on retina screen.
6409	 */
6410	var graphIsFastZoomEnabled = Graph.prototype.isFastZoomEnabled;
6411
6412	Graph.prototype.isFastZoomEnabled = function()
6413	{
6414		return graphIsFastZoomEnabled.apply(this, arguments) && (!this.shadowVisible || !mxClient.IS_SF);
6415	};
6416
6417	/**
6418	 * Updates the global variables from the vars URL parameter.
6419	 */
6420	Graph.prototype.updateGlobalUrlVariables = function()
6421	{
6422		this.globalVars = Editor.globalVars;
6423
6424		if (urlParams['vars'] != null)
6425		{
6426			try
6427			{
6428				this.globalVars = (this.globalVars != null) ? mxUtils.clone(this.globalVars) : {};
6429				var vars = JSON.parse(decodeURIComponent(urlParams['vars']));
6430
6431				if (vars != null)
6432				{
6433					for (var key in vars)
6434					{
6435						this.globalVars[key] = vars[key];
6436					}
6437				}
6438			}
6439			catch (e)
6440			{
6441				if (window.console != null)
6442				{
6443					console.log('Error in vars URL parameter: ' + e);
6444				}
6445			}
6446		}
6447	};
6448
6449	/**
6450	 * Returns all global variables used for export. This function never returns null.
6451	 * This can be overridden by plugins to return global variables for export.
6452	 */
6453	Graph.prototype.getExportVariables = function()
6454	{
6455		return (this.globalVars != null) ? mxUtils.clone(this.globalVars) : {};
6456	};
6457
6458	/**
6459	 * Adds support for vars URL parameter.
6460	 */
6461	var graphGetGlobalVariable = Graph.prototype.getGlobalVariable;
6462
6463	Graph.prototype.getGlobalVariable = function(name)
6464	{
6465		var val = graphGetGlobalVariable.apply(this, arguments);
6466
6467		if (val == null && this.globalVars != null)
6468		{
6469			val = this.globalVars[name];
6470		}
6471
6472		return val;
6473	};
6474
6475	/**
6476	 * Cached default stylesheet for image export in dark mode.
6477	 */
6478	Graph.prototype.getDefaultStylesheet = function()
6479	{
6480		if (this.defaultStylesheet == null)
6481		{
6482			var node = this.themes['default-style2'];
6483			var dec = new mxCodec(node.ownerDocument);
6484			this.defaultStylesheet = dec.decode(node);
6485		}
6486
6487		return this.defaultStylesheet;
6488	};
6489
6490	/**
6491	 * Overiddes function to use url parameter
6492	 */
6493	Graph.prototype.isViewer = function()
6494	{
6495		return urlParams['viewer'];
6496	};
6497
6498	/**
6499	 * Temporarily overrides stylesheet during image export in dark mode.
6500	 */
6501	var graphGetSvg = Graph.prototype.getSvg;
6502
6503	Graph.prototype.getSvg = function(background, scale, border, nocrop, crisp,
6504		ignoreSelection, showText, imgExport, linkTarget, hasShadow,
6505		incExtFonts, keepTheme, exportType, cells)
6506	{
6507		var temp = null;
6508		var tempFg = null;
6509		var tempBg = null;
6510
6511		if (!keepTheme && this.themes != null && this.defaultThemeName == 'darkTheme')
6512		{
6513			temp = this.stylesheet;
6514			tempFg = this.shapeForegroundColor;
6515			tempBg = this.shapeBackgroundColor;
6516			this.shapeForegroundColor = (this.defaultThemeName == 'darkTheme') ?
6517				'#000000' : Editor.lightColor;
6518			this.shapeBackgroundColor = (this.defaultThemeName == 'darkTheme') ?
6519				'#ffffff' : Editor.darkColor;
6520			this.stylesheet = this.getDefaultStylesheet();
6521			// LATER: Fix math export in dark mode by fetching text nodes before
6522			// calling refresh and changing the font color in-place
6523			this.refresh();
6524		}
6525
6526		var result = graphGetSvg.apply(this, arguments);
6527		var extFonts = this.getCustomFonts();
6528
6529		// Adds external fonts
6530		if (incExtFonts && extFonts.length > 0)
6531		{
6532			var svgDoc = result.ownerDocument;
6533			var style = (svgDoc.createElementNS != null) ?
6534		    	svgDoc.createElementNS(mxConstants.NS_SVG, 'style') : svgDoc.createElement('style');
6535			svgDoc.setAttributeNS != null? style.setAttributeNS('type', 'text/css') : style.setAttribute('type', 'text/css');
6536
6537			var prefix = '';
6538			var postfix = '';
6539
6540			for (var i = 0; i < extFonts.length; i++)
6541			{
6542				var fontName = extFonts[i].name, fontUrl = extFonts[i].url;
6543
6544				if (Graph.isCssFontUrl(fontUrl))
6545				{
6546					prefix += '@import url(' + fontUrl + ');\n';
6547				}
6548				else
6549				{
6550					postfix += '@font-face {\n' +
6551			            'font-family: "' + fontName + '";\n' +
6552			            'src: url("' + fontUrl + '");\n}\n';
6553				}
6554			}
6555
6556			style.appendChild(svgDoc.createTextNode(prefix + postfix));
6557			result.getElementsByTagName('defs')[0].appendChild(style);
6558		}
6559
6560		if (temp != null)
6561		{
6562			this.shapeBackgroundColor = tempBg;
6563			this.shapeForegroundColor = tempFg;
6564			this.stylesheet = temp;
6565			this.refresh();
6566		}
6567
6568		return result;
6569	};
6570
6571	/**
6572	 * Overridden to support client-side math typesetting.
6573	 */
6574	var graphCreateSvgImageExport = Graph.prototype.createSvgImageExport;
6575
6576	Graph.prototype.createSvgImageExport = function()
6577	{
6578		var imgExport = graphCreateSvgImageExport.apply(this, arguments);
6579
6580		if (this.mathEnabled)
6581		{
6582			var drawText = imgExport.drawText;
6583
6584			// Replaces input with rendered markup
6585			imgExport.drawText = function(state, c)
6586			{
6587				if (state.text != null && state.text.value != null && state.text.checkBounds() &&
6588					(mxUtils.isNode(state.text.value) || state.text.dialect == mxConstants.DIALECT_STRICTHTML))
6589				{
6590					var clone = state.text.getContentNode();
6591
6592					if (clone != null)
6593					{
6594						clone = clone.cloneNode(true);
6595
6596						// Removes duplicate math output
6597						if (clone.getElementsByTagNameNS)
6598						{
6599							var ele = clone.getElementsByTagNameNS('http://www.w3.org/1998/Math/MathML', 'math');
6600
6601							while (ele.length > 0)
6602							{
6603								ele[0].parentNode.removeChild(ele[0]);
6604							}
6605						}
6606
6607						if (clone.innerHTML != null)
6608						{
6609							var prev = state.text.value;
6610							state.text.value = clone.innerHTML;
6611							drawText.apply(this, arguments);
6612							state.text.value = prev;
6613						}
6614					}
6615				}
6616				else
6617				{
6618					drawText.apply(this, arguments);
6619				}
6620			};
6621		}
6622
6623		return imgExport;
6624	};
6625
6626	/**
6627	 * Adds workaround for math rendering in Chrome.
6628	 *
6629	 * Workaround for https://bugs.webkit.org/show_bug.cgi?id=93358 in WebKit
6630	 *
6631	 * Adding an absolute position DIV before the SVG seems to mitigate the problem.
6632	 */
6633	var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage;
6634
6635	mxGraphView.prototype.validateBackgroundPage = function()
6636	{
6637		graphViewValidateBackgroundPage.apply(this, arguments);
6638
6639		if (mxClient.IS_GC && this.getDrawPane() != null)
6640		{
6641			var g = this.getDrawPane().parentNode;
6642
6643			if (this.graph.mathEnabled && !mxClient.NO_FO &&
6644				(this.webKitForceRepaintNode == null ||
6645				this.webKitForceRepaintNode.parentNode == null) &&
6646				this.graph.container.firstChild.nodeName == 'svg')
6647			{
6648				this.webKitForceRepaintNode = document.createElement('div');
6649				this.webKitForceRepaintNode.style.cssText = 'position:absolute;';
6650				g.ownerSVGElement.parentNode.insertBefore(this.webKitForceRepaintNode, g.ownerSVGElement);
6651			}
6652			else if (this.webKitForceRepaintNode != null && (!this.graph.mathEnabled ||
6653					(this.graph.container.firstChild.nodeName != 'svg' &&
6654					this.graph.container.firstChild != this.webKitForceRepaintNode)))
6655			{
6656				if (this.webKitForceRepaintNode.parentNode != null)
6657				{
6658					this.webKitForceRepaintNode.parentNode.removeChild(this.webKitForceRepaintNode);
6659				}
6660
6661				this.webKitForceRepaintNode = null;
6662			}
6663		}
6664	};
6665
6666	/**
6667	 * Updates the SVG for the background image if it references another page.
6668	 */
6669	var graphRefresh = Graph.prototype.refresh;
6670	Graph.prototype.refresh = function()
6671	{
6672		graphRefresh.apply(this, arguments);
6673		this.refreshBackgroundImage();
6674	};
6675
6676	/**
6677	 * Updates the SVG for the background image if it references another page.
6678	 */
6679	Graph.prototype.refreshBackgroundImage = function()
6680	{
6681		if (this.backgroundImage != null && this.backgroundImage.originalSrc != null)
6682		{
6683			this.setBackgroundImage(this.backgroundImage);
6684			this.view.validateBackgroundImage();
6685		}
6686	};
6687
6688	/**
6689	 * Sets default style (used in editor.get/setGraphXml below)
6690	 */
6691	var graphLoadStylesheet = Graph.prototype.loadStylesheet;
6692
6693	Graph.prototype.loadStylesheet = function()
6694	{
6695		graphLoadStylesheet.apply(this, arguments);
6696		this.currentStyle = 'default-style2';
6697	};
6698
6699	/**
6700	 * Adds support for data:action/json,{"actions":[actions]} where actions is
6701	 * a comma-separated list of JSON objects.
6702	 *
6703	 * An example action is:
6704	 *
6705	 * data:action/json,{"actions":[{"toggle": {"cells": ["3", "4"]}}]}
6706	 *
6707	 * This toggles the visible state of the cells with ID 3 and 4.
6708	 */
6709	Graph.prototype.handleCustomLink = function(href)
6710	{
6711		if (href.substring(0, 17) == 'data:action/json,')
6712		{
6713			var link = JSON.parse(href.substring(17));
6714
6715			if (link.actions != null)
6716			{
6717				this.executeCustomActions(link.actions);
6718			}
6719		}
6720	};
6721
6722	/**
6723	 * Runs the given actions and invokes done when all actions have been executed.
6724	 * When adding new actions that reference cell IDs support for updating
6725	 * those cell IDs must be handled in Graph.updateCustomLinkActions
6726	 */
6727	Graph.prototype.executeCustomActions = function(actions, done)
6728	{
6729		if (!this.executingCustomActions)
6730		{
6731			this.executingCustomActions = true;
6732			var updatingModel = false;
6733			var waitCounter = 0;
6734			var index = 0;
6735
6736			var beginUpdate = mxUtils.bind(this, function()
6737			{
6738				if (!updatingModel)
6739				{
6740					updatingModel = true;
6741					this.model.beginUpdate();
6742				}
6743			});
6744
6745			var endUpdate = mxUtils.bind(this, function()
6746			{
6747				if (updatingModel)
6748				{
6749					updatingModel = false;
6750					this.model.endUpdate();
6751				}
6752			});
6753
6754			var waitAndExecute = mxUtils.bind(this, function()
6755			{
6756				if (waitCounter > 0)
6757				{
6758					waitCounter--;
6759				}
6760
6761				if (waitCounter == 0)
6762				{
6763					executeNextAction()
6764				}
6765			});
6766
6767			var executeNextAction = mxUtils.bind(this, function()
6768			{
6769				if (index < actions.length)
6770				{
6771					var stop = this.stoppingCustomActions;
6772					var action = actions[index++];
6773					var animations = [];
6774
6775					// Executes open actions before starting transaction
6776					if (action.open != null)
6777					{
6778						endUpdate();
6779
6780						if (this.isCustomLink(action.open))
6781						{
6782							if (!this.customLinkClicked(action.open))
6783							{
6784								return;
6785							}
6786						}
6787						else
6788						{
6789							this.openLink(action.open);
6790						}
6791					}
6792
6793					if (action.wait != null && !stop)
6794					{
6795						this.pendingExecuteNextAction = mxUtils.bind(this, function()
6796						{
6797							this.pendingExecuteNextAction = null;
6798							this.pendingWaitThread = null;
6799							waitAndExecute();
6800						});
6801
6802						waitCounter++;
6803						this.pendingWaitThread = window.setTimeout(this.pendingExecuteNextAction,
6804							(action.wait != '') ? parseInt(action.wait) : 1000);
6805						endUpdate();
6806					}
6807
6808					if (action.opacity != null && action.opacity.value != null)
6809					{
6810						Graph.setOpacityForNodes(this.getNodesForCells(
6811							this.getCellsForAction(action.opacity, true)),
6812							action.opacity.value);
6813					}
6814
6815					if (action.fadeIn != null)
6816					{
6817						waitCounter++;
6818						Graph.fadeNodes(this.getNodesForCells(
6819							this.getCellsForAction(action.fadeIn, true)),
6820							0, 1, waitAndExecute, (stop) ?
6821							0 : action.fadeIn.delay);
6822					}
6823
6824					if (action.fadeOut != null)
6825					{
6826						waitCounter++;
6827						Graph.fadeNodes(this.getNodesForCells(
6828							this.getCellsForAction(action.fadeOut, true)),
6829							1, 0, waitAndExecute, (stop) ?
6830							0 : action.fadeOut.delay);
6831					}
6832
6833					if (action.wipeIn != null)
6834					{
6835						animations = animations.concat(this.createWipeAnimations(
6836							this.getCellsForAction(action.wipeIn, true), true));
6837					}
6838
6839					if (action.wipeOut != null)
6840					{
6841						animations = animations.concat(this.createWipeAnimations(
6842							this.getCellsForAction(action.wipeOut, true), false));
6843					}
6844
6845					// Executes all actions that change cell states
6846					if (action.toggle != null)
6847					{
6848						beginUpdate();
6849						this.toggleCells(this.getCellsForAction(action.toggle, true));
6850					}
6851
6852					if (action.show != null)
6853					{
6854						beginUpdate();
6855						var temp = this.getCellsForAction(action.show, true);
6856						Graph.setOpacityForNodes(this.getNodesForCells(temp), 1);
6857						this.setCellsVisible(temp, true);
6858					}
6859
6860					if (action.hide != null)
6861					{
6862						beginUpdate();
6863						var temp = this.getCellsForAction(action.hide, true);
6864						Graph.setOpacityForNodes(this.getNodesForCells(temp), 0);
6865						this.setCellsVisible(temp, false);
6866					}
6867
6868					if (action.toggleStyle != null && action.toggleStyle.key != null)
6869					{
6870						beginUpdate();
6871						this.toggleCellStyles(action.toggleStyle.key, (action.toggleStyle.defaultValue != null) ?
6872							action.toggleStyle.defaultValue : '0', this.getCellsForAction(action.toggleStyle, true));
6873					}
6874
6875					if (action.style != null && action.style.key != null)
6876					{
6877						beginUpdate();
6878						this.setCellStyles(action.style.key, action.style.value,
6879							this.getCellsForAction(action.style, true));
6880					}
6881
6882					// Executes stateless actions on cells
6883					var cells = [];
6884
6885					if (action.select != null && this.isEnabled())
6886					{
6887						cells = this.getCellsForAction(action.select);
6888						this.setSelectionCells(cells);
6889					}
6890
6891					if (action.highlight != null)
6892					{
6893						cells = this.getCellsForAction(action.highlight);
6894						this.highlightCells(cells, action.highlight.color,
6895							action.highlight.duration,
6896							action.highlight.opacity);
6897					}
6898
6899					if (action.scroll != null)
6900					{
6901						cells = this.getCellsForAction(action.scroll);
6902					}
6903
6904					if (action.viewbox != null)
6905					{
6906						this.fitWindow(action.viewbox, action.viewbox.border);
6907					}
6908
6909					if (cells.length > 0)
6910					{
6911						this.scrollCellToVisible(cells[0]);
6912					}
6913
6914					if (action.tags != null)
6915					{
6916						var hidden = [];
6917
6918						if (action.tags.hidden != null)
6919						{
6920							hidden = hidden.concat(action.tags.hidden);
6921						}
6922
6923						if (action.tags.visible != null)
6924						{
6925							var all = this.getAllTags();
6926
6927							for (var i = 0; i < all.length; i++)
6928							{
6929								if (mxUtils.indexOf(action.tags.visible, all[i]) < 0 &&
6930									mxUtils.indexOf(hidden, all[i]) < 0)
6931								{
6932									hidden.push(all[i]);
6933								}
6934							}
6935						}
6936
6937						this.hiddenTags = hidden;
6938						this.refresh();
6939					}
6940
6941					if (animations.length > 0)
6942					{
6943						waitCounter++;
6944						this.executeAnimations(animations, waitAndExecute,
6945							(stop) ? 1 : action.steps,
6946							(stop) ? 0 : action.delay);
6947					}
6948
6949					if (waitCounter == 0)
6950					{
6951						executeNextAction();
6952					}
6953					else
6954					{
6955						endUpdate();
6956					}
6957				}
6958				else
6959				{
6960					this.executingCustomActions = false;
6961					this.stoppingCustomActions = false;
6962					endUpdate();
6963
6964					if (done != null)
6965					{
6966						done();
6967					}
6968				}
6969			});
6970
6971			executeNextAction();
6972		}
6973		else
6974		{
6975			this.stoppingCustomActions = true;
6976
6977			if (this.pendingWaitThread != null)
6978			{
6979				window.clearTimeout(this.pendingWaitThread);
6980			}
6981
6982			if (this.pendingExecuteNextAction != null)
6983			{
6984				this.pendingExecuteNextAction();
6985			}
6986
6987			this.fireEvent(new mxEventObject('stopExecutingCustomActions'));
6988		}
6989	};
6990
6991	/**
6992	 * Updates cell IDs in custom links on the given cell and its label.
6993	 */
6994	Graph.prototype.doUpdateCustomLinksForCell = function(mapping, cell)
6995	{
6996		var href = this.getLinkForCell(cell);
6997
6998		if (href != null && href.substring(0, 17) == 'data:action/json,')
6999		{
7000			this.setLinkForCell(cell, this.updateCustomLink(mapping, href));
7001		}
7002
7003		if (this.isHtmlLabel(cell))
7004		{
7005			var temp = document.createElement('div');
7006			temp.innerHTML = this.sanitizeHtml(this.getLabel(cell));
7007			var links = temp.getElementsByTagName('a');
7008			var changed = false;
7009
7010			for (var i = 0; i < links.length; i++)
7011			{
7012				href = links[i].getAttribute('href');
7013
7014				if (href != null && href.substring(0, 17) == 'data:action/json,')
7015				{
7016					links[i].setAttribute('href', this.updateCustomLink(mapping, href));
7017					changed = true;
7018				}
7019			}
7020
7021			if (changed)
7022			{
7023				this.labelChanged(cell, temp.innerHTML);
7024			}
7025		}
7026	};
7027
7028	/**
7029	 * Updates cell IDs in the given custom link and returns the updated link.
7030	 */
7031	Graph.prototype.updateCustomLink = function(mapping, href)
7032	{
7033		if (href.substring(0, 17) == 'data:action/json,')
7034		{
7035			try
7036			{
7037				var link = JSON.parse(href.substring(17));
7038
7039				if (link.actions != null)
7040				{
7041					this.updateCustomLinkActions(mapping, link.actions);
7042					href = 'data:action/json,' + JSON.stringify(link);
7043				}
7044			}
7045			catch (e)
7046			{
7047				// Ignore
7048			}
7049		}
7050
7051		return href;
7052	};
7053
7054	/**
7055	 * Updates cell IDs in the given custom link actions.
7056	 */
7057	Graph.prototype.updateCustomLinkActions = function(mapping, actions)
7058	{
7059		for (var i = 0; i < actions.length; i++)
7060		{
7061			var action = actions[i];
7062
7063			for (var name in action)
7064			{
7065				this.updateCustomLinkAction(mapping, action[name], 'cells');
7066				this.updateCustomLinkAction(mapping, action[name], 'excludeCells');
7067			}
7068		}
7069	};
7070
7071	/**
7072	 * Updates cell IDs in the given custom link action.
7073	 */
7074	Graph.prototype.updateCustomLinkAction = function(mapping, action, name)
7075	{
7076		if (action != null && action[name] != null)
7077		{
7078			var result = [];
7079
7080			for (var i = 0; i < action[name].length; i++)
7081			{
7082				if (action[name][i] == '*')
7083				{
7084					result.push(action[name][i]);
7085				}
7086				else
7087				{
7088					var temp = mapping[action[name][i]];
7089
7090					if (temp != null)
7091					{
7092						if (temp != '')
7093						{
7094							result.push(temp);
7095						}
7096					}
7097					else
7098					{
7099						result.push(action[name][i]);
7100					}
7101				}
7102			}
7103
7104			action[name] = result;
7105		}
7106	};
7107
7108	/**
7109	 * Handles each action in the action array of a custom link. This code
7110	 * handles toggle actions for cell IDs.
7111	 */
7112	Graph.prototype.getCellsForAction = function(action, layers)
7113	{
7114		var result = this.getCellsById(action.cells).concat(
7115			this.getCellsForTags(action.tags, null, layers));
7116
7117		// Removes excluded cells
7118		if (action.excludeCells != null)
7119		{
7120			var temp = [];
7121
7122			for (var i = 0; i < result.length; i++)
7123			{
7124				if (action.excludeCells.indexOf(result[i].id) < 0)
7125				{
7126					temp.push(result[i]);
7127				}
7128			}
7129
7130			result = temp;
7131		}
7132
7133		return result;
7134	};
7135
7136	/**
7137	 * Returns the cells in the model (or given array) that have all of the
7138	 * given tags in their tags property.
7139	 */
7140	Graph.prototype.getCellsById = function(ids)
7141	{
7142		var result = [];
7143
7144		if (ids != null)
7145		{
7146			for (var i = 0; i < ids.length; i++)
7147			{
7148				if (ids[i] == '*')
7149				{
7150					var parent = this.model.getRoot();
7151
7152					result = result.concat(this.model.filterDescendants(function(cell)
7153					{
7154						return cell != parent;
7155					}, parent));
7156				}
7157				else
7158				{
7159					var cell = this.model.getCell(ids[i]);
7160
7161					if (cell != null)
7162					{
7163						result.push(cell);
7164					}
7165				}
7166			}
7167		}
7168
7169		return result;
7170	};
7171
7172	/**
7173	 * Adds support for custom fonts in cell styles.
7174	 */
7175	var graphIsCellVisible = Graph.prototype.isCellVisible;
7176	Graph.prototype.isCellVisible = function(cell)
7177	{
7178		return graphIsCellVisible.apply(this, arguments) &&
7179			!this.isAllTagsHidden(this.getTagsForCell(cell));
7180	};
7181
7182	/**
7183	 * Returns the cells in the model (or given array) that have all of the
7184	 * given tags in their tags property.
7185	 */
7186	Graph.prototype.isAllTagsHidden = function(tags)
7187	{
7188		if (tags == null || tags.length == 0 ||
7189			this.hiddenTags.length == 0)
7190		{
7191			return false;
7192		}
7193		else
7194		{
7195			var tmp = tags.split(' ');
7196
7197			if (tmp.length > this.hiddenTags.length)
7198			{
7199				return false;
7200			}
7201			else
7202			{
7203				for (var i = 0; i < tmp.length; i++)
7204				{
7205					if (mxUtils.indexOf(this.hiddenTags, tmp[i]) < 0)
7206					{
7207						return false;
7208					}
7209				}
7210
7211				return true;
7212			}
7213		}
7214	};
7215
7216	/**
7217	 * Returns the cells in the model (or given array) that have all of the
7218	 * given tags in their tags property.
7219	 */
7220	Graph.prototype.getCellsForTags = function(tagList, cells, includeLayers, checkVisible)
7221	{
7222		var result = [];
7223
7224		if (tagList != null)
7225		{
7226			cells = (cells != null) ? cells : this.model.getDescendants(this.model.getRoot());
7227
7228			var tagCount = 0;
7229			var lookup = {};
7230
7231			for (var i = 0; i < tagList.length; i++)
7232			{
7233				if (tagList[i].length > 0)
7234				{
7235					lookup[tagList[i]] = true;
7236					tagCount++;
7237				}
7238			}
7239
7240			for (var i = 0; i < cells.length; i++)
7241			{
7242				if ((includeLayers && this.model.getParent(cells[i]) == this.model.root) ||
7243					this.model.isVertex(cells[i]) || this.model.isEdge(cells[i]))
7244				{
7245					var tags = this.getTagsForCell(cells[i]);
7246					var match = false;
7247
7248					if (tags.length > 0)
7249					{
7250						var tmp = tags.split(' ');
7251
7252						if (tmp.length >= tagList.length)
7253						{
7254							var matchCount = 0;
7255
7256							for (var j = 0; j < tmp.length && (matchCount < tagCount); j++)
7257							{
7258								if (lookup[tmp[j]] != null)
7259								{
7260									matchCount++;
7261								}
7262							}
7263
7264							match = matchCount == tagCount;
7265						}
7266					}
7267
7268					if (match && ((checkVisible != true) || this.isCellVisible(cells[i])))
7269					{
7270						result.push(cells[i]);
7271					}
7272				}
7273			}
7274		}
7275
7276		return result;
7277	};
7278	/**
7279	 * Returns all tags in the diagram.
7280	 */
7281	Graph.prototype.getAllTags = function()
7282	{
7283		return this.getTagsForCells(
7284			this.model.getDescendants(
7285				this.model.getRoot()));
7286
7287	};
7288
7289	/**
7290	 * Returns the common tags for the given cells as a array.
7291	 */
7292	Graph.prototype.getCommonTagsForCells = function(cells)
7293	{
7294		var commonTokens = null;
7295		var validTags = [];
7296
7297		for (var i = 0; i < cells.length; i++)
7298		{
7299			var tags = this.getTagsForCell(cells[i]);
7300			validTags = [];
7301
7302			if (tags.length > 0)
7303			{
7304				var tokens = tags.split(' ');
7305				var temp = {};
7306
7307				for (var j = 0; j < tokens.length; j++)
7308				{
7309					if (commonTokens == null || commonTokens[tokens[j]] != null)
7310					{
7311						temp[tokens[j]] = true;
7312						validTags.push(tokens[j]);
7313					}
7314				}
7315
7316				commonTokens = temp;
7317			}
7318			else
7319			{
7320				return [];
7321			}
7322		}
7323
7324		return validTags;
7325	};
7326
7327	/**
7328	 * Returns all tags for the given cells as an array.
7329	 */
7330	Graph.prototype.getTagsForCells = function(cells)
7331	{
7332		var tokens = [];
7333		var temp = {};
7334
7335		for (var i = 0; i < cells.length; i++)
7336		{
7337			var tags = this.getTagsForCell(cells[i]);
7338
7339			if (tags.length > 0)
7340			{
7341				var t = tags.split(' ');
7342
7343				for (var j = 0; j < t.length; j++)
7344				{
7345					if (temp[t[j]] == null)
7346					{
7347						temp[t[j]] = true;
7348						tokens.push(t[j]);
7349					}
7350				}
7351			}
7352		}
7353
7354		return tokens;
7355	};
7356
7357	/**
7358	 * Returns the tags for the given cell as a string.
7359	 */
7360	Graph.prototype.getTagsForCell = function(cell)
7361	{
7362		return this.getAttributeForCell(cell, 'tags', '');
7363	};
7364
7365	/**
7366	 * Adds the given array of tags to the given array cells.
7367	 */
7368	Graph.prototype.addTagsForCells = function(cells, tagList)
7369	{
7370		if (cells.length > 0 && tagList.length > 0)
7371		{
7372			this.model.beginUpdate();
7373
7374			try
7375			{
7376				for (var i = 0; i < cells.length; i++)
7377				{
7378					var temp = this.getTagsForCell(cells[i]);
7379					var tags = temp.split(' ');
7380					var changed = false;
7381
7382					for (var j = 0; j < tagList.length; j++)
7383					{
7384						var tag = mxUtils.trim(tagList[j]);
7385
7386						if (tag != '' && mxUtils.indexOf(tags, tag) < 0)
7387						{
7388							temp = (temp.length > 0) ? temp + ' ' + tag : tag;
7389							changed = true;
7390						}
7391					}
7392
7393					if (changed)
7394					{
7395						this.setAttributeForCell(cells[i], 'tags', temp);
7396					}
7397				}
7398			}
7399			finally
7400			{
7401				this.model.endUpdate();
7402			}
7403		}
7404	};
7405
7406	/**
7407	 * Removes the given array of tags from the given array cells.
7408	 */
7409	Graph.prototype.removeTagsForCells = function(cells, tagList)
7410	{
7411		if (cells.length > 0 && tagList.length > 0)
7412		{
7413			this.model.beginUpdate();
7414
7415			try
7416			{
7417				for (var i = 0; i < cells.length; i++)
7418				{
7419					var tags = this.getTagsForCell(cells[i]);
7420
7421					if (tags.length > 0)
7422					{
7423						var tokens = tags.split(' ');
7424						var changed = false;
7425
7426						for (var j = 0; j < tagList.length; j++)
7427						{
7428							var idx = mxUtils.indexOf(tokens, tagList[j]);
7429
7430							if (idx >= 0)
7431							{
7432								tokens.splice(idx, 1);
7433								changed = true;
7434							}
7435						}
7436
7437						if (changed)
7438						{
7439							this.setAttributeForCell(cells[i], 'tags', tokens.join(' '));
7440						}
7441					}
7442				}
7443			}
7444			finally
7445			{
7446				this.model.endUpdate();
7447			}
7448		}
7449	};
7450
7451	/**
7452	 * Shows or hides the given cells.
7453	 */
7454	Graph.prototype.toggleCells = function(cells)
7455	{
7456		this.model.beginUpdate();
7457		try
7458		{
7459			for (var i = 0; i < cells.length; i++)
7460			{
7461				this.model.setVisible(cells[i], !this.model.isVisible(cells[i]))
7462			}
7463		}
7464		finally
7465		{
7466			this.model.endUpdate();
7467		}
7468	};
7469
7470	/**
7471	 * Shows or hides the given cells.
7472	 */
7473	Graph.prototype.setCellsVisible = function(cells, visible)
7474	{
7475		this.model.beginUpdate();
7476		try
7477		{
7478			for (var i = 0; i < cells.length; i++)
7479			{
7480				this.model.setVisible(cells[i], visible);
7481			}
7482		}
7483		finally
7484		{
7485			this.model.endUpdate();
7486		}
7487	};
7488
7489	/**
7490	 * Highlights the given cell.
7491	 */
7492	Graph.prototype.highlightCells = function(cells, color, duration, opacity)
7493	{
7494		for (var i = 0; i < cells.length; i++)
7495		{
7496			this.highlightCell(cells[i], color, duration, opacity);
7497		}
7498	};
7499
7500	/**
7501	 * Highlights the given cell.
7502	 */
7503	Graph.prototype.highlightCell = function(cell, color, duration, opacity)
7504	{
7505		color = (color != null) ? color : mxConstants.DEFAULT_VALID_COLOR;
7506		duration = (duration != null) ? duration : 1000;
7507		var state = this.view.getState(cell);
7508
7509		if (state != null)
7510		{
7511			var sw = Math.max(5, mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1) + 4);
7512			var hl = new mxCellHighlight(this, color, sw, false);
7513
7514			if (opacity != null)
7515			{
7516				hl.opacity = opacity;
7517			}
7518
7519			hl.highlight(state);
7520
7521			// Fades out the highlight after a duration
7522			window.setTimeout(function()
7523			{
7524				if (hl.shape != null)
7525				{
7526				 	mxUtils.setPrefixedStyle(hl.shape.node.style, 'transition', 'all 1200ms ease-in-out');
7527					hl.shape.node.style.opacity = 0;
7528				}
7529
7530				// Destroys the highlight after the fade
7531				window.setTimeout(function()
7532				{
7533					hl.destroy();
7534				}, 1200);
7535			}, duration);
7536		}
7537	};
7538
7539	/**
7540	 * Adds a shadow filter to the given svg root.
7541	 */
7542	Graph.prototype.addSvgShadow = function(svgRoot, group, createOnly, extend)
7543	{
7544		createOnly = (createOnly != null) ? createOnly : false;
7545		extend = (extend != null) ? extend : true;
7546
7547		var svgDoc = svgRoot.ownerDocument;
7548
7549		var filter = (svgDoc.createElementNS != null) ?
7550			svgDoc.createElementNS(mxConstants.NS_SVG, 'filter') : svgDoc.createElement('filter');
7551		filter.setAttribute('id', this.shadowId);
7552
7553		var blur = (svgDoc.createElementNS != null) ?
7554				svgDoc.createElementNS(mxConstants.NS_SVG, 'feGaussianBlur') : svgDoc.createElement('feGaussianBlur');
7555		blur.setAttribute('in', 'SourceAlpha');
7556		blur.setAttribute('stdDeviation', this.svgShadowBlur);
7557		blur.setAttribute('result', 'blur');
7558		filter.appendChild(blur);
7559
7560		var offset = (svgDoc.createElementNS != null) ?
7561				svgDoc.createElementNS(mxConstants.NS_SVG, 'feOffset') : svgDoc.createElement('feOffset');
7562		offset.setAttribute('in', 'blur');
7563		offset.setAttribute('dx', this.svgShadowSize);
7564		offset.setAttribute('dy', this.svgShadowSize);
7565		offset.setAttribute('result', 'offsetBlur');
7566		filter.appendChild(offset);
7567
7568		var flood = (svgDoc.createElementNS != null) ?
7569				svgDoc.createElementNS(mxConstants.NS_SVG, 'feFlood') : svgDoc.createElement('feFlood');
7570		flood.setAttribute('flood-color', this.svgShadowColor);
7571		flood.setAttribute('flood-opacity', this.svgShadowOpacity);
7572		flood.setAttribute('result', 'offsetColor');
7573		filter.appendChild(flood);
7574
7575		var composite = (svgDoc.createElementNS != null) ?
7576				svgDoc.createElementNS(mxConstants.NS_SVG, 'feComposite') : svgDoc.createElement('feComposite');
7577		composite.setAttribute('in', 'offsetColor');
7578		composite.setAttribute('in2', 'offsetBlur');
7579		composite.setAttribute('operator', 'in');
7580		composite.setAttribute('result', 'offsetBlur');
7581		filter.appendChild(composite);
7582
7583		var feBlend = (svgDoc.createElementNS != null) ?
7584				svgDoc.createElementNS(mxConstants.NS_SVG, 'feBlend') : svgDoc.createElement('feBlend');
7585		feBlend.setAttribute('in', 'SourceGraphic');
7586		feBlend.setAttribute('in2', 'offsetBlur');
7587		filter.appendChild(feBlend);
7588
7589		// Creates defs element if not available
7590		var defs = svgRoot.getElementsByTagName('defs');
7591		var defsElt = null;
7592
7593		if (defs.length == 0)
7594		{
7595			defsElt = (svgDoc.createElementNS != null) ?
7596				svgDoc.createElementNS(mxConstants.NS_SVG, 'defs') : svgDoc.createElement('defs');
7597
7598			if (svgRoot.firstChild != null)
7599			{
7600				svgRoot.insertBefore(defsElt, svgRoot.firstChild);
7601			}
7602			else
7603			{
7604				svgRoot.appendChild(defsElt);
7605			}
7606		}
7607		else
7608		{
7609			defsElt = defs[0];
7610		}
7611
7612		defsElt.appendChild(filter);
7613
7614		if (!createOnly)
7615		{
7616			group = (group != null) ? group : svgRoot.getElementsByTagName('g')[0];
7617
7618			if (group != null)
7619			{
7620				group.setAttribute('filter', 'url(#' + this.shadowId + ')');
7621
7622				if (!isNaN(parseInt(svgRoot.getAttribute('width'))) && extend)
7623				{
7624					svgRoot.setAttribute('width', parseInt(svgRoot.getAttribute('width')) + 6);
7625					svgRoot.setAttribute('height', parseInt(svgRoot.getAttribute('height')) + 6);
7626
7627					// Updates viewbox if one exists
7628					var vb = svgRoot.getAttribute('viewBox');
7629
7630					if (vb != null && vb.length > 0)
7631					{
7632						var tokens = vb.split(' ');
7633
7634						if (tokens.length > 3)
7635						{
7636							w = parseFloat(tokens[2]) + 6;
7637							h = parseFloat(tokens[3]) + 6;
7638
7639							svgRoot.setAttribute('viewBox', tokens[0] + ' ' + tokens[1] + ' ' + w + ' ' + h);
7640						}
7641					}
7642				}
7643			}
7644		}
7645
7646		return filter;
7647	};
7648
7649	/**
7650	 * Loads the stylesheet for this graph.
7651	 */
7652	Graph.prototype.setShadowVisible = function(value, fireEvent)
7653	{
7654		if (mxClient.IS_SVG && !mxClient.IS_SF)
7655		{
7656			fireEvent = (fireEvent != null) ? fireEvent : true;
7657			this.shadowVisible = value;
7658
7659			if (this.shadowVisible)
7660			{
7661				this.view.getDrawPane().setAttribute('filter', 'url(#' + this.shadowId + ')');
7662			}
7663			else
7664			{
7665				this.view.getDrawPane().removeAttribute('filter');
7666			}
7667
7668			if (fireEvent)
7669			{
7670				this.fireEvent(new mxEventObject('shadowVisibleChanged'));
7671			}
7672		}
7673	};
7674
7675	/**
7676	 * Selects first unlocked layer if one exists
7677	 */
7678	Graph.prototype.selectUnlockedLayer = function()
7679	{
7680		if (this.defaultParent == null)
7681		{
7682			var childCount = this.model.getChildCount(this.model.root);
7683			var cell = null;
7684			var index = 0;
7685
7686			do
7687			{
7688				cell = this.model.getChildAt(this.model.root, index);
7689			} while (index++ < childCount && mxUtils.getValue(this.getCellStyle(cell), 'locked', '0') == '1')
7690
7691			if (cell != null)
7692			{
7693				this.setDefaultParent(cell);
7694			}
7695		}
7696	};
7697
7698	/**
7699	 * Specifies special libraries that are loaded via dynamic JS. Add cases
7700	 * where the filename cannot be worked out from the package name. The
7701	 * standard scheme for this mapping is stencils/packagename.xml. If there
7702	 * are multiple XML files, any JS files or any anomalies in the filename or
7703	 * directory that contains the file, then an entry must be added here and
7704	 * in EmbedServlet2 for the loading of the shapes to work.
7705	 */
7706	// Required to avoid 404 for mockup.xml since naming of mxgraph.mockup.anchor does not contain
7707	// buttons even though it is defined in the mxMockupButtons.js file. This could only be fixed
7708	// with aliases for existing shapes or aliases for basenames, but this is essentially the same.
7709	mxStencilRegistry.libraries['mockup'] = [SHAPES_PATH + '/mockup/mxMockupButtons.js'];
7710
7711	mxStencilRegistry.libraries['arrows2'] = [SHAPES_PATH + '/mxArrows.js'];
7712	mxStencilRegistry.libraries['atlassian'] = [STENCIL_PATH + '/atlassian.xml', SHAPES_PATH + '/mxAtlassian.js'];
7713	mxStencilRegistry.libraries['bpmn'] = [SHAPES_PATH + '/mxBasic.js', STENCIL_PATH + '/bpmn.xml', SHAPES_PATH + '/bpmn/mxBpmnShape2.js'];
7714	mxStencilRegistry.libraries['bpmn2'] = [SHAPES_PATH + '/mxBasic.js', STENCIL_PATH + '/bpmn.xml', SHAPES_PATH + '/bpmn/mxBpmnShape2.js'];
7715	mxStencilRegistry.libraries['c4'] = [SHAPES_PATH + '/mxC4.js'];
7716	mxStencilRegistry.libraries['cisco19'] = [SHAPES_PATH + '/mxCisco19.js', STENCIL_PATH + '/cisco19.xml'];
7717	mxStencilRegistry.libraries['cisco_safe'] = [SHAPES_PATH + '/mxCiscoSafe.js', STENCIL_PATH + '/cisco_safe/architecture.xml', STENCIL_PATH + '/cisco_safe/business_icons.xml', STENCIL_PATH + '/cisco_safe/capability.xml', STENCIL_PATH + '/cisco_safe/design.xml', STENCIL_PATH + '/cisco_safe/iot_things_icons.xml', STENCIL_PATH + '/cisco_safe/people_places_things_icons.xml', STENCIL_PATH + '/cisco_safe/security_icons.xml', STENCIL_PATH + '/cisco_safe/technology_icons.xml', STENCIL_PATH + '/cisco_safe/threat.xml'];
7718	mxStencilRegistry.libraries['dfd'] = [SHAPES_PATH + '/mxDFD.js'];
7719	mxStencilRegistry.libraries['er'] = [SHAPES_PATH + '/er/mxER.js'];
7720	mxStencilRegistry.libraries['kubernetes'] = [SHAPES_PATH + '/mxKubernetes.js', STENCIL_PATH + '/kubernetes.xml'];
7721	mxStencilRegistry.libraries['flowchart'] = [SHAPES_PATH + '/mxFlowchart.js', STENCIL_PATH + '/flowchart.xml'];
7722	mxStencilRegistry.libraries['ios'] = [SHAPES_PATH + '/mockup/mxMockupiOS.js'];
7723	mxStencilRegistry.libraries['rackGeneral'] = [SHAPES_PATH + '/rack/mxRack.js', STENCIL_PATH + '/rack/general.xml'];
7724	mxStencilRegistry.libraries['rackF5'] = [STENCIL_PATH + '/rack/f5.xml'];
7725	mxStencilRegistry.libraries['lean_mapping'] = [SHAPES_PATH + '/mxLeanMap.js', STENCIL_PATH + '/lean_mapping.xml'];
7726	mxStencilRegistry.libraries['basic'] = [SHAPES_PATH + '/mxBasic.js', STENCIL_PATH + '/basic.xml'];
7727	mxStencilRegistry.libraries['ios7icons'] = [STENCIL_PATH + '/ios7/icons.xml'];
7728	mxStencilRegistry.libraries['ios7ui'] = [SHAPES_PATH + '/ios7/mxIOS7Ui.js', STENCIL_PATH + '/ios7/misc.xml'];
7729	mxStencilRegistry.libraries['android'] = [SHAPES_PATH + '/mxAndroid.js', STENCIL_PATH + '/android/android.xml'];
7730	mxStencilRegistry.libraries['electrical/abstract'] = [SHAPES_PATH + '/mxElectrical.js', STENCIL_PATH + '/electrical/abstract.xml'];
7731	mxStencilRegistry.libraries['electrical/logic_gates'] = [SHAPES_PATH + '/mxElectrical.js', STENCIL_PATH + '/electrical/logic_gates.xml'];
7732	mxStencilRegistry.libraries['electrical/miscellaneous'] = [SHAPES_PATH + '/mxElectrical.js', STENCIL_PATH + '/electrical/miscellaneous.xml'];
7733	mxStencilRegistry.libraries['electrical/signal_sources'] = [SHAPES_PATH + '/mxElectrical.js', STENCIL_PATH + '/electrical/signal_sources.xml'];
7734	mxStencilRegistry.libraries['electrical/transmission'] = [SHAPES_PATH + '/mxElectrical.js', STENCIL_PATH + '/electrical/transmission.xml'];
7735	mxStencilRegistry.libraries['infographic'] = [SHAPES_PATH + '/mxInfographic.js'];
7736	mxStencilRegistry.libraries['mockup/buttons'] = [SHAPES_PATH + '/mockup/mxMockupButtons.js'];
7737	mxStencilRegistry.libraries['mockup/containers'] = [SHAPES_PATH + '/mockup/mxMockupContainers.js'];
7738	mxStencilRegistry.libraries['mockup/forms'] = [SHAPES_PATH + '/mockup/mxMockupForms.js'];
7739	mxStencilRegistry.libraries['mockup/graphics'] = [SHAPES_PATH + '/mockup/mxMockupGraphics.js', STENCIL_PATH + '/mockup/misc.xml'];
7740	mxStencilRegistry.libraries['mockup/markup'] = [SHAPES_PATH + '/mockup/mxMockupMarkup.js'];
7741	mxStencilRegistry.libraries['mockup/misc'] = [SHAPES_PATH + '/mockup/mxMockupMisc.js', STENCIL_PATH + '/mockup/misc.xml'];
7742	mxStencilRegistry.libraries['mockup/navigation'] = [SHAPES_PATH + '/mockup/mxMockupNavigation.js', STENCIL_PATH + '/mockup/misc.xml'];
7743	mxStencilRegistry.libraries['mockup/text'] = [SHAPES_PATH + '/mockup/mxMockupText.js'];
7744	mxStencilRegistry.libraries['floorplan'] = [SHAPES_PATH + '/mxFloorplan.js', STENCIL_PATH + '/floorplan.xml'];
7745	mxStencilRegistry.libraries['bootstrap'] = [SHAPES_PATH + '/mxBootstrap.js', SHAPES_PATH + '/mxBasic.js', STENCIL_PATH + '/bootstrap.xml'];
7746	mxStencilRegistry.libraries['gmdl'] = [SHAPES_PATH + '/mxGmdl.js', STENCIL_PATH + '/gmdl.xml'];
7747	mxStencilRegistry.libraries['gcp2'] = [SHAPES_PATH + '/mxGCP2.js', STENCIL_PATH + '/gcp2.xml'];
7748	mxStencilRegistry.libraries['ibm'] = [SHAPES_PATH + '/mxIBM.js', STENCIL_PATH + '/ibm.xml'];
7749	mxStencilRegistry.libraries['cabinets'] = [SHAPES_PATH + '/mxCabinets.js', STENCIL_PATH + '/cabinets.xml'];
7750	mxStencilRegistry.libraries['archimate'] = [SHAPES_PATH + '/mxArchiMate.js'];
7751	mxStencilRegistry.libraries['archimate3'] = [SHAPES_PATH + '/mxArchiMate3.js'];
7752	mxStencilRegistry.libraries['sysml'] = [SHAPES_PATH + '/mxSysML.js'];
7753	mxStencilRegistry.libraries['eip'] = [SHAPES_PATH + '/mxEip.js', STENCIL_PATH + '/eip.xml'];
7754	mxStencilRegistry.libraries['networks'] = [SHAPES_PATH + '/mxNetworks.js', STENCIL_PATH + '/networks.xml'];
7755	mxStencilRegistry.libraries['aws3d'] = [SHAPES_PATH + '/mxAWS3D.js', STENCIL_PATH + '/aws3d.xml'];
7756	mxStencilRegistry.libraries['aws4'] = [SHAPES_PATH + '/mxAWS4.js', STENCIL_PATH + '/aws4.xml'];
7757	mxStencilRegistry.libraries['aws4b'] = [SHAPES_PATH + '/mxAWS4.js', STENCIL_PATH + '/aws4.xml'];
7758	mxStencilRegistry.libraries['uml25'] = [SHAPES_PATH + '/mxUML25.js'];
7759	mxStencilRegistry.libraries['veeam'] = [STENCIL_PATH + '/veeam/2d.xml', STENCIL_PATH + '/veeam/3d.xml', STENCIL_PATH + '/veeam/veeam.xml'];
7760	mxStencilRegistry.libraries['veeam2'] = [STENCIL_PATH + '/veeam/2d.xml', STENCIL_PATH + '/veeam/3d.xml', STENCIL_PATH + '/veeam/veeam2.xml'];
7761	mxStencilRegistry.libraries['pid2inst'] = [SHAPES_PATH + '/pid2/mxPidInstruments.js'];
7762	mxStencilRegistry.libraries['pid2misc'] = [SHAPES_PATH + '/pid2/mxPidMisc.js', STENCIL_PATH + '/pid/misc.xml'];
7763	mxStencilRegistry.libraries['pid2valves'] = [SHAPES_PATH + '/pid2/mxPidValves.js'];
7764	mxStencilRegistry.libraries['pidFlowSensors'] = [STENCIL_PATH + '/pid/flow_sensors.xml'];
7765
7766	// Triggers dynamic loading for markers
7767	mxMarker.getPackageForType = function(type)
7768	{
7769		var name = null;
7770
7771		if (type != null && type.length > 0)
7772		{
7773			if (type.substring(0, 2) == 'ER')
7774			{
7775				name = 'mxgraph.er';
7776			}
7777			else if (type.substring(0, 5) == 'sysML')
7778			{
7779				name = 'mxgraph.sysml';
7780			}
7781		}
7782
7783		return name;
7784	};
7785
7786	var mxMarkerCreateMarker = mxMarker.createMarker;
7787
7788	mxMarker.createMarker = function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
7789	{
7790		if (type != null)
7791		{
7792			var f = mxMarker.markers[type];
7793
7794			if (f == null)
7795			{
7796				var name = this.getPackageForType(type);
7797
7798				if (name != null)
7799				{
7800					mxStencilRegistry.getStencil(name);
7801				}
7802			}
7803		}
7804
7805		return mxMarkerCreateMarker.apply(this, arguments);
7806	};
7807
7808	/**
7809	 * Constructs a new print dialog.
7810	 */
7811	PrintDialog.prototype.create = function(editorUi, titleText)
7812	{
7813		var graph = editorUi.editor.graph;
7814		var div = document.createElement('div');
7815
7816		var title = document.createElement('h3');
7817		title.style.width = '100%';
7818		title.style.textAlign = 'center';
7819		title.style.marginTop = '0px';
7820		mxUtils.write(title, titleText || mxResources.get('print'));
7821		div.appendChild(title);
7822
7823		var pageCount = 1;
7824		var currentPage = 1;
7825
7826		// Pages
7827		var pagesSection = document.createElement('div');
7828		pagesSection.style.cssText = 'border-bottom:1px solid lightGray;padding-bottom:12px;margin-bottom:12px;';
7829
7830		var allPagesRadio = document.createElement('input');
7831		allPagesRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
7832		allPagesRadio.setAttribute('value', 'all');
7833		allPagesRadio.setAttribute('type', 'radio');
7834		allPagesRadio.setAttribute('name', 'pages-printdialog');
7835
7836		pagesSection.appendChild(allPagesRadio);
7837
7838		var span = document.createElement('span');
7839		mxUtils.write(span, mxResources.get('printAllPages'));
7840		pagesSection.appendChild(span);
7841
7842		mxUtils.br(pagesSection);
7843
7844		// Pages ... to ...
7845		var pagesRadio = allPagesRadio.cloneNode(true);
7846		allPagesRadio.setAttribute('checked', 'checked');
7847		pagesRadio.setAttribute('value', 'range');
7848		pagesSection.appendChild(pagesRadio);
7849
7850		var span = document.createElement('span');
7851		mxUtils.write(span, mxResources.get('pages') + ':');
7852		pagesSection.appendChild(span);
7853
7854		var pagesFromInput = document.createElement('input');
7855		pagesFromInput.style.cssText = 'margin:0 8px 0 8px;'
7856		pagesFromInput.setAttribute('value', '1');
7857		pagesFromInput.setAttribute('type', 'number');
7858		pagesFromInput.setAttribute('min', '1');
7859		pagesFromInput.style.width = '50px';
7860		pagesSection.appendChild(pagesFromInput);
7861
7862		var span = document.createElement('span');
7863		mxUtils.write(span, mxResources.get('to'));
7864		pagesSection.appendChild(span);
7865
7866		var pagesToInput = pagesFromInput.cloneNode(true);
7867		pagesSection.appendChild(pagesToInput);
7868
7869		mxEvent.addListener(pagesFromInput, 'focus', function()
7870		{
7871			pagesRadio.checked = true;
7872		});
7873
7874		mxEvent.addListener(pagesToInput, 'focus', function()
7875		{
7876			pagesRadio.checked = true;
7877		});
7878
7879		function validatePageRange()
7880		{
7881			pagesToInput.value = Math.max(1, Math.min(pageCount, Math.max(parseInt(pagesToInput.value), parseInt(pagesFromInput.value))));
7882			pagesFromInput.value = Math.max(1, Math.min(pageCount, Math.min(parseInt(pagesToInput.value), parseInt(pagesFromInput.value))));
7883		};
7884
7885		mxEvent.addListener(pagesFromInput, 'change', validatePageRange);
7886		mxEvent.addListener(pagesToInput, 'change', validatePageRange);
7887
7888		if (editorUi.pages != null)
7889		{
7890			pageCount = editorUi.pages.length;
7891
7892			if (editorUi.currentPage != null)
7893			{
7894				for (var i = 0; i < editorUi.pages.length; i++)
7895				{
7896					if (editorUi.currentPage == editorUi.pages[i])
7897					{
7898						currentPage = i + 1;
7899						pagesFromInput.value = currentPage;
7900						pagesToInput.value = currentPage;
7901						break;
7902					}
7903				}
7904			}
7905		}
7906
7907		pagesFromInput.setAttribute('max', pageCount);
7908		pagesToInput.setAttribute('max', pageCount);
7909
7910		if (!editorUi.isPagesEnabled())
7911		{
7912			pagesRadio.checked = true;
7913		}
7914		else if (pageCount > 1)
7915		{
7916			div.appendChild(pagesSection);
7917			pagesRadio.checked = true;
7918		}
7919
7920		// Adjust to ...
7921		var adjustSection = document.createElement('div');
7922		adjustSection.style.marginBottom = '10px';
7923
7924		var adjustRadio = document.createElement('input');
7925		adjustRadio.style.marginRight = '8px';
7926
7927		adjustRadio.setAttribute('value', 'adjust');
7928		adjustRadio.setAttribute('type', 'radio');
7929		adjustRadio.setAttribute('name', 'printZoom');
7930		adjustSection.appendChild(adjustRadio);
7931
7932		var span = document.createElement('span');
7933		mxUtils.write(span, mxResources.get('adjustTo'));
7934		adjustSection.appendChild(span);
7935
7936		var zoomInput = document.createElement('input');
7937		zoomInput.style.cssText = 'margin:0 8px 0 8px;';
7938		zoomInput.setAttribute('value', '100 %');
7939		zoomInput.style.width = '50px';
7940		adjustSection.appendChild(zoomInput);
7941
7942		mxEvent.addListener(zoomInput, 'focus', function()
7943		{
7944			adjustRadio.checked = true;
7945		});
7946
7947		div.appendChild(adjustSection);
7948
7949		// Fit to ...
7950		var fitSection = pagesSection.cloneNode(false);
7951
7952		var fitRadio = adjustRadio.cloneNode(true);
7953		fitRadio.setAttribute('value', 'fit');
7954		adjustRadio.setAttribute('checked', 'checked');
7955
7956		var spanFitRadio = document.createElement('div');
7957		spanFitRadio.style.cssText = 'display:inline-block;height:100%;vertical-align:top;padding-top:2px;';
7958		spanFitRadio.appendChild(fitRadio);
7959		fitSection.appendChild(spanFitRadio);
7960
7961		var table = document.createElement('table');
7962		table.style.display = 'inline-block';
7963		var tbody = document.createElement('tbody');
7964
7965		var row1 = document.createElement('tr');
7966		var row2 = row1.cloneNode(true);
7967
7968		var td1 = document.createElement('td');
7969		var td2 = td1.cloneNode(true);
7970		var td3 = td1.cloneNode(true);
7971
7972		var td4 = td1.cloneNode(true);
7973		var td5 = td1.cloneNode(true);
7974		var td6 = td1.cloneNode(true);
7975
7976		td1.style.textAlign = 'right';
7977		td4.style.textAlign = 'right';
7978
7979		mxUtils.write(td1, mxResources.get('fitTo'));
7980
7981		var sheetsAcrossInput = document.createElement('input');
7982		sheetsAcrossInput.style.cssText = 'margin:0 8px 0 8px;';
7983		sheetsAcrossInput.setAttribute('value', '1');
7984		sheetsAcrossInput.setAttribute('min', '1');
7985		sheetsAcrossInput.setAttribute('type', 'number');
7986		sheetsAcrossInput.style.width = '40px';
7987		td2.appendChild(sheetsAcrossInput);
7988
7989		var span = document.createElement('span');
7990		mxUtils.write(span, mxResources.get('fitToSheetsAcross'));
7991		td3.appendChild(span);
7992
7993		mxUtils.write(td4, mxResources.get('fitToBy'));
7994
7995		var sheetsDownInput = sheetsAcrossInput.cloneNode(true);
7996		td5.appendChild(sheetsDownInput);
7997
7998		mxEvent.addListener(sheetsAcrossInput, 'focus', function()
7999		{
8000			fitRadio.checked = true;
8001		});
8002
8003		mxEvent.addListener(sheetsDownInput, 'focus', function()
8004		{
8005			fitRadio.checked = true;
8006		});
8007
8008		var span = document.createElement('span');
8009		mxUtils.write(span, mxResources.get('fitToSheetsDown'));
8010		td6.appendChild(span);
8011
8012		row1.appendChild(td1);
8013		row1.appendChild(td2);
8014		row1.appendChild(td3);
8015
8016		row2.appendChild(td4);
8017		row2.appendChild(td5);
8018		row2.appendChild(td6);
8019
8020		tbody.appendChild(row1);
8021		tbody.appendChild(row2);
8022		table.appendChild(tbody);
8023		fitSection.appendChild(table);
8024
8025		div.appendChild(fitSection);
8026
8027		// Page scale ...
8028		var pageScaleSection = document.createElement('div');
8029
8030		var span = document.createElement('div');
8031		span.style.fontWeight = 'bold';
8032		span.style.marginBottom = '12px';
8033		mxUtils.write(span, mxResources.get('paperSize'));
8034		pageScaleSection.appendChild(span);
8035
8036		var span = document.createElement('div');
8037		span.style.marginBottom = '12px';
8038
8039		var accessor = PageSetupDialog.addPageFormatPanel(span, 'printdialog',
8040			editorUi.editor.graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT);
8041		pageScaleSection.appendChild(span);
8042
8043		var span = document.createElement('span');
8044		mxUtils.write(span, mxResources.get('pageScale'));
8045		pageScaleSection.appendChild(span);
8046
8047		var pageScaleInput = document.createElement('input');
8048		pageScaleInput.style.cssText = 'margin:0 8px 0 8px;';
8049		pageScaleInput.setAttribute('value', '100 %');
8050		pageScaleInput.style.width = '60px';
8051		pageScaleSection.appendChild(pageScaleInput);
8052
8053		div.appendChild(pageScaleSection);
8054
8055		// Buttons
8056		var buttons = document.createElement('div');
8057		buttons.style.cssText = 'text-align:right;margin:48px 0 0 0;';
8058
8059		// Overall scale for print-out to account for print borders in dialogs etc
8060		function preview(print)
8061		{
8062			var printScale = parseInt(pageScaleInput.value) / 100;
8063
8064			if (isNaN(printScale))
8065			{
8066				printScale = 1;
8067				pageScaleInput.value = '100 %';
8068			}
8069
8070			// Workaround to match available paper size in actual print output
8071			printScale *= 0.75;
8072
8073			// Disables dark mode while printing
8074			var darkStylesheet = null;
8075
8076			if (graph.themes != null && graph.defaultThemeName == 'darkTheme')
8077			{
8078				darkStylesheet = graph.stylesheet;
8079				graph.stylesheet = graph.getDefaultStylesheet()
8080				graph.refresh();
8081			}
8082
8083			function printGraph(thisGraph, pv, forcePageBreaks)
8084			{
8085				// Workaround for CSS transforms affecting the print output
8086				// is to disable during print output and restore after
8087				var prev = thisGraph.useCssTransforms;
8088				var prevTranslate = thisGraph.currentTranslate;
8089				var prevScale = thisGraph.currentScale;
8090				var prevViewTranslate = thisGraph.view.translate;
8091				var prevViewScale = thisGraph.view.scale;
8092
8093				if (thisGraph.useCssTransforms)
8094				{
8095					thisGraph.useCssTransforms = false;
8096					thisGraph.currentTranslate = new mxPoint(0,0);
8097					thisGraph.currentScale = 1;
8098					thisGraph.view.translate = new mxPoint(0,0);
8099					thisGraph.view.scale = 1;
8100				}
8101
8102				// Negative coordinates are cropped or shifted if page visible
8103				var gb = thisGraph.getGraphBounds();
8104				var border = 0;
8105				var x0 = 0;
8106				var y0 = 0;
8107
8108				var pf = accessor.get();
8109				var scale = 1 / thisGraph.pageScale;
8110				var autoOrigin = fitRadio.checked;
8111
8112				if (autoOrigin)
8113				{
8114					var h = parseInt(sheetsAcrossInput.value);
8115					var v = parseInt(sheetsDownInput.value);
8116
8117					scale = Math.min((pf.height * v) / (gb.height / thisGraph.view.scale),
8118						(pf.width * h) / (gb.width / thisGraph.view.scale));
8119				}
8120				else
8121				{
8122					scale = parseInt(zoomInput.value) / (100 * thisGraph.pageScale);
8123
8124					if (isNaN(scale))
8125					{
8126						printScale = 1 / thisGraph.pageScale;
8127						zoomInput.value = '100 %';
8128					}
8129				}
8130
8131				// Applies print scale
8132				pf = mxRectangle.fromRectangle(pf);
8133				pf.width = Math.ceil(pf.width * printScale);
8134				pf.height = Math.ceil(pf.height * printScale);
8135				scale *= printScale;
8136
8137				// Starts at first visible page
8138				if (!autoOrigin && thisGraph.pageVisible)
8139				{
8140					var layout = thisGraph.getPageLayout();
8141					x0 -= layout.x * pf.width;
8142					y0 -= layout.y * pf.height;
8143				}
8144				else
8145				{
8146					autoOrigin = true;
8147				}
8148
8149				if (pv == null)
8150				{
8151					pv = PrintDialog.createPrintPreview(thisGraph, scale, pf, border, x0, y0, autoOrigin);
8152					pv.pageSelector = false;
8153					pv.mathEnabled = false;
8154
8155					var file = editorUi.getCurrentFile();
8156
8157					if (file != null)
8158					{
8159						pv.title = file.getTitle();
8160					}
8161
8162					var writeHead = pv.writeHead;
8163
8164					// Overridden to add custom fonts
8165					pv.writeHead = function(doc)
8166					{
8167						writeHead.apply(this, arguments);
8168
8169						// Workaround for zoomed math clipping in Webkit
8170						if (mxClient.IS_GC || mxClient.IS_SF)
8171						{
8172							doc.writeln('<style type="text/css">');
8173							doc.writeln(Editor.mathJaxWebkitCss);
8174							doc.writeln('</style>');
8175						}
8176
8177						// Fixes font weight for PDF export in Chrome
8178						if (mxClient.IS_GC)
8179						{
8180							doc.writeln('<style type="text/css">');
8181							doc.writeln('@media print {');
8182							doc.writeln('span.MathJax_SVG svg { shape-rendering: crispEdges; }');
8183							doc.writeln('}');
8184							doc.writeln('</style>');
8185						}
8186
8187						if (editorUi.editor.fontCss != null)
8188						{
8189							doc.writeln('<style type="text/css">');
8190							doc.writeln(editorUi.editor.fontCss);
8191							doc.writeln('</style>');
8192						}
8193
8194						var extFonts = thisGraph.getCustomFonts();
8195
8196						for (var i = 0; i < extFonts.length; i++)
8197						{
8198							var fontName = extFonts[i].name;
8199							var fontUrl = extFonts[i].url;
8200
8201							if (Graph.isCssFontUrl(fontUrl))
8202							{
8203						   		doc.writeln('<link rel="stylesheet" href="' +
8204						   			mxUtils.htmlEntities(fontUrl) +
8205						   			'" charset="UTF-8" type="text/css">');
8206							}
8207							else
8208							{
8209						   		doc.writeln('<style type="text/css">');
8210						   		doc.writeln('@font-face {\n' +
8211						   			'font-family: "' + mxUtils.htmlEntities(fontName) + '";\n' +
8212						   			'src: url("' + mxUtils.htmlEntities(fontUrl) + '");\n}');
8213						   		doc.writeln('</style>');
8214							}
8215						}
8216					};
8217
8218					if (typeof(MathJax) !== 'undefined')
8219					{
8220						// Adds class to ignore if math is disabled
8221						var printPreviewRenderPage = pv.renderPage;
8222
8223						pv.renderPage = function(w, h, dx, dy, content, pageNumber)
8224						{
8225							var prev = mxClient.NO_FO;
8226							mxClient.NO_FO = (this.graph.mathEnabled && !editorUi.editor.useForeignObjectForMath) ?
8227								true : editorUi.editor.originalNoForeignObject;
8228							var result = printPreviewRenderPage.apply(this, arguments);
8229							mxClient.NO_FO = prev;
8230
8231							if (this.graph.mathEnabled)
8232							{
8233								this.mathEnabled = this.mathEnabled || true;
8234							}
8235							else
8236							{
8237								result.className = 'geDisableMathJax';
8238							}
8239
8240							return result;
8241						};
8242					}
8243
8244					// Switches stylesheet for print output in dark mode
8245					var temp = null;
8246
8247					// Disables dashed printing of flowAnimation
8248					var enableFlowAnimation = graph.enableFlowAnimation;
8249					graph.enableFlowAnimation = false;
8250
8251					if (graph.themes != null && graph.defaultThemeName == 'darkTheme')
8252					{
8253						temp = graph.stylesheet;
8254						graph.stylesheet = graph.getDefaultStylesheet()
8255						graph.refresh();
8256					}
8257
8258					// Generates the print output
8259					pv.open(null, null, forcePageBreaks, true);
8260
8261					// Restores flowAnimation
8262					graph.enableFlowAnimation = enableFlowAnimation;
8263
8264					// Restores the stylesheet
8265					if (temp != null)
8266					{
8267						graph.stylesheet = temp;
8268						graph.refresh();
8269					}
8270				}
8271				else
8272				{
8273					var bg = thisGraph.background;
8274
8275					if (bg == null || bg == '' || bg == mxConstants.NONE)
8276					{
8277						bg = '#ffffff';
8278					}
8279
8280					pv.backgroundColor = bg;
8281					pv.autoOrigin = autoOrigin;
8282					pv.appendGraph(thisGraph, scale, x0, y0, forcePageBreaks, true);
8283
8284					var extFonts = thisGraph.getCustomFonts();
8285
8286					if (pv.wnd != null)
8287					{
8288						for (var i = 0; i < extFonts.length; i++)
8289						{
8290							var fontName = extFonts[i].name;
8291							var fontUrl = extFonts[i].url;
8292
8293							if (Graph.isCssFontUrl(fontUrl))
8294							{
8295						   		pv.wnd.document.writeln('<link rel="stylesheet" href="' +
8296						   			mxUtils.htmlEntities(fontUrl) +
8297						   			'" charset="UTF-8" type="text/css">');
8298							}
8299							else
8300							{
8301						   		pv.wnd.document.writeln('<style type="text/css">');
8302						   		pv.wnd.document.writeln('@font-face {\n' +
8303						   			'font-family: "' + mxUtils.htmlEntities(fontName) + '";\n' +
8304						   			'src: url("' + mxUtils.htmlEntities(fontUrl) + '");\n}');
8305						   		pv.wnd.document.writeln('</style>');
8306							}
8307						}
8308					}
8309				}
8310
8311				// Restores state if css transforms are used
8312				if (prev)
8313				{
8314					thisGraph.useCssTransforms = prev;
8315					thisGraph.currentTranslate = prevTranslate;
8316					thisGraph.currentScale = prevScale;
8317					thisGraph.view.translate = prevViewTranslate;
8318					thisGraph.view.scale = prevViewScale;
8319				}
8320
8321				return pv;
8322			};
8323
8324			var pagesFrom = pagesFromInput.value;
8325			var pagesTo = pagesToInput.value;
8326			var ignorePages = !allPagesRadio.checked;
8327			var pv = null;
8328
8329			if (EditorUi.isElectronApp)
8330			{
8331				PrintDialog.electronPrint(editorUi, allPagesRadio.checked, pagesFrom, pagesTo,  fitRadio.checked,
8332					sheetsAcrossInput.value, sheetsDownInput.value, parseInt(zoomInput.value) / 100,
8333					parseInt(pageScaleInput.value) / 100, accessor.get());
8334
8335				return;
8336			}
8337
8338			if (ignorePages)
8339			{
8340				ignorePages = pagesFrom == currentPage && pagesTo == currentPage;
8341			}
8342
8343			if (!ignorePages && editorUi.pages != null && editorUi.pages.length)
8344			{
8345				var i0 = 0;
8346				var imax = editorUi.pages.length - 1;
8347
8348				if (!allPagesRadio.checked)
8349				{
8350					i0 = parseInt(pagesFrom) - 1;
8351					imax = parseInt(pagesTo) - 1;
8352				}
8353
8354				for (var i = i0; i <= imax; i++)
8355				{
8356					var page = editorUi.pages[i];
8357					var tempGraph = (page == editorUi.currentPage) ? graph : null;
8358
8359					if (tempGraph == null)
8360					{
8361						tempGraph = editorUi.createTemporaryGraph(graph.stylesheet);
8362
8363						// Restores graph settings that are relevant for printing
8364						var pageVisible = true;
8365						var mathEnabled = false;
8366						var bg = null;
8367						var bgImage = null;
8368
8369						if (page.viewState == null)
8370						{
8371							// Workaround to extract view state from XML node
8372							// This changes the state of the page and parses
8373							// the XML for the graph model even if not needed.
8374							if (page.root == null)
8375							{
8376								editorUi.updatePageRoot(page);
8377							}
8378						}
8379
8380						if (page.viewState != null)
8381						{
8382							pageVisible = page.viewState.pageVisible;
8383							mathEnabled = page.viewState.mathEnabled;
8384							bg = page.viewState.background;
8385							bgImage = page.viewState.backgroundImage;
8386							tempGraph.extFonts = page.viewState.extFonts;
8387						}
8388
8389						tempGraph.background = bg;
8390						tempGraph.backgroundImage = (bgImage != null) ? new mxImage(bgImage.src, bgImage.width, bgImage.height) : null;
8391						tempGraph.pageVisible = pageVisible;
8392						tempGraph.mathEnabled = mathEnabled;
8393
8394						// Redirects placeholders to current page
8395						var graphGetGlobalVariable = tempGraph.getGlobalVariable;
8396
8397						tempGraph.getGlobalVariable = function(name)
8398						{
8399							if (name == 'page')
8400							{
8401								return page.getName();
8402							}
8403							else if (name == 'pagenumber')
8404							{
8405								return i + 1;
8406							}
8407							else if (name == 'pagecount')
8408							{
8409								return (editorUi.pages != null) ? editorUi.pages.length : 1;
8410							}
8411
8412							return graphGetGlobalVariable.apply(this, arguments);
8413						};
8414
8415						document.body.appendChild(tempGraph.container);
8416						editorUi.updatePageRoot(page);
8417						tempGraph.model.setRoot(page.root);
8418					}
8419
8420					pv = printGraph(tempGraph, pv, i != imax);
8421
8422					if (tempGraph != graph)
8423					{
8424						tempGraph.container.parentNode.removeChild(tempGraph.container);
8425					}
8426				}
8427			}
8428			else
8429			{
8430				pv = printGraph(graph);
8431			}
8432
8433			if (pv == null)
8434			{
8435				editorUi.handleError({message: mxResources.get('errorUpdatingPreview')});
8436			}
8437			else
8438			{
8439				if (pv.mathEnabled)
8440				{
8441					var doc = pv.wnd.document;
8442
8443					// Adds asynchronous printing when MathJax finishes rendering
8444					// via global variable that is checked in math-print.js to
8445					// avoid generating unsafe-inline script or adding SHA to CSP
8446					if (print)
8447					{
8448						pv.wnd.IMMEDIATE_PRINT = true;
8449					}
8450
8451					doc.writeln('<script type="text/javascript" src="' + DRAWIO_BASE_URL + '/js/math-print.js"></script>');
8452				}
8453
8454				pv.closeDocument();
8455
8456				if (!pv.mathEnabled && print)
8457				{
8458					PrintDialog.printPreview(pv);
8459				}
8460			}
8461
8462			// Restores dark mode
8463			if (darkStylesheet != null)
8464			{
8465				graph.stylesheet = darkStylesheet;
8466				graph.refresh();
8467			}
8468		};
8469
8470		var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
8471		{
8472			editorUi.hideDialog();
8473		});
8474		cancelBtn.className = 'geBtn';
8475
8476		if (editorUi.editor.cancelFirst)
8477		{
8478			buttons.appendChild(cancelBtn);
8479		}
8480
8481		if (!editorUi.isOffline())
8482		{
8483			var helpBtn = mxUtils.button(mxResources.get('help'), function()
8484			{
8485				graph.openLink('https://www.diagrams.net/doc/faq/print-diagram');
8486			});
8487
8488			helpBtn.className = 'geBtn';
8489			buttons.appendChild(helpBtn);
8490		}
8491
8492		if (PrintDialog.previewEnabled)
8493		{
8494			var previewBtn = mxUtils.button(mxResources.get('preview'), function()
8495			{
8496				editorUi.hideDialog();
8497				preview(false);
8498			});
8499			previewBtn.className = 'geBtn';
8500			buttons.appendChild(previewBtn);
8501		}
8502
8503		var printBtn = mxUtils.button(mxResources.get((!PrintDialog.previewEnabled) ? 'ok' : 'print'), function()
8504		{
8505			editorUi.hideDialog();
8506			preview(true);
8507		});
8508		printBtn.className = 'geBtn gePrimaryBtn';
8509		buttons.appendChild(printBtn);
8510
8511		if (!editorUi.editor.cancelFirst)
8512		{
8513			buttons.appendChild(cancelBtn);
8514		}
8515
8516		div.appendChild(buttons);
8517
8518		this.container = div;
8519	};
8520
8521    // Execute fit page on page setup changes
8522    var changePageSetupExecute = ChangePageSetup.prototype.execute;
8523
8524    ChangePageSetup.prototype.execute = function()
8525    {
8526        if (this.page == null)
8527        {
8528            this.page = this.ui.currentPage;
8529        }
8530
8531        // Workaround for redo existing change with different current page
8532        if (this.page != this.ui.currentPage)
8533        {
8534            if (this.page.viewState != null)
8535            {
8536                if (!this.ignoreColor)
8537                {
8538                    this.page.viewState.background = this.color;
8539                }
8540
8541                if (!this.ignoreImage)
8542                {
8543					var img = this.image;
8544
8545					if (img != null && img.src != null && Graph.isPageLink(img.src))
8546					{
8547						img = {originalSrc: img.src};
8548					}
8549
8550                    this.page.viewState.backgroundImage = img;
8551                }
8552
8553                if (this.format != null)
8554                {
8555                    this.page.viewState.pageFormat = this.format;
8556                }
8557
8558                if (this.mathEnabled != null)
8559                {
8560                    this.page.viewState.mathEnabled = this.mathEnabled;
8561                }
8562
8563                if (this.shadowVisible != null)
8564            	{
8565            		this.page.viewState.shadowVisible = this.shadowVisible;
8566            	}
8567            }
8568        }
8569        else
8570        {
8571            changePageSetupExecute.apply(this, arguments);
8572
8573            if (this.mathEnabled != null && this.mathEnabled != this.ui.isMathEnabled())
8574            {
8575                this.ui.setMathEnabled(this.mathEnabled);
8576                this.mathEnabled = !this.mathEnabled;
8577            }
8578
8579            if (this.shadowVisible != null && this.shadowVisible != this.ui.editor.graph.shadowVisible)
8580            {
8581            	this.ui.editor.graph.setShadowVisible(this.shadowVisible);
8582                this.shadowVisible = !this.shadowVisible;
8583            }
8584        }
8585    };
8586
8587    /**
8588	 * Capability check for canvas export
8589	 */
8590	Editor.prototype.useCanvasForExport = false;
8591
8592	try
8593	{
8594		var canvas = document.createElement('canvas');
8595		var img = new Image();
8596
8597		// LATER: Capability check should not be async
8598		img.onload = function()
8599		{
8600			try
8601			{
8602		   		var ctx = canvas.getContext('2d');
8603		   		ctx.drawImage(img, 0, 0);
8604
8605		   		// Works in Chrome, Firefox, Edge, Safari and Opera
8606				var result = canvas.toDataURL('image/png');
8607				Editor.prototype.useCanvasForExport = result != null && result.length > 6;
8608			}
8609			catch (e)
8610			{
8611				// ignore
8612			}
8613		};
8614
8615		// Checks if SVG with foreignObject can be exported
8616		var svg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1px" height="1px" version="1.1"><foreignObject pointer-events="all" width="1" height="1"><div xmlns="http://www.w3.org/1999/xhtml"></div></foreignObject></svg>';
8617		img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
8618	}
8619	catch (e)
8620	{
8621		// ignore
8622	}
8623
8624})();
8625
8626// Extends codec for ChangePageSetup
8627(function()
8628{
8629	var codec = new mxObjectCodec(new ChangePageSetup(),  ['ui', 'previousColor', 'previousImage', 'previousFormat']);
8630
8631	codec.beforeDecode = function(dec, node, obj)
8632	{
8633		obj.ui = dec.ui;
8634
8635		return node;
8636	};
8637
8638	codec.afterDecode = function(dec, node, obj)
8639	{
8640		obj.previousColor = obj.color;
8641		obj.previousImage = obj.image;
8642		obj.previousFormat = obj.format;
8643
8644        if (obj.foldingEnabled != null)
8645        {
8646        		obj.foldingEnabled = !obj.foldingEnabled;
8647        }
8648
8649        if (obj.mathEnabled != null)
8650        {
8651        		obj.mathEnabled = !obj.mathEnabled;
8652        }
8653
8654        if (obj.shadowVisible != null)
8655        {
8656        		obj.shadowVisible = !obj.shadowVisible;
8657        }
8658
8659		return obj;
8660	};
8661
8662	mxCodecRegistry.register(codec);
8663})();
8664
8665// Extends codec for ChangeGridColor
8666(function()
8667{
8668	var codec = new mxObjectCodec(new ChangeGridColor(),  ['ui']);
8669
8670	codec.beforeDecode = function(dec, node, obj)
8671	{
8672		obj.ui = dec.ui;
8673
8674		return node;
8675	};
8676
8677	mxCodecRegistry.register(codec);
8678})();
8679
8680