mxJsCanvas.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. /**
  2. * mxJsCanvas
  3. *
  4. * Open Issues:
  5. *
  6. * - Canvas has no built-in dash-pattern for strokes
  7. * - Use AS code for straight lines
  8. * - Must use proxy for cross domain images
  9. * - Use html2canvas for HTML rendering (Replaces complete page with
  10. * canvas currently, needs API call to render elt to canvas)
  11. */
  12. /**
  13. * Extends mxAbstractCanvas2D
  14. */
  15. function mxJsCanvas(canvas)
  16. {
  17. mxAbstractCanvas2D.call(this);
  18. this.ctx = canvas.getContext('2d');
  19. this.ctx.textBaseline = 'top';
  20. this.ctx.fillStyle = 'rgba(255,255,255,0)';
  21. this.ctx.strokeStyle = 'rgba(0, 0, 0, 0)';
  22. //this.ctx.translate(0.5, 0.5);
  23. this.M_RAD_PER_DEG = Math.PI / 180;
  24. this.images = this.images == null ? [] : this.images;
  25. this.subCanvas = this.subCanvas == null ? [] : this.subCanvas;
  26. };
  27. /**
  28. * Extends mxAbstractCanvas2D
  29. */
  30. mxUtils.extend(mxJsCanvas, mxAbstractCanvas2D);
  31. /**
  32. * Variable: ctx
  33. *
  34. * Holds the current canvas context
  35. */
  36. mxJsCanvas.prototype.ctx = null;
  37. /**
  38. * Variable: ctx
  39. *
  40. * Holds the current canvas context
  41. */
  42. mxJsCanvas.prototype.waitCounter = 0;
  43. /**
  44. * Variable: ctx
  45. *
  46. * Holds the current canvas context
  47. */
  48. mxJsCanvas.prototype.onComplete = null;
  49. /**
  50. * Variable: images
  51. *
  52. * Ordered array of images used in this canvas
  53. */
  54. mxJsCanvas.prototype.images = null;
  55. /**
  56. * Variable: subCanvas
  57. *
  58. * Ordered array of sub canvas elements in this canvas
  59. */
  60. mxJsCanvas.prototype.subCanvas = null;
  61. /**
  62. * Variable: canvasIndex
  63. *
  64. * The current index into the canvas sub-canvas array being processed
  65. */
  66. mxJsCanvas.prototype.canvasIndex = 0;
  67. mxJsCanvas.prototype.hexToRgb = function(hex) {
  68. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  69. var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  70. hex = hex.replace(shorthandRegex, function(m, r, g, b) {
  71. return r + r + g + g + b + b;
  72. });
  73. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  74. return result ? {
  75. r: parseInt(result[1], 16),
  76. g: parseInt(result[2], 16),
  77. b: parseInt(result[3], 16)
  78. } : null;
  79. };
  80. mxJsCanvas.prototype.incWaitCounter = function()
  81. {
  82. this.waitCounter++;
  83. };
  84. mxJsCanvas.prototype.decWaitCounter = function()
  85. {
  86. this.waitCounter--;
  87. if (this.waitCounter == 0 && this.onComplete != null)
  88. {
  89. this.onComplete();
  90. this.onComplete = null;
  91. }
  92. };
  93. mxJsCanvas.prototype.updateFont = function()
  94. {
  95. var style = '';
  96. if ((this.state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  97. {
  98. style += 'bold ';
  99. }
  100. if ((this.state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  101. {
  102. style += 'italic ';
  103. }
  104. this.ctx.font = style + this.state.fontSize + 'px ' + this.state.fontFamily;
  105. };
  106. mxJsCanvas.prototype.save = function()
  107. {
  108. this.states.push(this.state);
  109. this.state = mxUtils.clone(this.state);
  110. this.ctx.save();
  111. };
  112. mxJsCanvas.prototype.restore = function()
  113. {
  114. this.state = this.states.pop();
  115. this.ctx.restore();
  116. };
  117. mxJsCanvas.prototype.scale = function(s)
  118. {
  119. this.state.scale *= s;
  120. this.state.strokeWidth *= s;
  121. this.ctx.scale(s, s);
  122. };
  123. mxJsCanvas.prototype.translate = function(dx, dy)
  124. {
  125. this.state.dx += dx;
  126. this.state.dy += dy;
  127. this.ctx.translate(dx, dy);
  128. };
  129. mxJsCanvas.prototype.rotate = function(theta, flipH, flipV, cx, cy)
  130. {
  131. // This is a special case where the rotation center is scaled so dx/dy,
  132. // which are also scaled, must be applied after scaling the center.
  133. cx -= this.state.dx;
  134. cy -= this.state.dy;
  135. this.ctx.translate(cx, cy);
  136. if (flipH || flipV)
  137. {
  138. var sx = (flipH) ? -1 : 1;
  139. var sy = (flipV) ? -1 : 1;
  140. this.ctx.scale(sx, sy);
  141. }
  142. this.ctx.rotate(theta * this.M_RAD_PER_DEG);
  143. this.ctx.translate(-cx, -cy);
  144. };
  145. mxJsCanvas.prototype.setAlpha = function(alpha)
  146. {
  147. this.state.alpha = alpha;
  148. this.ctx.globalAlpha = alpha;
  149. };
  150. /**
  151. * Function: setFillColor
  152. *
  153. * Sets the current fill color.
  154. */
  155. mxJsCanvas.prototype.setFillColor = function(value)
  156. {
  157. if (value == mxConstants.NONE)
  158. {
  159. value = null;
  160. }
  161. this.state.fillColor = value;
  162. this.state.gradientColor = null;
  163. this.ctx.fillStyle = value;
  164. };
  165. mxJsCanvas.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
  166. {
  167. var gradient = this.ctx.createLinearGradient(0, y, 0, y + h);
  168. var s = this.state;
  169. s.fillColor = color1;
  170. s.fillAlpha = (alpha1 != null) ? alpha1 : 1;
  171. s.gradientColor = color2;
  172. s.gradientAlpha = (alpha2 != null) ? alpha2 : 1;
  173. s.gradientDirection = direction;
  174. var rgb1 = this.hexToRgb(color1);
  175. var rgb2 = this.hexToRgb(color2);
  176. if (rgb1 != null)
  177. {
  178. gradient.addColorStop(0, 'rgba(' + rgb1.r + ',' + rgb1.g + ',' + rgb1.b + ',' + s.fillAlpha + ')');
  179. }
  180. if (rgb2 != null)
  181. {
  182. gradient.addColorStop(1, 'rgba(' + rgb2.r + ',' + rgb2.g + ',' + rgb2.b + ',' + s.gradientAlpha + ')');
  183. }
  184. this.ctx.fillStyle = gradient;
  185. };
  186. mxJsCanvas.prototype.setStrokeColor = function(value)
  187. {
  188. if (value == null)
  189. {
  190. // null value ignored
  191. }
  192. else if (value == mxConstants.NONE)
  193. {
  194. this.state.strokeColor = null;
  195. this.ctx.strokeStyle = 'rgba(0, 0, 0, 0)';
  196. }
  197. else
  198. {
  199. this.ctx.strokeStyle = value;
  200. this.state.strokeColor = value;
  201. }
  202. };
  203. mxJsCanvas.prototype.setStrokeWidth = function(value)
  204. {
  205. this.ctx.lineWidth = value;
  206. };
  207. mxJsCanvas.prototype.setDashed = function(value)
  208. {
  209. this.state.dashed = value;
  210. if (value)
  211. {
  212. var dashArray = this.state.dashPattern.split(" ");
  213. for (var i = 0; i < dashArray.length; i++)
  214. {
  215. dashArray[i] = parseInt(dashArray[i], 10);
  216. }
  217. this.setLineDash(dashArray);
  218. }
  219. else
  220. {
  221. this.setLineDash([0]);
  222. }
  223. };
  224. mxJsCanvas.prototype.setLineDash = function(value)
  225. {
  226. try
  227. {
  228. if (typeof this.ctx.setLineDash === "function")
  229. {
  230. this.ctx.setLineDash(value);
  231. }
  232. else
  233. {
  234. // Line dash not supported IE 10-
  235. }
  236. }
  237. catch (e)
  238. {
  239. // ignore
  240. }
  241. };
  242. mxJsCanvas.prototype.setDashPattern = function(value)
  243. {
  244. this.state.dashPattern = value;
  245. if (this.state.dashed)
  246. {
  247. var dashArray = value.split(" ");
  248. for (var i = 0; i < dashArray.length; i++)
  249. {
  250. dashArray[i] = parseInt(dashArray[i], 10);
  251. }
  252. this.ctx.setLineDash(dashArray);
  253. }
  254. };
  255. mxJsCanvas.prototype.setLineCap = function(value)
  256. {
  257. this.ctx.lineCap = value;
  258. };
  259. mxJsCanvas.prototype.setLineJoin = function(value)
  260. {
  261. this.ctx.lineJoin = value;
  262. };
  263. mxJsCanvas.prototype.setMiterLimit = function(value)
  264. {
  265. this.ctx.lineJoin = value;
  266. };
  267. mxJsCanvas.prototype.setFontColor = function(value)
  268. {
  269. this.ctx.fillStyle = value;
  270. };
  271. mxJsCanvas.prototype.setFontBackgroundColor = function(value)
  272. {
  273. if (value == mxConstants.NONE)
  274. {
  275. value = null;
  276. }
  277. this.state.fontBackgroundColor = value;
  278. };
  279. mxJsCanvas.prototype.setFontBorderColor = function(value)
  280. {
  281. if (value == mxConstants.NONE)
  282. {
  283. value = null;
  284. }
  285. this.state.fontBorderColor = value;
  286. };
  287. mxJsCanvas.prototype.setFontSize = function(value)
  288. {
  289. this.state.fontSize = value;
  290. };
  291. mxJsCanvas.prototype.setFontFamily = function(value)
  292. {
  293. this.state.fontFamily = value;
  294. };
  295. mxJsCanvas.prototype.setFontStyle = function(value)
  296. {
  297. this.state.fontStyle = value;
  298. };
  299. /**
  300. * Function: setShadow
  301. *
  302. * Enables or disables and configures the current shadow.
  303. */
  304. mxJsCanvas.prototype.setShadow = function(enabled)
  305. {
  306. this.state.shadow = enabled;
  307. if (enabled)
  308. {
  309. this.setShadowOffset(this.state.shadowDx, this.state.shadowDy);
  310. this.setShadowAlpha(this.state.shadowAlpha);
  311. }
  312. else
  313. {
  314. this.ctx.shadowColor = 'transparent';
  315. this.ctx.shadowBlur = 0;
  316. this.ctx.shadowOffsetX = 0;
  317. this.ctx.shadowOffsetY = 0;
  318. }
  319. };
  320. /**
  321. * Function: setShadowColor
  322. *
  323. * Enables or disables and configures the current shadow.
  324. */
  325. mxJsCanvas.prototype.setShadowColor = function(value)
  326. {
  327. if (value == null || value == mxConstants.NONE)
  328. {
  329. value = null;
  330. this.ctx.shadowColor = 'transparent';
  331. }
  332. this.state.shadowColor = value;
  333. if (this.state.shadow && value != null)
  334. {
  335. var alpha = (this.state.shadowAlpha != null) ? this.state.shadowAlpha : 1;
  336. var rgb = this.hexToRgb(value);
  337. this.ctx.shadowColor = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + alpha + ')';
  338. }
  339. };
  340. /**
  341. * Function: setShadowAlpha
  342. *
  343. * Enables or disables and configures the current shadow.
  344. */
  345. mxJsCanvas.prototype.setShadowAlpha = function(value)
  346. {
  347. this.state.shadowAlpha = value;
  348. this.setShadowColor(this.state.shadowColor);
  349. };
  350. /**
  351. * Function: setShadowOffset
  352. *
  353. * Enables or disables and configures the current shadow.
  354. */
  355. mxJsCanvas.prototype.setShadowOffset = function(dx, dy)
  356. {
  357. this.state.shadowDx = dx;
  358. this.state.shadowDy = dy;
  359. if (this.state.shadow)
  360. {
  361. this.ctx.shadowOffsetX = dx;
  362. this.ctx.shadowOffsetY = dy;
  363. }
  364. };
  365. mxJsCanvas.prototype.moveTo = function(x, y)
  366. {
  367. this.ctx.moveTo(x, y);
  368. this.lastMoveX = x;
  369. this.lastMoveY = y;
  370. };
  371. mxJsCanvas.prototype.lineTo = function(x, y)
  372. {
  373. this.ctx.lineTo(x, y);
  374. this.lastMoveX = x;
  375. this.lastMoveY = y;
  376. };
  377. mxJsCanvas.prototype.quadTo = function(x1, y1, x2, y2)
  378. {
  379. this.ctx.quadraticCurveTo(x1, y1, x2, y2);
  380. this.lastMoveX = x2;
  381. this.lastMoveY = y2;
  382. };
  383. mxJsCanvas.prototype.arcTo = function(rx, ry, angle, largeArcFlag, sweepFlag, x, y)
  384. {
  385. var curves = mxUtils.arcToCurves(this.lastMoveX, this.lastMoveY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);
  386. if (curves != null)
  387. {
  388. for (var i = 0; i < curves.length; i += 6)
  389. {
  390. this.curveTo(curves[i], curves[i + 1], curves[i + 2],
  391. curves[i + 3], curves[i + 4], curves[i + 5]);
  392. }
  393. }
  394. };
  395. mxJsCanvas.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
  396. {
  397. this.ctx.bezierCurveTo(x1, y1, x2, y2 , x3, y3);
  398. this.lastMoveX = x3;
  399. this.lastMoveY = y3;
  400. };
  401. mxJsCanvas.prototype.rect = function(x, y, w, h)
  402. {
  403. // TODO: Check if fillRect/strokeRect is faster
  404. this.begin();
  405. this.moveTo(x, y);
  406. this.lineTo(x + w, y);
  407. this.lineTo(x + w, y + h);
  408. this.lineTo(x, y + h);
  409. this.close();
  410. };
  411. mxJsCanvas.prototype.roundrect = function(x, y, w, h, dx, dy)
  412. {
  413. this.begin();
  414. this.moveTo(x + dx, y);
  415. this.lineTo(x + w - dx, y);
  416. this.quadTo(x + w, y, x + w, y + dy);
  417. this.lineTo(x + w, y + h - dy);
  418. this.quadTo(x + w, y + h, x + w - dx, y + h);
  419. this.lineTo(x + dx, y + h);
  420. this.quadTo(x, y + h, x, y + h - dy);
  421. this.lineTo(x, y + dy);
  422. this.quadTo(x, y, x + dx, y);
  423. };
  424. mxJsCanvas.prototype.ellipse = function(x, y, w, h)
  425. {
  426. this.ctx.save();
  427. this.ctx.translate((x + w / 2), (y + h / 2));
  428. this.ctx.scale(w / 2, h / 2);
  429. this.ctx.beginPath();
  430. this.ctx.arc(0, 0, 1, 0, 2 * Math.PI, false);
  431. this.ctx.restore();
  432. };
  433. //Redirect can be implemented via a hook
  434. mxJsCanvas.prototype.rewriteImageSource = function(src)
  435. {
  436. if (src.substring(0, 7) == 'http://' || src.substring(0, 8) == 'https://')
  437. {
  438. src = '/proxy?url=' + encodeURIComponent(src);
  439. }
  440. return src;
  441. };
  442. mxJsCanvas.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
  443. {
  444. var scale = this.state.scale;
  445. // x = this.state.tx + x / scale;
  446. // y = this.state.ty + y / scale;
  447. // w /= scale;
  448. // h /= scale;
  449. src = this.rewriteImageSource(src);
  450. var image = this.images[src];
  451. function drawImage(ctx, image, x, y, w, h)
  452. {
  453. ctx.save();
  454. if (aspect)
  455. {
  456. var iw = image.width;
  457. var ih = image.height;
  458. var s = Math.min(w / iw, h / ih);
  459. var x0 = (w - iw * s) / 2;
  460. var y0 = (h - ih * s) / 2;
  461. x += x0;
  462. y += y0;
  463. w = iw * s;
  464. h = ih * s;
  465. }
  466. var s = this.state.scale;
  467. if (flipH)
  468. {
  469. ctx.translate(2 * x + w, 0);
  470. ctx.scale(-1, 1);
  471. }
  472. if (flipV)
  473. {
  474. ctx.translate(0, 2 * y + h);
  475. ctx.scale(1, -1);
  476. }
  477. ctx.drawImage(image, x, y, w, h);
  478. ctx.restore();
  479. };
  480. if (image != null && image.height > 0 && image.width > 0)
  481. {
  482. drawImage.call(this, this.ctx, image, x, y, w, h);
  483. }
  484. else
  485. {
  486. // TODO flag error that image wasn't obtaining in canvas preprocessing
  487. }
  488. };
  489. mxJsCanvas.prototype.begin = function()
  490. {
  491. this.ctx.beginPath();
  492. };
  493. mxJsCanvas.prototype.close = function()
  494. {
  495. this.ctx.closePath();
  496. };
  497. mxJsCanvas.prototype.fill = function()
  498. {
  499. this.ctx.fill();
  500. };
  501. mxJsCanvas.prototype.stroke = function()
  502. {
  503. this.ctx.stroke();
  504. };
  505. mxJsCanvas.prototype.fillAndStroke = function()
  506. {
  507. // If you fill then stroke, the shadow of the stroke appears over the fill
  508. // So stroke, fill, disable shadow, stroke, restore previous shadow
  509. if (!this.state.shadow)
  510. {
  511. this.ctx.fill();
  512. this.ctx.stroke();
  513. }
  514. else
  515. {
  516. this.ctx.stroke();
  517. this.ctx.fill();
  518. var shadowColor = this.ctx.shadowColor;
  519. var shadowOffsetX = this.ctx.shadowOffsetX;
  520. var shadowOffsetY = this.ctx.shadowOffsetY;
  521. this.ctx.shadowColor = 'transparent';
  522. this.ctx.shadowOffsetX = 0;
  523. this.ctx.shadowOffsetY = 0;
  524. this.ctx.stroke();
  525. this.ctx.shadowColor = shadowColor;
  526. this.ctx.shadowOffsetX = shadowOffsetX;
  527. this.ctx.shadowOffsetY = shadowOffsetY;
  528. }
  529. };
  530. mxJsCanvas.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
  531. {
  532. if (str == null || str.length == 0)
  533. {
  534. return;
  535. }
  536. var sc = this.state.scale;
  537. w *= sc;
  538. h *= sc;
  539. if (rotation != 0)
  540. {
  541. this.ctx.translate(Math.round(x), Math.round(y));
  542. this.ctx.rotate(rotation * Math.PI / 180);
  543. this.ctx.translate(Math.round(-x), Math.round(-y));
  544. }
  545. if (format == 'html')
  546. {
  547. var subCanvas = this.subCanvas[this.canvasIndex++];
  548. var cavHeight = subCanvas.height;
  549. var cavWidth = subCanvas.width;
  550. switch (valign)
  551. {
  552. case mxConstants.ALIGN_MIDDLE:
  553. y -= cavHeight / 2 /sc;
  554. break;
  555. case mxConstants.ALIGN_BOTTOM:
  556. y -= cavHeight / sc;
  557. break;
  558. }
  559. switch (align)
  560. {
  561. case mxConstants.ALIGN_CENTER:
  562. x -= cavWidth / 2 / sc;
  563. break;
  564. case mxConstants.ALIGN_RIGHT:
  565. x -= cavWidth / sc;
  566. break;
  567. }
  568. this.ctx.save();
  569. if (this.state.fontBackgroundColor != null || this.state.fontBorderColor != null)
  570. {
  571. if (this.state.fontBackgroundColor != null)
  572. {
  573. this.ctx.fillStyle = this.state.fontBackgroundColor;
  574. this.ctx.fillRect(Math.round(x) - 0.5, Math.round(y) - 0.5, Math.round(subCanvas.width / sc), Math.round(subCanvas.height / sc));
  575. }
  576. if (this.state.fontBorderColor != null)
  577. {
  578. this.ctx.strokeStyle = this.state.fontBorderColor;
  579. this.ctx.lineWidth = 1;
  580. this.ctx.strokeRect(Math.round(x) - 0.5, Math.round(y) - 0.5, Math.round(subCanvas.width / sc), Math.round(subCanvas.height / sc));
  581. }
  582. }
  583. //if (sc < 1)
  584. //{
  585. this.ctx.scale(1/sc, 1/sc);
  586. //}
  587. this.ctx.drawImage(subCanvas, Math.round(x * sc) ,Math.round(y * sc));
  588. this.ctx.restore();
  589. }
  590. else
  591. {
  592. this.ctx.save();
  593. this.updateFont();
  594. var div = document.createElement("div");
  595. div.innerHTML = str;
  596. div.style.position = 'absolute';
  597. div.style.top = '-9999px';
  598. div.style.left = '-9999px';
  599. div.style.fontFamily = this.state.fontFamily;
  600. div.style.fontWeight = 'bold';
  601. div.style.fontSize = this.state.fontSize + 'pt';
  602. document.body.appendChild(div);
  603. var measuredFont = [div.offsetWidth, div.offsetHeight];
  604. document.body.removeChild(div);
  605. var lines = str.split('\n');
  606. var lineHeight = measuredFont[1];
  607. this.ctx.textBaseline = 'top';
  608. var backgroundY = y;
  609. switch (valign)
  610. {
  611. case mxConstants.ALIGN_MIDDLE:
  612. this.ctx.textBaseline = 'middle';
  613. y -= (lines.length-1) * lineHeight / 2;
  614. backgroundY = y - this.state.fontSize / 2;
  615. break;
  616. case mxConstants.ALIGN_BOTTOM:
  617. this.ctx.textBaseline = 'alphabetic';
  618. y -= lineHeight * (lines.length-1);
  619. backgroundY = y - this.state.fontSize;
  620. break;
  621. }
  622. var lineWidth = [];
  623. var lineX = [];
  624. for (var i = 0; i < lines.length; i++)
  625. {
  626. lineX[i] = x;
  627. lineWidth[i] = this.ctx.measureText(lines[i]).width;
  628. if (align != null && align != mxConstants.ALIGN_LEFT)
  629. {
  630. lineX[i] -= lineWidth[i];
  631. if (align == mxConstants.ALIGN_CENTER)
  632. {
  633. lineX[i] += lineWidth[i] / 2;
  634. }
  635. }
  636. }
  637. if (this.state.fontBackgroundColor != null || this.state.fontBorderColor != null)
  638. {
  639. var startMostX = lineX[0];
  640. var maxWidth = lineWidth[0];
  641. for (var i = 1; i < lines.length; i++)
  642. {
  643. startMostX = Math.min(startMostX, lineX[i]);
  644. maxWidth = Math.max(maxWidth, lineWidth[i]);
  645. }
  646. this.ctx.save();
  647. startMostX = Math.round(startMostX) - 0.5;
  648. backgroundY = Math.round(backgroundY) - 0.5;
  649. if (this.state.fontBackgroundColor != null)
  650. {
  651. this.ctx.fillStyle = this.state.fontBackgroundColor;
  652. this.ctx.fillRect(startMostX, backgroundY, maxWidth, this.state.fontSize * mxConstants.LINE_HEIGHT * lines.length);
  653. }
  654. if (this.state.fontBorderColor != null)
  655. {
  656. this.ctx.strokeStyle = this.state.fontBorderColor;
  657. this.ctx.lineWidth = 1;
  658. this.ctx.strokeRect(startMostX, backgroundY, maxWidth, this.state.fontSize * mxConstants.LINE_HEIGHT * lines.length);
  659. }
  660. this.ctx.restore();
  661. }
  662. for (var i = 0; i < lines.length; i++)
  663. {
  664. this.ctx.fillText(lines[i], lineX[i], y);
  665. y += this.state.fontSize * mxConstants.LINE_HEIGHT;
  666. }
  667. this.ctx.restore();
  668. }
  669. };
  670. mxJsCanvas.prototype.getCanvas = function()
  671. {
  672. return canvas;
  673. };
  674. mxJsCanvas.prototype.finish = function(handler)
  675. {
  676. // TODO: Check if waitCounter updates need a monitor. Question is
  677. // if image load-handler can be executed in parallel leading to
  678. // race conditions when updating the "shared" waitCounter.
  679. if (this.waitCounter == 0)
  680. {
  681. handler();
  682. }
  683. else
  684. {
  685. this.onComplete = handler;
  686. }
  687. };