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