mxVsdxCanvas2D.js 29 KB

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