mxVmlCanvas2D.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. *
  7. * Class: mxVmlCanvas2D
  8. *
  9. * Implements a canvas to be used for rendering VML. Here is an example of implementing a
  10. * fallback for SVG images which are not supported in VML-based browsers.
  11. *
  12. * (code)
  13. * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;
  14. * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
  15. * {
  16. * if (src.substring(src.length - 4, src.length) == '.svg')
  17. * {
  18. * src = 'http://www.jgraph.com/images/mxgraph.gif';
  19. * }
  20. *
  21. * mxVmlCanvas2DImage.apply(this, arguments);
  22. * };
  23. * (end)
  24. *
  25. * To disable anti-aliasing in the output, use the following code.
  26. *
  27. * (code)
  28. * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}';
  29. * (end)
  30. *
  31. * A description of the public API is available in <mxXmlCanvas2D>. Note that
  32. * there is a known issue in VML where gradients are painted using the outer
  33. * bounding box of rotated shapes, not the actual bounds of the shape. See
  34. * also <text> for plain text label restrictions in shapes for VML.
  35. */
  36. var mxVmlCanvas2D = function(root)
  37. {
  38. mxAbstractCanvas2D.call(this);
  39. /**
  40. * Variable: root
  41. *
  42. * Reference to the container for the SVG content.
  43. */
  44. this.root = root;
  45. };
  46. /**
  47. * Extends mxAbstractCanvas2D
  48. */
  49. mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);
  50. /**
  51. * Variable: path
  52. *
  53. * Holds the current DOM node.
  54. */
  55. mxVmlCanvas2D.prototype.node = null;
  56. /**
  57. * Variable: textEnabled
  58. *
  59. * Specifies if text output should be enabledetB. Default is true.
  60. */
  61. mxVmlCanvas2D.prototype.textEnabled = true;
  62. /**
  63. * Variable: moveOp
  64. *
  65. * Contains the string used for moving in paths. Default is 'm'.
  66. */
  67. mxVmlCanvas2D.prototype.moveOp = 'm';
  68. /**
  69. * Variable: lineOp
  70. *
  71. * Contains the string used for moving in paths. Default is 'l'.
  72. */
  73. mxVmlCanvas2D.prototype.lineOp = 'l';
  74. /**
  75. * Variable: curveOp
  76. *
  77. * Contains the string used for bezier curves. Default is 'c'.
  78. */
  79. mxVmlCanvas2D.prototype.curveOp = 'c';
  80. /**
  81. * Variable: closeOp
  82. *
  83. * Holds the operator for closing curves. Default is 'x e'.
  84. */
  85. mxVmlCanvas2D.prototype.closeOp = 'x';
  86. /**
  87. * Variable: rotatedHtmlBackground
  88. *
  89. * Background color for rotated HTML. Default is ''. This can be set to eg.
  90. * white to improve rendering of rotated text in VML for IE9.
  91. */
  92. mxVmlCanvas2D.prototype.rotatedHtmlBackground = '';
  93. /**
  94. * Variable: vmlScale
  95. *
  96. * Specifies the scale used to draw VML shapes.
  97. */
  98. mxVmlCanvas2D.prototype.vmlScale = 1;
  99. /**
  100. * Function: createElement
  101. *
  102. * Creates the given element using the document.
  103. */
  104. mxVmlCanvas2D.prototype.createElement = function(name)
  105. {
  106. return document.createElement(name);
  107. };
  108. /**
  109. * Function: createVmlElement
  110. *
  111. * Creates a new element using <createElement> and prefixes the given name with
  112. * <mxClient.VML_PREFIX>.
  113. */
  114. mxVmlCanvas2D.prototype.createVmlElement = function(name)
  115. {
  116. return this.createElement(mxClient.VML_PREFIX + ':' + name);
  117. };
  118. /**
  119. * Function: addNode
  120. *
  121. * Adds the current node to the <root>.
  122. */
  123. mxVmlCanvas2D.prototype.addNode = function(filled, stroked)
  124. {
  125. var node = this.node;
  126. var s = this.state;
  127. if (node != null)
  128. {
  129. if (node.nodeName == 'shape')
  130. {
  131. // Checks if the path is not empty
  132. if (this.path != null && this.path.length > 0)
  133. {
  134. node.path = this.path.join(' ') + ' e';
  135. node.style.width = this.root.style.width;
  136. node.style.height = this.root.style.height;
  137. node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
  138. }
  139. else
  140. {
  141. return;
  142. }
  143. }
  144. node.strokeweight = this.format(Math.max(1, s.strokeWidth * s.scale / this.vmlScale)) + 'px';
  145. if (s.shadow)
  146. {
  147. this.root.appendChild(this.createShadow(node,
  148. filled && s.fillColor != null,
  149. stroked && s.strokeColor != null));
  150. }
  151. if (stroked && s.strokeColor != null)
  152. {
  153. node.stroked = 'true';
  154. node.strokecolor = s.strokeColor;
  155. }
  156. else
  157. {
  158. node.stroked = 'false';
  159. }
  160. node.appendChild(this.createStroke());
  161. if (filled && s.fillColor != null)
  162. {
  163. node.appendChild(this.createFill());
  164. }
  165. else if (this.pointerEvents && (node.nodeName != 'shape' ||
  166. this.path[this.path.length - 1] == this.closeOp))
  167. {
  168. node.appendChild(this.createTransparentFill());
  169. }
  170. else
  171. {
  172. node.filled = 'false';
  173. }
  174. // LATER: Update existing DOM for performance
  175. this.root.appendChild(node);
  176. }
  177. };
  178. /**
  179. * Function: createTransparentFill
  180. *
  181. * Creates a transparent fill.
  182. */
  183. mxVmlCanvas2D.prototype.createTransparentFill = function()
  184. {
  185. var fill = this.createVmlElement('fill');
  186. fill.src = mxClient.imageBasePath + '/transparent.gif';
  187. fill.type = 'tile';
  188. return fill;
  189. };
  190. /**
  191. * Function: createFill
  192. *
  193. * Creates a fill for the current state.
  194. */
  195. mxVmlCanvas2D.prototype.createFill = function()
  196. {
  197. var s = this.state;
  198. // Gradients in foregrounds not supported because special gradients
  199. // with bounds must be created for each element in graphics-canvases
  200. var fill = this.createVmlElement('fill');
  201. fill.color = s.fillColor;
  202. if (s.gradientColor != null)
  203. {
  204. fill.type = 'gradient';
  205. fill.method = 'none';
  206. fill.color2 = s.gradientColor;
  207. var angle = 180 - s.rotation;
  208. if (s.gradientDirection == mxConstants.DIRECTION_WEST)
  209. {
  210. angle -= 90 + ((this.root.style.flip == 'x') ? 180 : 0);
  211. }
  212. else if (s.gradientDirection == mxConstants.DIRECTION_EAST)
  213. {
  214. angle += 90 + ((this.root.style.flip == 'x') ? 180 : 0);
  215. }
  216. else if (s.gradientDirection == mxConstants.DIRECTION_NORTH)
  217. {
  218. angle -= 180 + ((this.root.style.flip == 'y') ? -180 : 0);
  219. }
  220. else
  221. {
  222. angle += ((this.root.style.flip == 'y') ? -180 : 0);
  223. }
  224. if (this.root.style.flip == 'x' || this.root.style.flip == 'y')
  225. {
  226. angle *= -1;
  227. }
  228. // LATER: Fix outer bounding box for rotated shapes used in VML.
  229. fill.angle = mxUtils.mod(angle, 360);
  230. fill.opacity = (s.alpha * s.gradientFillAlpha * 100) + '%';
  231. fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', (s.alpha * s.gradientAlpha * 100) + '%');
  232. }
  233. else if (s.alpha < 1 || s.fillAlpha < 1)
  234. {
  235. fill.opacity = (s.alpha * s.fillAlpha * 100) + '%';
  236. }
  237. return fill;
  238. };
  239. /**
  240. * Function: createStroke
  241. *
  242. * Creates a fill for the current state.
  243. */
  244. mxVmlCanvas2D.prototype.createStroke = function()
  245. {
  246. var s = this.state;
  247. var stroke = this.createVmlElement('stroke');
  248. stroke.endcap = s.lineCap || 'flat';
  249. stroke.joinstyle = s.lineJoin || 'miter';
  250. stroke.miterlimit = s.miterLimit || '10';
  251. if (s.alpha < 1 || s.strokeAlpha < 1)
  252. {
  253. stroke.opacity = (s.alpha * s.strokeAlpha * 100) + '%';
  254. }
  255. if (s.dashed)
  256. {
  257. stroke.dashstyle = this.getVmlDashStyle();
  258. }
  259. return stroke;
  260. };
  261. /**
  262. * Function: getVmlDashPattern
  263. *
  264. * Returns a VML dash pattern for the current dashPattern.
  265. * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
  266. */
  267. mxVmlCanvas2D.prototype.getVmlDashStyle = function()
  268. {
  269. var result = 'dash';
  270. if (typeof(this.state.dashPattern) === 'string')
  271. {
  272. var tok = this.state.dashPattern.split(' ');
  273. if (tok.length > 0 && tok[0] == 1)
  274. {
  275. result = '0 2';
  276. }
  277. }
  278. return result;
  279. };
  280. /**
  281. * Function: createShadow
  282. *
  283. * Creates a shadow for the given node.
  284. */
  285. mxVmlCanvas2D.prototype.createShadow = function(node, filled, stroked)
  286. {
  287. var s = this.state;
  288. var rad = -s.rotation * (Math.PI / 180);
  289. var cos = Math.cos(rad);
  290. var sin = Math.sin(rad);
  291. var dx = s.shadowDx * s.scale;
  292. var dy = s.shadowDy * s.scale;
  293. if (this.root.style.flip == 'x')
  294. {
  295. dx *= -1;
  296. }
  297. else if (this.root.style.flip == 'y')
  298. {
  299. dy *= -1;
  300. }
  301. var shadow = node.cloneNode(true);
  302. shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px';
  303. shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px';
  304. // Workaround for wrong cloning in IE8 standards mode
  305. if (document.documentMode == 8)
  306. {
  307. shadow.strokeweight = node.strokeweight;
  308. if (node.nodeName == 'shape')
  309. {
  310. shadow.path = this.path.join(' ') + ' e';
  311. shadow.style.width = this.root.style.width;
  312. shadow.style.height = this.root.style.height;
  313. shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
  314. }
  315. }
  316. if (stroked)
  317. {
  318. shadow.strokecolor = s.shadowColor;
  319. shadow.appendChild(this.createShadowStroke());
  320. }
  321. else
  322. {
  323. shadow.stroked = 'false';
  324. }
  325. if (filled)
  326. {
  327. shadow.appendChild(this.createShadowFill());
  328. }
  329. else
  330. {
  331. shadow.filled = 'false';
  332. }
  333. return shadow;
  334. };
  335. /**
  336. * Function: createShadowFill
  337. *
  338. * Creates the fill for the shadow.
  339. */
  340. mxVmlCanvas2D.prototype.createShadowFill = function()
  341. {
  342. var fill = this.createVmlElement('fill');
  343. fill.color = this.state.shadowColor;
  344. fill.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
  345. return fill;
  346. };
  347. /**
  348. * Function: createShadowStroke
  349. *
  350. * Creates the stroke for the shadow.
  351. */
  352. mxVmlCanvas2D.prototype.createShadowStroke = function()
  353. {
  354. var stroke = this.createStroke();
  355. stroke.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
  356. return stroke;
  357. };
  358. /**
  359. * Function: rotate
  360. *
  361. * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
  362. */
  363. mxVmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
  364. {
  365. if (flipH && flipV)
  366. {
  367. theta += 180;
  368. }
  369. else if (flipH)
  370. {
  371. this.root.style.flip = 'x';
  372. }
  373. else if (flipV)
  374. {
  375. this.root.style.flip = 'y';
  376. }
  377. if (flipH ? !flipV : flipV)
  378. {
  379. theta *= -1;
  380. }
  381. this.root.style.rotation = theta;
  382. this.state.rotation = this.state.rotation + theta;
  383. this.state.rotationCx = cx;
  384. this.state.rotationCy = cy;
  385. };
  386. /**
  387. * Function: begin
  388. *
  389. * Extends superclass to create path.
  390. */
  391. mxVmlCanvas2D.prototype.begin = function()
  392. {
  393. mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
  394. this.node = this.createVmlElement('shape');
  395. this.node.style.position = 'absolute';
  396. };
  397. /**
  398. * Function: quadTo
  399. *
  400. * Replaces quadratic curve with bezier curve in VML.
  401. */
  402. mxVmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
  403. {
  404. var s = this.state;
  405. var cpx0 = (this.lastX + s.dx) * s.scale;
  406. var cpy0 = (this.lastY + s.dy) * s.scale;
  407. var qpx1 = (x1 + s.dx) * s.scale;
  408. var qpy1 = (y1 + s.dy) * s.scale;
  409. var cpx3 = (x2 + s.dx) * s.scale;
  410. var cpy3 = (y2 + s.dy) * s.scale;
  411. var cpx1 = cpx0 + 2/3 * (qpx1 - cpx0);
  412. var cpy1 = cpy0 + 2/3 * (qpy1 - cpy0);
  413. var cpx2 = cpx3 + 2/3 * (qpx1 - cpx3);
  414. var cpy2 = cpy3 + 2/3 * (qpy1 - cpy3);
  415. this.path.push('c ' + this.format(cpx1) + ' ' + this.format(cpy1) +
  416. ' ' + this.format(cpx2) + ' ' + this.format(cpy2) +
  417. ' ' + this.format(cpx3) + ' ' + this.format(cpy3));
  418. this.lastX = (cpx3 / s.scale) - s.dx;
  419. this.lastY = (cpy3 / s.scale) - s.dy;
  420. };
  421. /**
  422. * Function: createRect
  423. *
  424. * Sets the glass gradient.
  425. */
  426. mxVmlCanvas2D.prototype.createRect = function(nodeName, x, y, w, h)
  427. {
  428. var s = this.state;
  429. var n = this.createVmlElement(nodeName);
  430. n.style.position = 'absolute';
  431. n.style.left = this.format((x + s.dx) * s.scale) + 'px';
  432. n.style.top = this.format((y + s.dy) * s.scale) + 'px';
  433. n.style.width = this.format(w * s.scale) + 'px';
  434. n.style.height = this.format(h * s.scale) + 'px';
  435. return n;
  436. };
  437. /**
  438. * Function: rect
  439. *
  440. * Sets the current path to a rectangle.
  441. */
  442. mxVmlCanvas2D.prototype.rect = function(x, y, w, h)
  443. {
  444. this.node = this.createRect('rect', x, y, w, h);
  445. };
  446. /**
  447. * Function: roundrect
  448. *
  449. * Sets the current path to a rounded rectangle.
  450. */
  451. mxVmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
  452. {
  453. this.node = this.createRect('roundrect', x, y, w, h);
  454. // SetAttribute needed here for IE8
  455. this.node.setAttribute('arcsize', Math.max(dx * 100 / w, dy * 100 / h) + '%');
  456. };
  457. /**
  458. * Function: ellipse
  459. *
  460. * Sets the current path to an ellipse.
  461. */
  462. mxVmlCanvas2D.prototype.ellipse = function(x, y, w, h)
  463. {
  464. this.node = this.createRect('oval', x, y, w, h);
  465. };
  466. /**
  467. * Function: image
  468. *
  469. * Paints an image.
  470. */
  471. mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
  472. {
  473. var node = null;
  474. if (!aspect)
  475. {
  476. node = this.createRect('image', x, y, w, h);
  477. node.src = src;
  478. }
  479. else
  480. {
  481. // Uses fill with aspect to avoid asynchronous update of size
  482. node = this.createRect('rect', x, y, w, h);
  483. node.stroked = 'false';
  484. // Handles image aspect via fill
  485. var fill = this.createVmlElement('fill');
  486. fill.aspect = (aspect) ? 'atmost' : 'ignore';
  487. fill.rotate = 'true';
  488. fill.type = 'frame';
  489. fill.src = src;
  490. node.appendChild(fill);
  491. }
  492. if (flipH && flipV)
  493. {
  494. node.style.rotation = '180';
  495. }
  496. else if (flipH)
  497. {
  498. node.style.flip = 'x';
  499. }
  500. else if (flipV)
  501. {
  502. node.style.flip = 'y';
  503. }
  504. if (this.state.alpha < 1 || this.state.fillAlpha < 1)
  505. {
  506. // KNOWN: Borders around transparent images in IE<9. Using fill.opacity
  507. // fixes this problem by adding a white background in all IE versions.
  508. node.style.filter += 'alpha(opacity=' + (this.state.alpha * this.state.fillAlpha * 100) + ')';
  509. }
  510. this.root.appendChild(node);
  511. };
  512. /**
  513. * Function: createText
  514. *
  515. * Creates the innermost element that contains the HTML text.
  516. */
  517. mxVmlCanvas2D.prototype.createDiv = function(str, align, valign, overflow)
  518. {
  519. var div = this.createElement('div');
  520. var state = this.state;
  521. var css = '';
  522. if (state.fontBackgroundColor != null)
  523. {
  524. css += 'background-color:' + mxUtils.htmlEntities(state.fontBackgroundColor) + ';';
  525. }
  526. if (state.fontBorderColor != null)
  527. {
  528. css += 'border:1px solid ' + mxUtils.htmlEntities(state.fontBorderColor) + ';';
  529. }
  530. if (mxUtils.isNode(str))
  531. {
  532. div.appendChild(str);
  533. }
  534. else
  535. {
  536. if (overflow != 'fill' && overflow != 'width')
  537. {
  538. var div2 = this.createElement('div');
  539. div2.style.cssText = css;
  540. div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
  541. div2.style.zoom = '1';
  542. div2.style.textDecoration = 'inherit';
  543. div2.innerHTML = str;
  544. div.appendChild(div2);
  545. }
  546. else
  547. {
  548. div.style.cssText = css;
  549. div.innerHTML = str;
  550. }
  551. }
  552. var style = div.style;
  553. style.fontSize = (state.fontSize / this.vmlScale) + 'px';
  554. style.fontFamily = state.fontFamily;
  555. style.color = state.fontColor;
  556. style.verticalAlign = 'top';
  557. style.textAlign = align || 'left';
  558. style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (state.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + 'px' : mxConstants.LINE_HEIGHT;
  559. if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  560. {
  561. style.fontWeight = 'bold';
  562. }
  563. if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  564. {
  565. style.fontStyle = 'italic';
  566. }
  567. if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
  568. {
  569. style.textDecoration = 'underline';
  570. }
  571. return div;
  572. };
  573. /**
  574. * Function: text
  575. *
  576. * Paints the given text. Possible values for format are empty string for plain
  577. * text and html for HTML markup. Clipping, text background and border are not
  578. * supported for plain text in VML.
  579. */
  580. mxVmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
  581. {
  582. if (this.textEnabled && str != null)
  583. {
  584. var s = this.state;
  585. if (format == 'html')
  586. {
  587. if (s.rotation != null)
  588. {
  589. var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);
  590. x = pt.x;
  591. y = pt.y;
  592. }
  593. if (document.documentMode == 8 && !mxClient.IS_EM)
  594. {
  595. x += s.dx;
  596. y += s.dy;
  597. // Workaround for rendering offsets
  598. if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP)
  599. {
  600. y -= 1;
  601. }
  602. }
  603. else
  604. {
  605. x *= s.scale;
  606. y *= s.scale;
  607. }
  608. // Adds event transparency in IE8 standards without the transparent background
  609. // filter which cannot be used due to bugs in the zoomed bounding box (too slow)
  610. // FIXME: No event transparency if inside v:rect (ie part of shape)
  611. // KNOWN: Offset wrong for rotated text with word that are longer than the wrapping
  612. // width in IE8 because real width of text cannot be determined here.
  613. // This should be fixed in mxText.updateBoundingBox by calling before this and
  614. // passing the real width to this method if not clipped and wrapped.
  615. var abs = (document.documentMode == 8 && !mxClient.IS_EM) ? this.createVmlElement('group') : this.createElement('div');
  616. abs.style.position = 'absolute';
  617. abs.style.display = 'inline';
  618. abs.style.left = this.format(x) + 'px';
  619. abs.style.top = this.format(y) + 'px';
  620. abs.style.zoom = s.scale;
  621. var box = this.createElement('div');
  622. box.style.position = 'relative';
  623. box.style.display = 'inline';
  624. var margin = mxUtils.getAlignmentAsPoint(align, valign);
  625. var dx = margin.x;
  626. var dy = margin.y;
  627. var div = this.createDiv(str, align, valign, overflow);
  628. var inner = this.createElement('div');
  629. if (dir != null)
  630. {
  631. div.setAttribute('dir', dir);
  632. }
  633. if (wrap && w > 0)
  634. {
  635. if (!clip)
  636. {
  637. div.style.width = Math.round(w) + 'px';
  638. }
  639. div.style.wordWrap = mxConstants.WORD_WRAP;
  640. div.style.whiteSpace = 'normal';
  641. // LATER: Check if other cases need to be handled
  642. if (div.style.wordWrap == 'break-word')
  643. {
  644. var tmp = div;
  645. if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV')
  646. {
  647. tmp.firstChild.style.width = '100%';
  648. }
  649. }
  650. }
  651. else
  652. {
  653. div.style.whiteSpace = 'nowrap';
  654. }
  655. var rot = s.rotation + (rotation || 0);
  656. if (this.rotateHtml && rot != 0)
  657. {
  658. inner.style.display = 'inline';
  659. inner.style.zoom = '1';
  660. inner.appendChild(div);
  661. // Box not needed for rendering in IE8 standards
  662. if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV')
  663. {
  664. box.appendChild(inner);
  665. abs.appendChild(box);
  666. }
  667. else
  668. {
  669. abs.appendChild(inner);
  670. }
  671. }
  672. else if (document.documentMode == 8 && !mxClient.IS_EM)
  673. {
  674. box.appendChild(div);
  675. abs.appendChild(box);
  676. }
  677. else
  678. {
  679. div.style.display = 'inline';
  680. abs.appendChild(div);
  681. }
  682. // Inserts the node into the DOM
  683. if (this.root.nodeName != 'DIV')
  684. {
  685. // Rectangle to fix position in group
  686. var rect = this.createVmlElement('rect');
  687. rect.stroked = 'false';
  688. rect.filled = 'false';
  689. rect.appendChild(abs);
  690. this.root.appendChild(rect);
  691. }
  692. else
  693. {
  694. this.root.appendChild(abs);
  695. }
  696. if (clip)
  697. {
  698. div.style.overflow = 'hidden';
  699. div.style.width = Math.round(w) + 'px';
  700. if (!mxClient.IS_QUIRKS)
  701. {
  702. div.style.maxHeight = Math.round(h) + 'px';
  703. }
  704. }
  705. else if (overflow == 'fill')
  706. {
  707. // KNOWN: Affects horizontal alignment in quirks
  708. // but fill should only be used with align=left
  709. div.style.overflow = 'hidden';
  710. div.style.width = (Math.max(0, w) + 1) + 'px';
  711. div.style.height = (Math.max(0, h) + 1) + 'px';
  712. }
  713. else if (overflow == 'width')
  714. {
  715. // KNOWN: Affects horizontal alignment in quirks
  716. // but fill should only be used with align=left
  717. div.style.overflow = 'hidden';
  718. div.style.width = (Math.max(0, w) + 1) + 'px';
  719. div.style.maxHeight = (Math.max(0, h) + 1) + 'px';
  720. }
  721. if (this.rotateHtml && rot != 0)
  722. {
  723. var rad = rot * (Math.PI / 180);
  724. // Precalculate cos and sin for the rotation
  725. var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
  726. var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
  727. rad %= 2 * Math.PI;
  728. if (rad < 0) rad += 2 * Math.PI;
  729. rad %= Math.PI;
  730. if (rad > Math.PI / 2) rad = Math.PI - rad;
  731. var cos = Math.cos(rad);
  732. var sin = Math.sin(rad);
  733. // Adds div to document to measure size
  734. if (document.documentMode == 8 && !mxClient.IS_EM)
  735. {
  736. div.style.display = 'inline-block';
  737. inner.style.display = 'inline-block';
  738. box.style.display = 'inline-block';
  739. }
  740. div.style.visibility = 'hidden';
  741. div.style.position = 'absolute';
  742. document.body.appendChild(div);
  743. var sizeDiv = div;
  744. if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
  745. {
  746. sizeDiv = sizeDiv.firstChild;
  747. }
  748. var tmp = sizeDiv.offsetWidth + 3;
  749. var oh = sizeDiv.offsetHeight;
  750. if (clip)
  751. {
  752. w = Math.min(w, tmp);
  753. oh = Math.min(oh, h);
  754. }
  755. else
  756. {
  757. w = tmp;
  758. }
  759. // Handles words that are longer than the given wrapping width
  760. if (wrap)
  761. {
  762. div.style.width = w + 'px';
  763. }
  764. // Simulates max-height in quirks
  765. if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h)
  766. {
  767. oh = h;
  768. // Quirks does not support maxHeight
  769. div.style.height = oh + 'px';
  770. }
  771. h = oh;
  772. var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5);
  773. var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5);
  774. if (abs.nodeName == 'group' && this.root.nodeName == 'DIV')
  775. {
  776. // Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards
  777. var pos = this.createElement('div');
  778. pos.style.display = 'inline-block';
  779. pos.style.position = 'absolute';
  780. pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px';
  781. pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px';
  782. abs.parentNode.appendChild(pos);
  783. pos.appendChild(abs);
  784. }
  785. else
  786. {
  787. var sc = (document.documentMode == 8 && !mxClient.IS_EM) ? 1 : s.scale;
  788. abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px';
  789. abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px';
  790. }
  791. // KNOWN: Rotated text rendering quality is bad for IE9 quirks
  792. inner.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+real_cos+", M12="+
  793. real_sin+", M21="+(-real_sin)+", M22="+real_cos+", sizingMethod='auto expand')";
  794. inner.style.backgroundColor = this.rotatedHtmlBackground;
  795. if (this.state.alpha < 1)
  796. {
  797. inner.style.filter += 'alpha(opacity=' + (this.state.alpha * 100) + ')';
  798. }
  799. // Restore parent node for DIV
  800. inner.appendChild(div);
  801. div.style.position = '';
  802. div.style.visibility = '';
  803. }
  804. else if (document.documentMode != 8 || mxClient.IS_EM)
  805. {
  806. div.style.verticalAlign = 'top';
  807. if (this.state.alpha < 1)
  808. {
  809. abs.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
  810. }
  811. // Adds div to document to measure size
  812. var divParent = div.parentNode;
  813. div.style.visibility = 'hidden';
  814. document.body.appendChild(div);
  815. w = div.offsetWidth;
  816. var oh = div.offsetHeight;
  817. // Simulates max-height in quirks
  818. if (mxClient.IS_QUIRKS && clip && oh > h)
  819. {
  820. oh = h;
  821. // Quirks does not support maxHeight
  822. div.style.height = oh + 'px';
  823. }
  824. h = oh;
  825. div.style.visibility = '';
  826. divParent.appendChild(div);
  827. abs.style.left = this.format(x + w * dx * this.state.scale) + 'px';
  828. abs.style.top = this.format(y + h * dy * this.state.scale) + 'px';
  829. }
  830. else
  831. {
  832. if (this.state.alpha < 1)
  833. {
  834. div.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
  835. }
  836. // Faster rendering in IE8 without offsetWidth/Height
  837. box.style.left = (dx * 100) + '%';
  838. box.style.top = (dy * 100) + '%';
  839. }
  840. }
  841. else
  842. {
  843. this.plainText(x, y, w, h, mxUtils.htmlEntities(str, false), align, valign, wrap, format, overflow, clip, rotation, dir);
  844. }
  845. }
  846. };
  847. /**
  848. * Function: plainText
  849. *
  850. * Paints the outline of the current path.
  851. */
  852. mxVmlCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
  853. {
  854. // TextDirection is ignored since this code is not used (format is always HTML in the text function)
  855. var s = this.state;
  856. x = (x + s.dx) * s.scale;
  857. y = (y + s.dy) * s.scale;
  858. var node = this.createVmlElement('shape');
  859. node.style.width = '1px';
  860. node.style.height = '1px';
  861. node.stroked = 'false';
  862. var fill = this.createVmlElement('fill');
  863. fill.color = s.fontColor;
  864. fill.opacity = (s.alpha * 100) + '%';
  865. node.appendChild(fill);
  866. var path = this.createVmlElement('path');
  867. path.textpathok = 'true';
  868. path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0);
  869. node.appendChild(path);
  870. // KNOWN: Font family and text decoration ignored
  871. var tp = this.createVmlElement('textpath');
  872. tp.style.cssText = 'v-text-align:' + align;
  873. tp.style.align = align;
  874. tp.style.fontFamily = s.fontFamily;
  875. tp.string = str;
  876. tp.on = 'true';
  877. // Scale via fontsize instead of node.style.zoom for correct offsets in IE8
  878. var size = s.fontSize * s.scale / this.vmlScale;
  879. tp.style.fontSize = size + 'px';
  880. // Bold
  881. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  882. {
  883. tp.style.fontWeight = 'bold';
  884. }
  885. // Italic
  886. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  887. {
  888. tp.style.fontStyle = 'italic';
  889. }
  890. // Underline
  891. if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
  892. {
  893. tp.style.textDecoration = 'underline';
  894. }
  895. var lines = str.split('\n');
  896. var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;
  897. var dx = 0;
  898. var dy = 0;
  899. if (valign == mxConstants.ALIGN_BOTTOM)
  900. {
  901. dy = - textHeight / 2;
  902. }
  903. else if (valign != mxConstants.ALIGN_MIDDLE) // top
  904. {
  905. dy = textHeight / 2;
  906. }
  907. if (rotation != null)
  908. {
  909. node.style.rotation = rotation;
  910. var rad = rotation * (Math.PI / 180);
  911. dx = Math.sin(rad) * dy;
  912. dy = Math.cos(rad) * dy;
  913. }
  914. // FIXME: Clipping is relative to bounding box
  915. /*if (clip)
  916. {
  917. node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';
  918. }*/
  919. node.appendChild(tp);
  920. node.style.left = this.format(x - dx) + 'px';
  921. node.style.top = this.format(y + dy) + 'px';
  922. this.root.appendChild(node);
  923. };
  924. /**
  925. * Function: stroke
  926. *
  927. * Paints the outline of the current path.
  928. */
  929. mxVmlCanvas2D.prototype.stroke = function()
  930. {
  931. this.addNode(false, true);
  932. };
  933. /**
  934. * Function: fill
  935. *
  936. * Fills the current path.
  937. */
  938. mxVmlCanvas2D.prototype.fill = function()
  939. {
  940. this.addNode(true, false);
  941. };
  942. /**
  943. * Function: fillAndStroke
  944. *
  945. * Fills and paints the outline of the current path.
  946. */
  947. mxVmlCanvas2D.prototype.fillAndStroke = function()
  948. {
  949. this.addNode(true, true);
  950. };
  951. __mxOutput.mxVmlCanvas2D = typeof mxVmlCanvas2D !== 'undefined' ? mxVmlCanvas2D : undefined;