mxSvgCanvas2D.js 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957
  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. console.log(2323,node)
  920. node.onerror = ()=>{
  921. node.href.baseVal=this.converter.convert('/static/assets/images/entity/png/matrix.png');
  922. }
  923. // Workaround for missing namespace support
  924. if (node.setAttributeNS == null)
  925. {
  926. node.setAttribute('xlink:href', src);
  927. }
  928. else
  929. {
  930. node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
  931. }
  932. if (!aspect)
  933. {
  934. node.setAttribute('preserveAspectRatio', 'none');
  935. }
  936. if (s.alpha < 1 || s.fillAlpha < 1)
  937. {
  938. node.setAttribute('opacity', s.alpha * s.fillAlpha);
  939. }
  940. var tr = this.state.transform || '';
  941. if (flipH || flipV)
  942. {
  943. var sx = 1;
  944. var sy = 1;
  945. var dx = 0;
  946. var dy = 0;
  947. if (flipH)
  948. {
  949. sx = -1;
  950. dx = -w - 2 * x;
  951. }
  952. if (flipV)
  953. {
  954. sy = -1;
  955. dy = -h - 2 * y;
  956. }
  957. // Adds image tansformation to existing transform
  958. tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';
  959. }
  960. if (tr.length > 0)
  961. {
  962. node.setAttribute('transform', tr);
  963. }
  964. if (!this.pointerEvents)
  965. {
  966. node.setAttribute('pointer-events', 'none');
  967. }
  968. this.root.appendChild(node);
  969. };
  970. /**
  971. * Function: convertHtml
  972. *
  973. * Converts the given HTML string to XHTML.
  974. */
  975. mxSvgCanvas2D.prototype.convertHtml = function(val)
  976. {
  977. if (this.useDomParser)
  978. {
  979. var doc = new DOMParser().parseFromString(val, 'text/html');
  980. if (doc != null)
  981. {
  982. val = new XMLSerializer().serializeToString(doc.body);
  983. // Extracts body content from DOM
  984. if (val.substring(0, 5) == '<body')
  985. {
  986. val = val.substring(val.indexOf('>', 5) + 1);
  987. }
  988. if (val.substring(val.length - 7, val.length) == '</body>')
  989. {
  990. val = val.substring(0, val.length - 7);
  991. }
  992. }
  993. }
  994. else if (document.implementation != null && document.implementation.createDocument != null)
  995. {
  996. var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
  997. var xb = xd.createElement('body');
  998. xd.documentElement.appendChild(xb);
  999. var div = document.createElement('div');
  1000. div.innerHTML = val;
  1001. var child = div.firstChild;
  1002. while (child != null)
  1003. {
  1004. var next = child.nextSibling;
  1005. xb.appendChild(xd.adoptNode(child));
  1006. child = next;
  1007. }
  1008. return xb.innerHTML;
  1009. }
  1010. else
  1011. {
  1012. var ta = document.createElement('textarea');
  1013. // Handles special HTML entities < and > and double escaping
  1014. // and converts unclosed br, hr and img tags to XHTML
  1015. // LATER: Convert all unclosed tags
  1016. ta.innerHTML = val.replace(/&amp;/g, '&amp;amp;').
  1017. replace(/&#60;/g, '&amp;lt;').replace(/&#62;/g, '&amp;gt;').
  1018. replace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;').
  1019. replace(/</g, '&lt;').replace(/>/g, '&gt;');
  1020. val = ta.value.replace(/&/g, '&amp;').replace(/&amp;lt;/g, '&lt;').
  1021. replace(/&amp;gt;/g, '&gt;').replace(/&amp;amp;/g, '&amp;').
  1022. replace(/<br>/g, '<br />').replace(/<hr>/g, '<hr />').
  1023. replace(/(<img[^>]+)>/gm, "$1 />");
  1024. }
  1025. return val;
  1026. };
  1027. /**
  1028. * Function: createDiv
  1029. *
  1030. * Private helper function to create SVG elements
  1031. */
  1032. mxSvgCanvas2D.prototype.createDiv = function(str)
  1033. {
  1034. var val = str;
  1035. if (!mxUtils.isNode(val))
  1036. {
  1037. val = '<div><div>' + this.convertHtml(val) + '</div></div>';
  1038. }
  1039. // IE uses this code for export as it cannot render foreignObjects
  1040. if (!mxClient.IS_IE && !mxClient.IS_IE11 && document.createElementNS)
  1041. {
  1042. var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
  1043. if (mxUtils.isNode(val))
  1044. {
  1045. var div2 = document.createElement('div');
  1046. var div3 = div2.cloneNode(false);
  1047. // Creates a copy for export
  1048. if (this.root.ownerDocument != document)
  1049. {
  1050. div2.appendChild(val.cloneNode(true));
  1051. }
  1052. else
  1053. {
  1054. div2.appendChild(val);
  1055. }
  1056. div3.appendChild(div2);
  1057. div.appendChild(div3);
  1058. }
  1059. else
  1060. {
  1061. div.innerHTML = val;
  1062. }
  1063. return div;
  1064. }
  1065. else
  1066. {
  1067. if (mxUtils.isNode(val))
  1068. {
  1069. val = '<div><div>' + mxUtils.getXml(val) + '</div></div>';
  1070. }
  1071. val = '<div xmlns="http://www.w3.org/1999/xhtml">' + val + '</div>';
  1072. // NOTE: FF 3.6 crashes if content CSS contains "height:100%"
  1073. return mxUtils.parseXml(val).documentElement;
  1074. }
  1075. };
  1076. /**
  1077. * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below.
  1078. */
  1079. mxSvgCanvas2D.prototype.updateText = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node)
  1080. {
  1081. if (node != null && node.firstChild != null && node.firstChild.firstChild != null)
  1082. {
  1083. this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node.firstChild);
  1084. }
  1085. };
  1086. /**
  1087. * Function: addForeignObject
  1088. *
  1089. * Creates a foreignObject for the given string and adds it to the given root.
  1090. */
  1091. mxSvgCanvas2D.prototype.addForeignObject = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir, div, root)
  1092. {
  1093. var group = this.createElement('g');
  1094. var fo = this.createElement('foreignObject');
  1095. // Workarounds for print clipping and static position in Safari
  1096. fo.setAttribute('style', 'overflow: visible; text-align: left;');
  1097. fo.setAttribute('pointer-events', 'none');
  1098. // Import needed for older versions of IE
  1099. if (div.ownerDocument != document)
  1100. {
  1101. div = mxUtils.importNodeImplementation(fo.ownerDocument, div, true);
  1102. }
  1103. fo.appendChild(div);
  1104. group.appendChild(fo);
  1105. this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, group);
  1106. // Alternate content if foreignObject not supported
  1107. if (this.root.ownerDocument != document)
  1108. {
  1109. var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
  1110. if (alt != null)
  1111. {
  1112. fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
  1113. var sw = this.createElement('switch');
  1114. sw.appendChild(fo);
  1115. sw.appendChild(alt);
  1116. group.appendChild(sw);
  1117. }
  1118. }
  1119. root.appendChild(group);
  1120. };
  1121. /**
  1122. * Updates existing DOM nodes for text rendering.
  1123. */
  1124. mxSvgCanvas2D.prototype.updateTextNodes = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, g)
  1125. {
  1126. var s = this.state.scale;
  1127. mxSvgCanvas2D.createCss(w + 2, h, align, valign, wrap, overflow, clip,
  1128. (this.state.fontBackgroundColor != null) ? this.state.fontBackgroundColor : null,
  1129. (this.state.fontBorderColor != null) ? this.state.fontBorderColor : null,
  1130. 'display: flex; align-items: unsafe ' +
  1131. ((valign == mxConstants.ALIGN_TOP) ? 'flex-start' :
  1132. ((valign == mxConstants.ALIGN_BOTTOM) ? 'flex-end' : 'center')) + '; ' +
  1133. 'justify-content: unsafe ' + ((align == mxConstants.ALIGN_LEFT) ? 'flex-start' :
  1134. ((align == mxConstants.ALIGN_RIGHT) ? 'flex-end' : 'center')) + '; ',
  1135. this.getTextCss(), s, mxUtils.bind(this, function(dx, dy, flex, item, block)
  1136. {
  1137. x += this.state.dx;
  1138. y += this.state.dy;
  1139. var fo = g.firstChild;
  1140. var div = fo.firstChild;
  1141. var box = div.firstChild;
  1142. var text = box.firstChild;
  1143. var r = ((this.rotateHtml) ? this.state.rotation : 0) + ((rotation != null) ? rotation : 0);
  1144. var t = ((this.foOffset != 0) ? 'translate(' + this.foOffset + ' ' + this.foOffset + ')' : '') +
  1145. ((s != 1) ? 'scale(' + s + ')' : '');
  1146. text.setAttribute('style', block);
  1147. box.setAttribute('style', item);
  1148. // Workaround for clipping in Webkit with scrolling and zoom
  1149. fo.setAttribute('width', Math.ceil(1 / Math.min(1, s) * 100) + '%');
  1150. fo.setAttribute('height', Math.ceil(1 / Math.min(1, s) * 100) + '%');
  1151. var yp = Math.round(y + dy);
  1152. // Allows for negative values which are causing problems with
  1153. // transformed content where the top edge of the foreignObject
  1154. // limits the text box being moved further up in the diagram.
  1155. // KNOWN: Possible clipping problems with zoom and scrolling
  1156. // but this is normally not used with scrollbars as the
  1157. // coordinates are always positive with scrollbars.
  1158. // Margin-top is ignored in Safari and no negative values allowed
  1159. // for padding.
  1160. if (yp < 0)
  1161. {
  1162. fo.setAttribute('y', yp);
  1163. }
  1164. else
  1165. {
  1166. fo.removeAttribute('y');
  1167. flex += 'padding-top: ' + yp + 'px; ';
  1168. }
  1169. div.setAttribute('style', flex + 'margin-left: ' + Math.round(x + dx) + 'px;');
  1170. t += ((r != 0) ? ('rotate(' + r + ' ' + x + ' ' + y + ')') : '');
  1171. // Output allows for reflow but Safari cannot use absolute position,
  1172. // transforms or opacity. https://bugs.webkit.org/show_bug.cgi?id=23113
  1173. if (t != '')
  1174. {
  1175. g.setAttribute('transform', t);
  1176. }
  1177. else
  1178. {
  1179. g.removeAttribute('transform');
  1180. }
  1181. if (this.state.alpha != 1)
  1182. {
  1183. g.setAttribute('opacity', this.state.alpha);
  1184. }
  1185. else
  1186. {
  1187. g.removeAttribute('opacity');
  1188. }
  1189. }));
  1190. };
  1191. /**
  1192. * Updates existing DOM nodes for text rendering.
  1193. */
  1194. mxSvgCanvas2D.createCss = function(w, h, align, valign, wrap, overflow, clip, bg, border, flex, block, s, callback)
  1195. {
  1196. var item = 'box-sizing: border-box; font-size: 0; text-align: ' + ((align == mxConstants.ALIGN_LEFT) ? 'left' :
  1197. ((align == mxConstants.ALIGN_RIGHT) ? 'right' : 'center')) + '; ';
  1198. var pt = mxUtils.getAlignmentAsPoint(align, valign);
  1199. var ofl = 'overflow: hidden; ';
  1200. var fw = 'width: 1px; ';
  1201. var fh = 'height: 1px; ';
  1202. var dx = pt.x * w;
  1203. var dy = pt.y * h;
  1204. if (clip)
  1205. {
  1206. fw = 'width: ' + Math.round(w) + 'px; ';
  1207. item += 'max-height: ' + Math.round(h) + 'px; ';
  1208. dy = 0;
  1209. }
  1210. else if (overflow == 'fill')
  1211. {
  1212. fw = 'width: ' + Math.round(w) + 'px; ';
  1213. fh = 'height: ' + Math.round(h) + 'px; ';
  1214. block += 'width: 100%; height: 100%; ';
  1215. item += fw + fh;
  1216. }
  1217. else if (overflow == 'width')
  1218. {
  1219. fw = 'width: ' + Math.round(w) + 'px; ';
  1220. block += 'width: 100%; ';
  1221. item += fw;
  1222. dy = 0;
  1223. if (h > 0)
  1224. {
  1225. item += 'max-height: ' + Math.round(h) + 'px; ';
  1226. }
  1227. }
  1228. else
  1229. {
  1230. ofl = '';
  1231. dy = 0;
  1232. }
  1233. var bgc = '';
  1234. if (bg != null)
  1235. {
  1236. bgc += 'background-color: ' + bg + '; ';
  1237. }
  1238. if (border != null)
  1239. {
  1240. bgc += 'border: 1px solid ' + border + '; ';
  1241. }
  1242. if (ofl == '' || clip)
  1243. {
  1244. block += bgc;
  1245. }
  1246. else
  1247. {
  1248. item += bgc;
  1249. }
  1250. if (wrap && w > 0)
  1251. {
  1252. block += 'white-space: normal; word-wrap: ' + mxConstants.WORD_WRAP + '; ';
  1253. fw = 'width: ' + Math.round(w) + 'px; ';
  1254. if (ofl != '' && overflow != 'fill')
  1255. {
  1256. dy = 0;
  1257. }
  1258. }
  1259. else
  1260. {
  1261. block += 'white-space: nowrap; ';
  1262. if (ofl == '')
  1263. {
  1264. dx = 0;
  1265. }
  1266. }
  1267. callback(dx, dy, flex + fw + fh, item + ofl, block, ofl);
  1268. };
  1269. /**
  1270. * Function: getTextCss
  1271. *
  1272. * Private helper function to create SVG elements
  1273. */
  1274. mxSvgCanvas2D.prototype.getTextCss = function()
  1275. {
  1276. var s = this.state;
  1277. var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' :
  1278. (mxConstants.LINE_HEIGHT * this.lineHeightCorrection);
  1279. var css = 'display: inline-block; font-size: ' + s.fontSize + 'px; ' +
  1280. 'font-family: ' + s.fontFamily + '; color: ' + s.fontColor + '; line-height: ' + lh +
  1281. '; pointer-events: ' + ((this.pointerEvents) ? this.pointerEventsValue : 'none') + '; ';
  1282. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  1283. {
  1284. css += 'font-weight: bold; ';
  1285. }
  1286. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  1287. {
  1288. css += 'font-style: italic; ';
  1289. }
  1290. var deco = [];
  1291. if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
  1292. {
  1293. deco.push('underline');
  1294. }
  1295. if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH)
  1296. {
  1297. deco.push('line-through');
  1298. }
  1299. if (deco.length > 0)
  1300. {
  1301. css += 'text-decoration: ' + deco.join(' ') + '; ';
  1302. }
  1303. return css;
  1304. };
  1305. /**
  1306. * Function: text
  1307. *
  1308. * Paints the given text. Possible values for format are empty string for plain
  1309. * text and html for HTML markup. Note that HTML markup is only supported if
  1310. * foreignObject is supported and <foEnabled> is true. (This means IE9 and later
  1311. * does currently not support HTML text as part of shapes.)
  1312. */
  1313. mxSvgCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
  1314. {
  1315. if (this.textEnabled && str != null)
  1316. {
  1317. rotation = (rotation != null) ? rotation : 0;
  1318. if (this.foEnabled && format == 'html')
  1319. {
  1320. var div = this.createDiv(str);
  1321. // Ignores invalid XHTML labels
  1322. if (div != null)
  1323. {
  1324. if (dir != null)
  1325. {
  1326. div.setAttribute('dir', dir);
  1327. }
  1328. this.addForeignObject(x, y, w, h, str, align, valign, wrap,
  1329. format, overflow, clip, rotation, dir, div, this.root);
  1330. }
  1331. }
  1332. else
  1333. {
  1334. this.plainText(x + this.state.dx, y + this.state.dy, w, h, str,
  1335. align, valign, wrap, overflow, clip, rotation, dir);
  1336. }
  1337. }
  1338. };
  1339. /**
  1340. * Function: createClip
  1341. *
  1342. * Creates a clip for the given coordinates.
  1343. */
  1344. mxSvgCanvas2D.prototype.createClip = function(x, y, w, h)
  1345. {
  1346. x = Math.round(x);
  1347. y = Math.round(y);
  1348. w = Math.round(w);
  1349. h = Math.round(h);
  1350. var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;
  1351. var counter = 0;
  1352. var tmp = id + '-' + counter;
  1353. // Resolves ID conflicts
  1354. while (document.getElementById(tmp) != null)
  1355. {
  1356. tmp = id + '-' + (++counter);
  1357. }
  1358. clip = this.createElement('clipPath');
  1359. clip.setAttribute('id', tmp);
  1360. var rect = this.createElement('rect');
  1361. rect.setAttribute('x', x);
  1362. rect.setAttribute('y', y);
  1363. rect.setAttribute('width', w);
  1364. rect.setAttribute('height', h);
  1365. clip.appendChild(rect);
  1366. return clip;
  1367. };
  1368. /**
  1369. * Function: plainText
  1370. *
  1371. * Paints the given text. Possible values for format are empty string for
  1372. * plain text and html for HTML markup.
  1373. */
  1374. mxSvgCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir)
  1375. {
  1376. rotation = (rotation != null) ? rotation : 0;
  1377. var s = this.state;
  1378. var size = s.fontSize;
  1379. var node = this.createElement('g');
  1380. var tr = s.transform || '';
  1381. this.updateFont(node);
  1382. // Ignores pointer events
  1383. if (!this.pointerEvents && this.originalRoot == null)
  1384. {
  1385. node.setAttribute('pointer-events', 'none');
  1386. }
  1387. // Non-rotated text
  1388. if (rotation != 0)
  1389. {
  1390. tr += 'rotate(' + rotation + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')';
  1391. }
  1392. if (dir != null)
  1393. {
  1394. node.setAttribute('direction', dir);
  1395. }
  1396. if (clip && w > 0 && h > 0)
  1397. {
  1398. var cx = x;
  1399. var cy = y;
  1400. if (align == mxConstants.ALIGN_CENTER)
  1401. {
  1402. cx -= w / 2;
  1403. }
  1404. else if (align == mxConstants.ALIGN_RIGHT)
  1405. {
  1406. cx -= w;
  1407. }
  1408. if (overflow != 'fill')
  1409. {
  1410. if (valign == mxConstants.ALIGN_MIDDLE)
  1411. {
  1412. cy -= h / 2;
  1413. }
  1414. else if (valign == mxConstants.ALIGN_BOTTOM)
  1415. {
  1416. cy -= h;
  1417. }
  1418. }
  1419. // LATER: Remove spacing from clip rectangle
  1420. var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4);
  1421. if (this.defs != null)
  1422. {
  1423. this.defs.appendChild(c);
  1424. }
  1425. else
  1426. {
  1427. // Makes sure clip is removed with referencing node
  1428. this.root.appendChild(c);
  1429. }
  1430. if (!mxClient.IS_CHROMEAPP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
  1431. !mxClient.IS_EDGE && this.root.ownerDocument == document)
  1432. {
  1433. // Workaround for potential base tag
  1434. var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
  1435. node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')');
  1436. }
  1437. else
  1438. {
  1439. node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')');
  1440. }
  1441. }
  1442. // Default is left
  1443. var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
  1444. (align == mxConstants.ALIGN_CENTER) ? 'middle' :
  1445. 'start';
  1446. // Text-anchor start is default in SVG
  1447. if (anchor != 'start')
  1448. {
  1449. node.setAttribute('text-anchor', anchor);
  1450. }
  1451. if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)
  1452. {
  1453. node.setAttribute('font-size', (size * s.scale) + 'px');
  1454. }
  1455. if (tr.length > 0)
  1456. {
  1457. node.setAttribute('transform', tr);
  1458. }
  1459. if (s.alpha < 1)
  1460. {
  1461. node.setAttribute('opacity', s.alpha);
  1462. }
  1463. var lines = str.split('\n');
  1464. var lh = Math.round(size * mxConstants.LINE_HEIGHT);
  1465. var textHeight = size + (lines.length - 1) * lh;
  1466. var cy = y + size - 1;
  1467. if (valign == mxConstants.ALIGN_MIDDLE)
  1468. {
  1469. if (overflow == 'fill')
  1470. {
  1471. cy -= h / 2;
  1472. }
  1473. else
  1474. {
  1475. var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2;
  1476. cy -= dy;
  1477. }
  1478. }
  1479. else if (valign == mxConstants.ALIGN_BOTTOM)
  1480. {
  1481. if (overflow == 'fill')
  1482. {
  1483. cy -= h;
  1484. }
  1485. else
  1486. {
  1487. var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight;
  1488. cy -= dy + 1;
  1489. }
  1490. }
  1491. for (var i = 0; i < lines.length; i++)
  1492. {
  1493. // Workaround for bounding box of empty lines and spaces
  1494. if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0)
  1495. {
  1496. var text = this.createElement('text');
  1497. // LATER: Match horizontal HTML alignment
  1498. text.setAttribute('x', this.format(x * s.scale) + this.textOffset);
  1499. text.setAttribute('y', this.format(cy * s.scale) + this.textOffset);
  1500. mxUtils.write(text, lines[i]);
  1501. node.appendChild(text);
  1502. }
  1503. cy += lh;
  1504. }
  1505. this.root.appendChild(node);
  1506. this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow);
  1507. };
  1508. /**
  1509. * Function: updateFont
  1510. *
  1511. * Updates the text properties for the given node. (NOTE: For this to work in
  1512. * IE, the given node must be a text or tspan element.)
  1513. */
  1514. mxSvgCanvas2D.prototype.updateFont = function(node)
  1515. {
  1516. var s = this.state;
  1517. node.setAttribute('fill', s.fontColor);
  1518. if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY)
  1519. {
  1520. node.setAttribute('font-family', s.fontFamily);
  1521. }
  1522. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  1523. {
  1524. node.setAttribute('font-weight', 'bold');
  1525. }
  1526. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  1527. {
  1528. node.setAttribute('font-style', 'italic');
  1529. }
  1530. var txtDecor = [];
  1531. if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
  1532. {
  1533. txtDecor.push('underline');
  1534. }
  1535. if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH)
  1536. {
  1537. txtDecor.push('line-through');
  1538. }
  1539. if (txtDecor.length > 0)
  1540. {
  1541. node.setAttribute('text-decoration', txtDecor.join(' '));
  1542. }
  1543. };
  1544. /**
  1545. * Function: addTextBackground
  1546. *
  1547. * Background color and border
  1548. */
  1549. mxSvgCanvas2D.prototype.addTextBackground = function(node, str, x, y, w, h, align, valign, overflow)
  1550. {
  1551. var s = this.state;
  1552. if (s.fontBackgroundColor != null || s.fontBorderColor != null)
  1553. {
  1554. var bbox = null;
  1555. if (overflow == 'fill' || overflow == 'width')
  1556. {
  1557. if (align == mxConstants.ALIGN_CENTER)
  1558. {
  1559. x -= w / 2;
  1560. }
  1561. else if (align == mxConstants.ALIGN_RIGHT)
  1562. {
  1563. x -= w;
  1564. }
  1565. if (valign == mxConstants.ALIGN_MIDDLE)
  1566. {
  1567. y -= h / 2;
  1568. }
  1569. else if (valign == mxConstants.ALIGN_BOTTOM)
  1570. {
  1571. y -= h;
  1572. }
  1573. bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale);
  1574. }
  1575. else if (node.getBBox != null && this.root.ownerDocument == document)
  1576. {
  1577. // Uses getBBox only if inside document for correct size
  1578. try
  1579. {
  1580. bbox = node.getBBox();
  1581. var ie = mxClient.IS_IE && mxClient.IS_SVG;
  1582. bbox = new mxRectangle(bbox.x, bbox.y + ((ie) ? 0 : 1), bbox.width, bbox.height + ((ie) ? 1 : 0));
  1583. }
  1584. catch (e)
  1585. {
  1586. // Ignores NS_ERROR_FAILURE in FF if container display is none.
  1587. }
  1588. }
  1589. if (bbox == null || bbox.width == 0 || bbox.height == 0)
  1590. {
  1591. // Computes size if not in document or no getBBox available
  1592. var div = document.createElement('div');
  1593. // Wrapping and clipping can be ignored here
  1594. div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
  1595. div.style.fontSize = s.fontSize + 'px';
  1596. div.style.fontFamily = s.fontFamily;
  1597. div.style.whiteSpace = 'nowrap';
  1598. div.style.position = 'absolute';
  1599. div.style.visibility = 'hidden';
  1600. div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
  1601. div.style.zoom = '1';
  1602. if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
  1603. {
  1604. div.style.fontWeight = 'bold';
  1605. }
  1606. if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
  1607. {
  1608. div.style.fontStyle = 'italic';
  1609. }
  1610. str = mxUtils.htmlEntities(str, false);
  1611. div.innerHTML = str.replace(/\n/g, '<br/>');
  1612. document.body.appendChild(div);
  1613. var w = div.offsetWidth;
  1614. var h = div.offsetHeight;
  1615. div.parentNode.removeChild(div);
  1616. if (align == mxConstants.ALIGN_CENTER)
  1617. {
  1618. x -= w / 2;
  1619. }
  1620. else if (align == mxConstants.ALIGN_RIGHT)
  1621. {
  1622. x -= w;
  1623. }
  1624. if (valign == mxConstants.ALIGN_MIDDLE)
  1625. {
  1626. y -= h / 2;
  1627. }
  1628. else if (valign == mxConstants.ALIGN_BOTTOM)
  1629. {
  1630. y -= h;
  1631. }
  1632. bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale);
  1633. }
  1634. if (bbox != null)
  1635. {
  1636. var n = this.createElement('rect');
  1637. n.setAttribute('fill', s.fontBackgroundColor || 'none');
  1638. n.setAttribute('stroke', s.fontBorderColor || 'none');
  1639. n.setAttribute('x', Math.floor(bbox.x - 1));
  1640. n.setAttribute('y', Math.floor(bbox.y - 1));
  1641. n.setAttribute('width', Math.ceil(bbox.width + 2));
  1642. n.setAttribute('height', Math.ceil(bbox.height));
  1643. var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0;
  1644. n.setAttribute('stroke-width', sw);
  1645. // Workaround for crisp rendering - only required if not exporting
  1646. if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1)
  1647. {
  1648. n.setAttribute('transform', 'translate(0.5, 0.5)');
  1649. }
  1650. node.insertBefore(n, node.firstChild);
  1651. }
  1652. }
  1653. };
  1654. /**
  1655. * Function: stroke
  1656. *
  1657. * Paints the outline of the current path.
  1658. */
  1659. mxSvgCanvas2D.prototype.stroke = function()
  1660. {
  1661. this.addNode(false, true);
  1662. };
  1663. /**
  1664. * Function: fill
  1665. *
  1666. * Fills the current path.
  1667. */
  1668. mxSvgCanvas2D.prototype.fill = function()
  1669. {
  1670. this.addNode(true, false);
  1671. };
  1672. /**
  1673. * Function: fillAndStroke
  1674. *
  1675. * Fills and paints the outline of the current path.
  1676. */
  1677. mxSvgCanvas2D.prototype.fillAndStroke = function()
  1678. {
  1679. this.addNode(true, true);
  1680. };
  1681. __mxOutput.mxSvgCanvas2D = typeof mxSvgCanvas2D !== 'undefined' ? mxSvgCanvas2D : undefined;