1/**
2 * Copyright (c) 2006-2015, JGraph Ltd
3 * Copyright (c) 2006-2015, Gaudenz Alder
4 */
5/**
6 * Voice plugin for draw.io
7 *
8 * Documentation:
9 *
10 * https://www.diagrams.net/doc/faq/voice-plugin
11 *
12 * TODO: Use grammer https://msdn.microsoft.com/en-us/library/ee800145.aspx
13 */
14Draw.loadPlugin(function(ui) {
15	// Speech recognition never supported without synthesis
16	if (!('speechSynthesis' in window))
17	{
18		ui.showError('Error', 'Speech output not supported in this browser.', 'OK');
19
20		return;
21	}
22	else
23	{
24		// Triggers loading of voices
25		speechSynthesis.getVoices();
26	}
27
28	// Do no use in chromeless mode
29	if (ui.editor.isChromelessView())
30	{
31		return;
32	}
33
34	// Mic PNG image
35	var outputImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBNDU1RDkxODcxREIxMUU0OTU3Qjg3REYyOTYxQzc0QiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBNDU1RDkxOTcxREIxMUU0OTU3Qjg3REYyOTYxQzc0QiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkE0NTVEOTE2NzFEQjExRTQ5NTdCODdERjI5NjFDNzRCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkE0NTVEOTE3NzFEQjExRTQ5NTdCODdERjI5NjFDNzRCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+QsVUnQAAAX5JREFUeNqck00oRFEUx+eNyYSFUj7KgpfPBUlJWZgFw3IWihpRFpZia4e1hZSlNDsLopSNfBTFQqSwkKmZ5SA2ylfq+Z3pXL15ehlu/fqf9849555777mW4ziBfEZ9rd2InEEsmU4dmP/WHxKEkT1oglaSZOR/0DvRssIVUOQKjEASsxoGoQDmjT/oDUaOdRUzbkASrsMdzMIwSe2cBASXIjtQpyvHYQvzGeLQDkOwDG8w8p2A4GJEJre5Vk5DFBbZ7yG6D+PYL3oW0WwCgh/Re4i4t8PEE2QOxqikDN2AbuwQKr4WU4E4S3wOfw1CWtktFEI5ZDTu5y34DMvPIQl24dTHL9f2CRfQAB/wAFXwlE3gOO990Im95GmcLmQGEpyHTB6AI2xJKL4r7xYmYdX1betpT0kzoT1yhdhyY71aeW4rcyNySJswTVWXWkklcq5N1AETsCAuqkn9+hZ09RXoh1e4hm2CR//7mJqlB8xjCgXyHzXaDzETLONLgAEAxwd5e6Mz+S4AAAAASUVORK5CYII=';
36	var micImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA5UlEQVR4Xr3SMYrCQBTGcSfIQiAgRIS9hOANBCurPUAuIAp7A7FVsFkIbLGYA9gKtoKNYG3jll5AFNKG518YMD6SISD4wY9J4MvkMYwRkZqOMSZkifGFe1b4pnvW3TqK8oMo14twxUgXPRSlDxU7TcUNPqATlG7wCi93cA2Iq2x7l7IJsgofB6UTiEjKklFqsabQSdFA5jqDAzrYQGeNNv5d9yDBEAME6NreFmP8Yuma4A8hFpiLSFNAYYYYn0jwCIUnxMcER4h1whS+7hseXKcu9ifGeQ+qeO8GjN7DPve+Q6+oewPhmE63Qfsb6AAAAABJRU5ErkJggg==';
37
38	// True if we're on ChromOs
39	var chromeOs = mxClient.IS_CHROMEOS;
40
41	// Maximum length of message to speak
42	var maxMessageLength = 1000;
43
44	// Maximum length of the label before the cell
45	// is called by its shapename
46	var maxLabelLength = 15;
47
48	// Maximum length of output queue.
49	var maxQueueLength = 3;
50
51	// Specifies if speech output is enabled.
52	var speechOutputEnabled = true;
53
54	// Specifies if speech output is enabled.
55	var speechInputEnabled = true;
56
57	// Last message is never repeated
58	var lastMessage = null;
59
60	// Timestamp of last message
61	var lastMessageTimestamp = null;
62
63	// Sets global recognition language
64	var lang = 'en-US';
65
66	// Caches action names
67	var actions = null;
68	var actionList = null;
69
70	// Caches shape names
71	var shapeList = null;
72
73	// Last inserted cell
74	var lastInserted = null;
75
76	// Current voice
77	var currentVoice = 10;
78
79	// Current recognition thread
80	var recognizing = null;
81
82	// Adds menu
83	mxResources.parse('voiceType=Voice Type');
84	mxResources.parse('speechOutput=Speech Output');
85	mxResources.parse('speechListen=Listen');
86	mxResources.parse('speechInstalled=Start with draw.io');
87	mxResources.parse('speechListenContinuous=Start/Stop Listen');
88	mxResources.parse('speechHint=Hint');
89	mxResources.parse('speechHelp=Help');
90	mxResources.parse('speechQuit=Quit');
91
92	// Installs footer click handler
93	function getOrCreateVoiceButton(ui)
94	{
95		if (ui.voiceButton == null)
96		{
97			ui.voiceButton = document.createElement('div');
98			ui.voiceButton.className = 'geBtn';
99			ui.voiceButton.style.width = '140px';
100			ui.voiceButton.style.minWidth = '140px';
101			ui.voiceButton.style.textOverflow = 'ellipsis';
102			ui.voiceButton.style.overflowX = 'hidden';
103			ui.voiceButton.style.fontWeight = 'bold';
104			ui.voiceButton.style.textAlign = 'center';
105			ui.voiceButton.style.display = 'inline-block';
106			ui.voiceButton.style.padding = '0 10px 0 10px';
107			ui.voiceButton.style.marginTop = '-4px';
108			ui.voiceButton.style.height = '28px';
109			ui.voiceButton.style.lineHeight = '28px';
110			ui.voiceButton.style.color = '#235695';
111
112			if (ui.buttonContainer.firstChild != null)
113			{
114				ui.buttonContainer.insertBefore(ui.voiceButton, ui.buttonContainer.firstChild);
115			}
116			else
117			{
118				ui.buttonContainer.appendChild(ui.voiceButton);
119			}
120		}
121
122		return ui.voiceButton;
123	};
124
125	var td = getOrCreateVoiceButton(ui);
126
127	if (td != null)
128	{
129		mxEvent.addGestureListeners(td, function(evt)
130		{
131			ui.editor.graph.popupMenuHandler.hideMenu();
132
133			if (ui.menubar == null && mxEvent.isPopupTrigger(evt))
134			{
135				ui.editor.graph.popupMenuHandler.hideMenu();
136				var menu = new mxPopupMenu(ui.menus.get('voice').funct);
137				menu.div.className += ' geMenubarMenu';
138				menu.smartSeparators = true;
139				menu.showDisabled = true;
140				menu.autoExpand = true;
141
142				// Disables autoexpand and destroys menu when hidden
143				menu.hideMenu = mxUtils.bind(this, function()
144				{
145					mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
146					menu.destroy();
147				});
148
149				var offset = mxUtils.getOffset(td);
150				menu.popup(offset.x, offset.y + td.offsetHeight, null, evt);
151
152				// Allows hiding by clicking on document
153				ui.setCurrentMenu(menu);
154				mxEvent.consume(evt);
155			}
156		}, null, function(evt)
157		{
158			if (!mxEvent.isPopupTrigger(evt))
159			{
160				if (speechSynthesis.speaking)
161				{
162					speechSynthesis.cancel();
163				}
164
165				App.listen(true);
166			}
167
168			mxEvent.consume(evt);
169		});
170
171		mxEvent.disableContextMenu(td);
172	}
173
174	function setPluginInstalled(value)
175	{
176		if (mxSettings != null)
177		{
178	    	var plugins = mxSettings.getPlugins();
179	    	var installed = mxUtils.indexOf(plugins, '/plugins/voice.js') >= 0;
180
181	    	if (value != installed)
182	    	{
183		    	if (installed)
184		    	{
185		    		mxUtils.remove('/plugins/voice.js', plugins);
186		    	}
187		    	else
188		    	{
189		    		plugins.push('/plugins/voice.js');
190		    	}
191
192				mxSettings.setPlugins(plugins);
193				mxSettings.save();
194	    	}
195		}
196	};
197
198	// Shows initial status message if only output is enable
199	if (!('webkitSpeechRecognition' in window))
200	{
201		if (td != null)
202		{
203			td.innerHTML = '<img style="margin-right:4px;" align="absmiddle" border="0" src="' + outputImage + '"/> Ready';
204			td.style.color = '#235695';
205		}
206	}
207
208	function updateStatusMessage()
209	{
210		if ('webkitSpeechRecognition' in window)
211		{
212			if (td != null)
213			{
214				if (recognizing != null)
215				{
216	    			td.innerHTML = '<img style="margin-right:4px;" align="absmiddle" border="0" src="' + micImage + '"/> Listening...';
217					td.setAttribute('title', 'Click to Stop (' + Editor.ctrlKey + '+O)');
218	    			td.style.color = 'darkGray';
219				}
220				else
221				{
222					td.innerHTML = '<img style="margin-right:4px;" align="absmiddle" border="0" src="' + micImage + '"/> Click to Speak';
223					td.setAttribute('title', 'Click to Speak (' + Editor.ctrlKey + '+O)');
224					td.style.color = '#235695';
225				}
226			}
227		}
228	};
229
230	updateStatusMessage();
231
232    var action = ui.actions.addAction('speechOutput', function()
233    {
234    	speechOutputEnabled = !speechOutputEnabled;
235    }, null, null, 'Ctrl/AltGr+Shift+Esc');
236	action.setToggleAction(true);
237	action.setSelectedCallback(function() { return speechOutputEnabled; });
238
239    var action = ui.actions.addAction('speechInstalled', function()
240    {
241    	setPluginInstalled();
242    });
243	action.setToggleAction(true);
244	action.setSelectedCallback(function() { return mxUtils.indexOf(mxSettings.getPlugins(), '/plugins/voice.js') >= 0; });
245
246    ui.actions.addAction('speechListen', function()
247    {
248    	App.listen();
249    }, null, null, 'Ctrl/AltGr+Esc');
250
251    ui.actions.addAction('speechListenContinuous', function()
252    {
253    	App.listen(true);
254    }, null, null, Editor.ctrlKey + '+O');
255
256    ui.actions.addAction('speechHint', function()
257    {
258    	App.sayHint();
259    }, null, null, 'Shift+Esc');
260
261    ui.actions.addAction('speechHelp', function()
262    {
263    	window.open('https://www.diagrams.net/doc/faq/voice-plugin');
264    });
265
266    // Hijacks the settings for storing current voice
267	if (mxSettings != null)
268	{
269	    var tmp = mxSettings.settings.voice;
270
271	    if (tmp != null)
272	    {
273	    	currentVoice = parseInt(tmp);
274	    }
275	}
276
277	ui.menus.put('voiceType', new Menu(mxUtils.bind(this, function(menu, parent)
278	{
279		var voices = speechSynthesis.getVoices();
280
281		if (voices.length == 0)
282		{
283			menu.addItem('Loading...', null, function() {}, parent, null, false);
284		}
285		else
286		{
287			for (var i = 0; i < voices.length; i++)
288			{
289				(function(index)
290				{
291					var item = menu.addItem(voices[index].name + ' (' + voices[i].lang + ')', null, function()
292					{
293						currentVoice = index;
294						App.say('hello');
295
296						if (mxSettings != null)
297						{
298							mxSettings.settings.voice = currentVoice;
299							mxSettings.save();
300						}
301					}, parent);
302
303					if (index == currentVoice)
304					{
305						menu.addCheckmark(item, Editor.checkmarkImage);
306					}
307				})(i);
308			}
309
310			parent.div.style.overflowX = 'hidden';
311			parent.div.style.overflowY = 'auto';
312			parent.div.style.maxHeight = '100%';
313			// Workaround for document scrollbars with 100% max height in Chrome
314			parent.div.style.marginBottom = '-20px';
315		}
316	})));
317
318    ui.actions.addAction('speechQuit', function()
319    {
320    	// Hides UI
321    	speechOutputEnabled = false;
322    	td.style.display = 'none';
323
324    	if (menu != null)
325    	{
326    		menu.style.display = 'none';
327    	}
328    });
329
330	ui.menus.put('voice', new Menu(function(menu, parent)
331	{
332    	ui.menus.addSubmenu('voiceType', menu, parent);
333		ui.menus.addMenuItems(menu, ['-', 'speechOutput', 'speechHint', '-', 'speechListen',
334            'speechListenContinuous', '-', 'speechInstalled',
335            'speechHelp', '-', 'speechQuit']);
336	}));
337
338    if (ui.menubar != null)
339    {
340		var menu = ui.menubar.addMenu('Voice', ui.menus.get('voice').funct);
341
342		// Inserts voice menu before help menu
343		menu.parentNode.insertBefore(menu, menu.previousSibling.previousSibling.previousSibling);
344    }
345
346	function insertShape(shape, done)
347	{
348		var searchTerm = mxUtils.trim(shape);
349
350		ui.sidebar.searchEntries(searchTerm, 1, 0, function(results, len, more)
351		{
352			if (results.length > 0)
353			{
354				var elt = results[0]();
355
356				// Click is blocked, must use mousedown/-up sequence
357				// LATER: Use touchstart or pointerEvents depending on system
358				dispatchEvent(elt, mouseEvent('mousedown', 1, 50, 1, 50));
359				dispatchEvent(document.body, mouseEvent('mouseup', 1, 50, 1, 50));
360			}
361			else
362			{
363				App.say('{1} not found', [searchTerm]);
364			}
365
366			if (done != null)
367			{
368				done();
369			}
370		});
371	};
372
373	// http://stackoverflow.com/questions/11919065/sort-an-array-by-the-levenshtein-distance-with-best-performance-in-javascript
374	//http://www.merriampark.com/ld.htm, http://www.mgilleland.com/ld/ldjavascript.htm, Damerau–Levenshtein distance (Wikipedia)
375	var levenshteinDist = function(s, t) {
376	    var d = []; //2d matrix
377
378	    // Step 1
379	    var n = s.length;
380	    var m = t.length;
381
382	    if (n == 0) return m;
383	    if (m == 0) return n;
384
385	    //Create an array of arrays in javascript (a descending loop is quicker)
386	    for (var i = n; i >= 0; i--) d[i] = [];
387
388	    // Step 2
389	    for (var i = n; i >= 0; i--) d[i][0] = i;
390	    for (var j = m; j >= 0; j--) d[0][j] = j;
391
392	    // Step 3
393	    for (var i = 1; i <= n; i++) {
394	        var s_i = s.charAt(i - 1);
395
396	        // Step 4
397	        for (var j = 1; j <= m; j++) {
398
399	            //Check the jagged ld total so far
400	            if (i == j && d[i][j] > 4) return n;
401
402	            var t_j = t.charAt(j - 1);
403	            var cost = (s_i == t_j) ? 0 : 1; // Step 5
404
405	            //Calculate the minimum
406	            var mi = d[i - 1][j] + 1;
407	            var b = d[i][j - 1] + 1;
408	            var c = d[i - 1][j - 1] + cost;
409
410	            if (b < mi) mi = b;
411	            if (c < mi) mi = c;
412
413	            d[i][j] = mi; // Step 6
414
415	            //Damerau transposition
416	            if (i > 1 && j > 1 && s_i == t.charAt(j - 2) && s.charAt(i - 2) == t_j) {
417	                d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
418	            }
419	        }
420	    }
421
422	    // Step 7
423	    return d[n][m];
424	}
425
426	function naiveHammingDistance(str1, str2) {
427	    var dist = 0;
428
429	    str1 = str1.toLowerCase();
430	    str2 = str2.toLowerCase();
431
432	     for(var i = 0; i < str1.length; i++)
433	     {
434	        if (str2[i] && str2[i] !== str1[i])
435	        {
436	            dist += Math.abs(str1.charCodeAt(i) - str2.charCodeAt(i)) + Math.abs(str2.indexOf( str1[i] )) * 2;
437	        }
438	        else if (!str2[i])
439	        {
440	            //  If there's no letter in the comparing string
441	            dist += dist;
442	        }
443	    }
444
445	    return dist;
446	};
447
448	function getBestWord(str1, words, useLevenshteinDist)
449	{
450		if (words == null || words.length == 0)
451		{
452			return str1;
453		}
454
455		useLevenshteinDist = (useLevenshteinDist != null) ? useLevenshteinDist : true;
456
457	    var bestWord = words[0];
458	    var minDist = ((useLevenshteinDist) ? levenshteinDist(str1, bestWord) :
459	    		naiveHammingDistance(str1, bestWord));
460
461	    for (var i = 1; i < words.length; i++)
462	    {
463		    	var tmp = ((useLevenshteinDist) ? levenshteinDist(str1, words[i]) :
464		    		((str1 == words[i]) ? 0 : naiveHammingDistance(str1, words[i])));
465
466		    	if (tmp < minDist || (tmp == minDist &&
467		    		str1.length > bestWord.length &&
468		    		bestWord.length < words[i].length))
469		    	{
470		    		bestWord = words[i];
471		    		minDist = tmp;
472		    	}
473
474		    	if (bestWord == str1)
475		    	{
476		    		break;
477		    	}
478	    }
479
480	    return bestWord;
481	}
482
483	function mouseEvent(type, sx, sy, cx, cy, shift)
484	{
485	  var evt;
486	  var e = {
487	    bubbles: true,
488	    cancelable: (type != "mousemove"),
489	    view: window,
490	    detail: 0,
491	    screenX: sx,
492	    screenY: sy,
493	    clientX: cx,
494	    clientY: cy,
495	    ctrlKey: false,
496	    altKey: false,
497	    shiftKey: (shift != null) ? shift : false,
498	    metaKey: false,
499	    button: 0,
500	    relatedTarget: undefined
501	  };
502
503	  if (typeof( document.createEvent ) == "function")
504	  {
505	    evt = document.createEvent("MouseEvents");
506	    evt.initMouseEvent(type,
507	      e.bubbles, e.cancelable, e.view, e.detail,
508	      e.screenX, e.screenY, e.clientX, e.clientY,
509	      e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
510	      e.button, document.body.parentNode);
511	  }
512	  else if (document.createEventObject)
513	  {
514	    evt = document.createEventObject();
515	    for (prop in e)
516	    {
517	    	evt[prop] = e[prop];
518	    }
519
520	    evt.button = { 0:1, 1:4, 2:2 }[evt.button] || evt.button;
521	  }
522
523	  return evt;
524	};
525
526	function dispatchEvent (el, evt)
527	{
528	  if (el.dispatchEvent)
529	  {
530	    el.dispatchEvent(evt);
531	  }
532	  else if (el.fireEvent)
533	  {
534	    el.fireEvent('on' + type, evt);
535	  }
536
537	  return evt;
538	};
539
540	var keyHandlerEscape = ui.keyHandler.escape;
541	ui.keyHandler.escape = function(evt)
542	{
543		if ((!mxClient.IS_MAC && mxEvent.isAltDown(evt)) ||
544			((mxClient.IS_MAC || chromeOs) && mxEvent.isControlDown(evt)))
545		{
546			if (speechOutputEnabled)
547			{
548				App.say('Speech output disabled');
549			}
550
551			speechOutputEnabled = !speechOutputEnabled;
552
553			if (speechOutputEnabled)
554			{
555				App.say('Speech output enabled');
556			}
557
558			mxEvent.consume(evt);
559		}
560		else if (mxEvent.isShiftDown(evt))
561		{
562			App.sayHint();
563			mxEvent.consume(evt);
564		}
565		else
566		{
567			keyHandlerEscape.apply(this, arguments);
568		}
569	};
570
571	ui.keyHandler.bindAction(32, true, 'speechListen'); // Ctrl+SPACE
572	ui.keyHandler.bindAction(79, true, 'speechListenContinuous'); // Ctrl+O
573
574	/**
575	 * Plays a beep.
576	 */
577	var beep = new Audio('data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=');
578	var beep2 = new Audio('data:audio/wav;base64,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');
579
580	App.beep = function(wav)
581	{
582		wav = (wav != null) ? wav : beep;
583
584    	wav.play();
585	};
586
587	// Thread to reset the label
588	var resetStatus = null;
589
590	/**
591	 *
592	 * Static method for speech output.
593	 */
594	App.say = function(message, params)
595	{
596		if ('speechSynthesis' in window && message != null)
597		{
598			var text = mxResources.replacePlaceholders(message, params || []);
599			lastMessageTimestamp = null;
600			lastMessage = text;
601
602			if (text != null && text.length > 0)
603			{
604				if (td != null)
605				{
606					var tmp = text;
607
608		    		// Capitalize string
609		    		if (tmp != null && tmp.length > 1)
610		    		{
611		    			tmp = tmp.charAt(0).toUpperCase() + tmp.slice(1);
612		    		}
613
614					td.innerHTML = ((speechOutputEnabled) ? '<img style="margin-right:4px;" align="absmiddle" border="0" src="' +
615							outputImage + '"/>' : '') + ' ' + tmp;
616		    		td.style.color = '#235695';
617
618					if (resetStatus != null)
619					{
620						window.clearTimeout(resetStatus);
621						resetStatus = null;
622					}
623
624					resetStatus = window.setTimeout(function()
625					{
626						updateStatusMessage();
627					}, (recognizing != null) ? 1000 : 3000);
628				}
629
630				// Workaround for talking too much
631				if (speechOutputEnabled && (!speechSynthesis.speaking || !speechSynthesis.pending))
632				{
633					if (text.length < maxMessageLength)
634					{
635						var msg = new SpeechSynthesisUtterance();
636
637						// Picks random voice with same main locale
638						var voices = speechSynthesis.getVoices();
639
640						// Say "again" for same last message except more than 10 secs ago or shorter than again
641						if (lastMessageTimestamp != null && text == lastMessage && text != null &&
642							text.length > 5 && lastMessage != 'again')
643						{
644							if (lastMessageTimestamp != null &&
645								new Date().getTime() - lastMessageTimestamp.getTime() < 10000)
646							{
647								text = 'repeat';
648							}
649						}
650
651						msg.voice = voices[currentVoice];
652						msg.voiceURI = 'native';
653						//msg.lang = lang;
654						msg.text = text;
655
656						console.log('App.say speak:', msg.text);
657						speechSynthesis.speak(msg);
658
659						lastMessageTimestamp = new Date();
660					}
661					else
662					{
663						console.log('App.say ignored:', text);
664					}
665				}
666				else
667				{
668					console.log('App.say skipped:', message);
669				}
670			}
671		}
672	};
673
674	/**
675	 * Static method for speech output.
676	 */
677	App.listen = function(continuous)
678	{
679		if ('webkitSpeechRecognition' in window && speechInputEnabled)
680		{
681			if (recognizing != null)
682			{
683				recognizing.stop();
684			}
685			else
686			{
687				var recognition = new webkitSpeechRecognition();
688				recognition.interimResults = true;
689
690				// TODO: Should use grammar instead of trying more alternatives
691				recognition.maxAlternatives = 5;
692
693				recognition.lang = lang;
694
695				if (continuous != null)
696				{
697					recognition.continuous = continuous;
698				}
699
700				recognition.onstart = function(event)
701				{
702					updateStatusMessage();
703					App.beep();
704				};
705
706				recognition.onresult = function(event)
707				{
708				    for (var i = event.resultIndex; i < event.results.length; ++i)
709				    {
710			    		if (td != null)
711			    		{
712			    			td.innerHTML = '<img style="margin-right:4px;" align="absmiddle" border="0" src="' + micImage +
713			    				'"/> ' + event.results[i][0].transcript;
714			    			td.style.color = (event.results[i].isFinal) ? '#235695' : 'darkGray';
715			    		}
716
717				    	if (event.results[i].isFinal)
718				    	{
719				    		var ok = false;
720
721				    		for (var j = 0; j < event.results[i].length; j++)
722				    		{
723				    			if (App.executeVoiceCommand(event.results[i][j].transcript, recognition))
724				    			{
725				    				ok = true;
726				    				break;
727				    			}
728				    		}
729
730				    		if (!ok)
731				    		{
732								App.say('{1} not found', [event.results[i][0].transcript]);
733				    		}
734
735							if (td != null)
736							{
737								if (resetStatus != null)
738								{
739									window.clearTimeout(resetStatus);
740									resetStatus = null;
741								}
742
743								resetStatus = window.setTimeout(function()
744								{
745									updateStatusMessage();
746								}, 1000);
747							}
748				    	}
749				    }
750				};
751
752				recognition.onend = function(event)
753				{
754					// Overrides footer
755					recognizing = null;
756					updateStatusMessage();
757					App.beep(beep2);
758				};
759
760				recognition.start();
761				recognizing = recognition;
762			}
763		}
764		else
765		{
766			speechOutputEnabled = !speechOutputEnabled;
767			lastMessageTimestamp = null;
768			App.say(lastMessage || 'Ready');
769			lastMessageTimestamp = null;
770		}
771	};
772
773	/**
774	 * Executes the given voice command.
775	 */
776	App.executeVoiceCommand = function(command, recognition)
777	{
778	    console.log('App.execute:', mxUtils.trim(command));
779	    var tokens = mxUtils.trim(command).split(' ');
780
781	    if (tokens.length > 0 && graph.isEnabled())
782	    {
783		    // Ask for Mic permissions
784		    // FIXME: Dialog seems to be hidden until tab is changed
785		    function resolveStylename(token, styles)
786		    {
787				var tmp = token.toLowerCase().replace(/ /g, '');
788		    	var style = null;
789
790		    	for (var i = 0; i < styles.length; i++)
791		    	{
792		    		if (styles[i].toLowerCase() == tmp)
793		    		{
794		    			style = styles[i];
795		    			break;
796		    		}
797		    	}
798
799		    	return style;
800		    };
801
802		    // Main command
803		    tokens[0] = tokens[0].toLowerCase();
804
805		    // TODO: Use hamming distance for best match command but include all possible actions
806		    // which might be too slow
807		    // console.log('connect', naiveHammingDistance(tokens[0], 'connect'), naiveHammingDistance('disable', 'connect'), naiveHammingDistance('change', 'connect'));
808
809		    if (graph.isEditing())
810		    {
811		    	if (tokens.length == 1 && tokens[0] == 'apply')
812			    {
813		    		graph.stopEditing();
814			    }
815		    	else if (tokens.length == 1 && tokens[0] == 'undo')
816			    {
817		    		document.execCommand('undo', false, null);
818			    }
819		    	else if (tokens.length == 1 && tokens[0] == 'redo')
820			    {
821		    		document.execCommand('redo', false, null);
822			    }
823		    	else
824		    	{
825		    		document.execCommand('insertHTML', false, command);
826		    	}
827
828		    	return true;
829		    }
830		    else if (tokens[0] == 'edit' && tokens[1] == 'text')
831		    {
832				var cells = graph.getSelectionCells();
833
834		    	if (cells.length == 1)
835		    	{
836		    		graph.startEditingAtCell(cells[0]);
837		    	}
838
839		    	return true;
840		    }
841		    else if (tokens[0] == 'hello' || tokens[0] == 'hi')
842			{
843				App.say('Hello! Try "Help", "Help Topic" or "Quick Start".');
844
845		    	return true;
846			}
847			else if (tokens[0] == 'help')
848			{
849				var wnd = ui.openLink('https://www.diagrams.net/doc/faq/voice-plugin');
850
851				if (wnd == null)
852				{
853					App.say('Popup blocked');
854				}
855				else if (tokens.length > 1)
856				{
857					// Just used to check if popup windows are allowed
858					wnd.close();
859					var searchTerm = mxUtils.trim(command.substring(tokens[0].length));
860
861					if (searchTerm !=  null && searchTerm.length > 0)
862					{
863						ui.openLink('https://www.google.com/search?q=site%3Adiagrams.net+inurl%3A%2Fdoc%2Ffaq%2F+' +
864							encodeURIComponent(searchTerm));
865						App.say(command);
866					}
867				}
868				else
869				{
870					App.say('help');
871				}
872
873		    	return true;
874			}
875			else if ((tokens[0] == 'info' && tokens.length == 1) || mxUtils.trim(command).toLowerCase() == 'what\'s this')
876			{
877				App.sayHint();
878
879		    	return true;
880			}
881			else if (tokens[0] == 'install' && tokens.length == 1)
882			{
883				setPluginInstalled(true);
884				App.say('Installed');
885
886		    	return true;
887			}
888			else if (tokens[0] == 'uninstall' && tokens.length == 1)
889			{
890				setPluginInstalled(false);
891				App.say('Uninstalled');
892
893		    	return true;
894			}
895			else if (tokens[0] == 'quick' && tokens.length > 0 && tokens[1].toLowerCase() == 'start')
896			{
897				var wnd = window.open('https://youtu.be/8OaMWa4R1SE?t=1');
898
899				if (wnd == null)
900				{
901					App.say('Popup blocked');
902				}
903
904		    	return true;
905			}
906			else if (tokens[0] == 'search' && tokens.length > 1)
907			{
908				var searchToken = tokens.slice(1, tokens.length).join(' ').toLowerCase();
909				var wnd = window.open('https://www.google.ch/search?q=' + encodeURIComponent(searchToken) + '&tbm=isch', 'voicePluginSearchResult');
910
911				if (wnd == null)
912				{
913					App.say('Popup blocked');
914				}
915
916		    	return true;
917			}
918			else if (tokens[0] == 'shrink')
919			{
920	    		var cell = graph.getSelectionCell();
921	    		var geo = graph.getCellGeometry(cell);
922
923		    	if (graph.getModel().isVertex(cell) && geo != null)
924		    	{
925		    		geo = geo.clone();
926
927		    		if (tokens.length == 1 || tokens[1] == 'height')
928		    		{
929		    			geo.height = graph.snap(Math.round(geo.height * 0.5));
930		    		}
931
932		    		if (tokens.length == 1 || tokens[1] != 'height')
933		    		{
934		    			geo.width = graph.snap(Math.round(geo.width * 0.5));
935		    		}
936
937		    		if (geo.width > graph.tolerance && geo.height > graph.tolerance)
938		    		{
939		    			graph.getModel().setGeometry(cell, geo);
940		    			App.say('Resized');
941		    		}
942		    		else
943			    	{
944			    		App.say('Too small');
945			    	}
946		    	}
947		    	else
948		    	{
949		    		App.say('No cell to resize');
950		    	}
951
952		    	return true;
953			}
954			else if (tokens[0] == 'double')
955			{
956	    		var cell = graph.getSelectionCell();
957	    		var geo = graph.getCellGeometry(cell);
958
959		    	if (graph.getModel().isVertex(cell) && geo != null)
960		    	{
961		    		geo = geo.clone();
962
963		    		if (tokens.length == 1 || tokens[1] == 'height')
964		    		{
965		    			geo.height *= 2;
966		    		}
967
968		    		if (tokens.length == 1 || tokens[1] != 'height')
969		    		{
970		    			geo.width *= 2;
971		    		}
972
973		    		if (geo.width > graph.tolerance && geo.height > graph.tolerance)
974		    		{
975		    			graph.getModel().setGeometry(cell, geo);
976		    			App.say('Resized');
977		    		}
978		    		else
979			    	{
980			    		App.say('Too small');
981			    	}
982		    	}
983		    	else
984		    	{
985		    		App.say('No cell to resize');
986		    	}
987
988		    	return true;
989			}
990			else if (tokens[0] == 'width' || tokens[0] == 'with' || tokens[0] == 'height')
991		    {
992				// Try numeric stylename if last token is numeric
993				var lastToken = tokens[tokens.length - 1].toLowerCase();
994
995				// Fixes some special cases for recoginition of short phrases
996				// like "stroke width 2" where it tries to be clever
997				if (lastToken == 'tool' || lastToken == 'tune' || lastToken == 'tomb' || lastToken == 'tube')
998				{
999					lastToken = '2';
1000				}
1001				else if (lastToken == 'd3')
1002				{
1003					lastToken = '3';
1004				}
1005				else if (lastToken == 'v')
1006				{
1007					lastToken = '5';
1008				}
1009				else
1010				{
1011					// Converts numeric words to numbers (system only seems to return written numbers for <= 4)
1012					var numbers = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
1013					var tmpIndex = mxUtils.indexOf(numbers, lastToken);
1014
1015					if (tmpIndex >= 0)
1016					{
1017						lastToken = tmpIndex;
1018					}
1019				}
1020
1021				var lastValue = parseFloat(tokens[1].toLowerCase());
1022
1023				if (tokens.length >= 2 && !isNaN(lastValue))
1024				{
1025		    		var cell = graph.getSelectionCell();
1026		    		var geo = graph.getCellGeometry(cell);
1027
1028			    	if (graph.getModel().isVertex(cell) && geo != null)
1029			    	{
1030			    		geo = geo.clone();
1031
1032			    		if (tokens[0] == 'height')
1033			    		{
1034			    			geo.height = lastValue;
1035			    		}
1036			    		else
1037			    		{
1038			    			geo.width = lastValue;
1039			    		}
1040
1041			    		graph.getModel().setGeometry(cell, geo);
1042			    		App.say('Resized');
1043			    	}
1044			    	else
1045			    	{
1046			    		App.say('No cell to resize');
1047			    	}
1048				}
1049
1050		    	return true;
1051		    }
1052			else if (tokens[0] == 'insert' && tokens.length > 1)
1053			{
1054				// Fixes some common mistakes
1055				if (tokens[1].toLowerCase() == 'edits')
1056				{
1057					tokens[1] = 'ellipse';
1058				}
1059
1060				var searchTerm = mxUtils.trim(tokens.slice(1, tokens.length).join(' '));
1061				var current = graph.getSelectionCell();
1062
1063				// Clears selection to disable built-in connecting
1064				graph.clearSelection();
1065
1066				insertShape(searchTerm, function()
1067				{
1068					var cell = graph.getSelectionCell();
1069
1070					// Connects dangling edge of previously selected edge and moves cell
1071					if (graph.model.isVertex(cell) && graph.model.isEdge(current) &&
1072						(graph.model.getTerminal(current, true) == null ||
1073						graph.model.getTerminal(current, false) == null))
1074					{
1075						var edgeState = graph.view.getState(current);
1076						var vertexState = graph.view.getState(cell);
1077
1078						if (vertexState != null && edgeState != null && edgeState.absolutePoints != null &&
1079							edgeState.absolutePoints.length > 1)
1080						{
1081							var source = graph.model.getTerminal(current, true) == null;
1082							var pts = edgeState.absolutePoints;
1083							var pt = pts[(source) ? 0 : pts.length - 1];
1084							var loc = new mxPoint(vertexState.getCenterX(), vertexState.getCenterY());
1085
1086							if (loc != null && edgeState != null)
1087							{
1088								var s = graph.view.scale;
1089								var dx = pt.x - loc.x;
1090								var dy = pt.y - loc.y;
1091
1092								// TODO: Should add insert to transaction but need absolute position
1093								graph.model.beginUpdate();
1094								try
1095								{
1096									graph.moveCells(graph.getSelectionCells(), dx / s, dy / s);
1097									graph.model.setTerminal(current, cell, source);
1098								}
1099								finally
1100								{
1101									graph.model.endUpdate();
1102								}
1103							}
1104						}
1105					}
1106				});
1107
1108		    	return true;
1109			}
1110			else if (tokens[0] == 'connect' || tokens[0] == 'kinect' || (tokens[0] == 'clone' && tokens.length > 1))
1111		    {
1112	    		var cell = graph.getSelectionCell();
1113
1114		    	if (graph.getModel().isVertex(cell))
1115		    	{
1116			    	// Uses east direction if token not understood
1117			    	var direction = mxConstants.DIRECTION_EAST;
1118
1119			    	if (tokens.length > 1)
1120			    	{
1121			    		tokens[1] = tokens[1].toLowerCase();
1122
1123			    		// Guessing direction based on minimum hamming distance for given set
1124			    		var guess = getBestWord(tokens[1], ['up', 'left', 'down', 'right', 'north', 'south', 'east', 'west']);
1125
1126				    	if (guess == 'up' || guess == mxConstants.DIRECTION_NORTH)
1127				    	{
1128				    		direction = mxConstants.DIRECTION_NORTH;
1129				    	}
1130				    	else if (guess == 'left' || guess == mxConstants.DIRECTION_WEST)
1131				    	{
1132				    		direction = mxConstants.DIRECTION_WEST;
1133				    	}
1134				    	else if (guess == 'down' || guess == mxConstants.DIRECTION_SOUTH)
1135				    	{
1136				    		direction = mxConstants.DIRECTION_SOUTH;
1137				    	}
1138		    		}
1139
1140			    	var length = graph.defaultEdgeLength;
1141			    	var cloneSource = tokens[0] == 'clone';
1142			    	var evt = mouseEvent('click', 1, 50, 1, 50, !cloneSource);
1143			    	var cells = graph.connectVertex(cell, direction, length, evt);
1144			    	graph.selectCellsForConnectVertex(cells, evt, ui.hoverIcons);
1145		    	}
1146
1147		    	return true;
1148		    }
1149			// Fixes drive and ride common mistakes for right
1150		    else if (mxUtils.indexOf(['and', 'drive', 'ride', 'move', 'up', 'left', 'down', 'right', 'north', 'south', 'east', 'west', 'downright'], tokens[0]) >= 0)
1151		    {
1152	    		var cell = graph.getSelectionCell();
1153
1154		    	if (!graph.isSelectionEmpty())
1155		    	{
1156			    	// Uses east direction if token not understood
1157			    	var dx = 0;
1158			    	var dy = 0;
1159
1160			    	for (var i = 0; i < tokens.length; i++)
1161			    	{
1162			    		tokens[i] = tokens[i].toLowerCase();
1163				    	var direction = null;
1164
1165				    	// Downright is a single word needs special handling
1166				    	if (tokens[i].toLowerCase() == 'downright')
1167				    	{
1168				    		dx += graph.defaultEdgeLength;
1169				    		dy += graph.defaultEdgeLength;
1170				    	}
1171				    	else
1172				    	{
1173				    		var guess = null;
1174
1175				    		// Guessing direction based on minimum hamming distance for given set
1176				    		// Handle some common cases
1177				    		if (tokens[i] == 'drive' || tokens[i] == 'ride')
1178				    		{
1179				    			guess = 'right';
1180				    		}
1181				    		else
1182				    		{
1183					    		guess = getBestWord(tokens[i], ['move', 'and', 'up', 'left', 'right', 'north', 'south', 'east', 'west', 'down', 'downright']);
1184				    		}
1185
1186				    		if (guess == 'up' || guess == mxConstants.DIRECTION_NORTH)
1187					    	{
1188					    		direction = mxConstants.DIRECTION_NORTH;
1189					    	}
1190					    	else if (guess == 'left' || guess == mxConstants.DIRECTION_WEST)
1191					    	{
1192					    		direction = mxConstants.DIRECTION_WEST;
1193					    	}
1194					    	else if (guess == 'down' || guess == mxConstants.DIRECTION_SOUTH)
1195					    	{
1196					    		direction = mxConstants.DIRECTION_SOUTH;
1197					    	}
1198					    	else if (guess == 'right' || guess == mxConstants.DIRECTION_EAST)
1199					    	{
1200					    		direction = mxConstants.DIRECTION_EAST;
1201					    	}
1202
1203					    	if (direction != null)
1204					    	{
1205						    	if (direction == mxConstants.DIRECTION_NORTH)
1206						    	{
1207						    		dy += -graph.defaultEdgeLength;
1208						    	}
1209						    	else if (direction == mxConstants.DIRECTION_WEST)
1210						    	{
1211						    		dx += -graph.defaultEdgeLength;
1212						    	}
1213						    	else if (direction == mxConstants.DIRECTION_SOUTH)
1214						    	{
1215						    		dy += graph.defaultEdgeLength;
1216						    	}
1217						    	else if (direction == mxConstants.DIRECTION_EAST)
1218						    	{
1219						    		dx += graph.defaultEdgeLength;
1220						    	}
1221					    	}
1222				    	}
1223			    	}
1224
1225			    	if (dx != 0 || dy != null)
1226			    	{
1227			    		graph.moveCells(graph.getSelectionCells(), dx, dy);
1228			    		App.say('Moved');
1229			    	}
1230
1231			    	return true;
1232		    	}
1233		    }
1234		    else if (tokens[0] == 'text')
1235		    {
1236				var cells = graph.getSelectionCells();
1237
1238		    	if (cells.length == 0 && graph.model.contains(lastInserted))
1239		    	{
1240		    		cells = [lastInserted];
1241		    	}
1242
1243		    	if (cells.length == 0)
1244		    	{
1245					App.say('No cell for text');
1246		    	}
1247		    	else if (tokens[0].length >= 2)
1248				{
1249		    		var value = tokens.slice(1, tokens.length).join(' ');
1250
1251		    		if (value.length > 0)
1252		    		{
1253			    		// Capitalize string
1254			    		if (value.length > 1)
1255			    		{
1256			    			value = value.charAt(0).toUpperCase() + value.slice(1);
1257			    		}
1258
1259		    			graph.labelChanged(cells[0], value);
1260		    		}
1261				}
1262
1263		    	return true;
1264		    }
1265		    else if (tokens[0] == 'disconnect' && tokens.length == 1)
1266		    {
1267				var cells = graph.getAllEdges(graph.getSelectionCells());
1268
1269		    	if (cells.length == 0)
1270		    	{
1271					App.say('No cell to disconnect');
1272		    	}
1273		    	else
1274		    	{
1275		    		graph.removeCells(cells);
1276		    	}
1277
1278		    	return true;
1279		    }
1280		    else if (tokens[0] == 'deselect' && tokens.length == 1)
1281		    {
1282		    	graph.clearSelection();
1283
1284		    	return true;
1285		    }
1286		    else if (tokens[0] === 'select' && (tokens.length == 1 || mxUtils.indexOf(
1287		    	['vertices', 'edges', 'none', 'all'], tokens[1].toLowerCase()) < 0))
1288			{
1289		    	// Handles some other shortcuts
1290		    	if (tokens.length == 1 || mxUtils.indexOf(['last'], tokens[1].toLowerCase()) >= 0)
1291		    	{
1292		    		if (graph.model.contains(lastInserted))
1293		    		{
1294		    			graph.setSelectionCell(lastInserted);
1295		    			App.say('{1} selected', [graph.getWordForCell(lastInserted).replace(/([A-Z])/g, ' $1')]);
1296				    	return true;
1297		    		}
1298		    	}
1299		    	// Handles alternative cases of edges
1300		    	else if (mxUtils.indexOf(['connection', 'connections', 'inches'], tokens[1].toLowerCase()) >= 0)
1301		    	{
1302					var edges = graph.getAllEdges(graph.getSelectionCells());
1303
1304			    	if (edges.length > 0)
1305			    	{
1306			    		graph.setSelectionCells(edges);
1307			    	}
1308			    	else
1309			    	{
1310			    		ui.actions.get('selectEdges').funct();
1311			    	}
1312
1313		    		App.sayHint();
1314			    	return true;
1315		    	}
1316		    	// Handles alternative version of vertices
1317		    	else if (mxUtils.indexOf(['shapes'], tokens[1].toLowerCase()) >= 0)
1318		    	{
1319		    		ui.actions.get('selectVertices').funct();
1320		    		App.sayHint();
1321			    	return true;
1322		    	}
1323		    	else if (mxUtils.indexOf(['previous'], tokens[1].toLowerCase()) >= 0)
1324		    	{
1325		    		if (graph.isSelectionEmpty())
1326		    		{
1327		    			// Selects the first vertex
1328		    			var parent = graph.getDefaultParent();
1329		    			var childCount = graph.model.getChildCount(parent);
1330
1331		    			for (var i = childCount - 1; i >= 0; i--)
1332		    			{
1333		    				var child = graph.model.getChildAt(parent, i);
1334
1335		    				if (graph.model.isVertex(child))
1336		    				{
1337		    					graph.setSelectionCell(child);
1338		    					App.say('{1} selected', [graph.getWordForCell(child).replace(/([A-Z])/g, ' $1')]);
1339
1340		    					return true;
1341		    				}
1342		    			}
1343		    		}
1344		    		else
1345		    		{
1346			    		var cell = graph.getSelectionCell();
1347			    		var model = graph.getModel();
1348			    		var index = model.getParent(cell).getIndex(cell);
1349			    		var childCount = model.getChildCount(model.getParent(cell));
1350
1351			    		if (index >= 0)
1352			    		{
1353			    			var next = model.getParent(cell).getChildAt(((index == 0) ? childCount : index) - 1);
1354
1355			    			if (next != null)
1356			    			{
1357			    				graph.setSelectionCell(next);
1358			    				App.say('{1} selected', [graph.getWordForCell(next)]);
1359
1360			    				return true;
1361			    			}
1362			    		}
1363
1364			    		App.say('Previous not found');
1365		    		}
1366		    	}
1367		    	else if (mxUtils.indexOf(['source', 'target'], tokens[1].toLowerCase()) >= 0)
1368		    	{
1369		    		var cell = graph.getSelectionCell();
1370
1371		    		if (graph.model.isEdge(cell))
1372		    		{
1373		    			var terminal = graph.model.getTerminal(cell, tokens[1].toLowerCase() == 'source');
1374
1375		    			if (terminal != null)
1376		    			{
1377		    				graph.setSelectionCell(terminal);
1378		    		    	return true;
1379		    			}
1380		    		}
1381		    	}
1382		    	else if (mxUtils.indexOf(['next'], tokens[1].toLowerCase()) >= 0)
1383		    	{
1384		    		if (graph.isSelectionEmpty())
1385		    		{
1386		    			// Selects the first vertex
1387		    			var parent = graph.getDefaultParent();
1388		    			var childCount = graph.model.getChildCount(parent);
1389
1390		    			for (var i = 0; i < childCount; i++)
1391		    			{
1392		    				var child = graph.model.getChildAt(parent, i);
1393
1394		    				if (graph.model.isVertex(child))
1395		    				{
1396		    					graph.setSelectionCell(child);
1397		    					App.say('{1} selected', [graph.getWordForCell(child).replace(/([A-Z])/g, ' $1')]);
1398
1399		    					return true;
1400		    				}
1401		    			}
1402		    		}
1403		    		else
1404		    		{
1405			    		var cell = graph.getSelectionCell();
1406			    		var model = graph.getModel();
1407			    		var index = model.getParent(cell).getIndex(cell);
1408			    		var childCount = model.getChildCount(model.getParent(cell));
1409
1410			    		if (index < childCount)
1411			    		{
1412			    			var next = model.getParent(cell).getChildAt(((index == childCount - 1) ? 0 : index + 1));
1413
1414			    			if (next != null)
1415			    			{
1416			    				graph.setSelectionCell(next);
1417			    				App.say('{1} selected', [graph.getWordForCell(next).replace(/([A-Z])/g, ' $1')]);
1418
1419			    				return true;
1420			    			}
1421			    		}
1422		    		}
1423		    	}
1424		    	else
1425		    	{
1426			    	var states = graph.view.states.getValues();
1427			    	var token = tokens[1].toLowerCase();
1428			    	var searchToken = tokens.slice(1, tokens.length).join('').toLowerCase();
1429
1430			    	for (var i = states.length - 1; i > 0; i--)
1431			    	{
1432			    		// Simple matching for shape name or label
1433				    	if (!graph.isCellSelected(states[i].cell))
1434				    	{
1435				    		// Tries label search first
1436				    		var lab = graph.getLabel(states[i].cell);
1437
1438				    		if (lab != null && lab.length > 0)
1439				    		{
1440				    			lab = lab.toLowerCase().replace(/ /g, '');
1441
1442						    	// Some common mistakes
1443						    	if (lab.charAt(lab.length - 1) == '2' && tokens[tokens.length - 1].toLowerCase() == 'to')
1444						    	{
1445						    		lab = lab.substring(0, lab.length - 1) + 'to';
1446						    	}
1447
1448				    			var min = Math.min(searchToken.length, lab.length);
1449
1450				    			if (searchToken.substring(0, min) == lab.substring(0, min))
1451				    			{
1452				    				graph.setSelectionCell(states[i].cell);
1453					    			App.say('{1} selected', [graph.getWordForCell(states[i].cell)]);
1454
1455					    			return true;
1456				    			}
1457				    		}
1458
1459				    		// Then tries shapename search
1460					    	if (tokens.length == 2)
1461					    	{
1462						    	// Some common names
1463						    	if (mxUtils.indexOf(['circle'], token) >= 0)
1464						    	{
1465						    		searchToken = 'ellipse';
1466						    	}
1467						    	else if (mxUtils.indexOf(['condition', 'decision'], token) >= 0)
1468						    	{
1469						    		searchToken = 'rhombus';
1470						    	}
1471						    	else if (mxUtils.indexOf(['preparation'], token) >= 0)
1472						    	{
1473						    		searchToken = 'hexagon';
1474						    	}
1475						    	else if (mxUtils.indexOf(['trapez'], token) >= 0)
1476						    	{
1477						    		searchToken = 'parallelogram';
1478						    	}
1479					    	}
1480
1481				    		var wrd = graph.getWordForCell(states[i].cell, true);
1482				    		var min = Math.min(wrd.length, searchToken.length);
1483
1484				    		if (searchToken.substring(0, min) == wrd.substring(0, min))
1485				    		{
1486				    			graph.setSelectionCell(states[i].cell);
1487				    			App.say('{1} selected', [graph.getWordForCell(states[i].cell)]);
1488
1489				    			return true;
1490				    		}
1491				    	}
1492			    	}
1493		    	}
1494			}
1495		    else if (tokens[0] === 'remove')
1496			{
1497				var cells = graph.getSelectionCells();
1498
1499		    	if (cells.length == 0 && graph.model.contains(lastInserted))
1500		    	{
1501		    		cells = [lastInserted];
1502		    	}
1503
1504		    	if (cells.length == 0)
1505		    	{
1506					App.say('No cells');
1507		    	}
1508		    	else if (tokens[1].toLowerCase() == 'text')
1509				{
1510	    			graph.labelChanged(cells[0], '');
1511				}
1512				else
1513			    {
1514					// Remove style
1515					var styleToken = mxUtils.trim(tokens.slice(1, tokens.length).join(' ').toLowerCase());
1516
1517					// Fixes common recognition errors
1518					styleToken = styleToken.replace(/a line/g, 'align')
1519
1520					// Merges to single word
1521					styleToken = styleToken.replace(/ /g, '')
1522
1523			    	// Fixes common speech errors that make no sense in the context
1524	    			var keys = [mxConstants.STYLE_FILLCOLOR, mxConstants.STYLE_GRADIENTCOLOR,
1525	    			               mxConstants.STYLE_STROKECOLOR, mxConstants.STYLE_FONTCOLOR,
1526	    			               mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, mxConstants.STYLE_LABEL_BORDERCOLOR,
1527	    			        mxConstants.STYLE_STROKEWIDTH, mxConstants.STYLE_FONTSIZE, mxConstants.STYLE_SPACING,
1528	    		            mxConstants.STYLE_SPACING_TOP, mxConstants.STYLE_SPACING_LEFT, mxConstants.STYLE_SPACING_RIGHT,
1529	    		            mxConstants.STYLE_SPACING_BOTTOM, mxConstants.STYLE_PERIMETER_SPACING,
1530	    		            mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, mxConstants.STYLE_OPACITY,
1531	    		            mxConstants.STYLE_TEXT_OPACITY, mxConstants.STYLE_ROTATION,
1532	    		            mxConstants.STYLE_ALIGN, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.STYLE_FONTFAMILY,
1533	    			        mxConstants.STYLE_LABEL_POSITION, mxConstants.STYLE_VERTICAL_LABEL_POSITION,
1534	    			        mxConstants.STYLE_GRADIENT_DIRECTION];
1535
1536		    		// Guesses best word
1537		    		var	style = getBestWord(styleToken, keys);
1538
1539		    		if (style != null)
1540			    	{
1541			    		graph.setCellStyles(style, null, cells);
1542			    		ui.fireEvent(new mxEventObject('styleChanged', 'keys', [style], 'values', [null], 'cells', cells));
1543			    	}
1544			    	else
1545			    	{
1546			    		App.say('Unknown style {1}', [changeTokens[0]]);
1547			    	}
1548			    }
1549
1550		    	return true;
1551			}
1552			else
1553			{
1554				var cells = graph.getSelectionCells();
1555
1556		    	if (cells.length == 0 && graph.model.contains(lastInserted))
1557		    	{
1558		    		cells = [lastInserted];
1559		    	}
1560
1561				// Try numeric stylename if last token is numeric
1562				var lastToken = tokens[tokens.length - 1].toLowerCase();
1563
1564				// Fixes some special cases for recoginition of short phrases
1565				// like "stroke width 2" where it tries to be clever
1566				if (lastToken == 'tool' || lastToken == 'tune' || lastToken == 'tomb' || lastToken == 'tube')
1567				{
1568					lastToken = '2';
1569				}
1570				else if (lastToken == 'd3')
1571				{
1572					lastToken = '3';
1573				}
1574				else if (lastToken == 'v')
1575				{
1576					lastToken = '5';
1577				}
1578				else
1579				{
1580					// Converts numeric words to numbers (system only seems to return written numbers for <= 4)
1581					var numbers = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
1582					var tmpIndex = mxUtils.indexOf(numbers, lastToken);
1583
1584					if (tmpIndex >= 0)
1585					{
1586						lastToken = tmpIndex;
1587					}
1588				}
1589
1590				// If last token is numeric
1591				var lastValue = parseFloat(lastToken);
1592
1593				if (tokens.length >= 2 && !isNaN(lastValue))
1594				{
1595					var styleToken = tokens.slice(0, tokens.length - 1).join('').toLowerCase();
1596
1597					// Numeric styles
1598		    		var keys = [mxConstants.STYLE_STROKEWIDTH, mxConstants.STYLE_FONTSIZE, mxConstants.STYLE_SPACING,
1599		    		            mxConstants.STYLE_SPACING_TOP, mxConstants.STYLE_SPACING_LEFT, mxConstants.STYLE_SPACING_RIGHT,
1600		    		            mxConstants.STYLE_SPACING_BOTTOM, mxConstants.STYLE_PERIMETER_SPACING,
1601		    		            mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, mxConstants.STYLE_OPACITY,
1602		    		            mxConstants.STYLE_TEXT_OPACITY, mxConstants.STYLE_ROTATION]
1603					var style = null;
1604
1605					// Workaround for problem with "font size" is to say "size" ("font" is near "phone")
1606					if (styleToken == 'size')
1607					{
1608						style = mxConstants.STYLE_FONTSIZE;
1609					}
1610					else
1611					{
1612						// Guesses best word
1613						style = getBestWord(styleToken, keys);
1614					}
1615
1616					if (style != null)
1617			    	{
1618						if (cells.length == 0)
1619				    	{
1620							App.say('No cell for {1}', [style]);
1621				    	}
1622						// Plausibility check
1623						else if (lastValue <= 400)
1624						{
1625							graph.setCellStyles(style, lastValue, cells);
1626			    			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [style], 'values', [lastValue], 'cells', cells));
1627						}
1628						else
1629						{
1630							App.say('{1} ignored', [lastValue]);
1631						}
1632
1633		    			// Terminate
1634		    			return true;
1635			    	}
1636				}
1637
1638				// If "color" appears in the command
1639				if (tokens.length >= 2)
1640				{
1641					// Replaces GB spelling with US spelling for internal lookups
1642					var colorCommand = mxUtils.trim(command.toLowerCase().replace(/colour/g, 'color'));
1643					var colorIndex = colorCommand.indexOf('color');
1644
1645					// "Color" (word alone) matches to fill color
1646					if (colorIndex >= 0)
1647					{
1648						// Text from first space after color* word
1649						var colorToken = mxUtils.trim(colorCommand.substring(
1650							colorCommand.indexOf(' ', colorIndex))).replace(/ /g, '');
1651						var color = null;
1652
1653						// Checks for color code of token using ntc
1654				    	for (var i = 0; i < ntc.names.length; i++)
1655				    	{
1656				    		if (ntc.names[i][1].toLowerCase().replace(/ /g, '') == colorToken)
1657				    		{
1658				    			color = '#' + ntc.names[i][0];
1659				    			break;
1660				    		}
1661				    	}
1662
1663				    	// Color is known
1664				    	if (color != null)
1665				    	{
1666				    		var styleToken = colorCommand.substring(0, colorIndex + 'color'.length).replace(/ /g, '');
1667
1668				    		keys = [mxConstants.STYLE_GRADIENTCOLOR,
1669				    		        mxConstants.STYLE_STROKECOLOR, mxConstants.STYLE_FONTCOLOR,
1670				    		        mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, mxConstants.STYLE_LABEL_BORDERCOLOR,
1671				    		        mxConstants.STYLE_FILLCOLOR];
1672				    		var style = null;
1673
1674				    		// Handles some special cases
1675				    		if (styleToken == 'thecolor' || styleToken == 'color')
1676				    		{
1677				    			if (graph.model.isEdge(graph.getSelectionCell()))
1678				    			{
1679				    				style = mxConstants.STYLE_STROKECOLOR;
1680				    			}
1681				    			else
1682				    			{
1683				    				style = mxConstants.STYLE_FILLCOLOR;
1684				    			}
1685				    		}
1686				    		else
1687				    		{
1688				    			// Guesses best word
1689				    			style = getBestWord(styleToken, keys);
1690				    		}
1691
1692				    		if (style != null)
1693				    		{
1694								if (cells.length == 0)
1695						    	{
1696									App.say('No cell for {1}', [style]);
1697						    	}
1698								else
1699								{
1700									graph.setCellStyles(style, color, cells);
1701					    			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [style], 'values', [color], 'cells', cells));
1702								}
1703
1704				    			// Terminate
1705				    			return true;
1706				    		}
1707				    	}
1708					}
1709				}
1710
1711				// If the first token is a general stylename
1712				if (tokens.length >= 2)
1713				{
1714					var keys = [mxConstants.STYLE_ALIGN, mxConstants.STYLE_VERTICAL_ALIGN,
1715					        mxConstants.STYLE_FONTFAMILY, mxConstants.STYLE_LABEL_POSITION,
1716					        mxConstants.STYLE_VERTICAL_LABEL_POSITION,
1717				            mxConstants.STYLE_GRADIENT_DIRECTION];
1718
1719					var styleToken = mxUtils.trim(tokens.slice(0, tokens.length - 1).join(' ').toLowerCase());
1720
1721					// Fixes common recognition errors
1722					styleToken = styleToken.replace(/a line/g, 'align')
1723
1724					// Merges to single word
1725					styleToken = styleToken.replace(/ /g, '')
1726
1727					var style = resolveStylename(styleToken, keys);
1728					var value = mxUtils.trim(tokens[tokens.length - 1].toLowerCase());
1729
1730					if (style != null && value != null && value.length > 0)
1731					{
1732						if (cells.length == 0)
1733				    	{
1734							App.say('No cell for {1}', [style]);
1735				    	}
1736						else
1737						{
1738							graph.setCellStyles(style, value, cells);
1739			    			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [style], 'values', [value], 'cells', cells));
1740						}
1741
1742		    			// Terminate
1743		    			return true;
1744					}
1745				}
1746
1747				// Checks if the command is a shape name
1748				var vertices = [];
1749
1750				for (var i = 0; i < cells.length; i++)
1751				{
1752					var cell = cells[i];
1753
1754					if (graph.model.isVertex(cell))
1755					{
1756						vertices.push(cell);
1757					}
1758				}
1759
1760		    	var shapenameToken = mxUtils.trim(tokens.join('')).toLowerCase();
1761
1762		    	// Searches for registered shape names
1763		    	// LATER: Using search like insert requires access to
1764		    	// the cells of the search result items
1765		    	if (shapeList == null)
1766		    	{
1767		    		shapeList = [];
1768
1769		    		for (var tmp in mxCellRenderer.defaultShapes)
1770		    		{
1771		    			shapeList.push(tmp.toLowerCase());
1772		    		}
1773		    	}
1774
1775		    	// Only exact matches allowed here
1776		    	// LATER: Support rounded style
1777		    	var shape = null;
1778
1779		    	// Some common mappings
1780		    	if (mxUtils.indexOf(['cirlce', 'event', 'start', 'end'], shapenameToken) >= 0)
1781		    	{
1782		    		shapenameToken = shape = 'ellipse';
1783		    	}
1784		    	else if (mxUtils.indexOf(['condition', 'decision'], shapenameToken) >= 0)
1785		    	{
1786		    		shapenameToken = shape = 'rhombus';
1787		    	}
1788		    	else if (mxUtils.indexOf(['preparation'], shapenameToken) >= 0)
1789		    	{
1790		    		shapenameToken = shape = 'hexagon';
1791		    	}
1792		    	else if (mxUtils.indexOf(['trapez'], shapenameToken) >= 0)
1793		    	{
1794		    		shapenameToken = shape = 'parallelogram';
1795		    	}
1796		    	else
1797		    	{
1798		    		shape = getBestWord(shapenameToken, shapeList);
1799		    	}
1800
1801		    	// LATER: Ignore all edge shapes
1802	    		if (shape != null && shape.toLowerCase() == shapenameToken && shape != 'connector' &&
1803	    			shape != 'arrow' && shape != 'flexarrow' && shape != 'arrowconnector' &&
1804	    			shape != 'link')
1805		    	{
1806			    	if (cells.length == 0)
1807			    	{
1808			    		App.say('No cell for {1}', [shape]);
1809			    	}
1810			    	else if (vertices.length == 0)
1811				{
1812			    		App.say('Connections ignored');
1813				}
1814			    	else
1815				{
1816				    	// LATER: Add perimeter style etc
1817			    		graph.setCellStyles(mxConstants.STYLE_SHAPE, shapenameToken, vertices);
1818			    		ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_SHAPE],
1819			    			'values', [shapenameToken], 'cells', vertices));
1820
1821			    		// Terminate
1822			    		return true;
1823				}
1824		    	}
1825
1826				// Lazy creation of cache for action names
1827				if (actions == null)
1828				{
1829					actions = {};
1830					actionList = [];
1831
1832					for (var name in ui.actions.actions)
1833					{
1834						var shortname = mxUtils.trim(name).toLowerCase().replace(/ /g, '');
1835						actions[shortname] = ui.actions.actions[name];
1836						actionList.push(shortname);
1837					}
1838				}
1839
1840				var name = mxUtils.trim(command).toLowerCase().replace(/ /g, '');
1841
1842				if (urlParams['dev'] == '1')
1843				{
1844					var guess = getBestWord(name, actionList, true);
1845					console.log('App.say guess:', name, '=>', guess, '(' +
1846						naiveHammingDistance(name, guess) + ', ' +
1847						levenshteinDist(name, guess) +
1848						')');
1849				}
1850
1851				// Some common redirects and mistakes
1852				if (tokens[0] == 'back')
1853				{
1854					command = name = 'undo';
1855				}
1856				else if (tokens[0] == 'restore')
1857				{
1858					command = name = 'redo';
1859				}
1860				else if (tokens[0] == 'clone')
1861				{
1862					command = name = 'duplicate';
1863				}
1864				else if (mxUtils.indexOf(['all', 'small', 'wall', 'call'], name) >= 0)
1865				{
1866					command = name = 'selectall';
1867				}
1868				else if (mxUtils.indexOf(['both', 'boat', 'phone', 'don\'t', 'food', 'bolt', 'bull'], tokens[0]) >= 0)
1869				{
1870					command = name = 'bold';
1871				}
1872
1873				var action = actions[name];
1874
1875				if (action != null)
1876				{
1877					if (action.isEnabled())
1878					{
1879						App.say(name);
1880						action.funct();
1881					}
1882					else
1883					{
1884						// Cannot use name here as it has no spaces
1885						App.say('{1} disabled', [command]);
1886					}
1887
1888					return true;
1889				}
1890
1891				return false;
1892			}
1893	    }
1894	};
1895
1896	if ('speechSynthesis' in window)
1897	{
1898		// Shows dialog for mic access
1899		if (navigator.webkitGetUserMedia)
1900		{
1901			window.addEventListener('load', function()
1902			{
1903				navigator.webkitGetUserMedia({audio:true}, function()
1904				{
1905	    			//console.log('access to mic ok');
1906	    		}, function(e)
1907	    		{
1908	    			//alert('Error getting audio');
1909	    			console.log(e);
1910	    		});
1911	        });
1912		}
1913
1914		// Installs helper methods and listeners for speech output
1915		var graph = ui.editor.graph;
1916
1917		App.reloadVoicePlugin = function()
1918		{
1919	    	// Shows UI
1920			speechOutputEnabled = true;
1921	    	menu.style.display = '';
1922	    	td.style.display = 'inline-block';
1923		};
1924
1925		App.sayHint = function()
1926		{
1927			if (graph.getSelectionCount() == 0)
1928			{
1929				App.say('No cell selected.' + ((ui.isDiagramEmpty()) ? ' Diagram is empty.' : ''));
1930			}
1931			else if (graph.getSelectionCount() == 1)
1932			{
1933				var cell = graph.getSelectionCell();
1934
1935				if (graph.getModel().isVertex(cell))
1936				{
1937					var geo = graph.getCellGeometry(cell)
1938
1939					if (geo != null)
1940					{
1941						App.say('{1} at {2} and {3} is {4} times {5} pixels', [graph.getWordForCell(cell, true),
1942						                                                       geo.x, geo.y, geo.width, geo.height]);
1943					}
1944				}
1945				else
1946				{
1947					App.say('One connection selected');
1948				}
1949
1950				var lab = mxUtils.trim(graph.getLabel(cell));
1951
1952				if (lab != null && lab.length > 0 && lab.length < 20)
1953				{
1954					App.say('Label is {1}', [lab]);
1955				}
1956			}
1957			else
1958			{
1959				var cells = graph.getSelectionCells();
1960				var vertexCount = 0;
1961				var edgeCount = 0;
1962
1963				for (var i = 0; i < cells.length; i++)
1964				{
1965					var cell = cells[i];
1966
1967					if (graph.model.isEdge(cell))
1968					{
1969						edgeCount++;
1970					}
1971					else if (graph.model.isVertex(cell))
1972					{
1973						vertexCount++;
1974					}
1975				}
1976
1977				if (vertexCount > 0 && edgeCount > 0)
1978				{
1979					var tmp = '';
1980
1981					if (vertexCount == 1)
1982					{
1983						tmp += 'One shape';
1984					}
1985					else
1986					{
1987						tmp += '{1} shapes';
1988					}
1989
1990					tmp += ' and ';
1991
1992					if (edgeCount == 1)
1993					{
1994						tmp += 'one connection';
1995					}
1996					else
1997					{
1998						tmp += '{2} connections';
1999					}
2000
2001					App.say(tmp + ' selected', [vertexCount, edgeCount]);
2002				}
2003				else if (vertexCount > 0)
2004				{
2005					App.say('{1} shapes selected', [vertexCount]);
2006				}
2007				else
2008				{
2009					App.say('{1} connections selected', [edgeCount]);
2010				}
2011			}
2012		};
2013
2014		// Can return more than first word
2015		function firstWord(value)
2016		{
2017			// TODO Use regex
2018			if (value != null && value.length > maxLabelLength)
2019			{
2020				var space = value.indexOf(' ');
2021
2022				// Add second word if label is short
2023				var tmp = value.indexOf(' ', space + 1);
2024
2025				if (tmp >= 0 && tmp <= maxLabelLength)
2026				{
2027					space = tmp;
2028
2029					// Add third word if label is short
2030					var tmp = value.indexOf(' ', space + 1);
2031
2032					if (tmp >= 0 && tmp <= maxLabelLength)
2033					{
2034						space = tmp;
2035					}
2036				}
2037
2038				if (space >= 0)
2039				{
2040					value = value.substring(0, space);
2041				}
2042			}
2043
2044			return value;
2045		};
2046
2047		graph.getWordForCell = function(cell, ignoreLabel)
2048		{
2049			var label = 'no';
2050
2051			if (cell != null)
2052			{
2053				label = (ignoreLabel) ? null : firstWord(this.getLabel(cell));
2054
2055				if (label == null || label.length == 0 || label.length > maxLabelLength || mxUtils.isNumeric(label))
2056				{
2057					var style = this.getCurrentCellStyle(cell);
2058					var tmp = style[mxConstants.STYLE_SHAPE];
2059
2060					if (tmp == 'label')
2061					{
2062						label = 'rectangle';
2063					}
2064					else if (tmp != null)
2065					{
2066						var dot = tmp.lastIndexOf('.');
2067
2068						if (dot >= 0)
2069						{
2070							tmp = tmp.substring(dot + 1);
2071						}
2072
2073						label = tmp
2074					}
2075				}
2076
2077				var div = document.createElement('div');
2078				div.innerHTML = label;
2079				label = div.innerText;
2080			}
2081
2082			return label;
2083		};
2084
2085		ui.addListener('styleChanged', function(sender, evt)
2086		{
2087			//console.log('styleChanged', evt, evt.getProperty('keys'));
2088			var cells = evt.getProperty('cells');
2089			var keys = evt.getProperty('keys');
2090			var values = evt.getProperty('values');
2091
2092			if (cells != null && keys != null && keys.length == 1 && values.length == 1)
2093			{
2094				var tmp = values[0];
2095
2096				if (typeof tmp === 'string' && tmp.charAt(0) == '#')
2097				{
2098					var n_match  = ntc.name(tmp);
2099
2100					//if (n_match[2]) /* exact match */
2101					{
2102						tmp = n_match[1];
2103					}
2104				}
2105
2106				if (tmp == mxConstants.NONE || tmp == null)
2107				{
2108					App.say('Removed {1}', [keys[0]]);
2109				}
2110				else
2111				{
2112					// Replaces camel case notation with spaces
2113					var key = keys[0].replace(/([A-Z])/g, ' $1')
2114					App.say('{1} {2}', [key, tmp]);
2115				}
2116			}
2117		});
2118		graph.addListener(mxEvent.LABEL_CHANGED, function(sender, evt)
2119		{
2120			//console.log('CELL_CONNECTED', evt);
2121			var cell = evt.getProperty('cell');
2122			var old = evt.getProperty('old');
2123
2124			// Resolves placeholders in new label
2125			var value = this.getLabel(cell);
2126
2127			if ((value != null && value.length > 20) ||
2128				(old != null && old.length > 20))
2129			{
2130				App.say('Label changed');
2131			}
2132			else if (value != null && value.length == 0)
2133			{
2134				App.say('Label removed');
2135			}
2136			else if (value != null)
2137			{
2138				App.say('{1}', [value]);
2139			}
2140		});
2141
2142		graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)
2143		{
2144//				console.log('CELL_CONNECTED', evt);
2145//				var edge = evt.getProperty('edge');
2146//				var terminal = evt.getProperty('terminal');
2147//				var other = graph.model.getTerminal(edge, !evt.getProperty('source'));
2148//
2149//				if (other != null && terminal != null)
2150//				{
2151//					App.say('connected {1} to {2}', [getWordForCell(other), getWordForCell(terminal)]);
2152//				}
2153		});
2154
2155		graph.addListener(mxEvent.CELLS_ADDED, function(sender, evt)
2156		{
2157			//console.log('CELLS_ADDED', evt);
2158			var cells = evt.getProperty('cells');
2159
2160			if (cells.length > 1)
2161			{
2162				var vertexCount = 0;
2163				var edgeCount = 0;
2164
2165				for (var i = 0; i < cells.length; i++)
2166				{
2167					var cell = cells[i];
2168
2169					if (graph.model.isEdge(cell))
2170					{
2171						edgeCount++;
2172					}
2173					else if (graph.model.isVertex(cell))
2174					{
2175						vertexCount++;
2176					}
2177				}
2178
2179				if (vertexCount > 0 && edgeCount > 0)
2180				{
2181					var tmp = '';
2182
2183					if (vertexCount == 1)
2184					{
2185						tmp += 'One shape';
2186					}
2187					else
2188					{
2189						tmp += '{1} shapes';
2190					}
2191
2192					tmp += ' and ';
2193
2194					if (edgeCount == 1)
2195					{
2196						tmp += 'one connection';
2197					}
2198					else
2199					{
2200						tmp += '{2} connections';
2201					}
2202
2203					App.say(tmp + ' inserted', [vertexCount, edgeCount]);
2204				}
2205				else if (vertexCount > 0)
2206				{
2207					App.say('{1} shapes', [vertexCount]);
2208				}
2209				else
2210				{
2211					App.say('{1} connections', [edgeCount]);
2212				}
2213			}
2214			else
2215			{
2216				cell = cells[0];
2217
2218				if (graph.model.isEdge(cell) && graph.model.getTerminal(cell, true) != null &&
2219					graph.model.getTerminal(cell, false) != null)
2220				{
2221					if (graph.getWordForCell(graph.model.getTerminal(cell, true)) ==
2222						graph.getWordForCell(graph.model.getTerminal(cell, false)))
2223					{
2224						App.say('Connected');
2225					}
2226					else
2227					{
2228						App.say('{1} connected to {2}', [graph.getWordForCell(graph.model.getTerminal(cell, true)),
2229					                                 graph.getWordForCell(graph.model.getTerminal(cell, false))]);
2230					}
2231				}
2232				else if (graph.model.isVertex(cell))
2233				{
2234					lastInserted = cell;
2235
2236					// Replaces camel case notation with spaces
2237					var word = graph.getWordForCell(cell).replace(/([A-Z])/g, ' $1');
2238
2239					App.say('{1}', [word]);
2240				}
2241			}
2242		});
2243		graph.addListener(mxEvent.CELLS_REMOVED, function(sender, evt)
2244		{
2245			//console.log('CELLS_REMOVED', evt);
2246			var cells = evt.getProperty('cells');
2247
2248			if (cells.length > 1)
2249			{
2250				App.say('{1} cells deleted', [cells.length]);
2251			}
2252			else
2253			{
2254				cell = cells[0];
2255				App.say('{1} deleted', [graph.getWordForCell(cell)]);
2256			}
2257		});
2258	}
2259});
2260
2261/** Code for color to voice based on ntc (name that color)
2262
2263/*
2264
2265+-----------------------------------------------------------------+
2266|     Created by Chirag Mehta - http://chir.ag/projects/ntc       |
2267|-----------------------------------------------------------------|
2268|               ntc js (Name that Color JavaScript)               |
2269+-----------------------------------------------------------------+
2270
2271All the functions, code, lists etc. have been written specifically
2272for the Name that Color JavaScript by Chirag Mehta unless otherwise
2273specified.
2274
2275This script is released under the: Creative Commons License:
2276Attribution 2.5 http://creativecommons.org/licenses/by/2.5/
2277
2278Sample Usage:
2279
2280  <script type="text/javascript" src="ntc.js"></script>
2281
2282  <script type="text/javascript">
2283
2284    var n_match  = ntc.name("#6195ED");
2285    n_rgb        = n_match[0]; // This is the RGB value of the closest matching color
2286    n_name       = n_match[1]; // This is the text string for the name of the match
2287    n_exactmatch = n_match[2]; // True if exact color match, False if close-match
2288
2289    alert(n_match);
2290
2291  </script>
2292
2293*/
2294
2295var ntc = {
2296
2297  init: function() {
2298    var color, rgb, hsl;
2299    for(var i = 0; i < ntc.names.length; i++)
2300    {
2301      color = "#" + ntc.names[i][0];
2302      rgb = ntc.rgb(color);
2303      hsl = ntc.hsl(color);
2304      ntc.names[i].push(rgb[0], rgb[1], rgb[2], hsl[0], hsl[1], hsl[2]);
2305    }
2306  },
2307
2308  name: function(color) {
2309
2310    color = color.toUpperCase();
2311    if(color.length < 3 || color.length > 7)
2312      return ["#000000", "Invalid Color: " + color, false];
2313    if(color.length % 3 == 0)
2314      color = "#" + color;
2315    if(color.length == 4)
2316      color = "#" + color.substr(1, 1) + color.substr(1, 1) + color.substr(2, 1) + color.substr(2, 1) + color.substr(3, 1) + color.substr(3, 1);
2317
2318    var rgb = ntc.rgb(color);
2319    var r = rgb[0], g = rgb[1], b = rgb[2];
2320    var hsl = ntc.hsl(color);
2321    var h = hsl[0], s = hsl[1], l = hsl[2];
2322    var ndf1 = 0; ndf2 = 0; ndf = 0;
2323    var cl = -1, df = -1;
2324
2325    for(var i = 0; i < ntc.names.length; i++)
2326    {
2327      if(color == "#" + ntc.names[i][0])
2328        return ["#" + ntc.names[i][0], ntc.names[i][1], true];
2329
2330      ndf1 = Math.pow(r - ntc.names[i][2], 2) + Math.pow(g - ntc.names[i][3], 2) + Math.pow(b - ntc.names[i][4], 2);
2331      ndf2 = Math.pow(h - ntc.names[i][5], 2) + Math.pow(s - ntc.names[i][6], 2) + Math.pow(l - ntc.names[i][7], 2);
2332      ndf = ndf1 + ndf2 * 2;
2333      if(df < 0 || df > ndf)
2334      {
2335        df = ndf;
2336        cl = i;
2337      }
2338    }
2339
2340    return (cl < 0 ? ["#000000", "Invalid Color: " + color, false] : ["#" + ntc.names[cl][0], ntc.names[cl][1], false]);
2341  },
2342
2343  // adopted from: Farbtastic 1.2
2344  // http://acko.net/dev/farbtastic
2345  hsl: function (color) {
2346
2347    var rgb = [parseInt('0x' + color.substring(1, 3)) / 255, parseInt('0x' + color.substring(3, 5)) / 255, parseInt('0x' + color.substring(5, 7)) / 255];
2348    var min, max, delta, h, s, l;
2349    var r = rgb[0], g = rgb[1], b = rgb[2];
2350
2351    min = Math.min(r, Math.min(g, b));
2352    max = Math.max(r, Math.max(g, b));
2353    delta = max - min;
2354    l = (min + max) / 2;
2355
2356    s = 0;
2357    if(l > 0 && l < 1)
2358      s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
2359
2360    h = 0;
2361    if(delta > 0)
2362    {
2363      if (max == r && max != g) h += (g - b) / delta;
2364      if (max == g && max != b) h += (2 + (b - r) / delta);
2365      if (max == b && max != r) h += (4 + (r - g) / delta);
2366      h /= 6;
2367    }
2368    return [parseInt(h * 255), parseInt(s * 255), parseInt(l * 255)];
2369  },
2370
2371  // adopted from: Farbtastic 1.2
2372  // http://acko.net/dev/farbtastic
2373  rgb: function(color) {
2374    return [parseInt('0x' + color.substring(1, 3)), parseInt('0x' + color.substring(3, 5)),  parseInt('0x' + color.substring(5, 7))];
2375  },
2376
2377  names: [
2378
2379 // Added all standard HTML color names / gaudenz, oct-2015
2380["F0F8FF", "aliceblue"],
2381["FAEBD7", "antiquewhite"],
2382["00FFFF", "aqua"],
2383["7FFFD4", "aquamarine"],
2384["F0FFFF", "azure"],
2385["F5F5DC", "beige"],
2386["FFE4C4", "bisque"],
2387["000000", "black"],
2388["FFEBCD", "blanchedalmond"],
2389["0000FF", "blue"],
2390["8A2BE2", "blueviolet"],
2391["A52A2A", "brown"],
2392["DEB887", "burlywood"],
2393["5F9EA0", "cadetblue"],
2394["7FFF00", "chartreuse"],
2395["D2691E", "chocolate"],
2396["FF7F50", "coral"],
2397["6495ED", "cornflowerblue"],
2398["FFF8DC", "cornsilk"],
2399["DC143C", "crimson"],
2400["00FFFF", "cyan"],
2401["00008B", "darkblue"],
2402["008B8B", "darkcyan"],
2403["B8860B", "darkgoldenrod"],
2404["A9A9A9", "darkgray"],
2405["A9A9A9", "darkgrey"],
2406["006400", "darkgreen"],
2407["BDB76B", "darkkhaki"],
2408["8B008B", "darkmagenta"],
2409["556B2F", "darkolivegreen"],
2410["FF8C00", "darkorange"],
2411["9932CC", "darkorchid"],
2412["8B0000", "darkred"],
2413["E9967A", "darksalmon"],
2414["8FBC8F", "darkseagreen"],
2415["483D8B", "darkslateblue"],
2416["2F4F4F", "darkslategray"],
2417["2F4F4F", "darkslategrey"],
2418["00CED1", "darkturquoise"],
2419["9400D3", "darkviolet"],
2420["FF1493", "deeppink"],
2421["00BFFF", "deepskyblue"],
2422["696969", "dimgray"],
2423["696969", "dimgrey"],
2424["1E90FF", "dodgerblue"],
2425["B22222", "firebrick"],
2426["FFFAF0", "floralwhite"],
2427["228B22", "forestgreen"],
2428["FF00FF", "fuchsia"],
2429["DCDCDC", "gainsboro"],
2430["F8F8FF", "ghostwhite"],
2431["FFD700", "gold"],
2432["DAA520", "goldenrod"],
2433["808080", "gray"],
2434["808080", "grey"],
2435["008000", "green"],
2436["ADFF2F", "greenyellow"],
2437["F0FFF0", "honeydew"],
2438["FF69B4", "hotpink"],
2439["CD5C5C", "indianred "],
2440["4B0082", "indigo "],
2441["FFFFF0", "ivory"],
2442["F0E68C", "khaki"],
2443["E6E6FA", "lavender"],
2444["FFF0F5", "lavenderblush"],
2445["7CFC00", "lawngreen"],
2446["FFFACD", "lemonchiffon"],
2447["ADD8E6", "lightblue"],
2448["F08080", "lightcoral"],
2449["E0FFFF", "lightcyan"],
2450["FAFAD2", "lightgoldenrodyellow"],
2451["D3D3D3", "lightgray"],
2452["D3D3D3", "lightgrey"],
2453["90EE90", "lightgreen"],
2454["FFB6C1", "lightpink"],
2455["FFA07A", "lightsalmon"],
2456["20B2AA", "lightseagreen"],
2457["87CEFA", "lightskyblue"],
2458["778899", "lightslategray"],
2459["778899", "lightslategrey"],
2460["B0C4DE", "lightsteelblue"],
2461["FFFFE0", "lightyellow"],
2462["00FF00", "lime"],
2463["32CD32", "limegreen"],
2464["FAF0E6", "linen"],
2465["FF00FF", "magenta"],
2466["800000", "maroon"],
2467["66CDAA", "mediumaquamarine"],
2468["0000CD", "mediumblue"],
2469["BA55D3", "mediumorchid"],
2470["9370DB", "mediumpurple"],
2471["3CB371", "mediumseagreen"],
2472["7B68EE", "mediumslateblue"],
2473["00FA9A", "mediumspringgreen"],
2474["48D1CC", "mediumturquoise"],
2475["C71585", "mediumvioletred"],
2476["191970", "midnightblue"],
2477["F5FFFA", "mintcream"],
2478["FFE4E1", "mistyrose"],
2479["FFE4B5", "moccasin"],
2480["FFDEAD", "navajowhite"],
2481["000080", "navy"],
2482["FDF5E6", "oldlace"],
2483["808000", "olive"],
2484["6B8E23", "olivedrab"],
2485["FFA500", "orange"],
2486["FF4500", "orangered"],
2487["DA70D6", "orchid"],
2488["EEE8AA", "palegoldenrod"],
2489["98FB98", "palegreen"],
2490["AFEEEE", "paleturquoise"],
2491["DB7093", "palevioletred"],
2492["FFEFD5", "papayawhip"],
2493["FFDAB9", "peachpuff"],
2494["CD853F", "peru"],
2495["FFC0CB", "pink"],
2496["DDA0DD", "plum"],
2497["B0E0E6", "powderblue"],
2498["800080", "purple"],
2499["FF0000", "red"],
2500["BC8F8F", "rosybrown"],
2501["4169E1", "royalblue"],
2502["8B4513", "saddlebrown"],
2503["FA8072", "salmon"],
2504["F4A460", "sandybrown"],
2505["2E8B57", "seagreen"],
2506["FFF5EE", "seashell"],
2507["A0522D", "sienna"],
2508["C0C0C0", "silver"],
2509["87CEEB", "skyblue"],
2510["6A5ACD", "slateblue"],
2511["708090", "slategray"],
2512["708090", "slategrey"],
2513["FFFAFA", "snow"],
2514["00FF7F", "springgreen"],
2515["4682B4", "steelblue"],
2516["D2B48C", "tan"],
2517["008080", "teal"],
2518["D8BFD8", "thistle"],
2519["FF6347", "tomato"],
2520["40E0D0", "turquoise"],
2521["EE82EE", "violet"],
2522["F5DEB3", "wheat"],
2523["FFFFFF", "white"],
2524["F5F5F5", "whitesmoke"],
2525["FFFF00", "yellow"],
2526["9ACD32", "yellowgreen"],
2527
2528// Existing table
2529
2530["000000", "Black"],
2531["000080", "Navy Blue"],
2532["0000C8", "Dark Blue"],
2533["0000FF", "Blue"],
2534["000741", "Stratos"],
2535["001B1C", "Swamp"],
2536["002387", "Resolution Blue"],
2537["002900", "Deep Fir"],
2538["002E20", "Burnham"],
2539["002FA7", "International Klein Blue"],
2540["003153", "Prussian Blue"],
2541["003366", "Midnight Blue"],
2542["003399", "Smalt"],
2543["003532", "Deep Teal"],
2544["003E40", "Cyprus"],
2545["004620", "Kaitoke Green"],
2546["0047AB", "Cobalt"],
2547["004816", "Crusoe"],
2548["004950", "Sherpa Blue"],
2549["0056A7", "Endeavour"],
2550["00581A", "Camarone"],
2551["0066CC", "Science Blue"],
2552["0066FF", "Blue Ribbon"],
2553["00755E", "Tropical Rain Forest"],
2554["0076A3", "Allports"],
2555["007BA7", "Deep Cerulean"],
2556["007EC7", "Lochmara"],
2557["007FFF", "Azure Radiance"],
2558["008080", "Teal"],
2559["0095B6", "Bondi Blue"],
2560["009DC4", "Pacific Blue"],
2561["00A693", "Persian Green"],
2562["00A86B", "Jade"],
2563["00CC99", "Caribbean Green"],
2564["00CCCC", "Robin's Egg Blue"],
2565["00FF00", "Green"],
2566["00FF7F", "Spring Green"],
2567["00FFFF", "Cyan / Aqua"],
2568["010D1A", "Blue Charcoal"],
2569["011635", "Midnight"],
2570["011D13", "Holly"],
2571["012731", "Daintree"],
2572["01361C", "Cardin Green"],
2573["01371A", "County Green"],
2574["013E62", "Astronaut Blue"],
2575["013F6A", "Regal Blue"],
2576["014B43", "Aqua Deep"],
2577["015E85", "Orient"],
2578["016162", "Blue Stone"],
2579["016D39", "Fun Green"],
2580["01796F", "Pine Green"],
2581["017987", "Blue Lagoon"],
2582["01826B", "Deep Sea"],
2583["01A368", "Green Haze"],
2584["022D15", "English Holly"],
2585["02402C", "Sherwood Green"],
2586["02478E", "Congress Blue"],
2587["024E46", "Evening Sea"],
2588["026395", "Bahama Blue"],
2589["02866F", "Observatory"],
2590["02A4D3", "Cerulean"],
2591["03163C", "Tangaroa"],
2592["032B52", "Green Vogue"],
2593["036A6E", "Mosque"],
2594["041004", "Midnight Moss"],
2595["041322", "Black Pearl"],
2596["042E4C", "Blue Whale"],
2597["044022", "Zuccini"],
2598["044259", "Teal Blue"],
2599["051040", "Deep Cove"],
2600["051657", "Gulf Blue"],
2601["055989", "Venice Blue"],
2602["056F57", "Watercourse"],
2603["062A78", "Catalina Blue"],
2604["063537", "Tiber"],
2605["069B81", "Gossamer"],
2606["06A189", "Niagara"],
2607["073A50", "Tarawera"],
2608["080110", "Jaguar"],
2609["081910", "Black Bean"],
2610["082567", "Deep Sapphire"],
2611["088370", "Elf Green"],
2612["08E8DE", "Bright Turquoise"],
2613["092256", "Downriver"],
2614["09230F", "Palm Green"],
2615["09255D", "Madison"],
2616["093624", "Bottle Green"],
2617["095859", "Deep Sea Green"],
2618["097F4B", "Salem"],
2619["0A001C", "Black Russian"],
2620["0A480D", "Dark Fern"],
2621["0A6906", "Japanese Laurel"],
2622["0A6F75", "Atoll"],
2623["0B0B0B", "Cod Gray"],
2624["0B0F08", "Marshland"],
2625["0B1107", "Gordons Green"],
2626["0B1304", "Black Forest"],
2627["0B6207", "San Felix"],
2628["0BDA51", "Malachite"],
2629["0C0B1D", "Ebony"],
2630["0C0D0F", "Woodsmoke"],
2631["0C1911", "Racing Green"],
2632["0C7A79", "Surfie Green"],
2633["0C8990", "Blue Chill"],
2634["0D0332", "Black Rock"],
2635["0D1117", "Bunker"],
2636["0D1C19", "Aztec"],
2637["0D2E1C", "Bush"],
2638["0E0E18", "Cinder"],
2639["0E2A30", "Firefly"],
2640["0F2D9E", "Torea Bay"],
2641["10121D", "Vulcan"],
2642["101405", "Green Waterloo"],
2643["105852", "Eden"],
2644["110C6C", "Arapawa"],
2645["120A8F", "Ultramarine"],
2646["123447", "Elephant"],
2647["126B40", "Jewel"],
2648["130000", "Diesel"],
2649["130A06", "Asphalt"],
2650["13264D", "Blue Zodiac"],
2651["134F19", "Parsley"],
2652["140600", "Nero"],
2653["1450AA", "Tory Blue"],
2654["151F4C", "Bunting"],
2655["1560BD", "Denim"],
2656["15736B", "Genoa"],
2657["161928", "Mirage"],
2658["161D10", "Hunter Green"],
2659["162A40", "Big Stone"],
2660["163222", "Celtic"],
2661["16322C", "Timber Green"],
2662["163531", "Gable Green"],
2663["171F04", "Pine Tree"],
2664["175579", "Chathams Blue"],
2665["182D09", "Deep Forest Green"],
2666["18587A", "Blumine"],
2667["19330E", "Palm Leaf"],
2668["193751", "Nile Blue"],
2669["1959A8", "Fun Blue"],
2670["1A1A68", "Lucky Point"],
2671["1AB385", "Mountain Meadow"],
2672["1B0245", "Tolopea"],
2673["1B1035", "Haiti"],
2674["1B127B", "Deep Koamaru"],
2675["1B1404", "Acadia"],
2676["1B2F11", "Seaweed"],
2677["1B3162", "Biscay"],
2678["1B659D", "Matisse"],
2679["1C1208", "Crowshead"],
2680["1C1E13", "Rangoon Green"],
2681["1C39BB", "Persian Blue"],
2682["1C402E", "Everglade"],
2683["1C7C7D", "Elm"],
2684["1D6142", "Green Pea"],
2685["1E0F04", "Creole"],
2686["1E1609", "Karaka"],
2687["1E1708", "El Paso"],
2688["1E385B", "Cello"],
2689["1E433C", "Te Papa Green"],
2690["1E90FF", "Dodger Blue"],
2691["1E9AB0", "Eastern Blue"],
2692["1F120F", "Night Rider"],
2693["1FC2C2", "Java"],
2694["20208D", "Jacksons Purple"],
2695["202E54", "Cloud Burst"],
2696["204852", "Blue Dianne"],
2697["211A0E", "Eternity"],
2698["220878", "Deep Blue"],
2699["228B22", "Forest Green"],
2700["233418", "Mallard"],
2701["240A40", "Violet"],
2702["240C02", "Kilamanjaro"],
2703["242A1D", "Log Cabin"],
2704["242E16", "Black Olive"],
2705["24500F", "Green House"],
2706["251607", "Graphite"],
2707["251706", "Cannon Black"],
2708["251F4F", "Port Gore"],
2709["25272C", "Shark"],
2710["25311C", "Green Kelp"],
2711["2596D1", "Curious Blue"],
2712["260368", "Paua"],
2713["26056A", "Paris M"],
2714["261105", "Wood Bark"],
2715["261414", "Gondola"],
2716["262335", "Steel Gray"],
2717["26283B", "Ebony Clay"],
2718["273A81", "Bay of Many"],
2719["27504B", "Plantation"],
2720["278A5B", "Eucalyptus"],
2721["281E15", "Oil"],
2722["283A77", "Astronaut"],
2723["286ACD", "Mariner"],
2724["290C5E", "Violent Violet"],
2725["292130", "Bastille"],
2726["292319", "Zeus"],
2727["292937", "Charade"],
2728["297B9A", "Jelly Bean"],
2729["29AB87", "Jungle Green"],
2730["2A0359", "Cherry Pie"],
2731["2A140E", "Coffee Bean"],
2732["2A2630", "Baltic Sea"],
2733["2A380B", "Turtle Green"],
2734["2A52BE", "Cerulean Blue"],
2735["2B0202", "Sepia Black"],
2736["2B194F", "Valhalla"],
2737["2B3228", "Heavy Metal"],
2738["2C0E8C", "Blue Gem"],
2739["2C1632", "Revolver"],
2740["2C2133", "Bleached Cedar"],
2741["2C8C84", "Lochinvar"],
2742["2D2510", "Mikado"],
2743["2D383A", "Outer Space"],
2744["2D569B", "St Tropaz"],
2745["2E0329", "Jacaranda"],
2746["2E1905", "Jacko Bean"],
2747["2E3222", "Rangitoto"],
2748["2E3F62", "Rhino"],
2749["2E8B57", "Sea Green"],
2750["2EBFD4", "Scooter"],
2751["2F270E", "Onion"],
2752["2F3CB3", "Governor Bay"],
2753["2F519E", "Sapphire"],
2754["2F5A57", "Spectra"],
2755["2F6168", "Casal"],
2756["300529", "Melanzane"],
2757["301F1E", "Cocoa Brown"],
2758["302A0F", "Woodrush"],
2759["304B6A", "San Juan"],
2760["30D5C8", "Turquoise"],
2761["311C17", "Eclipse"],
2762["314459", "Pickled Bluewood"],
2763["315BA1", "Azure"],
2764["31728D", "Calypso"],
2765["317D82", "Paradiso"],
2766["32127A", "Persian Indigo"],
2767["32293A", "Blackcurrant"],
2768["323232", "Mine Shaft"],
2769["325D52", "Stromboli"],
2770["327C14", "Bilbao"],
2771["327DA0", "Astral"],
2772["33036B", "Christalle"],
2773["33292F", "Thunder"],
2774["33CC99", "Shamrock"],
2775["341515", "Tamarind"],
2776["350036", "Mardi Gras"],
2777["350E42", "Valentino"],
2778["350E57", "Jagger"],
2779["353542", "Tuna"],
2780["354E8C", "Chambray"],
2781["363050", "Martinique"],
2782["363534", "Tuatara"],
2783["363C0D", "Waiouru"],
2784["36747D", "Ming"],
2785["368716", "La Palma"],
2786["370202", "Chocolate"],
2787["371D09", "Clinker"],
2788["37290E", "Brown Tumbleweed"],
2789["373021", "Birch"],
2790["377475", "Oracle"],
2791["380474", "Blue Diamond"],
2792["381A51", "Grape"],
2793["383533", "Dune"],
2794["384555", "Oxford Blue"],
2795["384910", "Clover"],
2796["394851", "Limed Spruce"],
2797["396413", "Dell"],
2798["3A0020", "Toledo"],
2799["3A2010", "Sambuca"],
2800["3A2A6A", "Jacarta"],
2801["3A686C", "William"],
2802["3A6A47", "Killarney"],
2803["3AB09E", "Keppel"],
2804["3B000B", "Temptress"],
2805["3B0910", "Aubergine"],
2806["3B1F1F", "Jon"],
2807["3B2820", "Treehouse"],
2808["3B7A57", "Amazon"],
2809["3B91B4", "Boston Blue"],
2810["3C0878", "Windsor"],
2811["3C1206", "Rebel"],
2812["3C1F76", "Meteorite"],
2813["3C2005", "Dark Ebony"],
2814["3C3910", "Camouflage"],
2815["3C4151", "Bright Gray"],
2816["3C4443", "Cape Cod"],
2817["3C493A", "Lunar Green"],
2818["3D0C02", "Bean  "],
2819["3D2B1F", "Bistre"],
2820["3D7D52", "Goblin"],
2821["3E0480", "Kingfisher Daisy"],
2822["3E1C14", "Cedar"],
2823["3E2B23", "English Walnut"],
2824["3E2C1C", "Black Marlin"],
2825["3E3A44", "Ship Gray"],
2826["3EABBF", "Pelorous"],
2827["3F2109", "Bronze"],
2828["3F2500", "Cola"],
2829["3F3002", "Madras"],
2830["3F307F", "Minsk"],
2831["3F4C3A", "Cabbage Pont"],
2832["3F583B", "Tom Thumb"],
2833["3F5D53", "Mineral Green"],
2834["3FC1AA", "Puerto Rico"],
2835["3FFF00", "Harlequin"],
2836["401801", "Brown Pod"],
2837["40291D", "Cork"],
2838["403B38", "Masala"],
2839["403D19", "Thatch Green"],
2840["405169", "Fiord"],
2841["40826D", "Viridian"],
2842["40A860", "Chateau Green"],
2843["410056", "Ripe Plum"],
2844["411F10", "Paco"],
2845["412010", "Deep Oak"],
2846["413C37", "Merlin"],
2847["414257", "Gun Powder"],
2848["414C7D", "East Bay"],
2849["4169E1", "Royal Blue"],
2850["41AA78", "Ocean Green"],
2851["420303", "Burnt Maroon"],
2852["423921", "Lisbon Brown"],
2853["427977", "Faded Jade"],
2854["431560", "Scarlet Gum"],
2855["433120", "Iroko"],
2856["433E37", "Armadillo"],
2857["434C59", "River Bed"],
2858["436A0D", "Green Leaf"],
2859["44012D", "Barossa"],
2860["441D00", "Morocco Brown"],
2861["444954", "Mako"],
2862["454936", "Kelp"],
2863["456CAC", "San Marino"],
2864["45B1E8", "Picton Blue"],
2865["460B41", "Loulou"],
2866["462425", "Crater Brown"],
2867["465945", "Gray Asparagus"],
2868["4682B4", "Steel Blue"],
2869["480404", "Rustic Red"],
2870["480607", "Bulgarian Rose"],
2871["480656", "Clairvoyant"],
2872["481C1C", "Cocoa Bean"],
2873["483131", "Woody Brown"],
2874["483C32", "Taupe"],
2875["49170C", "Van Cleef"],
2876["492615", "Brown Derby"],
2877["49371B", "Metallic Bronze"],
2878["495400", "Verdun Green"],
2879["496679", "Blue Bayoux"],
2880["497183", "Bismark"],
2881["4A2A04", "Bracken"],
2882["4A3004", "Deep Bronze"],
2883["4A3C30", "Mondo"],
2884["4A4244", "Tundora"],
2885["4A444B", "Gravel"],
2886["4A4E5A", "Trout"],
2887["4B0082", "Pigment Indigo"],
2888["4B5D52", "Nandor"],
2889["4C3024", "Saddle"],
2890["4C4F56", "Abbey"],
2891["4D0135", "Blackberry"],
2892["4D0A18", "Cab Sav"],
2893["4D1E01", "Indian Tan"],
2894["4D282D", "Cowboy"],
2895["4D282E", "Livid Brown"],
2896["4D3833", "Rock"],
2897["4D3D14", "Punga"],
2898["4D400F", "Bronzetone"],
2899["4D5328", "Woodland"],
2900["4E0606", "Mahogany"],
2901["4E2A5A", "Bossanova"],
2902["4E3B41", "Matterhorn"],
2903["4E420C", "Bronze Olive"],
2904["4E4562", "Mulled Wine"],
2905["4E6649", "Axolotl"],
2906["4E7F9E", "Wedgewood"],
2907["4EABD1", "Shakespeare"],
2908["4F1C70", "Honey Flower"],
2909["4F2398", "Daisy Bush"],
2910["4F69C6", "Indigo"],
2911["4F7942", "Fern Green"],
2912["4F9D5D", "Fruit Salad"],
2913["4FA83D", "Apple"],
2914["504351", "Mortar"],
2915["507096", "Kashmir Blue"],
2916["507672", "Cutty Sark"],
2917["50C878", "Emerald"],
2918["514649", "Emperor"],
2919["516E3D", "Chalet Green"],
2920["517C66", "Como"],
2921["51808F", "Smalt Blue"],
2922["52001F", "Castro"],
2923["520C17", "Maroon Oak"],
2924["523C94", "Gigas"],
2925["533455", "Voodoo"],
2926["534491", "Victoria"],
2927["53824B", "Hippie Green"],
2928["541012", "Heath"],
2929["544333", "Judge Gray"],
2930["54534D", "Fuscous Gray"],
2931["549019", "Vida Loca"],
2932["55280C", "Cioccolato"],
2933["555B10", "Saratoga"],
2934["556D56", "Finlandia"],
2935["5590D9", "Havelock Blue"],
2936["56B4BE", "Fountain Blue"],
2937["578363", "Spring Leaves"],
2938["583401", "Saddle Brown"],
2939["585562", "Scarpa Flow"],
2940["587156", "Cactus"],
2941["589AAF", "Hippie Blue"],
2942["591D35", "Wine Berry"],
2943["592804", "Brown Bramble"],
2944["593737", "Congo Brown"],
2945["594433", "Millbrook"],
2946["5A6E9C", "Waikawa Gray"],
2947["5A87A0", "Horizon"],
2948["5B3013", "Jambalaya"],
2949["5C0120", "Bordeaux"],
2950["5C0536", "Mulberry Wood"],
2951["5C2E01", "Carnaby Tan"],
2952["5C5D75", "Comet"],
2953["5D1E0F", "Redwood"],
2954["5D4C51", "Don Juan"],
2955["5D5C58", "Chicago"],
2956["5D5E37", "Verdigris"],
2957["5D7747", "Dingley"],
2958["5DA19F", "Breaker Bay"],
2959["5E483E", "Kabul"],
2960["5E5D3B", "Hemlock"],
2961["5F3D26", "Irish Coffee"],
2962["5F5F6E", "Mid Gray"],
2963["5F6672", "Shuttle Gray"],
2964["5FA777", "Aqua Forest"],
2965["5FB3AC", "Tradewind"],
2966["604913", "Horses Neck"],
2967["605B73", "Smoky"],
2968["606E68", "Corduroy"],
2969["6093D1", "Danube"],
2970["612718", "Espresso"],
2971["614051", "Eggplant"],
2972["615D30", "Costa Del Sol"],
2973["61845F", "Glade Green"],
2974["622F30", "Buccaneer"],
2975["623F2D", "Quincy"],
2976["624E9A", "Butterfly Bush"],
2977["625119", "West Coast"],
2978["626649", "Finch"],
2979["639A8F", "Patina"],
2980["63B76C", "Fern"],
2981["6456B7", "Blue Violet"],
2982["646077", "Dolphin"],
2983["646463", "Storm Dust"],
2984["646A54", "Siam"],
2985["646E75", "Nevada"],
2986["6495ED", "Cornflower Blue"],
2987["64CCDB", "Viking"],
2988["65000B", "Rosewood"],
2989["651A14", "Cherrywood"],
2990["652DC1", "Purple Heart"],
2991["657220", "Fern Frond"],
2992["65745D", "Willow Grove"],
2993["65869F", "Hoki"],
2994["660045", "Pompadour"],
2995["660099", "Purple"],
2996["66023C", "Tyrian Purple"],
2997["661010", "Dark Tan"],
2998["66B58F", "Silver Tree"],
2999["66FF00", "Bright Green"],
3000["66FF66", "Screamin' Green"],
3001["67032D", "Black Rose"],
3002["675FA6", "Scampi"],
3003["676662", "Ironside Gray"],
3004["678975", "Viridian Green"],
3005["67A712", "Christi"],
3006["683600", "Nutmeg Wood Finish"],
3007["685558", "Zambezi"],
3008["685E6E", "Salt Box"],
3009["692545", "Tawny Port"],
3010["692D54", "Finn"],
3011["695F62", "Scorpion"],
3012["697E9A", "Lynch"],
3013["6A442E", "Spice"],
3014["6A5D1B", "Himalaya"],
3015["6A6051", "Soya Bean"],
3016["6B2A14", "Hairy Heath"],
3017["6B3FA0", "Royal Purple"],
3018["6B4E31", "Shingle Fawn"],
3019["6B5755", "Dorado"],
3020["6B8BA2", "Bermuda Gray"],
3021["6B8E23", "Olive Drab"],
3022["6C3082", "Eminence"],
3023["6CDAE7", "Turquoise Blue"],
3024["6D0101", "Lonestar"],
3025["6D5E54", "Pine Cone"],
3026["6D6C6C", "Dove Gray"],
3027["6D9292", "Juniper"],
3028["6D92A1", "Gothic"],
3029["6E0902", "Red Oxide"],
3030["6E1D14", "Moccaccino"],
3031["6E4826", "Pickled Bean"],
3032["6E4B26", "Dallas"],
3033["6E6D57", "Kokoda"],
3034["6E7783", "Pale Sky"],
3035["6F440C", "Cafe Royale"],
3036["6F6A61", "Flint"],
3037["6F8E63", "Highland"],
3038["6F9D02", "Limeade"],
3039["6FD0C5", "Downy"],
3040["701C1C", "Persian Plum"],
3041["704214", "Sepia"],
3042["704A07", "Antique Bronze"],
3043["704F50", "Ferra"],
3044["706555", "Coffee"],
3045["708090", "Slate Gray"],
3046["711A00", "Cedar Wood Finish"],
3047["71291D", "Metallic Copper"],
3048["714693", "Affair"],
3049["714AB2", "Studio"],
3050["715D47", "Tobacco Brown"],
3051["716338", "Yellow Metal"],
3052["716B56", "Peat"],
3053["716E10", "Olivetone"],
3054["717486", "Storm Gray"],
3055["718080", "Sirocco"],
3056["71D9E2", "Aquamarine Blue"],
3057["72010F", "Venetian Red"],
3058["724A2F", "Old Copper"],
3059["726D4E", "Go Ben"],
3060["727B89", "Raven"],
3061["731E8F", "Seance"],
3062["734A12", "Raw Umber"],
3063["736C9F", "Kimberly"],
3064["736D58", "Crocodile"],
3065["737829", "Crete"],
3066["738678", "Xanadu"],
3067["74640D", "Spicy Mustard"],
3068["747D63", "Limed Ash"],
3069["747D83", "Rolling Stone"],
3070["748881", "Blue Smoke"],
3071["749378", "Laurel"],
3072["74C365", "Mantis"],
3073["755A57", "Russett"],
3074["7563A8", "Deluge"],
3075["76395D", "Cosmic"],
3076["7666C6", "Blue Marguerite"],
3077["76BD17", "Lima"],
3078["76D7EA", "Sky Blue"],
3079["770F05", "Dark Burgundy"],
3080["771F1F", "Crown of Thorns"],
3081["773F1A", "Walnut"],
3082["776F61", "Pablo"],
3083["778120", "Pacifika"],
3084["779E86", "Oxley"],
3085["77DD77", "Pastel Green"],
3086["780109", "Japanese Maple"],
3087["782D19", "Mocha"],
3088["782F16", "Peanut"],
3089["78866B", "Camouflage Green"],
3090["788A25", "Wasabi"],
3091["788BBA", "Ship Cove"],
3092["78A39C", "Sea Nymph"],
3093["795D4C", "Roman Coffee"],
3094["796878", "Old Lavender"],
3095["796989", "Rum"],
3096["796A78", "Fedora"],
3097["796D62", "Sandstone"],
3098["79DEEC", "Spray"],
3099["7A013A", "Siren"],
3100["7A58C1", "Fuchsia Blue"],
3101["7A7A7A", "Boulder"],
3102["7A89B8", "Wild Blue Yonder"],
3103["7AC488", "De York"],
3104["7B3801", "Red Beech"],
3105["7B3F00", "Cinnamon"],
3106["7B6608", "Yukon Gold"],
3107["7B7874", "Tapa"],
3108["7B7C94", "Waterloo "],
3109["7B8265", "Flax Smoke"],
3110["7B9F80", "Amulet"],
3111["7BA05B", "Asparagus"],
3112["7C1C05", "Kenyan Copper"],
3113["7C7631", "Pesto"],
3114["7C778A", "Topaz"],
3115["7C7B7A", "Concord"],
3116["7C7B82", "Jumbo"],
3117["7C881A", "Trendy Green"],
3118["7CA1A6", "Gumbo"],
3119["7CB0A1", "Acapulco"],
3120["7CB7BB", "Neptune"],
3121["7D2C14", "Pueblo"],
3122["7DA98D", "Bay Leaf"],
3123["7DC8F7", "Malibu"],
3124["7DD8C6", "Bermuda"],
3125["7E3A15", "Copper Canyon"],
3126["7F1734", "Claret"],
3127["7F3A02", "Peru Tan"],
3128["7F626D", "Falcon"],
3129["7F7589", "Mobster"],
3130["7F76D3", "Moody Blue"],
3131["7FFF00", "Chartreuse"],
3132["7FFFD4", "Aquamarine"],
3133["800000", "Maroon"],
3134["800B47", "Rose Bud Cherry"],
3135["801818", "Falu Red"],
3136["80341F", "Red Robin"],
3137["803790", "Vivid Violet"],
3138["80461B", "Russet"],
3139["807E79", "Friar Gray"],
3140["808000", "Olive"],
3141["808080", "Gray"],
3142["80B3AE", "Gulf Stream"],
3143["80B3C4", "Glacier"],
3144["80CCEA", "Seagull"],
3145["81422C", "Nutmeg"],
3146["816E71", "Spicy Pink"],
3147["817377", "Empress"],
3148["819885", "Spanish Green"],
3149["826F65", "Sand Dune"],
3150["828685", "Gunsmoke"],
3151["828F72", "Battleship Gray"],
3152["831923", "Merlot"],
3153["837050", "Shadow"],
3154["83AA5D", "Chelsea Cucumber"],
3155["83D0C6", "Monte Carlo"],
3156["843179", "Plum"],
3157["84A0A0", "Granny Smith"],
3158["8581D9", "Chetwode Blue"],
3159["858470", "Bandicoot"],
3160["859FAF", "Bali Hai"],
3161["85C4CC", "Half Baked"],
3162["860111", "Red Devil"],
3163["863C3C", "Lotus"],
3164["86483C", "Ironstone"],
3165["864D1E", "Bull Shot"],
3166["86560A", "Rusty Nail"],
3167["868974", "Bitter"],
3168["86949F", "Regent Gray"],
3169["871550", "Disco"],
3170["87756E", "Americano"],
3171["877C7B", "Hurricane"],
3172["878D91", "Oslo Gray"],
3173["87AB39", "Sushi"],
3174["885342", "Spicy Mix"],
3175["886221", "Kumera"],
3176["888387", "Suva Gray"],
3177["888D65", "Avocado"],
3178["893456", "Camelot"],
3179["893843", "Solid Pink"],
3180["894367", "Cannon Pink"],
3181["897D6D", "Makara"],
3182["8A3324", "Burnt Umber"],
3183["8A73D6", "True V"],
3184["8A8360", "Clay Creek"],
3185["8A8389", "Monsoon"],
3186["8A8F8A", "Stack"],
3187["8AB9F1", "Jordy Blue"],
3188["8B00FF", "Electric Violet"],
3189["8B0723", "Monarch"],
3190["8B6B0B", "Corn Harvest"],
3191["8B8470", "Olive Haze"],
3192["8B847E", "Schooner"],
3193["8B8680", "Natural Gray"],
3194["8B9C90", "Mantle"],
3195["8B9FEE", "Portage"],
3196["8BA690", "Envy"],
3197["8BA9A5", "Cascade"],
3198["8BE6D8", "Riptide"],
3199["8C055E", "Cardinal Pink"],
3200["8C472F", "Mule Fawn"],
3201["8C5738", "Potters Clay"],
3202["8C6495", "Trendy Pink"],
3203["8D0226", "Paprika"],
3204["8D3D38", "Sanguine Brown"],
3205["8D3F3F", "Tosca"],
3206["8D7662", "Cement"],
3207["8D8974", "Granite Green"],
3208["8D90A1", "Manatee"],
3209["8DA8CC", "Polo Blue"],
3210["8E0000", "Red Berry"],
3211["8E4D1E", "Rope"],
3212["8E6F70", "Opium"],
3213["8E775E", "Domino"],
3214["8E8190", "Mamba"],
3215["8EABC1", "Nepal"],
3216["8F021C", "Pohutukawa"],
3217["8F3E33", "El Salva"],
3218["8F4B0E", "Korma"],
3219["8F8176", "Squirrel"],
3220["8FD6B4", "Vista Blue"],
3221["900020", "Burgundy"],
3222["901E1E", "Old Brick"],
3223["907874", "Hemp"],
3224["907B71", "Almond Frost"],
3225["908D39", "Sycamore"],
3226["92000A", "Sangria"],
3227["924321", "Cumin"],
3228["926F5B", "Beaver"],
3229["928573", "Stonewall"],
3230["928590", "Venus"],
3231["9370DB", "Medium Purple"],
3232["93CCEA", "Cornflower"],
3233["93DFB8", "Algae Green"],
3234["944747", "Copper Rust"],
3235["948771", "Arrowtown"],
3236["950015", "Scarlett"],
3237["956387", "Strikemaster"],
3238["959396", "Mountain Mist"],
3239["960018", "Carmine"],
3240["964B00", "Brown"],
3241["967059", "Leather"],
3242["9678B6", "Purple Mountain's Majesty"],
3243["967BB6", "Lavender Purple"],
3244["96A8A1", "Pewter"],
3245["96BBAB", "Summer Green"],
3246["97605D", "Au Chico"],
3247["9771B5", "Wisteria"],
3248["97CD2D", "Atlantis"],
3249["983D61", "Vin Rouge"],
3250["9874D3", "Lilac Bush"],
3251["98777B", "Bazaar"],
3252["98811B", "Hacienda"],
3253["988D77", "Pale Oyster"],
3254["98FF98", "Mint Green"],
3255["990066", "Fresh Eggplant"],
3256["991199", "Violet Eggplant"],
3257["991613", "Tamarillo"],
3258["991B07", "Totem Pole"],
3259["996666", "Copper Rose"],
3260["9966CC", "Amethyst"],
3261["997A8D", "Mountbatten Pink"],
3262["9999CC", "Blue Bell"],
3263["9A3820", "Prairie Sand"],
3264["9A6E61", "Toast"],
3265["9A9577", "Gurkha"],
3266["9AB973", "Olivine"],
3267["9AC2B8", "Shadow Green"],
3268["9B4703", "Oregon"],
3269["9B9E8F", "Lemon Grass"],
3270["9C3336", "Stiletto"],
3271["9D5616", "Hawaiian Tan"],
3272["9DACB7", "Gull Gray"],
3273["9DC209", "Pistachio"],
3274["9DE093", "Granny Smith Apple"],
3275["9DE5FF", "Anakiwa"],
3276["9E5302", "Chelsea Gem"],
3277["9E5B40", "Sepia Skin"],
3278["9EA587", "Sage"],
3279["9EA91F", "Citron"],
3280["9EB1CD", "Rock Blue"],
3281["9EDEE0", "Morning Glory"],
3282["9F381D", "Cognac"],
3283["9F821C", "Reef Gold"],
3284["9F9F9C", "Star Dust"],
3285["9FA0B1", "Santas Gray"],
3286["9FD7D3", "Sinbad"],
3287["9FDD8C", "Feijoa"],
3288["A02712", "Tabasco"],
3289["A1750D", "Buttered Rum"],
3290["A1ADB5", "Hit Gray"],
3291["A1C50A", "Citrus"],
3292["A1DAD7", "Aqua Island"],
3293["A1E9DE", "Water Leaf"],
3294["A2006D", "Flirt"],
3295["A23B6C", "Rouge"],
3296["A26645", "Cape Palliser"],
3297["A2AAB3", "Gray Chateau"],
3298["A2AEAB", "Edward"],
3299["A3807B", "Pharlap"],
3300["A397B4", "Amethyst Smoke"],
3301["A3E3ED", "Blizzard Blue"],
3302["A4A49D", "Delta"],
3303["A4A6D3", "Wistful"],
3304["A4AF6E", "Green Smoke"],
3305["A50B5E", "Jazzberry Jam"],
3306["A59B91", "Zorba"],
3307["A5CB0C", "Bahia"],
3308["A62F20", "Roof Terracotta"],
3309["A65529", "Paarl"],
3310["A68B5B", "Barley Corn"],
3311["A69279", "Donkey Brown"],
3312["A6A29A", "Dawn"],
3313["A72525", "Mexican Red"],
3314["A7882C", "Luxor Gold"],
3315["A85307", "Rich Gold"],
3316["A86515", "Reno Sand"],
3317["A86B6B", "Coral Tree"],
3318["A8989B", "Dusty Gray"],
3319["A899E6", "Dull Lavender"],
3320["A8A589", "Tallow"],
3321["A8AE9C", "Bud"],
3322["A8AF8E", "Locust"],
3323["A8BD9F", "Norway"],
3324["A8E3BD", "Chinook"],
3325["A9A491", "Gray Olive"],
3326["A9ACB6", "Aluminium"],
3327["A9B2C3", "Cadet Blue"],
3328["A9B497", "Schist"],
3329["A9BDBF", "Tower Gray"],
3330["A9BEF2", "Perano"],
3331["A9C6C2", "Opal"],
3332["AA375A", "Night Shadz"],
3333["AA4203", "Fire"],
3334["AA8B5B", "Muesli"],
3335["AA8D6F", "Sandal"],
3336["AAA5A9", "Shady Lady"],
3337["AAA9CD", "Logan"],
3338["AAABB7", "Spun Pearl"],
3339["AAD6E6", "Regent St Blue"],
3340["AAF0D1", "Magic Mint"],
3341["AB0563", "Lipstick"],
3342["AB3472", "Royal Heath"],
3343["AB917A", "Sandrift"],
3344["ABA0D9", "Cold Purple"],
3345["ABA196", "Bronco"],
3346["AC8A56", "Limed Oak"],
3347["AC91CE", "East Side"],
3348["AC9E22", "Lemon Ginger"],
3349["ACA494", "Napa"],
3350["ACA586", "Hillary"],
3351["ACA59F", "Cloudy"],
3352["ACACAC", "Silver Chalice"],
3353["ACB78E", "Swamp Green"],
3354["ACCBB1", "Spring Rain"],
3355["ACDD4D", "Conifer"],
3356["ACE1AF", "Celadon"],
3357["AD781B", "Mandalay"],
3358["ADBED1", "Casper"],
3359["ADDFAD", "Moss Green"],
3360["ADE6C4", "Padua"],
3361["ADFF2F", "Green Yellow"],
3362["AE4560", "Hippie Pink"],
3363["AE6020", "Desert"],
3364["AE809E", "Bouquet"],
3365["AF4035", "Medium Carmine"],
3366["AF4D43", "Apple Blossom"],
3367["AF593E", "Brown Rust"],
3368["AF8751", "Driftwood"],
3369["AF8F2C", "Alpine"],
3370["AF9F1C", "Lucky"],
3371["AFA09E", "Martini"],
3372["AFB1B8", "Bombay"],
3373["AFBDD9", "Pigeon Post"],
3374["B04C6A", "Cadillac"],
3375["B05D54", "Matrix"],
3376["B05E81", "Tapestry"],
3377["B06608", "Mai Tai"],
3378["B09A95", "Del Rio"],
3379["B0E0E6", "Powder Blue"],
3380["B0E313", "Inch Worm"],
3381["B10000", "Bright Red"],
3382["B14A0B", "Vesuvius"],
3383["B1610B", "Pumpkin Skin"],
3384["B16D52", "Santa Fe"],
3385["B19461", "Teak"],
3386["B1E2C1", "Fringy Flower"],
3387["B1F4E7", "Ice Cold"],
3388["B20931", "Shiraz"],
3389["B2A1EA", "Biloba Flower"],
3390["B32D29", "Tall Poppy"],
3391["B35213", "Fiery Orange"],
3392["B38007", "Hot Toddy"],
3393["B3AF95", "Taupe Gray"],
3394["B3C110", "La Rioja"],
3395["B43332", "Well Read"],
3396["B44668", "Blush"],
3397["B4CFD3", "Jungle Mist"],
3398["B57281", "Turkish Rose"],
3399["B57EDC", "Lavender"],
3400["B5A27F", "Mongoose"],
3401["B5B35C", "Olive Green"],
3402["B5D2CE", "Jet Stream"],
3403["B5ECDF", "Cruise"],
3404["B6316C", "Hibiscus"],
3405["B69D98", "Thatch"],
3406["B6B095", "Heathered Gray"],
3407["B6BAA4", "Eagle"],
3408["B6D1EA", "Spindle"],
3409["B6D3BF", "Gum Leaf"],
3410["B7410E", "Rust"],
3411["B78E5C", "Muddy Waters"],
3412["B7A214", "Sahara"],
3413["B7A458", "Husk"],
3414["B7B1B1", "Nobel"],
3415["B7C3D0", "Heather"],
3416["B7F0BE", "Madang"],
3417["B81104", "Milano Red"],
3418["B87333", "Copper"],
3419["B8B56A", "Gimblet"],
3420["B8C1B1", "Green Spring"],
3421["B8C25D", "Celery"],
3422["B8E0F9", "Sail"],
3423["B94E48", "Chestnut"],
3424["B95140", "Crail"],
3425["B98D28", "Marigold"],
3426["B9C46A", "Wild Willow"],
3427["B9C8AC", "Rainee"],
3428["BA0101", "Guardsman Red"],
3429["BA450C", "Rock Spray"],
3430["BA6F1E", "Bourbon"],
3431["BA7F03", "Pirate Gold"],
3432["BAB1A2", "Nomad"],
3433["BAC7C9", "Submarine"],
3434["BAEEF9", "Charlotte"],
3435["BB3385", "Medium Red Violet"],
3436["BB8983", "Brandy Rose"],
3437["BBD009", "Rio Grande"],
3438["BBD7C1", "Surf"],
3439["BCC9C2", "Powder Ash"],
3440["BD5E2E", "Tuscany"],
3441["BD978E", "Quicksand"],
3442["BDB1A8", "Silk"],
3443["BDB2A1", "Malta"],
3444["BDB3C7", "Chatelle"],
3445["BDBBD7", "Lavender Gray"],
3446["BDBDC6", "French Gray"],
3447["BDC8B3", "Clay Ash"],
3448["BDC9CE", "Loblolly"],
3449["BDEDFD", "French Pass"],
3450["BEA6C3", "London Hue"],
3451["BEB5B7", "Pink Swan"],
3452["BEDE0D", "Fuego"],
3453["BF5500", "Rose of Sharon"],
3454["BFB8B0", "Tide"],
3455["BFBED8", "Blue Haze"],
3456["BFC1C2", "Silver Sand"],
3457["BFC921", "Key Lime Pie"],
3458["BFDBE2", "Ziggurat"],
3459["BFFF00", "Lime"],
3460["C02B18", "Thunderbird"],
3461["C04737", "Mojo"],
3462["C08081", "Old Rose"],
3463["C0C0C0", "Silver"],
3464["C0D3B9", "Pale Leaf"],
3465["C0D8B6", "Pixie Green"],
3466["C1440E", "Tia Maria"],
3467["C154C1", "Fuchsia Pink"],
3468["C1A004", "Buddha Gold"],
3469["C1B7A4", "Bison Hide"],
3470["C1BAB0", "Tea"],
3471["C1BECD", "Gray Suit"],
3472["C1D7B0", "Sprout"],
3473["C1F07C", "Sulu"],
3474["C26B03", "Indochine"],
3475["C2955D", "Twine"],
3476["C2BDB6", "Cotton Seed"],
3477["C2CAC4", "Pumice"],
3478["C2E8E5", "Jagged Ice"],
3479["C32148", "Maroon Flush"],
3480["C3B091", "Indian Khaki"],
3481["C3BFC1", "Pale Slate"],
3482["C3C3BD", "Gray Nickel"],
3483["C3CDE6", "Periwinkle Gray"],
3484["C3D1D1", "Tiara"],
3485["C3DDF9", "Tropical Blue"],
3486["C41E3A", "Cardinal"],
3487["C45655", "Fuzzy Wuzzy Brown"],
3488["C45719", "Orange Roughy"],
3489["C4C4BC", "Mist Gray"],
3490["C4D0B0", "Coriander"],
3491["C4F4EB", "Mint Tulip"],
3492["C54B8C", "Mulberry"],
3493["C59922", "Nugget"],
3494["C5994B", "Tussock"],
3495["C5DBCA", "Sea Mist"],
3496["C5E17A", "Yellow Green"],
3497["C62D42", "Brick Red"],
3498["C6726B", "Contessa"],
3499["C69191", "Oriental Pink"],
3500["C6A84B", "Roti"],
3501["C6C3B5", "Ash"],
3502["C6C8BD", "Kangaroo"],
3503["C6E610", "Las Palmas"],
3504["C7031E", "Monza"],
3505["C71585", "Red Violet"],
3506["C7BCA2", "Coral Reef"],
3507["C7C1FF", "Melrose"],
3508["C7C4BF", "Cloud"],
3509["C7C9D5", "Ghost"],
3510["C7CD90", "Pine Glade"],
3511["C7DDE5", "Botticelli"],
3512["C88A65", "Antique Brass"],
3513["C8A2C8", "Lilac"],
3514["C8A528", "Hokey Pokey"],
3515["C8AABF", "Lily"],
3516["C8B568", "Laser"],
3517["C8E3D7", "Edgewater"],
3518["C96323", "Piper"],
3519["C99415", "Pizza"],
3520["C9A0DC", "Light Wisteria"],
3521["C9B29B", "Rodeo Dust"],
3522["C9B35B", "Sundance"],
3523["C9B93B", "Earls Green"],
3524["C9C0BB", "Silver Rust"],
3525["C9D9D2", "Conch"],
3526["C9FFA2", "Reef"],
3527["C9FFE5", "Aero Blue"],
3528["CA3435", "Flush Mahogany"],
3529["CABB48", "Turmeric"],
3530["CADCD4", "Paris White"],
3531["CAE00D", "Bitter Lemon"],
3532["CAE6DA", "Skeptic"],
3533["CB8FA9", "Viola"],
3534["CBCAB6", "Foggy Gray"],
3535["CBD3B0", "Green Mist"],
3536["CBDBD6", "Nebula"],
3537["CC3333", "Persian Red"],
3538["CC5500", "Burnt Orange"],
3539["CC7722", "Ochre"],
3540["CC8899", "Puce"],
3541["CCCAA8", "Thistle Green"],
3542["CCCCFF", "Periwinkle"],
3543["CCFF00", "Electric Lime"],
3544["CD5700", "Tenn"],
3545["CD5C5C", "Chestnut Rose"],
3546["CD8429", "Brandy Punch"],
3547["CDF4FF", "Onahau"],
3548["CEB98F", "Sorrell Brown"],
3549["CEBABA", "Cold Turkey"],
3550["CEC291", "Yuma"],
3551["CEC7A7", "Chino"],
3552["CFA39D", "Eunry"],
3553["CFB53B", "Old Gold"],
3554["CFDCCF", "Tasman"],
3555["CFE5D2", "Surf Crest"],
3556["CFF9F3", "Humming Bird"],
3557["CFFAF4", "Scandal"],
3558["D05F04", "Red Stage"],
3559["D06DA1", "Hopbush"],
3560["D07D12", "Meteor"],
3561["D0BEF8", "Perfume"],
3562["D0C0E5", "Prelude"],
3563["D0F0C0", "Tea Green"],
3564["D18F1B", "Geebung"],
3565["D1BEA8", "Vanilla"],
3566["D1C6B4", "Soft Amber"],
3567["D1D2CA", "Celeste"],
3568["D1D2DD", "Mischka"],
3569["D1E231", "Pear"],
3570["D2691E", "Hot Cinnamon"],
3571["D27D46", "Raw Sienna"],
3572["D29EAA", "Careys Pink"],
3573["D2B48C", "Tan"],
3574["D2DA97", "Deco"],
3575["D2F6DE", "Blue Romance"],
3576["D2F8B0", "Gossip"],
3577["D3CBBA", "Sisal"],
3578["D3CDC5", "Swirl"],
3579["D47494", "Charm"],
3580["D4B6AF", "Clam Shell"],
3581["D4BF8D", "Straw"],
3582["D4C4A8", "Akaroa"],
3583["D4CD16", "Bird Flower"],
3584["D4D7D9", "Iron"],
3585["D4DFE2", "Geyser"],
3586["D4E2FC", "Hawkes Blue"],
3587["D54600", "Grenadier"],
3588["D591A4", "Can Can"],
3589["D59A6F", "Whiskey"],
3590["D5D195", "Winter Hazel"],
3591["D5F6E3", "Granny Apple"],
3592["D69188", "My Pink"],
3593["D6C562", "Tacha"],
3594["D6CEF6", "Moon Raker"],
3595["D6D6D1", "Quill Gray"],
3596["D6FFDB", "Snowy Mint"],
3597["D7837F", "New York Pink"],
3598["D7C498", "Pavlova"],
3599["D7D0FF", "Fog"],
3600["D84437", "Valencia"],
3601["D87C63", "Japonica"],
3602["D8BFD8", "Thistle"],
3603["D8C2D5", "Maverick"],
3604["D8FCFA", "Foam"],
3605["D94972", "Cabaret"],
3606["D99376", "Burning Sand"],
3607["D9B99B", "Cameo"],
3608["D9D6CF", "Timberwolf"],
3609["D9DCC1", "Tana"],
3610["D9E4F5", "Link Water"],
3611["D9F7FF", "Mabel"],
3612["DA3287", "Cerise"],
3613["DA5B38", "Flame Pea"],
3614["DA6304", "Bamboo"],
3615["DA6A41", "Red Damask"],
3616["DA70D6", "Orchid"],
3617["DA8A67", "Copperfield"],
3618["DAA520", "Golden Grass"],
3619["DAECD6", "Zanah"],
3620["DAF4F0", "Iceberg"],
3621["DAFAFF", "Oyster Bay"],
3622["DB5079", "Cranberry"],
3623["DB9690", "Petite Orchid"],
3624["DB995E", "Di Serria"],
3625["DBDBDB", "Alto"],
3626["DBFFF8", "Frosted Mint"],
3627["DC143C", "Crimson"],
3628["DC4333", "Punch"],
3629["DCB20C", "Galliano"],
3630["DCB4BC", "Blossom"],
3631["DCD747", "Wattle"],
3632["DCD9D2", "Westar"],
3633["DCDDCC", "Moon Mist"],
3634["DCEDB4", "Caper"],
3635["DCF0EA", "Swans Down"],
3636["DDD6D5", "Swiss Coffee"],
3637["DDF9F1", "White Ice"],
3638["DE3163", "Cerise Red"],
3639["DE6360", "Roman"],
3640["DEA681", "Tumbleweed"],
3641["DEBA13", "Gold Tips"],
3642["DEC196", "Brandy"],
3643["DECBC6", "Wafer"],
3644["DED4A4", "Sapling"],
3645["DED717", "Barberry"],
3646["DEE5C0", "Beryl Green"],
3647["DEF5FF", "Pattens Blue"],
3648["DF73FF", "Heliotrope"],
3649["DFBE6F", "Apache"],
3650["DFCD6F", "Chenin"],
3651["DFCFDB", "Lola"],
3652["DFECDA", "Willow Brook"],
3653["DFFF00", "Chartreuse Yellow"],
3654["E0B0FF", "Mauve"],
3655["E0B646", "Anzac"],
3656["E0B974", "Harvest Gold"],
3657["E0C095", "Calico"],
3658["E0FFFF", "Baby Blue"],
3659["E16865", "Sunglo"],
3660["E1BC64", "Equator"],
3661["E1C0C8", "Pink Flare"],
3662["E1E6D6", "Periglacial Blue"],
3663["E1EAD4", "Kidnapper"],
3664["E1F6E8", "Tara"],
3665["E25465", "Mandy"],
3666["E2725B", "Terracotta"],
3667["E28913", "Golden Bell"],
3668["E292C0", "Shocking"],
3669["E29418", "Dixie"],
3670["E29CD2", "Light Orchid"],
3671["E2D8ED", "Snuff"],
3672["E2EBED", "Mystic"],
3673["E2F3EC", "Apple Green"],
3674["E30B5C", "Razzmatazz"],
3675["E32636", "Alizarin Crimson"],
3676["E34234", "Cinnabar"],
3677["E3BEBE", "Cavern Pink"],
3678["E3F5E1", "Peppermint"],
3679["E3F988", "Mindaro"],
3680["E47698", "Deep Blush"],
3681["E49B0F", "Gamboge"],
3682["E4C2D5", "Melanie"],
3683["E4CFDE", "Twilight"],
3684["E4D1C0", "Bone"],
3685["E4D422", "Sunflower"],
3686["E4D5B7", "Grain Brown"],
3687["E4D69B", "Zombie"],
3688["E4F6E7", "Frostee"],
3689["E4FFD1", "Snow Flurry"],
3690["E52B50", "Amaranth"],
3691["E5841B", "Zest"],
3692["E5CCC9", "Dust Storm"],
3693["E5D7BD", "Stark White"],
3694["E5D8AF", "Hampton"],
3695["E5E0E1", "Bon Jour"],
3696["E5E5E5", "Mercury"],
3697["E5F9F6", "Polar"],
3698["E64E03", "Trinidad"],
3699["E6BE8A", "Gold Sand"],
3700["E6BEA5", "Cashmere"],
3701["E6D7B9", "Double Spanish White"],
3702["E6E4D4", "Satin Linen"],
3703["E6F2EA", "Harp"],
3704["E6F8F3", "Off Green"],
3705["E6FFE9", "Hint of Green"],
3706["E6FFFF", "Tranquil"],
3707["E77200", "Mango Tango"],
3708["E7730A", "Christine"],
3709["E79F8C", "Tonys Pink"],
3710["E79FC4", "Kobi"],
3711["E7BCB4", "Rose Fog"],
3712["E7BF05", "Corn"],
3713["E7CD8C", "Putty"],
3714["E7ECE6", "Gray Nurse"],
3715["E7F8FF", "Lily White"],
3716["E7FEFF", "Bubbles"],
3717["E89928", "Fire Bush"],
3718["E8B9B3", "Shilo"],
3719["E8E0D5", "Pearl Bush"],
3720["E8EBE0", "Green White"],
3721["E8F1D4", "Chrome White"],
3722["E8F2EB", "Gin"],
3723["E8F5F2", "Aqua Squeeze"],
3724["E96E00", "Clementine"],
3725["E97451", "Burnt Sienna"],
3726["E97C07", "Tahiti Gold"],
3727["E9CECD", "Oyster Pink"],
3728["E9D75A", "Confetti"],
3729["E9E3E3", "Ebb"],
3730["E9F8ED", "Ottoman"],
3731["E9FFFD", "Clear Day"],
3732["EA88A8", "Carissma"],
3733["EAAE69", "Porsche"],
3734["EAB33B", "Tulip Tree"],
3735["EAC674", "Rob Roy"],
3736["EADAB8", "Raffia"],
3737["EAE8D4", "White Rock"],
3738["EAF6EE", "Panache"],
3739["EAF6FF", "Solitude"],
3740["EAF9F5", "Aqua Spring"],
3741["EAFFFE", "Dew"],
3742["EB9373", "Apricot"],
3743["EBC2AF", "Zinnwaldite"],
3744["ECA927", "Fuel Yellow"],
3745["ECC54E", "Ronchi"],
3746["ECC7EE", "French Lilac"],
3747["ECCDB9", "Just Right"],
3748["ECE090", "Wild Rice"],
3749["ECEBBD", "Fall Green"],
3750["ECEBCE", "Aths Special"],
3751["ECF245", "Starship"],
3752["ED0A3F", "Red Ribbon"],
3753["ED7A1C", "Tango"],
3754["ED9121", "Carrot Orange"],
3755["ED989E", "Sea Pink"],
3756["EDB381", "Tacao"],
3757["EDC9AF", "Desert Sand"],
3758["EDCDAB", "Pancho"],
3759["EDDCB1", "Chamois"],
3760["EDEA99", "Primrose"],
3761["EDF5DD", "Frost"],
3762["EDF5F5", "Aqua Haze"],
3763["EDF6FF", "Zumthor"],
3764["EDF9F1", "Narvik"],
3765["EDFC84", "Honeysuckle"],
3766["EE82EE", "Lavender Magenta"],
3767["EEC1BE", "Beauty Bush"],
3768["EED794", "Chalky"],
3769["EED9C4", "Almond"],
3770["EEDC82", "Flax"],
3771["EEDEDA", "Bizarre"],
3772["EEE3AD", "Double Colonial White"],
3773["EEEEE8", "Cararra"],
3774["EEEF78", "Manz"],
3775["EEF0C8", "Tahuna Sands"],
3776["EEF0F3", "Athens Gray"],
3777["EEF3C3", "Tusk"],
3778["EEF4DE", "Loafer"],
3779["EEF6F7", "Catskill White"],
3780["EEFDFF", "Twilight Blue"],
3781["EEFF9A", "Jonquil"],
3782["EEFFE2", "Rice Flower"],
3783["EF863F", "Jaffa"],
3784["EFEFEF", "Gallery"],
3785["EFF2F3", "Porcelain"],
3786["F091A9", "Mauvelous"],
3787["F0D52D", "Golden Dream"],
3788["F0DB7D", "Golden Sand"],
3789["F0DC82", "Buff"],
3790["F0E2EC", "Prim"],
3791["F0E68C", "Khaki"],
3792["F0EEFD", "Selago"],
3793["F0EEFF", "Titan White"],
3794["F0F8FF", "Alice Blue"],
3795["F0FCEA", "Feta"],
3796["F18200", "Gold Drop"],
3797["F19BAB", "Wewak"],
3798["F1E788", "Sahara Sand"],
3799["F1E9D2", "Parchment"],
3800["F1E9FF", "Blue Chalk"],
3801["F1EEC1", "Mint Julep"],
3802["F1F1F1", "Seashell"],
3803["F1F7F2", "Saltpan"],
3804["F1FFAD", "Tidal"],
3805["F1FFC8", "Chiffon"],
3806["F2552A", "Flamingo"],
3807["F28500", "Tangerine"],
3808["F2C3B2", "Mandys Pink"],
3809["F2F2F2", "Concrete"],
3810["F2FAFA", "Black Squeeze"],
3811["F34723", "Pomegranate"],
3812["F3AD16", "Buttercup"],
3813["F3D69D", "New Orleans"],
3814["F3D9DF", "Vanilla Ice"],
3815["F3E7BB", "Sidecar"],
3816["F3E9E5", "Dawn Pink"],
3817["F3EDCF", "Wheatfield"],
3818["F3FB62", "Canary"],
3819["F3FBD4", "Orinoco"],
3820["F3FFD8", "Carla"],
3821["F400A1", "Hollywood Cerise"],
3822["F4A460", "Sandy brown"],
3823["F4C430", "Saffron"],
3824["F4D81C", "Ripe Lemon"],
3825["F4EBD3", "Janna"],
3826["F4F2EE", "Pampas"],
3827["F4F4F4", "Wild Sand"],
3828["F4F8FF", "Zircon"],
3829["F57584", "Froly"],
3830["F5C85C", "Cream Can"],
3831["F5C999", "Manhattan"],
3832["F5D5A0", "Maize"],
3833["F5DEB3", "Wheat"],
3834["F5E7A2", "Sandwisp"],
3835["F5E7E2", "Pot Pourri"],
3836["F5E9D3", "Albescent White"],
3837["F5EDEF", "Soft Peach"],
3838["F5F3E5", "Ecru White"],
3839["F5F5DC", "Beige"],
3840["F5FB3D", "Golden Fizz"],
3841["F5FFBE", "Australian Mint"],
3842["F64A8A", "French Rose"],
3843["F653A6", "Brilliant Rose"],
3844["F6A4C9", "Illusion"],
3845["F6F0E6", "Merino"],
3846["F6F7F7", "Black Haze"],
3847["F6FFDC", "Spring Sun"],
3848["F7468A", "Violet Red"],
3849["F77703", "Chilean Fire"],
3850["F77FBE", "Persian Pink"],
3851["F7B668", "Rajah"],
3852["F7C8DA", "Azalea"],
3853["F7DBE6", "We Peep"],
3854["F7F2E1", "Quarter Spanish White"],
3855["F7F5FA", "Whisper"],
3856["F7FAF7", "Snow Drift"],
3857["F8B853", "Casablanca"],
3858["F8C3DF", "Chantilly"],
3859["F8D9E9", "Cherub"],
3860["F8DB9D", "Marzipan"],
3861["F8DD5C", "Energy Yellow"],
3862["F8E4BF", "Givry"],
3863["F8F0E8", "White Linen"],
3864["F8F4FF", "Magnolia"],
3865["F8F6F1", "Spring Wood"],
3866["F8F7DC", "Coconut Cream"],
3867["F8F7FC", "White Lilac"],
3868["F8F8F7", "Desert Storm"],
3869["F8F99C", "Texas"],
3870["F8FACD", "Corn Field"],
3871["F8FDD3", "Mimosa"],
3872["F95A61", "Carnation"],
3873["F9BF58", "Saffron Mango"],
3874["F9E0ED", "Carousel Pink"],
3875["F9E4BC", "Dairy Cream"],
3876["F9E663", "Portica"],
3877["F9EAF3", "Amour"],
3878["F9F8E4", "Rum Swizzle"],
3879["F9FF8B", "Dolly"],
3880["F9FFF6", "Sugar Cane"],
3881["FA7814", "Ecstasy"],
3882["FA9D5A", "Tan Hide"],
3883["FAD3A2", "Corvette"],
3884["FADFAD", "Peach Yellow"],
3885["FAE600", "Turbo"],
3886["FAEAB9", "Astra"],
3887["FAECCC", "Champagne"],
3888["FAF0E6", "Linen"],
3889["FAF3F0", "Fantasy"],
3890["FAF7D6", "Citrine White"],
3891["FAFAFA", "Alabaster"],
3892["FAFDE4", "Hint of Yellow"],
3893["FAFFA4", "Milan"],
3894["FB607F", "Brink Pink"],
3895["FB8989", "Geraldine"],
3896["FBA0E3", "Lavender Rose"],
3897["FBA129", "Sea Buckthorn"],
3898["FBAC13", "Sun"],
3899["FBAED2", "Lavender Pink"],
3900["FBB2A3", "Rose Bud"],
3901["FBBEDA", "Cupid"],
3902["FBCCE7", "Classic Rose"],
3903["FBCEB1", "Apricot Peach"],
3904["FBE7B2", "Banana Mania"],
3905["FBE870", "Marigold Yellow"],
3906["FBE96C", "Festival"],
3907["FBEA8C", "Sweet Corn"],
3908["FBEC5D", "Candy Corn"],
3909["FBF9F9", "Hint of Red"],
3910["FBFFBA", "Shalimar"],
3911["FC0FC0", "Shocking Pink"],
3912["FC80A5", "Tickle Me Pink"],
3913["FC9C1D", "Tree Poppy"],
3914["FCC01E", "Lightning Yellow"],
3915["FCD667", "Goldenrod"],
3916["FCD917", "Candlelight"],
3917["FCDA98", "Cherokee"],
3918["FCF4D0", "Double Pearl Lusta"],
3919["FCF4DC", "Pearl Lusta"],
3920["FCF8F7", "Vista White"],
3921["FCFBF3", "Bianca"],
3922["FCFEDA", "Moon Glow"],
3923["FCFFE7", "China Ivory"],
3924["FCFFF9", "Ceramic"],
3925["FD0E35", "Torch Red"],
3926["FD5B78", "Wild Watermelon"],
3927["FD7B33", "Crusta"],
3928["FD7C07", "Sorbus"],
3929["FD9FA2", "Sweet Pink"],
3930["FDD5B1", "Light Apricot"],
3931["FDD7E4", "Pig Pink"],
3932["FDE1DC", "Cinderella"],
3933["FDE295", "Golden Glow"],
3934["FDE910", "Lemon"],
3935["FDF5E6", "Old Lace"],
3936["FDF6D3", "Half Colonial White"],
3937["FDF7AD", "Drover"],
3938["FDFEB8", "Pale Prim"],
3939["FDFFD5", "Cumulus"],
3940["FE28A2", "Persian Rose"],
3941["FE4C40", "Sunset Orange"],
3942["FE6F5E", "Bittersweet"],
3943["FE9D04", "California"],
3944["FEA904", "Yellow Sea"],
3945["FEBAAD", "Melon"],
3946["FED33C", "Bright Sun"],
3947["FED85D", "Dandelion"],
3948["FEDB8D", "Salomie"],
3949["FEE5AC", "Cape Honey"],
3950["FEEBF3", "Remy"],
3951["FEEFCE", "Oasis"],
3952["FEF0EC", "Bridesmaid"],
3953["FEF2C7", "Beeswax"],
3954["FEF3D8", "Bleach White"],
3955["FEF4CC", "Pipi"],
3956["FEF4DB", "Half Spanish White"],
3957["FEF4F8", "Wisp Pink"],
3958["FEF5F1", "Provincial Pink"],
3959["FEF7DE", "Half Dutch White"],
3960["FEF8E2", "Solitaire"],
3961["FEF8FF", "White Pointer"],
3962["FEF9E3", "Off Yellow"],
3963["FEFCED", "Orange White"],
3964["FF0000", "Red"],
3965["FF007F", "Rose"],
3966["FF00CC", "Purple Pizzazz"],
3967["FF00FF", "Magenta / Fuchsia"],
3968["FF2400", "Scarlet"],
3969["FF3399", "Wild Strawberry"],
3970["FF33CC", "Razzle Dazzle Rose"],
3971["FF355E", "Radical Red"],
3972["FF3F34", "Red Orange"],
3973["FF4040", "Coral Red"],
3974["FF4D00", "Vermilion"],
3975["FF4F00", "International Orange"],
3976["FF6037", "Outrageous Orange"],
3977["FF6600", "Blaze Orange"],
3978["FF66FF", "Pink Flamingo"],
3979["FF681F", "Orange"],
3980["FF69B4", "Hot Pink"],
3981["FF6B53", "Persimmon"],
3982["FF6FFF", "Blush Pink"],
3983["FF7034", "Burning Orange"],
3984["FF7518", "Pumpkin"],
3985["FF7D07", "Flamenco"],
3986["FF7F00", "Flush Orange"],
3987["FF7F50", "Coral"],
3988["FF8C69", "Salmon"],
3989["FF9000", "Pizazz"],
3990["FF910F", "West Side"],
3991["FF91A4", "Pink Salmon"],
3992["FF9933", "Neon Carrot"],
3993["FF9966", "Atomic Tangerine"],
3994["FF9980", "Vivid Tangerine"],
3995["FF9E2C", "Sunshade"],
3996["FFA000", "Orange Peel"],
3997["FFA194", "Mona Lisa"],
3998["FFA500", "Web Orange"],
3999["FFA6C9", "Carnation Pink"],
4000["FFAB81", "Hit Pink"],
4001["FFAE42", "Yellow Orange"],
4002["FFB0AC", "Cornflower Lilac"],
4003["FFB1B3", "Sundown"],
4004["FFB31F", "My Sin"],
4005["FFB555", "Texas Rose"],
4006["FFB7D5", "Cotton Candy"],
4007["FFB97B", "Macaroni and Cheese"],
4008["FFBA00", "Selective Yellow"],
4009["FFBD5F", "Koromiko"],
4010["FFBF00", "Amber"],
4011["FFC0A8", "Wax Flower"],
4012["FFC0CB", "Pink"],
4013["FFC3C0", "Your Pink"],
4014["FFC901", "Supernova"],
4015["FFCBA4", "Flesh"],
4016["FFCC33", "Sunglow"],
4017["FFCC5C", "Golden Tainoi"],
4018["FFCC99", "Peach Orange"],
4019["FFCD8C", "Chardonnay"],
4020["FFD1DC", "Pastel Pink"],
4021["FFD2B7", "Romantic"],
4022["FFD38C", "Grandis"],
4023["FFD700", "Gold"],
4024["FFD800", "School bus Yellow"],
4025["FFD8D9", "Cosmos"],
4026["FFDB58", "Mustard"],
4027["FFDCD6", "Peach Schnapps"],
4028["FFDDAF", "Caramel"],
4029["FFDDCD", "Tuft Bush"],
4030["FFDDCF", "Watusi"],
4031["FFDDF4", "Pink Lace"],
4032["FFDEAD", "Navajo White"],
4033["FFDEB3", "Frangipani"],
4034["FFE1DF", "Pippin"],
4035["FFE1F2", "Pale Rose"],
4036["FFE2C5", "Negroni"],
4037["FFE5A0", "Cream Brulee"],
4038["FFE5B4", "Peach"],
4039["FFE6C7", "Tequila"],
4040["FFE772", "Kournikova"],
4041["FFEAC8", "Sandy Beach"],
4042["FFEAD4", "Karry"],
4043["FFEC13", "Broom"],
4044["FFEDBC", "Colonial White"],
4045["FFEED8", "Derby"],
4046["FFEFA1", "Vis Vis"],
4047["FFEFC1", "Egg White"],
4048["FFEFD5", "Papaya Whip"],
4049["FFEFEC", "Fair Pink"],
4050["FFF0DB", "Peach Cream"],
4051["FFF0F5", "Lavender blush"],
4052["FFF14F", "Gorse"],
4053["FFF1B5", "Buttermilk"],
4054["FFF1D8", "Pink Lady"],
4055["FFF1EE", "Forget Me Not"],
4056["FFF1F9", "Tutu"],
4057["FFF39D", "Picasso"],
4058["FFF3F1", "Chardon"],
4059["FFF46E", "Paris Daisy"],
4060["FFF4CE", "Barley White"],
4061["FFF4DD", "Egg Sour"],
4062["FFF4E0", "Sazerac"],
4063["FFF4E8", "Serenade"],
4064["FFF4F3", "Chablis"],
4065["FFF5EE", "Seashell Peach"],
4066["FFF5F3", "Sauvignon"],
4067["FFF6D4", "Milk Punch"],
4068["FFF6DF", "Varden"],
4069["FFF6F5", "Rose White"],
4070["FFF8D1", "Baja White"],
4071["FFF9E2", "Gin Fizz"],
4072["FFF9E6", "Early Dawn"],
4073["FFFACD", "Lemon Chiffon"],
4074["FFFAF4", "Bridal Heath"],
4075["FFFBDC", "Scotch Mist"],
4076["FFFBF9", "Soapstone"],
4077["FFFC99", "Witch Haze"],
4078["FFFCEA", "Buttery White"],
4079["FFFCEE", "Island Spice"],
4080["FFFDD0", "Cream"],
4081["FFFDE6", "Chilean Heath"],
4082["FFFDE8", "Travertine"],
4083["FFFDF3", "Orchid White"],
4084["FFFDF4", "Quarter Pearl Lusta"],
4085["FFFEE1", "Half and Half"],
4086["FFFEEC", "Apricot White"],
4087["FFFEF0", "Rice Cake"],
4088["FFFEF6", "Black White"],
4089["FFFEFD", "Romance"],
4090["FFFF00", "Yellow"],
4091["FFFF66", "Laser Lemon"],
4092["FFFF99", "Pale Canary"],
4093["FFFFB4", "Portafino"],
4094["FFFFF0", "Ivory"],
4095["FFFFFF", "White"]
4096]
4097
4098}
4099
4100ntc.init();
4101