mxSvgCanvas2D.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxSvgCanvas2D
  7. *
  8. * Extends <mxAbstractCanvas2D> to implement a canvas for SVG. This canvas writes all
  9. * calls as SVG output to the given SVG root node.
  10. *
  11. * (code)
  12. * var svgDoc = mxUtils.createXmlDocument();
  13. * var root = (svgDoc.createElementNS != null) ?
  14. * svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
  15. *
  16. * if (svgDoc.createElementNS == null)
  17. * {
  18. * root.setAttribute('xmlns', mxConstants.NS_SVG);
  19. * root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
  20. * }
  21. * else
  22. * {
  23. * root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
  24. * }
  25. *
  26. * var bounds = graph.getGraphBounds();
  27. * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
  28. * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
  29. * root.setAttribute('version', '1.1');
  30. *
  31. * svgDoc.appendChild(root);
  32. *
  33. * var svgCanvas = new mxSvgCanvas2D(root);
  34. * (end)
  35. *
  36. * A description of the public API is available in <mxXmlCanvas2D>.
  37. *
  38. * To disable anti-aliasing in the output, use the following code.
  39. *
  40. * (code)
  41. * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');
  42. * (end)
  43. *
  44. * Or set the respective attribute in the SVG element directly.
  45. *
  46. * Constructor: mxSvgCanvas2D
  47. *
  48. * Constructs a new SVG canvas.
  49. *
  50. * Parameters:
  51. *
  52. * root - SVG container for the output.
  53. * styleEnabled - Optional boolean that specifies if a style section should be
  54. * added. The style section sets the default font-size, font-family and
  55. * stroke-miterlimit globally. Default is false.
  56. */
  57. function mxSvgCanvas2D(root, styleEnabled)
  58. {
  59. mxAbstractCanvas2D.call(this);
  60. /**
  61. * Variable: root
  62. *
  63. * Reference to the container for the SVG content.
  64. */
  65. this.root = root;
  66. /**
  67. * Variable: gradients
  68. *
  69. * Local cache of gradients for quick lookups.
  70. */
  71. this.gradients = [];
  72. /**
  73. * Variable: defs
  74. *
  75. * Reference to the defs section of the SVG document. Only for export.
  76. */
  77. this.defs = null;
  78. /**
  79. * Variable: styleEnabled
  80. *
  81. * Stores the value of styleEnabled passed to the constructor.
  82. */
  83. this.styleEnabled = (styleEnabled != null) ? styleEnabled : false;
  84. var svg = null;
  85. // Adds optional defs section for export
  86. if (root.ownerDocument != document)
  87. {
  88. var node = root;
  89. // Finds owner SVG element in XML DOM
  90. while (node != null && node.nodeName != 'svg')
  91. {
  92. node = node.parentNode;
  93. }
  94. svg = node;
  95. }
  96. if (svg != null)
  97. {
  98. // Tries to get existing defs section
  99. var tmp = svg.getElementsByTagName('defs');
  100. if (tmp.length > 0)
  101. {
  102. this.defs = svg.getElementsByTagName('defs')[0];
  103. }
  104. // Adds defs section if none exists
  105. if (this.defs == null)
  106. {
  107. this.defs = this.createElement('defs');
  108. if (svg.firstChild != null)
  109. {
  110. svg.insertBefore(this.defs, svg.firstChild);
  111. }
  112. else
  113. {
  114. svg.appendChild(this.defs);
  115. }
  116. }
  117. // Adds stylesheet
  118. if (this.styleEnabled)
  119. {
  120. this.defs.appendChild(this.createStyle());
  121. }
  122. }
  123. };
  124. /**
  125. * Extends mxAbstractCanvas2D
  126. */
  127. mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);
  128. /**
  129. * Capability check for DOM parser and checks if base tag is used.
  130. */
  131. (function()
  132. {
  133. mxSvgCanvas2D.prototype.useDomParser = !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';
  134. if (mxSvgCanvas2D.prototype.useDomParser)
  135. {
  136. // Checks using a generic test text if the parsing actually works. This is a workaround
  137. // for older browsers where the capability check returns true but the parsing fails.
  138. try
  139. {
  140. var doc = new DOMParser().parseFromString('test text', 'text/html');
  141. mxSvgCanvas2D.prototype.useDomParser = doc != null;
  142. }
  143. catch (e)
  144. {
  145. mxSvgCanvas2D.prototype.useDomParser = false;
  146. }
  147. }
  148. // Activates workaround for gradient ID resolution if base tag is used.
  149. mxSvgCanvas2D.prototype.useAbsoluteIds = !mxClient.IS_CHROMEAPP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
  150. !mxClient.IS_EDGE && document.getElementsByTagName('base').length > 0;
  151. })();
  152. /**
  153. * Variable: path
  154. *
  155. * Holds the current DOM node.
  156. */
  157. mxSvgCanvas2D.prototype.node = null;
  158. /**
  159. * Variable: matchHtmlAlignment
  160. *
  161. * Specifies if plain text output should match the vertical HTML alignment.
  162. * Defaul is true.
  163. */
  164. mxSvgCanvas2D.prototype.matchHtmlAlignment = true;
  165. /**
  166. * Variable: textEnabled
  167. *
  168. * Specifies if text output should be enabled. Default is true.
  169. */
  170. mxSvgCanvas2D.prototype.textEnabled = true;
  171. /**
  172. * Variable: foEnabled
  173. *
  174. * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
  175. */
  176. mxSvgCanvas2D.prototype.foEnabled = true;
  177. /**
  178. * Variable: foAltText
  179. *
  180. * Specifies the fallback text for unsupported foreignObjects in exported
  181. * documents. Default is '[Object]'. If this is set to null then no fallback
  182. * text is added to the exported document.
  183. */
  184. mxSvgCanvas2D.prototype.foAltText = '[Object]';
  185. /**
  186. * Variable: foOffset
  187. *
  188. * Offset to be used for foreignObjects.
  189. */
  190. mxSvgCanvas2D.prototype.foOffset = 0;
  191. /**
  192. * Variable: textOffset
  193. *
  194. * Offset to be used for text elements.
  195. */
  196. mxSvgCanvas2D.prototype.textOffset = 0;
  197. /**
  198. * Variable: imageOffset
  199. *
  200. * Offset to be used for image elements.
  201. */
  202. mxSvgCanvas2D.prototype.imageOffset = 0;
  203. /**
  204. * Variable: strokeTolerance
  205. *
  206. * Adds transparent paths for strokes.
  207. */
  208. mxSvgCanvas2D.prototype.strokeTolerance = 0;
  209. /**
  210. * Variable: minStrokeWidth
  211. *
  212. * Minimum stroke width for output.
  213. */
  214. mxSvgCanvas2D.prototype.minStrokeWidth = 1;
  215. /**
  216. * Variable: refCount
  217. *
  218. * Local counter for references in SVG export.
  219. */
  220. mxSvgCanvas2D.prototype.refCount = 0;
  221. /**
  222. * Variable: lineHeightCorrection
  223. *
  224. * Correction factor for <mxConstants.LINE_HEIGHT> in HTML output. Default is 1.
  225. */
  226. mxSvgCanvas2D.prototype.lineHeightCorrection = 1;
  227. /**
  228. * Variable: pointerEventsValue
  229. *
  230. * Default value for active pointer events. Default is all.
  231. */
  232. mxSvgCanvas2D.prototype.pointerEventsValue = 'all';
  233. /**
  234. * Variable: fontMetricsPadding
  235. *
  236. * Padding to be added for text that is not wrapped to account for differences
  237. * in font metrics on different platforms in pixels. Default is 10.
  238. */
  239. mxSvgCanvas2D.prototype.fontMetricsPadding = 10;
  240. /**
  241. * Variable: cacheOffsetSize
  242. *
  243. * Specifies if offsetWidth and offsetHeight should be cached. Default is true.
  244. * This is used to speed up repaint of text in <updateText>.
  245. */
  246. mxSvgCanvas2D.prototype.cacheOffsetSize = true;
  247. /**
  248. * Function: format
  249. *
  250. * Rounds all numbers to 2 decimal points.
  251. */
  252. mxSvgCanvas2D.prototype.format = function(value)
  253. {
  254. return parseFloat(parseFloat(value).toFixed(2));
  255. };
  256. /**
  257. * Function: getBaseUrl
  258. *
  259. * Returns the URL of the page without the hash part. This needs to use href to
  260. * include any search part with no params (ie question mark alone). This is a
  261. * workaround for the fact that window.location.search is empty if there is
  262. * no search string behind the question mark.
  263. */
  264. mxSvgCanvas2D.prototype.getBaseUrl = function()
  265. {
  266. var href = window.location.href;
  267. var hash = href.lastIndexOf('#');
  268. if (hash > 0)
  269. {
  270. href = href.substring(0, hash);
  271. }
  272. return href;
  273. };
  274. /**
  275. * Function: reset
  276. *
  277. * Returns any offsets for rendering pixels.
  278. */
  279. mxSvgCanvas2D.prototype.reset = function()
  280. {
  281. mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
  282. this.gradients = [];
  283. };
  284. /**
  285. * Function: createStyle
  286. *
  287. * Creates the optional style section.
  288. */
  289. mxSvgCanvas2D.prototype.createStyle = function(x)
  290. {
  291. var style = this.createElement('style');
  292. style.setAttribute('type', 'text/css');
  293. mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY +
  294. ';font-size:' + mxConstants.DEFAULT_FONTSIZE +
  295. ';fill:none;stroke-miterlimit:10}');
  296. return style;
  297. };
  298. /**
  299. * Function: createElement
  300. *
  301. * Private helper function to create SVG elements
  302. */
  303. mxSvgCanvas2D.prototype.createElement = function(tagName, namespace)
  304. {
  305. if (this.root.ownerDocument.createElementNS != null)
  306. {
  307. return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);
  308. }
  309. else
  310. {
  311. var elt = this.root.ownerDocument.createElement(tagName);
  312. if (namespace != null)
  313. {
  314. elt.setAttribute('xmlns', namespace);
  315. }
  316. return elt;
  317. }
  318. };
  319. /**
  320. * Function: getAlternateText
  321. *
  322. * Returns the alternate text string for the given foreignObject.
  323. */
  324. mxSvgCanvas2D.prototype.getAlternateText = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
  325. {
  326. return (str != null) ? this.foAltText : null;
  327. };
  328. /**
  329. * Function: getAlternateContent
  330. *
  331. * Returns the alternate content for the given foreignObject.
  332. */
  333. mxSvgCanvas2D.prototype.createAlternateContent = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
  334. {
  335. var text = this.getAlternateText(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
  336. var s = this.state;
  337. if (text != null && s.fontSize > 0)
  338. {
  339. var dy = (valign == mxConstants.ALIGN_TOP) ? 1 :
  340. (valign == mxConstants.ALIGN_BOTTOM) ? 0 : 0.3;
  341. var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
  342. (align == mxConstants.ALIGN_LEFT) ? 'start' :
  343. 'middle';
  344. var alt = this.createElement('text');
  345. alt.setAttribute('x', Math.round(x + s.dx));
  346. alt.setAttribute('y', Math.round(y + s.dy + dy * s.fontSize));
  347. alt.setAttribute('fill', s.fontColor || 'black');
  348. alt.setAttribute('font-family', s.fontFamily);
  349. alt.setAttribute('font-size', Math.round(s.fontSize) + 'px');
  350. // Text-anchor start is default in SVG
  351. if (anchor != 'start')
  352. {
  353. alt.setAttribute('text-anchor', anchor);
  354. }
  355. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  356. {
  357. alt.setAttribute('font-weight', 'bold');
  358. }
  359. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  360. {
  361. alt.setAttribute('font-style', 'italic');
  362. }
  363. var txtDecor = [];
  364. if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
  365. {
  366. txtDecor.push('underline');
  367. }
  368. if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH)
  369. {
  370. txtDecor.push('line-through');
  371. }
  372. if (txtDecor.length > 0)
  373. {
  374. alt.setAttribute('text-decoration', txtDecor.join(' '));
  375. }
  376. mxUtils.write(alt, text);
  377. return alt;
  378. }
  379. else
  380. {
  381. return null;
  382. }
  383. };
  384. /**
  385. * Function: createGradientId
  386. *
  387. * Private helper function to create SVG elements
  388. */
  389. mxSvgCanvas2D.prototype.createGradientId = function(start, end, alpha1, alpha2, direction)
  390. {
  391. // Removes illegal characters from gradient ID
  392. if (start.charAt(0) == '#')
  393. {
  394. start = start.substring(1);
  395. }
  396. if (end.charAt(0) == '#')
  397. {
  398. end = end.substring(1);
  399. }
  400. // Workaround for gradient IDs not working in Safari 5 / Chrome 6
  401. // if they contain uppercase characters
  402. start = start.toLowerCase() + '-' + alpha1;
  403. end = end.toLowerCase() + '-' + alpha2;
  404. // Wrong gradient directions possible?
  405. var dir = null;
  406. if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
  407. {
  408. dir = 's';
  409. }
  410. else if (direction == mxConstants.DIRECTION_EAST)
  411. {
  412. dir = 'e';
  413. }
  414. else
  415. {
  416. var tmp = start;
  417. start = end;
  418. end = tmp;
  419. if (direction == mxConstants.DIRECTION_NORTH)
  420. {
  421. dir = 's';
  422. }
  423. else if (direction == mxConstants.DIRECTION_WEST)
  424. {
  425. dir = 'e';
  426. }
  427. }
  428. return 'mx-gradient-' + start + '-' + end + '-' + dir;
  429. };
  430. /**
  431. * Function: getSvgGradient
  432. *
  433. * Private helper function to create SVG elements
  434. */
  435. mxSvgCanvas2D.prototype.getSvgGradient = function(start, end, alpha1, alpha2, direction)
  436. {
  437. var id = this.createGradientId(start, end, alpha1, alpha2, direction);
  438. var gradient = this.gradients[id];
  439. if (gradient == null)
  440. {
  441. var svg = this.root.ownerSVGElement;
  442. var counter = 0;
  443. var tmpId = id + '-' + counter;
  444. if (svg != null)
  445. {
  446. gradient = svg.ownerDocument.getElementById(tmpId);
  447. while (gradient != null && gradient.ownerSVGElement != svg)
  448. {
  449. tmpId = id + '-' + counter++;
  450. gradient = svg.ownerDocument.getElementById(tmpId);
  451. }
  452. }
  453. else
  454. {
  455. // Uses shorter IDs for export
  456. tmpId = 'id' + (++this.refCount);
  457. }
  458. if (gradient == null)
  459. {
  460. gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);
  461. gradient.setAttribute('id', tmpId);
  462. if (this.defs != null)
  463. {
  464. this.defs.appendChild(gradient);
  465. }
  466. else
  467. {
  468. svg.appendChild(gradient);
  469. }
  470. }
  471. this.gradients[id] = gradient;
  472. }
  473. return gradient.getAttribute('id');
  474. };
  475. /**
  476. * Function: createSvgGradient
  477. *
  478. * Creates the given SVG gradient.
  479. */
  480. mxSvgCanvas2D.prototype.createSvgGradient = function(start, end, alpha1, alpha2, direction)
  481. {
  482. var gradient = this.createElement('linearGradient');
  483. gradient.setAttribute('x1', '0%');
  484. gradient.setAttribute('y1', '0%');
  485. gradient.setAttribute('x2', '0%');
  486. gradient.setAttribute('y2', '0%');
  487. if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
  488. {
  489. gradient.setAttribute('y2', '100%');
  490. }
  491. else if (direction == mxConstants.DIRECTION_EAST)
  492. {
  493. gradient.setAttribute('x2', '100%');
  494. }
  495. else if (direction == mxConstants.DIRECTION_NORTH)
  496. {
  497. gradient.setAttribute('y1', '100%');
  498. }
  499. else if (direction == mxConstants.DIRECTION_WEST)
  500. {
  501. gradient.setAttribute('x1', '100%');
  502. }
  503. var op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : '';
  504. var stop = this.createElement('stop');
  505. stop.setAttribute('offset', '0%');
  506. stop.setAttribute('style', 'stop-color:' + start + op);
  507. gradient.appendChild(stop);
  508. op = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : '';
  509. stop = this.createElement('stop');
  510. stop.setAttribute('offset', '100%');
  511. stop.setAttribute('style', 'stop-color:' + end + op);
  512. gradient.appendChild(stop);
  513. return gradient;
  514. };
  515. /**
  516. * Function: addNode
  517. *
  518. * Private helper function to create SVG elements
  519. */
  520. mxSvgCanvas2D.prototype.addNode = function(filled, stroked)
  521. {
  522. var node = this.node;
  523. var s = this.state;
  524. if (node != null)
  525. {
  526. if (node.nodeName == 'path')
  527. {
  528. // Checks if the path is not empty
  529. if (this.path != null && this.path.length > 0)
  530. {
  531. node.setAttribute('d', this.path.join(' '));
  532. }
  533. else
  534. {
  535. return;
  536. }
  537. }
  538. if (filled && s.fillColor != null)
  539. {
  540. this.updateFill();
  541. }
  542. else if (!this.styleEnabled)
  543. {
  544. // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952
  545. if (node.nodeName == 'ellipse' && mxClient.IS_FF)
  546. {
  547. node.setAttribute('fill', 'transparent');
  548. }
  549. else
  550. {
  551. node.setAttribute('fill', 'none');
  552. }
  553. // Sets the actual filled state for stroke tolerance
  554. filled = false;
  555. }
  556. if (stroked && s.strokeColor != null)
  557. {
  558. this.updateStroke();
  559. }
  560. else if (!this.styleEnabled)
  561. {
  562. node.setAttribute('stroke', 'none');
  563. }
  564. if (s.transform != null && s.transform.length > 0)
  565. {
  566. node.setAttribute('transform', s.transform);
  567. }
  568. if (s.shadow)
  569. {
  570. this.root.appendChild(this.createShadow(node));
  571. }
  572. // Adds stroke tolerance
  573. if (this.strokeTolerance > 0 && !filled)
  574. {
  575. this.root.appendChild(this.createTolerance(node));
  576. }
  577. // Adds pointer events
  578. if (this.pointerEvents)
  579. {
  580. node.setAttribute('pointer-events', this.pointerEventsValue);
  581. }
  582. // Enables clicks for nodes inside a link element
  583. else if (!this.pointerEvents && this.originalRoot == null)
  584. {
  585. node.setAttribute('pointer-events', 'none');
  586. }
  587. // Removes invisible nodes from output if they don't handle events
  588. if ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||
  589. (node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||
  590. node.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none')
  591. {
  592. // LATER: Update existing DOM for performance
  593. this.root.appendChild(node);
  594. }
  595. this.node = null;
  596. }
  597. };
  598. /**
  599. * Function: updateFill
  600. *
  601. * Transfers the stroke attributes from <state> to <node>.
  602. */
  603. mxSvgCanvas2D.prototype.updateFill = function()
  604. {
  605. var s = this.state;
  606. if (s.alpha < 1 || s.fillAlpha < 1)
  607. {
  608. this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);
  609. }
  610. if (s.fillColor != null)
  611. {
  612. if (s.gradientColor != null)
  613. {
  614. var id = this.getSvgGradient(String(s.fillColor), String(s.gradientColor),
  615. s.gradientFillAlpha, s.gradientAlpha, s.gradientDirection);
  616. if (this.root.ownerDocument == document && this.useAbsoluteIds)
  617. {
  618. // Workaround for no fill with base tag in page (escape brackets)
  619. var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
  620. this.node.setAttribute('fill', 'url(' + base + '#' + id + ')');
  621. }
  622. else
  623. {
  624. this.node.setAttribute('fill', 'url(#' + id + ')');
  625. }
  626. }
  627. else
  628. {
  629. this.node.setAttribute('fill', String(s.fillColor).toLowerCase());
  630. }
  631. }
  632. };
  633. /**
  634. * Function: getCurrentStrokeWidth
  635. *
  636. * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
  637. */
  638. mxSvgCanvas2D.prototype.getCurrentStrokeWidth = function()
  639. {
  640. return Math.max(this.minStrokeWidth, Math.max(0.01, this.format(this.state.strokeWidth * this.state.scale)));
  641. };
  642. /**
  643. * Function: updateStroke
  644. *
  645. * Transfers the stroke attributes from <state> to <node>.
  646. */
  647. mxSvgCanvas2D.prototype.updateStroke = function()
  648. {
  649. var s = this.state;
  650. this.node.setAttribute('stroke', String(s.strokeColor).toLowerCase());
  651. if (s.alpha < 1 || s.strokeAlpha < 1)
  652. {
  653. this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);
  654. }
  655. var sw = this.getCurrentStrokeWidth();
  656. if (sw != 1)
  657. {
  658. this.node.setAttribute('stroke-width', sw);
  659. }
  660. if (this.node.nodeName == 'path')
  661. {
  662. this.updateStrokeAttributes();
  663. }
  664. if (s.dashed)
  665. {
  666. this.node.setAttribute('stroke-dasharray', this.createDashPattern(
  667. ((s.fixDash) ? 1 : s.strokeWidth) * s.scale));
  668. }
  669. };
  670. /**
  671. * Function: updateStrokeAttributes
  672. *
  673. * Transfers the stroke attributes from <state> to <node>.
  674. */
  675. mxSvgCanvas2D.prototype.updateStrokeAttributes = function()
  676. {
  677. var s = this.state;
  678. // Linejoin miter is default in SVG
  679. if (s.lineJoin != null && s.lineJoin != 'miter')
  680. {
  681. this.node.setAttribute('stroke-linejoin', s.lineJoin);
  682. }
  683. if (s.lineCap != null)
  684. {
  685. // flat is called butt in SVG
  686. var value = s.lineCap;
  687. if (value == 'flat')
  688. {
  689. value = 'butt';
  690. }
  691. // Linecap butt is default in SVG
  692. if (value != 'butt')
  693. {
  694. this.node.setAttribute('stroke-linecap', value);
  695. }
  696. }
  697. // Miterlimit 10 is default in our document
  698. if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10))
  699. {
  700. this.node.setAttribute('stroke-miterlimit', s.miterLimit);
  701. }
  702. };
  703. /**
  704. * Function: createDashPattern
  705. *
  706. * Creates the SVG dash pattern for the given state.
  707. */
  708. mxSvgCanvas2D.prototype.createDashPattern = function(scale)
  709. {
  710. var pat = [];
  711. if (typeof(this.state.dashPattern) === 'string')
  712. {
  713. var dash = this.state.dashPattern.split(' ');
  714. if (dash.length > 0)
  715. {
  716. for (var i = 0; i < dash.length; i++)
  717. {
  718. pat[i] = Number(dash[i]) * scale;
  719. }
  720. }
  721. }
  722. return pat.join(' ');
  723. };
  724. /**
  725. * Function: createTolerance
  726. *
  727. * Creates a hit detection tolerance shape for the given node.
  728. */
  729. mxSvgCanvas2D.prototype.createTolerance = function(node)
  730. {
  731. var tol = node.cloneNode(true);
  732. var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;
  733. tol.setAttribute('pointer-events', 'stroke');
  734. tol.setAttribute('visibility', 'hidden');
  735. tol.removeAttribute('stroke-dasharray');
  736. tol.setAttribute('stroke-width', sw);
  737. tol.setAttribute('fill', 'none');
  738. // Workaround for Opera ignoring the visiblity attribute above while
  739. // other browsers need a stroke color to perform the hit-detection but
  740. // do not ignore the visibility attribute. Side-effect is that Opera's
  741. // hit detection for horizontal/vertical edges seems to ignore the tol.
  742. tol.setAttribute('stroke', (mxClient.IS_OT) ? 'none' : 'white');
  743. return tol;
  744. };
  745. /**
  746. * Function: createShadow
  747. *
  748. * Creates a shadow for the given node.
  749. */
  750. mxSvgCanvas2D.prototype.createShadow = function(node)
  751. {
  752. var shadow = node.cloneNode(true);
  753. var s = this.state;
  754. // Firefox uses transparent for no fill in ellipses
  755. if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent'))
  756. {
  757. shadow.setAttribute('fill', s.shadowColor);
  758. }
  759. if (shadow.getAttribute('stroke') != 'none')
  760. {
  761. shadow.setAttribute('stroke', s.shadowColor);
  762. }
  763. shadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) +
  764. ',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || ''));
  765. shadow.setAttribute('opacity', s.shadowAlpha);
  766. return shadow;
  767. };
  768. /**
  769. * Function: setLink
  770. *
  771. * Experimental implementation for hyperlinks.
  772. */
  773. mxSvgCanvas2D.prototype.setLink = function(link)
  774. {
  775. if (link == null)
  776. {
  777. this.root = this.originalRoot;
  778. }
  779. else
  780. {
  781. this.originalRoot = this.root;
  782. var node = this.createElement('a');
  783. // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
  784. // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
  785. if (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null))
  786. {
  787. node.setAttribute('xlink:href', link);
  788. }
  789. else
  790. {
  791. node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);
  792. }
  793. this.root.appendChild(node);
  794. this.root = node;
  795. }
  796. };
  797. /**
  798. * Function: rotate
  799. *
  800. * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
  801. */
  802. mxSvgCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
  803. {
  804. if (theta != 0 || flipH || flipV)
  805. {
  806. var s = this.state;
  807. cx += s.dx;
  808. cy += s.dy;
  809. cx *= s.scale;
  810. cy *= s.scale;
  811. s.transform = s.transform || '';
  812. // This implementation uses custom scale/translate and built-in rotation
  813. // Rotation state is part of the AffineTransform in state.transform
  814. if (flipH && flipV)
  815. {
  816. theta += 180;
  817. }
  818. else if (flipH != flipV)
  819. {
  820. var tx = (flipH) ? cx : 0;
  821. var sx = (flipH) ? -1 : 1;
  822. var ty = (flipV) ? cy : 0;
  823. var sy = (flipV) ? -1 : 1;
  824. s.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' +
  825. 'scale(' + this.format(sx) + ',' + this.format(sy) + ')' +
  826. 'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')';
  827. }
  828. if (flipH ? !flipV : flipV)
  829. {
  830. theta *= -1;
  831. }
  832. if (theta != 0)
  833. {
  834. s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';
  835. }
  836. s.rotation = s.rotation + theta;
  837. s.rotationCx = cx;
  838. s.rotationCy = cy;
  839. }
  840. };
  841. /**
  842. * Function: begin
  843. *
  844. * Extends superclass to create path.
  845. */
  846. mxSvgCanvas2D.prototype.begin = function()
  847. {
  848. mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
  849. this.node = this.createElement('path');
  850. };
  851. /**
  852. * Function: rect
  853. *
  854. * Private helper function to create SVG elements
  855. */
  856. mxSvgCanvas2D.prototype.rect = function(x, y, w, h)
  857. {
  858. var s = this.state;
  859. var n = this.createElement('rect');
  860. n.setAttribute('x', this.format((x + s.dx) * s.scale));
  861. n.setAttribute('y', this.format((y + s.dy) * s.scale));
  862. n.setAttribute('width', this.format(w * s.scale));
  863. n.setAttribute('height', this.format(h * s.scale));
  864. this.node = n;
  865. };
  866. /**
  867. * Function: roundrect
  868. *
  869. * Private helper function to create SVG elements
  870. */
  871. mxSvgCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
  872. {
  873. this.rect(x, y, w, h);
  874. if (dx > 0)
  875. {
  876. this.node.setAttribute('rx', this.format(dx * this.state.scale));
  877. }
  878. if (dy > 0)
  879. {
  880. this.node.setAttribute('ry', this.format(dy * this.state.scale));
  881. }
  882. };
  883. /**
  884. * Function: ellipse
  885. *
  886. * Private helper function to create SVG elements
  887. */
  888. mxSvgCanvas2D.prototype.ellipse = function(x, y, w, h)
  889. {
  890. var s = this.state;
  891. var n = this.createElement('ellipse');
  892. // No rounding for consistent output with 1.x
  893. n.setAttribute('cx', this.format((x + w / 2 + s.dx) * s.scale));
  894. n.setAttribute('cy', this.format((y + h / 2 + s.dy) * s.scale));
  895. n.setAttribute('rx', w / 2 * s.scale);
  896. n.setAttribute('ry', h / 2 * s.scale);
  897. this.node = n;
  898. };
  899. /**
  900. * Function: image
  901. *
  902. * Private helper function to create SVG elements
  903. */
  904. mxSvgCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
  905. {
  906. src = this.converter.convert(src);
  907. // LATER: Add option for embedding images as base64.
  908. aspect = (aspect != null) ? aspect : true;
  909. flipH = (flipH != null) ? flipH : false;
  910. flipV = (flipV != null) ? flipV : false;
  911. var s = this.state;
  912. x += s.dx;
  913. y += s.dy;
  914. var node = this.createElement('image');
  915. node.setAttribute('x', this.format(x * s.scale) + this.imageOffset);
  916. node.setAttribute('y', this.format(y * s.scale) + this.imageOffset);
  917. node.setAttribute('width', this.format(w * s.scale));
  918. node.setAttribute('height', this.format(h * s.scale));
  919. // Workaround for missing namespace support
  920. if (node.setAttributeNS == null)
  921. {
  922. node.setAttribute('xlink:href', src);
  923. }
  924. else
  925. {
  926. node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
  927. }
  928. if (!aspect)
  929. {
  930. node.setAttribute('preserveAspectRatio', 'none');
  931. }
  932. if (s.alpha < 1 || s.fillAlpha < 1)
  933. {
  934. node.setAttribute('opacity', s.alpha * s.fillAlpha);
  935. }
  936. var tr = this.state.transform || '';
  937. if (flipH || flipV)
  938. {
  939. var sx = 1;
  940. var sy = 1;
  941. var dx = 0;
  942. var dy = 0;
  943. if (flipH)
  944. {
  945. sx = -1;
  946. dx = -w - 2 * x;
  947. }
  948. if (flipV)
  949. {
  950. sy = -1;
  951. dy = -h - 2 * y;
  952. }
  953. // Adds image tansformation to existing transform
  954. tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';
  955. }
  956. if (tr.length > 0)
  957. {
  958. node.setAttribute('transform', tr);
  959. }
  960. if (!this.pointerEvents)
  961. {
  962. node.setAttribute('pointer-events', 'none');
  963. }
  964. this.root.appendChild(node);
  965. };
  966. /**
  967. * Function: convertHtml
  968. *
  969. * Converts the given HTML string to XHTML.
  970. */
  971. mxSvgCanvas2D.prototype.convertHtml = function(val)
  972. {
  973. if (this.useDomParser)
  974. {
  975. var doc = new DOMParser().parseFromString(val, 'text/html');
  976. if (doc != null)
  977. {
  978. val = new XMLSerializer().serializeToString(doc.body);
  979. // Extracts body content from DOM
  980. if (val.substring(0, 5) == '<body')
  981. {
  982. val = val.substring(val.indexOf('>', 5) + 1);
  983. }
  984. if (val.substring(val.length - 7, val.length) == '</body>')
  985. {
  986. val = val.substring(0, val.length - 7);
  987. }
  988. }
  989. }
  990. else if (document.implementation != null && document.implementation.createDocument != null)
  991. {
  992. var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
  993. var xb = xd.createElement('body');
  994. xd.documentElement.appendChild(xb);
  995. var div = document.createElement('div');
  996. div.innerHTML = val;
  997. var child = div.firstChild;
  998. while (child != null)
  999. {
  1000. var next = child.nextSibling;
  1001. xb.appendChild(xd.adoptNode(child));
  1002. child = next;
  1003. }
  1004. return xb.innerHTML;
  1005. }
  1006. else
  1007. {
  1008. var ta = document.createElement('textarea');
  1009. // Handles special HTML entities < and > and double escaping
  1010. // and converts unclosed br, hr and img tags to XHTML
  1011. // LATER: Convert all unclosed tags
  1012. ta.innerHTML = val.replace(/&amp;/g, '&amp;amp;').
  1013. replace(/&#60;/g, '&amp;lt;').replace(/&#62;/g, '&amp;gt;').
  1014. replace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;').
  1015. replace(/</g, '&lt;').replace(/>/g, '&gt;');
  1016. val = ta.value.replace(/&/g, '&amp;').replace(/&amp;lt;/g, '&lt;').
  1017. replace(/&amp;gt;/g, '&gt;').replace(/&amp;amp;/g, '&amp;').
  1018. replace(/<br>/g, '<br />').replace(/<hr>/g, '<hr />').
  1019. replace(/(<img[^>]+)>/gm, "$1 />");
  1020. }
  1021. return val;
  1022. };
  1023. /**
  1024. * Function: createDiv
  1025. *
  1026. * Private helper function to create SVG elements
  1027. */
  1028. mxSvgCanvas2D.prototype.createDiv = function(str)
  1029. {
  1030. var val = str;
  1031. if (!mxUtils.isNode(val))
  1032. {
  1033. val = '<div><div>' + this.convertHtml(val) + '</div></div>';
  1034. }
  1035. // IE uses this code for export as it cannot render foreignObjects
  1036. if (!mxClient.IS_IE && !mxClient.IS_IE11 && document.createElementNS)
  1037. {
  1038. var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
  1039. if (mxUtils.isNode(val))
  1040. {
  1041. var div2 = document.createElement('div');
  1042. var div3 = div2.cloneNode(false);
  1043. // Creates a copy for export
  1044. if (this.root.ownerDocument != document)
  1045. {
  1046. div2.appendChild(val.cloneNode(true));
  1047. }
  1048. else
  1049. {
  1050. div2.appendChild(val);
  1051. }
  1052. div3.appendChild(div2);
  1053. div.appendChild(div3);
  1054. }
  1055. else
  1056. {
  1057. div.innerHTML = val;
  1058. }
  1059. return div;
  1060. }
  1061. else
  1062. {
  1063. if (mxUtils.isNode(val))
  1064. {
  1065. val = '<div><div>' + mxUtils.getXml(val) + '</div></div>';
  1066. }
  1067. val = '<div xmlns="http://www.w3.org/1999/xhtml">' + val + '</div>';
  1068. // NOTE: FF 3.6 crashes if content CSS contains "height:100%"
  1069. return mxUtils.parseXml(val).documentElement;
  1070. }
  1071. };
  1072. /**
  1073. * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below.
  1074. */
  1075. mxSvgCanvas2D.prototype.updateText = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node)
  1076. {
  1077. if (node != null && node.firstChild != null && node.firstChild.firstChild != null)
  1078. {
  1079. this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node.firstChild);
  1080. }
  1081. };
  1082. /**
  1083. * Function: addForeignObject
  1084. *
  1085. * Creates a foreignObject for the given string and adds it to the given root.
  1086. */
  1087. mxSvgCanvas2D.prototype.addForeignObject = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir, div, root)
  1088. {
  1089. var group = this.createElement('g');
  1090. var fo = this.createElement('foreignObject');
  1091. // Workarounds for print clipping and static position in Safari
  1092. fo.setAttribute('style', 'overflow: visible; text-align: left;');
  1093. fo.setAttribute('pointer-events', 'none');
  1094. // Import needed for older versions of IE
  1095. if (div.ownerDocument != document)
  1096. {
  1097. div = mxUtils.importNodeImplementation(fo.ownerDocument, div, true);
  1098. }
  1099. fo.appendChild(div);
  1100. group.appendChild(fo);
  1101. this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, group);
  1102. // Alternate content if foreignObject not supported
  1103. if (this.root.ownerDocument != document)
  1104. {
  1105. var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
  1106. if (alt != null)
  1107. {
  1108. fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
  1109. var sw = this.createElement('switch');
  1110. sw.appendChild(fo);
  1111. sw.appendChild(alt);
  1112. group.appendChild(sw);
  1113. }
  1114. }
  1115. root.appendChild(group);
  1116. };
  1117. /**
  1118. * Updates existing DOM nodes for text rendering.
  1119. */
  1120. mxSvgCanvas2D.prototype.updateTextNodes = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, g)
  1121. {
  1122. var s = this.state.scale;
  1123. mxSvgCanvas2D.createCss(w + 2, h, align, valign, wrap, overflow, clip,
  1124. (this.state.fontBackgroundColor != null) ? this.state.fontBackgroundColor : null,
  1125. (this.state.fontBorderColor != null) ? this.state.fontBorderColor : null,
  1126. 'display: flex; align-items: unsafe ' +
  1127. ((valign == mxConstants.ALIGN_TOP) ? 'flex-start' :
  1128. ((valign == mxConstants.ALIGN_BOTTOM) ? 'flex-end' : 'center')) + '; ' +
  1129. 'justify-content: unsafe ' + ((align == mxConstants.ALIGN_LEFT) ? 'flex-start' :
  1130. ((align == mxConstants.ALIGN_RIGHT) ? 'flex-end' : 'center')) + '; ',
  1131. this.getTextCss(), s, mxUtils.bind(this, function(dx, dy, flex, item, block)
  1132. {
  1133. x += this.state.dx;
  1134. y += this.state.dy;
  1135. var fo = g.firstChild;
  1136. var div = fo.firstChild;
  1137. var box = div.firstChild;
  1138. var text = box.firstChild;
  1139. var r = ((this.rotateHtml) ? this.state.rotation : 0) + ((rotation != null) ? rotation : 0);
  1140. var t = ((this.foOffset != 0) ? 'translate(' + this.foOffset + ' ' + this.foOffset + ')' : '') +
  1141. ((s != 1) ? 'scale(' + s + ')' : '');
  1142. text.setAttribute('style', block);
  1143. box.setAttribute('style', item);
  1144. // Workaround for clipping in Webkit with scrolling and zoom
  1145. fo.setAttribute('width', Math.ceil(1 / Math.min(1, s) * 100) + '%');
  1146. fo.setAttribute('height', Math.ceil(1 / Math.min(1, s) * 100) + '%');
  1147. var yp = Math.round(y + dy);
  1148. // Allows for negative values which are causing problems with
  1149. // transformed content where the top edge of the foreignObject
  1150. // limits the text box being moved further up in the diagram.
  1151. // KNOWN: Possible clipping problems with zoom and scrolling
  1152. // but this is normally not used with scrollbars as the
  1153. // coordinates are always positive with scrollbars.
  1154. // Margin-top is ignored in Safari and no negative values allowed
  1155. // for padding.
  1156. if (yp < 0)
  1157. {
  1158. fo.setAttribute('y', yp);
  1159. }
  1160. else
  1161. {
  1162. fo.removeAttribute('y');
  1163. flex += 'padding-top: ' + yp + 'px; ';
  1164. }
  1165. div.setAttribute('style', flex + 'margin-left: ' + Math.round(x + dx) + 'px;');
  1166. t += ((r != 0) ? ('rotate(' + r + ' ' + x + ' ' + y + ')') : '');
  1167. // Output allows for reflow but Safari cannot use absolute position,
  1168. // transforms or opacity. https://bugs.webkit.org/show_bug.cgi?id=23113
  1169. if (t != '')
  1170. {
  1171. g.setAttribute('transform', t);
  1172. }
  1173. else
  1174. {
  1175. g.removeAttribute('transform');
  1176. }
  1177. if (this.state.alpha != 1)
  1178. {
  1179. g.setAttribute('opacity', this.state.alpha);
  1180. }
  1181. else
  1182. {
  1183. g.removeAttribute('opacity');
  1184. }
  1185. }));
  1186. };
  1187. /**
  1188. * Updates existing DOM nodes for text rendering.
  1189. */
  1190. mxSvgCanvas2D.createCss = function(w, h, align, valign, wrap, overflow, clip, bg, border, flex, block, s, callback)
  1191. {
  1192. var item = 'box-sizing: border-box; font-size: 0; text-align: ' + ((align == mxConstants.ALIGN_LEFT) ? 'left' :
  1193. ((align == mxConstants.ALIGN_RIGHT) ? 'right' : 'center')) + '; ';
  1194. var pt = mxUtils.getAlignmentAsPoint(align, valign);
  1195. var ofl = 'overflow: hidden; ';
  1196. var fw = 'width: 1px; ';
  1197. var fh = 'height: 1px; ';
  1198. var dx = pt.x * w;
  1199. var dy = pt.y * h;
  1200. if (clip)
  1201. {
  1202. fw = 'width: ' + Math.round(w) + 'px; ';
  1203. item += 'max-height: ' + Math.round(h) + 'px; ';
  1204. dy = 0;
  1205. }
  1206. else if (overflow == 'fill')
  1207. {
  1208. fw = 'width: ' + Math.round(w) + 'px; ';
  1209. fh = 'height: ' + Math.round(h) + 'px; ';
  1210. block += 'width: 100%; height: 100%; ';
  1211. item += fw + fh;
  1212. }
  1213. else if (overflow == 'width')
  1214. {
  1215. fw = 'width: ' + Math.round(w) + 'px; ';
  1216. block += 'width: 100%; ';
  1217. item += fw;
  1218. dy = 0;
  1219. if (h > 0)
  1220. {
  1221. item += 'max-height: ' + Math.round(h) + 'px; ';
  1222. }
  1223. }
  1224. else
  1225. {
  1226. ofl = '';
  1227. dy = 0;
  1228. }
  1229. var bgc = '';
  1230. if (bg != null)
  1231. {
  1232. bgc += 'background-color: ' + bg + '; ';
  1233. }
  1234. if (border != null)
  1235. {
  1236. bgc += 'border: 1px solid ' + border + '; ';
  1237. }
  1238. if (ofl == '' || clip)
  1239. {
  1240. block += bgc;
  1241. }
  1242. else
  1243. {
  1244. item += bgc;
  1245. }
  1246. if (wrap && w > 0)
  1247. {
  1248. block += 'white-space: normal; word-wrap: ' + mxConstants.WORD_WRAP + '; ';
  1249. fw = 'width: ' + Math.round(w) + 'px; ';
  1250. if (ofl != '' && overflow != 'fill')
  1251. {
  1252. dy = 0;
  1253. }
  1254. }
  1255. else
  1256. {
  1257. block += 'white-space: nowrap; ';
  1258. if (ofl == '')
  1259. {
  1260. dx = 0;
  1261. }
  1262. }
  1263. callback(dx, dy, flex + fw + fh, item + ofl, block, ofl);
  1264. };
  1265. /**
  1266. * Function: getTextCss
  1267. *
  1268. * Private helper function to create SVG elements
  1269. */
  1270. mxSvgCanvas2D.prototype.getTextCss = function()
  1271. {
  1272. var s = this.state;
  1273. var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' :
  1274. (mxConstants.LINE_HEIGHT * this.lineHeightCorrection);
  1275. var css = 'display: inline-block; font-size: ' + s.fontSize + 'px; ' +
  1276. 'font-family: ' + s.fontFamily + '; color: ' + s.fontColor + '; line-height: ' + lh +
  1277. '; pointer-events: ' + ((this.pointerEvents) ? this.pointerEventsValue : 'none') + '; ';
  1278. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  1279. {
  1280. css += 'font-weight: bold; ';
  1281. }
  1282. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  1283. {
  1284. css += 'font-style: italic; ';
  1285. }
  1286. var deco = [];
  1287. if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
  1288. {
  1289. deco.push('underline');
  1290. }
  1291. if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH)
  1292. {
  1293. deco.push('line-through');
  1294. }
  1295. if (deco.length > 0)
  1296. {
  1297. css += 'text-decoration: ' + deco.join(' ') + '; ';
  1298. }
  1299. return css;
  1300. };
  1301. /**
  1302. * Function: text
  1303. *
  1304. * Paints the given text. Possible values for format are empty string for plain
  1305. * text and html for HTML markup. Note that HTML markup is only supported if
  1306. * foreignObject is supported and <foEnabled> is true. (This means IE9 and later
  1307. * does currently not support HTML text as part of shapes.)
  1308. */
  1309. mxSvgCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
  1310. {
  1311. if (this.textEnabled && str != null)
  1312. {
  1313. rotation = (rotation != null) ? rotation : 0;
  1314. if (this.foEnabled && format == 'html')
  1315. {
  1316. var div = this.createDiv(str);
  1317. // Ignores invalid XHTML labels
  1318. if (div != null)
  1319. {
  1320. if (dir != null)
  1321. {
  1322. div.setAttribute('dir', dir);
  1323. }
  1324. this.addForeignObject(x, y, w, h, str, align, valign, wrap,
  1325. format, overflow, clip, rotation, dir, div, this.root);
  1326. }
  1327. }
  1328. else
  1329. {
  1330. this.plainText(x + this.state.dx, y + this.state.dy, w, h, str,
  1331. align, valign, wrap, overflow, clip, rotation, dir);
  1332. }
  1333. }
  1334. };
  1335. /**
  1336. * Function: createClip
  1337. *
  1338. * Creates a clip for the given coordinates.
  1339. */
  1340. mxSvgCanvas2D.prototype.createClip = function(x, y, w, h)
  1341. {
  1342. x = Math.round(x);
  1343. y = Math.round(y);
  1344. w = Math.round(w);
  1345. h = Math.round(h);
  1346. var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;
  1347. var counter = 0;
  1348. var tmp = id + '-' + counter;
  1349. // Resolves ID conflicts
  1350. while (document.getElementById(tmp) != null)
  1351. {
  1352. tmp = id + '-' + (++counter);
  1353. }
  1354. clip = this.createElement('clipPath');
  1355. clip.setAttribute('id', tmp);
  1356. var rect = this.createElement('rect');
  1357. rect.setAttribute('x', x);
  1358. rect.setAttribute('y', y);
  1359. rect.setAttribute('width', w);
  1360. rect.setAttribute('height', h);
  1361. clip.appendChild(rect);
  1362. return clip;
  1363. };
  1364. /**
  1365. * Function: plainText
  1366. *
  1367. * Paints the given text. Possible values for format are empty string for
  1368. * plain text and html for HTML markup.
  1369. */
  1370. mxSvgCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir)
  1371. {
  1372. rotation = (rotation != null) ? rotation : 0;
  1373. var s = this.state;
  1374. var size = s.fontSize;
  1375. var node = this.createElement('g');
  1376. var tr = s.transform || '';
  1377. this.updateFont(node);
  1378. // Ignores pointer events
  1379. if (!this.pointerEvents && this.originalRoot == null)
  1380. {
  1381. node.setAttribute('pointer-events', 'none');
  1382. }
  1383. // Non-rotated text
  1384. if (rotation != 0)
  1385. {
  1386. tr += 'rotate(' + rotation + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')';
  1387. }
  1388. if (dir != null)
  1389. {
  1390. node.setAttribute('direction', dir);
  1391. }
  1392. if (clip && w > 0 && h > 0)
  1393. {
  1394. var cx = x;
  1395. var cy = y;
  1396. if (align == mxConstants.ALIGN_CENTER)
  1397. {
  1398. cx -= w / 2;
  1399. }
  1400. else if (align == mxConstants.ALIGN_RIGHT)
  1401. {
  1402. cx -= w;
  1403. }
  1404. if (overflow != 'fill')
  1405. {
  1406. if (valign == mxConstants.ALIGN_MIDDLE)
  1407. {
  1408. cy -= h / 2;
  1409. }
  1410. else if (valign == mxConstants.ALIGN_BOTTOM)
  1411. {
  1412. cy -= h;
  1413. }
  1414. }
  1415. // LATER: Remove spacing from clip rectangle
  1416. var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4);
  1417. if (this.defs != null)
  1418. {
  1419. this.defs.appendChild(c);
  1420. }
  1421. else
  1422. {
  1423. // Makes sure clip is removed with referencing node
  1424. this.root.appendChild(c);
  1425. }
  1426. if (!mxClient.IS_CHROMEAPP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
  1427. !mxClient.IS_EDGE && this.root.ownerDocument == document)
  1428. {
  1429. // Workaround for potential base tag
  1430. var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
  1431. node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')');
  1432. }
  1433. else
  1434. {
  1435. node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')');
  1436. }
  1437. }
  1438. // Default is left
  1439. var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
  1440. (align == mxConstants.ALIGN_CENTER) ? 'middle' :
  1441. 'start';
  1442. // Text-anchor start is default in SVG
  1443. if (anchor != 'start')
  1444. {
  1445. node.setAttribute('text-anchor', anchor);
  1446. }
  1447. if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)
  1448. {
  1449. node.setAttribute('font-size', (size * s.scale) + 'px');
  1450. }
  1451. if (tr.length > 0)
  1452. {
  1453. node.setAttribute('transform', tr);
  1454. }
  1455. if (s.alpha < 1)
  1456. {
  1457. node.setAttribute('opacity', s.alpha);
  1458. }
  1459. var lines = str.split('\n');
  1460. var lh = Math.round(size * mxConstants.LINE_HEIGHT);
  1461. var textHeight = size + (lines.length - 1) * lh;
  1462. var cy = y + size - 1;
  1463. if (valign == mxConstants.ALIGN_MIDDLE)
  1464. {
  1465. if (overflow == 'fill')
  1466. {
  1467. cy -= h / 2;
  1468. }
  1469. else
  1470. {
  1471. var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2;
  1472. cy -= dy;
  1473. }
  1474. }
  1475. else if (valign == mxConstants.ALIGN_BOTTOM)
  1476. {
  1477. if (overflow == 'fill')
  1478. {
  1479. cy -= h;
  1480. }
  1481. else
  1482. {
  1483. var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight;
  1484. cy -= dy + 1;
  1485. }
  1486. }
  1487. for (var i = 0; i < lines.length; i++)
  1488. {
  1489. // Workaround for bounding box of empty lines and spaces
  1490. if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0)
  1491. {
  1492. var text = this.createElement('text');
  1493. // LATER: Match horizontal HTML alignment
  1494. text.setAttribute('x', this.format(x * s.scale) + this.textOffset);
  1495. text.setAttribute('y', this.format(cy * s.scale) + this.textOffset);
  1496. mxUtils.write(text, lines[i]);
  1497. node.appendChild(text);
  1498. }
  1499. cy += lh;
  1500. }
  1501. this.root.appendChild(node);
  1502. this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow);
  1503. };
  1504. /**
  1505. * Function: updateFont
  1506. *
  1507. * Updates the text properties for the given node. (NOTE: For this to work in
  1508. * IE, the given node must be a text or tspan element.)
  1509. */
  1510. mxSvgCanvas2D.prototype.updateFont = function(node)
  1511. {
  1512. var s = this.state;
  1513. node.setAttribute('fill', s.fontColor);
  1514. if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY)
  1515. {
  1516. node.setAttribute('font-family', s.fontFamily);
  1517. }
  1518. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  1519. {
  1520. node.setAttribute('font-weight', 'bold');
  1521. }
  1522. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  1523. {
  1524. node.setAttribute('font-style', 'italic');
  1525. }
  1526. var txtDecor = [];
  1527. if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
  1528. {
  1529. txtDecor.push('underline');
  1530. }
  1531. if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH)
  1532. {
  1533. txtDecor.push('line-through');
  1534. }
  1535. if (txtDecor.length > 0)
  1536. {
  1537. node.setAttribute('text-decoration', txtDecor.join(' '));
  1538. }
  1539. };
  1540. /**
  1541. * Function: addTextBackground
  1542. *
  1543. * Background color and border
  1544. */
  1545. mxSvgCanvas2D.prototype.addTextBackground = function(node, str, x, y, w, h, align, valign, overflow)
  1546. {
  1547. var s = this.state;
  1548. if (s.fontBackgroundColor != null || s.fontBorderColor != null)
  1549. {
  1550. var bbox = null;
  1551. if (overflow == 'fill' || overflow == 'width')
  1552. {
  1553. if (align == mxConstants.ALIGN_CENTER)
  1554. {
  1555. x -= w / 2;
  1556. }
  1557. else if (align == mxConstants.ALIGN_RIGHT)
  1558. {
  1559. x -= w;
  1560. }
  1561. if (valign == mxConstants.ALIGN_MIDDLE)
  1562. {
  1563. y -= h / 2;
  1564. }
  1565. else if (valign == mxConstants.ALIGN_BOTTOM)
  1566. {
  1567. y -= h;
  1568. }
  1569. bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale);
  1570. }
  1571. else if (node.getBBox != null && this.root.ownerDocument == document)
  1572. {
  1573. // Uses getBBox only if inside document for correct size
  1574. try
  1575. {
  1576. bbox = node.getBBox();
  1577. var ie = mxClient.IS_IE && mxClient.IS_SVG;
  1578. bbox = new mxRectangle(bbox.x, bbox.y + ((ie) ? 0 : 1), bbox.width, bbox.height + ((ie) ? 1 : 0));
  1579. }
  1580. catch (e)
  1581. {
  1582. // Ignores NS_ERROR_FAILURE in FF if container display is none.
  1583. }
  1584. }
  1585. if (bbox == null || bbox.width == 0 || bbox.height == 0)
  1586. {
  1587. // Computes size if not in document or no getBBox available
  1588. var div = document.createElement('div');
  1589. // Wrapping and clipping can be ignored here
  1590. div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
  1591. div.style.fontSize = s.fontSize + 'px';
  1592. div.style.fontFamily = s.fontFamily;
  1593. div.style.whiteSpace = 'nowrap';
  1594. div.style.position = 'absolute';
  1595. div.style.visibility = 'hidden';
  1596. div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
  1597. div.style.zoom = '1';
  1598. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  1599. {
  1600. div.style.fontWeight = 'bold';
  1601. }
  1602. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  1603. {
  1604. div.style.fontStyle = 'italic';
  1605. }
  1606. str = mxUtils.htmlEntities(str, false);
  1607. div.innerHTML = str.replace(/\n/g, '<br/>');
  1608. document.body.appendChild(div);
  1609. var w = div.offsetWidth;
  1610. var h = div.offsetHeight;
  1611. div.parentNode.removeChild(div);
  1612. if (align == mxConstants.ALIGN_CENTER)
  1613. {
  1614. x -= w / 2;
  1615. }
  1616. else if (align == mxConstants.ALIGN_RIGHT)
  1617. {
  1618. x -= w;
  1619. }
  1620. if (valign == mxConstants.ALIGN_MIDDLE)
  1621. {
  1622. y -= h / 2;
  1623. }
  1624. else if (valign == mxConstants.ALIGN_BOTTOM)
  1625. {
  1626. y -= h;
  1627. }
  1628. bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale);
  1629. }
  1630. if (bbox != null)
  1631. {
  1632. var n = this.createElement('rect');
  1633. n.setAttribute('fill', s.fontBackgroundColor || 'none');
  1634. n.setAttribute('stroke', s.fontBorderColor || 'none');
  1635. n.setAttribute('x', Math.floor(bbox.x - 1));
  1636. n.setAttribute('y', Math.floor(bbox.y - 1));
  1637. n.setAttribute('width', Math.ceil(bbox.width + 2));
  1638. n.setAttribute('height', Math.ceil(bbox.height));
  1639. var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0;
  1640. n.setAttribute('stroke-width', sw);
  1641. // Workaround for crisp rendering - only required if not exporting
  1642. if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1)
  1643. {
  1644. n.setAttribute('transform', 'translate(0.5, 0.5)');
  1645. }
  1646. node.insertBefore(n, node.firstChild);
  1647. }
  1648. }
  1649. };
  1650. /**
  1651. * Function: stroke
  1652. *
  1653. * Paints the outline of the current path.
  1654. */
  1655. mxSvgCanvas2D.prototype.stroke = function()
  1656. {
  1657. this.addNode(false, true);
  1658. };
  1659. /**
  1660. * Function: fill
  1661. *
  1662. * Fills the current path.
  1663. */
  1664. mxSvgCanvas2D.prototype.fill = function()
  1665. {
  1666. this.addNode(true, false);
  1667. };
  1668. /**
  1669. * Function: fillAndStroke
  1670. *
  1671. * Fills and paints the outline of the current path.
  1672. */
  1673. mxSvgCanvas2D.prototype.fillAndStroke = function()
  1674. {
  1675. this.addNode(true, true);
  1676. };