1/**
2 * Copyright (c) 2006-2017, JGraph Ltd
3 */
4/**
5 * Class: mxVsdxCanvas2D
6 *
7 * Constructor: mxVsdxCanvas2D
8 *
9 * Constructs a new abstract canvas.
10 */
11function mxVsdxCanvas2D()
12{
13	mxAbstractCanvas2D.call(this);
14};
15
16/**
17 * Extends mxAbstractCanvas2D
18 */
19mxUtils.extend(mxVsdxCanvas2D, mxAbstractCanvas2D);
20
21
22/**
23 * Variable: textEnabled
24 *
25 * Specifies if text output should be enabled. Default is true.
26 */
27mxVsdxCanvas2D.prototype.textEnabled = true;
28
29/**
30 * Function: init
31 *
32 * Initialize the canvas for a new vsdx file.
33 */
34mxVsdxCanvas2D.prototype.init = function (zip)
35{
36	this.filesLoading = 0;
37	this.zip = zip;
38};
39
40/**
41 * Function: onFilesLoaded
42 *
43 * Called after all pending files have finished loading.
44 */
45mxVsdxCanvas2D.prototype.onFilesLoaded = function ()
46{
47	// hook for subclassers
48};
49
50/**
51 * Function: createElt
52 *
53 * Create a new geo section.
54 */
55mxVsdxCanvas2D.prototype.createElt = function (name)
56{
57	return (this.xmlDoc.createElementNS != null) ? this.xmlDoc.createElementNS(VsdxExport.prototype.XMLNS, name) :
58		this.xmlDoc.createElement(name);
59};
60
61
62/**
63 * Function: createGeoSec
64 *
65 * Create a new geo section.
66 */
67mxVsdxCanvas2D.prototype.createGeoSec = function ()
68{
69	if (this.geoSec != null)
70	{
71		this.shape.appendChild(this.geoSec);
72	}
73
74	var geoSec = this.createElt("Section");
75
76	geoSec.setAttribute("N", "Geometry");
77	geoSec.setAttribute("IX", this.geoIndex++);
78
79	this.geoSec = geoSec;
80	this.geoStepIndex = 1;
81	this.lastX = 0;
82	this.lastY = 0;
83	this.lastMoveToX = 0;
84	this.lastMoveToY = 0;
85};
86
87
88/**
89 * Function: newShape
90 *
91 * Create a new shape.
92 */
93mxVsdxCanvas2D.prototype.newShape = function (shape, cellState, xmlDoc)
94{
95	this.geoIndex = 0;
96	this.shape = shape;
97	this.cellState = cellState;
98	this.xmGeo = cellState.cell.geometry;
99	this.xmlDoc = xmlDoc;
100	this.geoSec = null;
101	this.shapeImg = null;
102	this.shapeType = "Shape";
103
104	this.createGeoSec();
105};
106
107
108/**
109 * Function: newEdge
110 *
111 * Create a new edge.
112 */
113mxVsdxCanvas2D.prototype.newEdge = function (shape, cellState, xmlDoc)
114{
115	this.shape = shape;
116	this.cellState = cellState;
117	this.xmGeo = cellState.cellBounds;
118	var s = this.state;
119	this.xmlDoc = xmlDoc;
120};
121
122/**
123 * Function: endShape
124 *
125 * End current shape.
126 */
127mxVsdxCanvas2D.prototype.endShape = function ()
128{
129	if (this.shapeImg != null)
130	{
131		this.addForeignData(this.shapeImg.type, this.shapeImg.id);
132	}
133};
134
135
136/**
137 * Function: newPage
138 *
139 * Start a new page.
140 */
141mxVsdxCanvas2D.prototype.newPage = function ()
142{
143	this.images = [];
144};
145
146/**
147 * Function: newPage
148 *
149 * Start a new page.
150 */
151mxVsdxCanvas2D.prototype.getShapeType = function ()
152{
153	return this.shapeType;
154};
155
156/**
157 * Function: getShapeGeo
158 *
159 * return the current geo section.
160 */
161mxVsdxCanvas2D.prototype.getShapeGeo = function ()
162{
163	return this.geoSec;
164};
165
166/**
167 * Function: createCellElemScaled
168 *
169 * Creates a cell element and scale the value.
170 */
171mxVsdxCanvas2D.prototype.createCellElemScaled = function (name, val, formula)
172{
173	return this.createCellElem(name, val / VsdxExport.prototype.CONVERSION_FACTOR, formula);
174};
175
176/**
177 * Function: createCellElem
178 *
179 * Creates a cell element.
180 */
181mxVsdxCanvas2D.prototype.createCellElem = function (name, val, formula)
182{
183	var cell = this.createElt("Cell");
184	cell.setAttribute("N", name);
185	cell.setAttribute("V", val);
186
187	if (formula) cell.setAttribute("F", formula);
188
189	return cell;
190};
191
192mxVsdxCanvas2D.prototype.createRowScaled = function(type, index, x, y, a, b, c , d, xF, yF, aF, bF, cF, dF)
193{
194	return this.createRowRel(type, index, x / VsdxExport.prototype.CONVERSION_FACTOR, y / VsdxExport.prototype.CONVERSION_FACTOR,
195			a / VsdxExport.prototype.CONVERSION_FACTOR, b / VsdxExport.prototype.CONVERSION_FACTOR,
196			c / VsdxExport.prototype.CONVERSION_FACTOR, d / VsdxExport.prototype.CONVERSION_FACTOR,
197			xF, yF, aF, bF, cF, dF);
198};
199
200mxVsdxCanvas2D.prototype.createRowRel = function(type, index, x, y, a, b, c , d, xF, yF, aF, bF, cF, dF)
201{
202	var row = this.createElt("Row");
203	row.setAttribute("T", type);
204	row.setAttribute("IX", index);
205	row.appendChild(this.createCellElem("X", x, xF));
206	row.appendChild(this.createCellElem("Y", y, yF));
207
208	if (a != null && isFinite(a)) row.appendChild(this.createCellElem("A", a, aF));
209	if (b != null && isFinite(b)) row.appendChild(this.createCellElem("B", b, bF));
210	if (c != null && isFinite(c)) row.appendChild(this.createCellElem("C", c, cF));
211	if (d != null && isFinite(d)) row.appendChild(this.createCellElem("D", d, dF));
212
213	return row;
214};
215
216
217/**
218 * Function: begin
219 *
220 * Extends superclass to create path.
221 */
222mxVsdxCanvas2D.prototype.begin = function()
223{
224	if (this.geoStepIndex > 1)	this.createGeoSec();
225};
226
227/**
228 * Function: rect
229 *
230 * Private helper function to create SVG elements
231 */
232mxVsdxCanvas2D.prototype.rect = function(x, y, w, h)
233{
234	if (this.geoStepIndex > 1)	this.createGeoSec();
235
236	var s = this.state;
237	w = w * s.scale;
238	h = h * s.scale;
239
240	var geo = this.xmGeo;
241	x = ((x - geo.x + s.dx) * s.scale);
242	y = ((geo.height - y + geo.y - s.dy) * s.scale);
243
244	this.geoSec.appendChild(this.createRowScaled("MoveTo", this.geoStepIndex++, x, y));
245	this.geoSec.appendChild(this.createRowScaled("LineTo", this.geoStepIndex++, x + w, y));
246	this.geoSec.appendChild(this.createRowScaled("LineTo", this.geoStepIndex++, x + w, y - h));
247	this.geoSec.appendChild(this.createRowScaled("LineTo", this.geoStepIndex++, x, y - h));
248	this.geoSec.appendChild(this.createRowScaled("LineTo", this.geoStepIndex++, x, y));
249};
250
251/**
252 * Function: roundrect
253 *
254 * Private helper function to create SVG elements
255 */
256mxVsdxCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
257{
258	this.rect(x, y, w, h);
259	//TODO this assume dx and dy are equal and only one rounding is needed
260	this.shape.appendChild(this.createCellElemScaled("Rounding", dx));
261};
262
263/**
264 * Function: ellipse
265 *
266 * Private helper function to create SVG elements
267 */
268mxVsdxCanvas2D.prototype.ellipse = function(x, y, w, h)
269{
270	if (this.geoStepIndex > 1)	this.createGeoSec();
271
272	var s = this.state;
273	w = w * s.scale;
274	h = h * s.scale;
275
276	var geo = this.xmGeo;
277	var gh = geo.height * s.scale;
278	var gw = geo.width * s.scale;
279	x = (x - geo.x + s.dx) * s.scale;
280	y = gh + (-y + geo.y - s.dy) * s.scale;
281
282	var xWr = (x + w/2) / gw;
283	var yHr = (y - h/2) / gh;
284	var aWr = x / gw;
285	var bHr = (y - h/2) / gh;
286	var cWr = (x + w/2) / gw;
287	var dHr = y / gh;
288
289	this.geoSec.appendChild(this.createRowScaled("Ellipse", this.geoStepIndex++, x + w/2, y - h/2, x, y - h/2, x + w/2, y
290			, "Width*" + xWr, "Height*" + yHr, "Width*" + aWr, "Height*" + bHr, "Width*" + cWr, "Height*" + dHr));
291};
292
293/**
294 * Function: moveTo
295 *
296 * Moves the current path the given point.
297 *
298 * Parameters:
299 *
300 * x - Number that represents the x-coordinate of the point.
301 * y - Number that represents the y-coordinate of the point.
302 */
303mxVsdxCanvas2D.prototype.moveTo = function(x, y)
304{
305	//MoveTo inside a geo usually produce incorrect fill
306	if (this.geoStepIndex > 1)	this.createGeoSec();
307
308	this.lastMoveToX = x;
309	this.lastMoveToY = y;
310	this.lastX = x;
311	this.lastY = y;
312
313	var geo = this.xmGeo;
314	var s = this.state;
315	x = (x - geo.x + s.dx) * s.scale;
316	y = (geo.height - y + geo.y - s.dy) * s.scale;
317	var h = geo.height * s.scale;
318	var w = geo.width * s.scale;
319
320	this.geoSec.appendChild(this.createRowRel("RelMoveTo", this.geoStepIndex++, x/w, y/h));
321};
322
323/**
324 * Function: lineTo
325 *
326 * Draws a line to the given coordinates.
327 *
328 * Parameters:
329 *
330 * x - Number that represents the x-coordinate of the endpoint.
331 * y - Number that represents the y-coordinate of the endpoint.
332 */
333mxVsdxCanvas2D.prototype.lineTo = function(x, y)
334{
335	this.lastX = x;
336	this.lastY = y;
337
338	var geo = this.xmGeo;
339	var s = this.state;
340	x = (x - geo.x + s.dx) * s.scale;
341	y = (geo.height - y + geo.y - s.dy) * s.scale;
342	var h = geo.height * s.scale;
343	var w = geo.width * s.scale;
344
345	this.geoSec.appendChild(this.createRowRel("RelLineTo", this.geoStepIndex++, x/w, y/h));
346};
347
348/**
349 * Function: quadTo
350 *
351 * Adds a quadratic curve to the current path.
352 *
353 * Parameters:
354 *
355 * x1 - Number that represents the x-coordinate of the control point.
356 * y1 - Number that represents the y-coordinate of the control point.
357 * x2 - Number that represents the x-coordinate of the endpoint.
358 * y2 - Number that represents the y-coordinate of the endpoint.
359 */
360mxVsdxCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
361{
362	this.lastX = x2;
363	this.lastY = y2;
364
365	var s = this.state;
366	var geo = this.xmGeo;
367
368	var h = geo.height * s.scale;
369	var w = geo.width * s.scale;
370
371	x1 = (x1 - geo.x + s.dx) * s.scale;
372	y1 = (geo.height - y1 + geo.y - s.dy) * s.scale;
373
374	x2 = (x2 - geo.x + s.dx) * s.scale;
375	y2 = (geo.height - y2 + geo.y - s.dy) * s.scale;
376
377	x1 = x1 / w;
378	y1 = y1 / h;
379	x2 = x2 / w;
380	y2 = y2 / h;
381
382	this.geoSec.appendChild(this.createRowRel("RelQuadBezTo", this.geoStepIndex++, x2, y2, x1, y1));
383};
384
385/**
386 * Function: curveTo
387 *
388 * Adds a bezier curve to the current path.
389 *
390 * Parameters:
391 *
392 * x1 - Number that represents the x-coordinate of the first control point.
393 * y1 - Number that represents the y-coordinate of the first control point.
394 * x2 - Number that represents the x-coordinate of the second control point.
395 * y2 - Number that represents the y-coordinate of the second control point.
396 * x3 - Number that represents the x-coordinate of the endpoint.
397 * y3 - Number that represents the y-coordinate of the endpoint.
398 */
399mxVsdxCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
400{
401	this.lastX = x3;
402	this.lastY = y3;
403
404	var s = this.state;
405	var geo = this.xmGeo;
406
407	var h = geo.height * s.scale;
408	var w = geo.width * s.scale;
409
410	x1 = (x1 - geo.x + s.dx) * s.scale;
411	y1 = (geo.height - y1 + geo.y - s.dy) * s.scale;
412
413	x2 = (x2 - geo.x + s.dx) * s.scale;
414	y2 = (geo.height - y2 + geo.y - s.dy) * s.scale;
415
416	x3 = (x3 - geo.x + s.dx) * s.scale;
417	y3 = (geo.height - y3 + geo.y - s.dy) * s.scale;
418
419	x1 = x1 / w;
420	y1 = y1 / h;
421	x2 = x2 / w;
422	y2 = y2 / h;
423	x3 = x3 / w;
424	y3 = y3 / h;
425
426	this.geoSec.appendChild(this.createRowRel("RelCubBezTo", this.geoStepIndex++, x3, y3, x1, y1, x2, y2));
427};
428
429/**
430 * Function: close
431 *
432 * Closes the current path.
433 */
434mxVsdxCanvas2D.prototype.close = function()
435{
436	//Closing with a line if last point != last MoveTo point
437	if (this.lastMoveToX != this.lastX || this.lastMoveToY != this.lastY)
438		this.lineTo(this.lastMoveToX, this.lastMoveToY);
439};
440
441/**
442 * Function: addForeignData
443 *
444 * Add ForeignData to current shape using last image in the images array
445 */
446mxVsdxCanvas2D.prototype.addForeignData = function(type, index)
447{
448	var foreignData = this.createElt("ForeignData");
449	foreignData.setAttribute("ForeignType", "Bitmap");
450
451	type = type.toUpperCase();
452
453	if (type != "BMP")
454		foreignData.setAttribute("CompressionType", type);
455
456	var rel = this.createElt("Rel");
457	rel.setAttribute("r:id", "rId" + index);
458
459
460	foreignData.appendChild(rel);
461	this.shape.appendChild(foreignData);
462	this.shapeType = "Foreign";
463};
464
465
466mxVsdxCanvas2D.prototype.convertSvg2Png = function(svgData, isBase64, callback)
467{
468	var that = this;
469	this.filesLoading++;
470	try
471	{
472		var canvas = document.createElement("canvas");
473		var ctx = canvas.getContext("2d");
474
475		if (!isBase64)
476		{
477			svgData = String.fromCharCode.apply(null, new Uint8Array(svgData));
478
479			svgData =  ((window.btoa)? btoa(svgData) : Base64.encode(svgData, true));
480		}
481
482		var svgUrl = "data:image/svg+xml;base64," + svgData;
483
484	    img = new Image;
485
486		img.onload = function () {
487			canvas.width = this.width;
488			canvas.height = this.height;
489
490		    ctx.drawImage(this, 0, 0);
491
492		    try
493		    {
494		    	callback(canvas.toDataURL("image/png"));
495		    }
496		    catch(e){}//ignore
497
498		    that.filesLoading--;
499
500		    if (that.filesLoading == 0)
501	    	{
502	    		that.onFilesLoaded();
503	    	}
504		};
505
506		img.onerror = function () {
507			console.log("SVG2PNG conversion failed");
508
509			try
510		    {
511		    	callback(svgData); //Error, just return the original data!
512		    }
513		    catch(e){}//ignore
514
515		    that.filesLoading--;
516
517	    	if (that.filesLoading == 0)
518	    	{
519	    		that.onFilesLoaded();
520	    	}
521		};
522
523		img.src = svgUrl;
524	}
525	catch(e)
526	{
527		console.log("SVG2PNG conversion failed" + e.message);
528
529		try
530		{
531			callback(svgData); //just to keep going!
532	    }
533	    catch(e){}//ignore
534
535		this.filesLoading--;
536
537		if (that.filesLoading == 0)
538    	{
539    		that.onFilesLoaded();
540    	}
541	}
542};
543
544
545/**
546 * Function: image
547 *
548 * Add image to vsdx file as a media (Foreign Object)
549 */
550mxVsdxCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
551{
552	var that = this;
553
554	//TODO image reusing, if the same image is used more than once, reuse it. Applicable for URLs specifically (but can also be applied to embedded ones)
555	var imgName = "image" + (this.images.length + 1) + ".";
556	var type;
557	if (src.indexOf("data:") == 0)
558	{
559		var p = src.indexOf("base64,");
560		var base64 = src.substring(p + 7); //7 is the length of "base64,"
561		type = src.substring(11, p-1); //11 is the length of "data:image/"
562
563		//SVG files cannot be embedded in vsdx files, TODO convert them to a visio shape
564		if (type.indexOf('svg') == 0) {
565			type = 'png';
566			imgName += type;
567			this.convertSvg2Png(base64, true, function(pngData){
568				that.zip.file("visio/media/" + imgName, pngData.substring(22), {base64: true}); //22 is the length of "data:image/png;base64,"
569			});
570		}
571		else
572		{
573			imgName += type;
574			this.zip.file("visio/media/" + imgName, base64, {base64: true});
575		}
576	}
577	else if (window.XMLHttpRequest) //URL src, fetch it
578	{
579		src = this.converter.convert(src);
580		this.filesLoading++;
581
582		var p = src.lastIndexOf(".");
583		type = src.substring(p+1);
584
585		var convertSvg = false;
586
587		if (type.indexOf('svg') == 0)
588		{
589			type = 'png';
590			convertSvg = true;
591		}
592
593		imgName += type;
594
595		//The old browsers binary workaround doesn't work with jszip and converting to base64 encoding doesn't work also
596		var xhr = new XMLHttpRequest();
597		xhr.open('GET', src, true);
598		xhr.responseType = 'arraybuffer';
599		xhr.onreadystatechange = function(e)
600		{
601		    if (this.readyState == 4)
602		    {
603		    	if (this.status == 200)
604	    		{
605		    		//SVG files cannot be embedded in vsdx files, TODO convert them to a visio shape
606		    		if (convertSvg)
607	    			{
608		    			that.convertSvg2Png(this.response, false, function(pngData){
609		    				that.zip.file("visio/media/" + imgName, pngData.substring(22), {base64: true}); //22 is the length of "data:image/png;base64,"
610		    			});
611		    		}
612		    		else
613		    		{
614		    			that.zip.file("visio/media/" + imgName, this.response);
615		    		}
616	    		}
617
618		    	that.filesLoading--;
619
620		    	if (that.filesLoading == 0)
621		    	{
622		    		that.onFilesLoaded();
623		    	}
624		    }
625		};
626		xhr.send();
627	}
628
629	this.images.push(imgName);
630
631	//TODO can a shape has more than one image?
632	//We add one to the id as rId1 is reserved for the edges master
633	this.shapeImg = {type: type, id: this.images.length + 1};
634
635	//TODO support these!
636	aspect = (aspect != null) ? aspect : true;
637	flipH = (flipH != null) ? flipH : false;
638	flipV = (flipV != null) ? flipV : false;
639
640	var s = this.state;
641	w = w * s.scale;
642	h = h * s.scale;
643
644	var geo = this.xmGeo;
645	x = (x - geo.x + s.dx) * s.scale;
646	y = (geo.height - y + geo.y - s.dy) * s.scale;
647
648	this.shape.appendChild(this.createCellElemScaled("ImgOffsetX", x));
649	this.shape.appendChild(this.createCellElemScaled("ImgOffsetY", y - h));
650	this.shape.appendChild(this.createCellElemScaled("ImgWidth", w));
651	this.shape.appendChild(this.createCellElemScaled("ImgHeight", h));
652
653//	var s = this.state;
654//	x += s.dx;
655//	y += s.dy;
656//
657//	if (s.alpha < 1 || s.fillAlpha < 1)
658//	{
659//		node.setAttribute('opacity', s.alpha * s.fillAlpha);
660//	}
661//
662//	var tr = this.state.transform || '';
663//
664//	if (flipH || flipV)
665//	{
666//		var sx = 1;
667//		var sy = 1;
668//		var dx = 0;
669//		var dy = 0;
670//
671//		if (flipH)
672//		{
673//			sx = -1;
674//			dx = -w - 2 * x;
675//		}
676//
677//		if (flipV)
678//		{
679//			sy = -1;
680//			dy = -h - 2 * y;
681//		}
682//
683//		// Adds image tansformation to existing transform
684//		tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';
685//	}
686//
687//	if (tr.length > 0)
688//	{
689//		node.setAttribute('transform', tr);
690//	}
691//
692//	if (!this.pointerEvents)
693//	{
694//		node.setAttribute('pointer-events', 'none');
695//	}
696};
697
698/**
699 * Function: text
700 *
701 * Paints the given text. Possible values for format are empty string for
702 * plain text and html for HTML markup. HTML labels
703 * are not available as part of shapes with no foreignObject support in SVG
704 * (eg. IE9, IE10).
705 *
706 * Parameters:
707 *
708 * x - Number that represents the x-coordinate of the text.
709 * y - Number that represents the y-coordinate of the text.
710 * w - Number that represents the available width for the text or 0 for automatic width.
711 * h - Number that represents the available height for the text or 0 for automatic height.
712 * str - String that specifies the text to be painted.
713 * align - String that represents the horizontal alignment.
714 * valign - String that represents the vertical alignment.
715 * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.
716 * format - Empty string for plain text or 'html' for HTML markup.
717 * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.
718 * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.
719 * rotation - Number that specifies the angle of the rotation around the anchor point of the text.
720 * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.
721 */
722mxVsdxCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
723{
724	var that = this;
725	if (this.textEnabled && str != null)
726	{
727		if (mxUtils.isNode(str))
728		{
729			str = mxUtils.getOuterHtml(str);
730		}
731
732		//This is the case with edges
733		if (w == 0 && h == 0)
734		{
735			var strSize = mxUtils.getSizeForString(str, that.cellState.style["fontSize"], that.cellState.style["fontFamily"]);
736			w = strSize.width * 2;
737			h = strSize.height * 2;
738		}
739
740		//TODO support HTML text formatting and remaining attributes
741		if (format == 'html')
742    	{
743    		if (mxUtils.getValue(this.cellState.style, 'nl2Br', '1') != '0')
744			{
745				// Removes newlines from HTML and converts breaks to newlines
746				// to match the HTML output in plain text
747    			str = str.replace(/\n/g, '').replace(/<br\s*.?>/g, '\n');
748			}
749
750    		// Removes HTML tags
751			if (this.html2txtDiv == null)
752				this.html2txtDiv = document.createElement('div');
753
754			this.html2txtDiv.innerHTML = Graph.sanitizeHtml(str);
755			str = mxUtils.extractTextWithWhitespace(this.html2txtDiv.childNodes);
756    	}
757
758		var s = this.state;
759		var geo = this.xmGeo;
760
761		w = w * s.scale;
762		h = h * s.scale;
763
764		var charSect = this.createElt("Section");
765		charSect.setAttribute('N', 'Character');
766
767		var pSect = this.createElt("Section");
768		pSect.setAttribute('N', 'Paragraph');
769
770		var text = this.createElt("Text");
771
772		var rgb2hex = function (rgb){
773			rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
774			return (rgb && rgb.length === 4) ? "#" +
775			  ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
776			  ("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
777			  ("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : '';
778		};
779
780		var rowIndex = 0, pIndex = 0;
781		var calcW = 0, calcH = 0, lastW = 0, lastH = 0, lineH = 0;
782
783		var createTextRow = function(styleMap, charSect, pSect, textEl, txt)
784		{
785			var fontSize = styleMap['fontSize'];
786			var fontFamily = styleMap['fontFamily'];
787
788			var strRect = mxUtils.getSizeForString(txt, fontSize, fontFamily);
789			var wrapped = false;
790
791			if (wrap && strRect.width > w)
792			{
793				strRect = mxUtils.getSizeForString(txt, fontSize, fontFamily, w);
794				wrapped = true;
795			}
796
797			if (styleMap['blockElem'])
798			{
799				lastW += strRect.width;
800				calcW = Math.min(Math.max(calcW, lastW), w);
801				lastW = 0;
802				lastH = Math.max(lastH, strRect.height);
803				calcH += lastH + lineH;
804				lineH = lastH;
805				lastH = 0;
806			}
807			else
808			{
809				lastW += strRect.width;
810				calcW = Math.min(Math.max(calcW, lastW), w);
811				lastH = Math.max(lastH, strRect.height);
812				calcH = Math.max(calcH, lastH);
813			}
814
815			var charRow = that.createElt("Row");
816			charRow.setAttribute('IX', rowIndex);
817
818
819			if (styleMap['fontColor'])	charRow.appendChild(that.createCellElem("Color", styleMap['fontColor']));
820
821			if (fontSize)	charRow.appendChild(that.createCellElemScaled("Size", fontSize * 0.97)); //the magic number 0.97 is needed such that text do not overflow
822
823			if (fontFamily)	charRow.appendChild(that.createCellElem("Font", fontFamily));
824
825			//0x00 No format
826			//0x01 Specifies that the text run has a bold character property.
827			//0x02 Specifies that the text run has an italic character property.
828			//0x04 Specifies that the text run has an underline character property.
829			//0x08 Specifies that the text run has a small caps character property.
830			var style = 0;
831			if (styleMap['bold']) style |= 0x11;
832			if (styleMap['italic']) style |= 0x22;
833			if (styleMap['underline']) style |= 0x4;
834
835			charRow.appendChild(that.createCellElem("Style", style));
836			charRow.appendChild(that.createCellElem("Case", "0"));
837			charRow.appendChild(that.createCellElem("Pos", "0"));
838			charRow.appendChild(that.createCellElem("FontScale", "1"));
839			charRow.appendChild(that.createCellElem("Letterspace", "0"));
840
841			charSect.appendChild(charRow);
842
843			var pRow = that.createElt("Row");
844			pRow.setAttribute('IX', pIndex);
845
846			var align = 1; //center is default
847
848			switch(styleMap['align'])
849			{
850				case 'left': align = 0; break;
851				case 'center': align = 1; break;
852				case 'right': align = 2; break;
853				case 'start': align = 0; break; //TODO check right-to-left
854				case 'end': align = 2; break; //TODO check right-to-left
855				case 'justify': align = 0; break;
856				default:
857					align = 1;
858			}
859
860			pRow.appendChild(that.createCellElem("HorzAlign", align));
861//			pRow.appendChild(that.createCellElem("SpLine", "-1.2"));
862			pSect.appendChild(pRow);
863
864//			var pp = that.createElt("pp");
865//			pp.setAttribute('IX', pIndex++);
866//			textEl.appendChild(pp);
867			var cp = that.createElt("cp");
868			cp.setAttribute('IX', rowIndex++);
869			textEl.appendChild(cp);
870			var txtNode = that.xmlDoc.createTextNode(txt + (styleMap['blockElem']? "\n" : ""));
871			textEl.appendChild(txtNode);
872		};
873
874		var processNodeChildren = function(ch, pStyle)
875		{
876			pStyle = pStyle || {};
877			for (var i=0; i<ch.length; i++)
878			{
879				var curCh = ch[i];
880
881				if (curCh.nodeType == 3)
882				{ //#text
883					var fontStyle = that.cellState.style["fontStyle"];
884					var styleMap = {
885						fontColor: pStyle['fontColor'] || that.cellState.style["fontColor"],
886						fontSize: pStyle['fontSize'] || that.cellState.style["fontSize"],
887						fontFamily: pStyle['fontFamily'] || that.cellState.style["fontFamily"],
888						align: pStyle['align'] || that.cellState.style["align"],
889						bold: pStyle['bold'] || (fontStyle & 1),
890						italic: pStyle['italic'] || (fontStyle & 2),
891						underline: pStyle['underline'] || (fontStyle & 4)
892					};
893
894					var brNext = false;
895
896					if (i + 1 < ch.length && ch[i + 1].nodeName.toUpperCase() == 'BR')
897					{
898						brNext = true;
899						i++;
900					}
901
902					//VSDX doesn't have numbered list!
903					createTextRow(styleMap, charSect, pSect, text, (pStyle['OL']? pStyle['LiIndex'] + '. ' : '') + curCh.textContent + (brNext? '\n' : ''));
904				}
905				else if (curCh.nodeType == 1)
906				{ //element
907					var nodeName = curCh.nodeName.toUpperCase();
908					var chLen = curCh.childNodes.length;
909					var style = window.getComputedStyle(curCh, null);
910					var styleMap = {
911						bold: style.getPropertyValue('font-weight') == 'bold' || pStyle['bold'],
912						italic: style.getPropertyValue('font-style') == 'italic' || pStyle['italic'],
913						underline: style.getPropertyValue('text-decoration').indexOf('underline') >= 0 || pStyle['underline'],
914						align: style.getPropertyValue('text-align'),
915						fontColor: rgb2hex(style.getPropertyValue('color')),
916						fontSize: parseFloat(style.getPropertyValue('font-size')),
917						fontFamily: style.getPropertyValue('font-family').replace(/"/g, ''), //remove quotes
918						blockElem: style.getPropertyValue('display') == 'block' || nodeName == "BR" || nodeName == "LI",
919						OL: pStyle['OL'],
920						LiIndex: pStyle['LiIndex']
921					};
922
923					if (nodeName == "UL")
924					{
925						var pRow = that.createElt("Row");
926						pRow.setAttribute('IX', pIndex);
927
928						pRow.appendChild(that.createCellElem("HorzAlign", "0"));
929						pRow.appendChild(that.createCellElem("Bullet", "1"));
930						pSect.appendChild(pRow);
931
932						var pp = that.createElt("pp");
933						pp.setAttribute('IX', pIndex++);
934						text.appendChild(pp);
935					}
936					//VSDX doesn't have numbered list!
937					else if (nodeName == "OL")
938					{
939						styleMap['OL'] = true;
940					}
941					else if (nodeName == "LI")
942					{
943						styleMap['LiIndex'] = i + 1;
944					}
945
946					if (chLen > 0)
947					{
948						processNodeChildren(curCh.childNodes, styleMap);
949
950						//Close the UL by adding another pp with no Vullets
951						if (nodeName == "UL")
952						{
953							var pRow = that.createElt("Row");
954							pRow.setAttribute('IX', pIndex);
955
956							pRow.appendChild(that.createCellElem("Bullet", "0"));
957							pSect.appendChild(pRow);
958
959							var pp = that.createElt("pp");
960							pp.setAttribute('IX', pIndex++);
961							text.appendChild(pp);
962						}
963
964						createTextRow(styleMap, charSect, pSect, text, ""); //to handle block elements if any
965					}
966					else
967					{
968						//VSDX doesn't have numbered list!
969						createTextRow(styleMap, charSect, pSect, text, (pStyle['OL']? pStyle['LiIndex'] + '. ' : '') + curCh.textContent);
970					}
971				}
972			}
973		};
974
975		if (format == 'html' && mxClient.IS_SVG)
976		{
977			//Get the actual HTML label node
978			var elt = this.cellState.text.node.getElementsByTagName('div')[mxClient.NO_FO? 0 : 1];
979
980			if (elt != null)
981			{
982				var ch = elt.childNodes;
983
984				processNodeChildren(ch, {});
985			}
986		}
987		else
988		{
989			//If it is not HTML or SVG, we fall back to removing html format
990			var styleMap = {
991				fontColor: that.cellState.style["fontColor"],
992				fontSize: that.cellState.style["fontSize"],
993				fontFamily: that.cellState.style["fontFamily"]
994			};
995			createTextRow(styleMap, charSect, pSect, text, str);
996		}
997
998		var wShift = 0, hShift = 0;
999
1000		h = Math.max(h, calcH);
1001		w = Math.max(w, calcW);
1002		var hw = w/2, hh = h/2;
1003		var pRotDegrees = parseInt(mxUtils.getValue(this.cellState.style, 'rotation', '0'));
1004		var pRot = pRotDegrees * Math.PI / 180;
1005
1006		//TODO Fix align and valign for rotated cases. Currently, all rotated shapes labels are centered
1007		switch(align)
1008		{
1009			case "right":
1010				if (pRotDegrees != 0)
1011				{
1012					x -= hw * Math.cos(pRot);
1013					y -= hw * Math.sin(pRot);
1014				}
1015				else
1016				{
1017					wShift = calcW/2;
1018				}
1019			break;
1020			case "center":
1021				//nothing
1022			break;
1023			case "left":
1024				if (pRotDegrees != 0)
1025				{
1026					x += hw * Math.cos(pRot);
1027					y += hw * Math.sin(pRot);
1028				}
1029				else
1030				{
1031					wShift = -calcW/2;
1032				}
1033			break;
1034		}
1035
1036		switch(valign)
1037		{
1038			case "top":
1039				if (pRotDegrees != 0)
1040				{
1041					x += hh * Math.sin(pRot);
1042					y += hh * Math.cos(pRot);
1043				}
1044				else
1045				{
1046					hShift = calcH/2;
1047				}
1048			break;
1049			case "middle":
1050				//nothing
1051			break;
1052			case "bottom":
1053				if (pRotDegrees != 0)
1054				{
1055					x -= hh * Math.sin(pRot);
1056					y -= hh * Math.cos(pRot);
1057				}
1058				else
1059				{
1060					hShift = -calcH/2;
1061				}
1062			break;
1063		}
1064
1065		x = (x - geo.x + s.dx) * s.scale;
1066		y = (geo.height - y + geo.y - s.dy) * s.scale;
1067
1068		this.shape.appendChild(this.createCellElemScaled("TxtPinX", x));
1069		this.shape.appendChild(this.createCellElemScaled("TxtPinY", y));
1070		this.shape.appendChild(this.createCellElemScaled("TxtWidth", w));
1071		this.shape.appendChild(this.createCellElemScaled("TxtHeight", h));
1072        this.shape.appendChild(this.createCellElemScaled("TxtLocPinX", hw + wShift));
1073        this.shape.appendChild(this.createCellElemScaled("TxtLocPinY", hh + hShift));
1074
1075
1076		rotation -= pRotDegrees;
1077
1078		if (rotation != 0)
1079			this.shape.appendChild(this.createCellElem("TxtAngle", (360 - rotation) * Math.PI / 180));
1080
1081
1082
1083		this.shape.appendChild(charSect);
1084		this.shape.appendChild(pSect);
1085		this.shape.appendChild(text);
1086//		if (overflow != null)
1087//		{
1088//			elem.setAttribute('overflow', overflow);
1089//		}
1090//
1091//		if (clip != null)
1092//		{
1093//			elem.setAttribute('clip', (clip) ? '1' : '0');
1094//		}
1095//
1096//		if (dir != null)
1097//		{
1098//			elem.setAttribute('dir', dir);
1099//		}
1100	}
1101};
1102
1103/**
1104 * Function: rotate
1105 *
1106 * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
1107 */
1108mxVsdxCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
1109{
1110	//Vsdx has flipX/Y support separate from rotation
1111	if (theta != 0)
1112	{
1113		var s = this.state;
1114		cx += s.dx;
1115		cy += s.dy;
1116
1117		cx *= s.scale;
1118		cy *= s.scale;
1119
1120		this.shape.appendChild(this.createCellElem("Angle", (360 - theta) * Math.PI / 180));
1121
1122		s.rotation = s.rotation + theta;
1123		s.rotationCx = cx;
1124		s.rotationCy = cy;
1125	}
1126};
1127
1128
1129/**
1130 * Function: stroke
1131 *
1132 * Paints the outline of the current drawing buffer.
1133 */
1134mxVsdxCanvas2D.prototype.stroke = function()
1135{
1136	this.geoSec.appendChild(this.createCellElem("NoFill", "1"));
1137	this.geoSec.appendChild(this.createCellElem("NoLine", "0"));
1138};
1139
1140/**
1141 * Function: fill
1142 *
1143 * Fills the current drawing buffer.
1144 */
1145mxVsdxCanvas2D.prototype.fill = function()
1146{
1147	this.geoSec.appendChild(this.createCellElem("NoFill", "0"));
1148	this.geoSec.appendChild(this.createCellElem("NoLine", "1"));
1149};
1150
1151/**
1152 * Function: fillAndStroke
1153 *
1154 * Fills the current drawing buffer and its outline.
1155 */
1156mxVsdxCanvas2D.prototype.fillAndStroke = function()
1157{
1158	this.geoSec.appendChild(this.createCellElem("NoFill", "0"));
1159	this.geoSec.appendChild(this.createCellElem("NoLine", "0"));
1160};
1161