graphpreviewer.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. class GraphPreviewer {
  2. constructor(p_vxcore, p_container) {
  3. this.vxcore = p_vxcore;
  4. // Preview will take place here.
  5. this.container = p_container;
  6. this.flowchartJsIdx = 0;
  7. this.waveDromIdx = 0;
  8. this.mermaidIdx = 0;
  9. // Used to decide the width with 100% relative value.
  10. this.windowWidth = 800;
  11. this.firstPreview = true;
  12. this.currentColor = null;
  13. window.addEventListener(
  14. 'resize',
  15. () => {
  16. if (window.innerWidth > 0) {
  17. this.windowWidth = window.innerWidth;
  18. }
  19. },
  20. { passive: true });
  21. }
  22. // Interface 1.
  23. previewGraph(p_id, p_timeStamp, p_lang, p_text) {
  24. if (p_text.length == 0) {
  25. this.setGraphPreviewData(p_id, p_timeStamp);
  26. return;
  27. }
  28. this.initOnFirstPreview();
  29. if (p_lang === 'flow' || p_lang === 'flowchart') {
  30. this.vxcore.getWorker('flowchartjs').renderText(this.container,
  31. p_text,
  32. this.flowchartJsIdx++,
  33. (graphDiv) => {
  34. this.processGraph(p_id, p_timeStamp, graphDiv);
  35. });
  36. } else if (p_lang === 'wavedrom') {
  37. this.vxcore.getWorker('wavedrom').renderText(this.container,
  38. p_text,
  39. this.waveDromIdx++,
  40. (graphDiv) => {
  41. this.processGraph(p_id, p_timeStamp, graphDiv);
  42. });
  43. } else if (p_lang === 'mermaid') {
  44. this.vxcore.getWorker('mermaid').renderText(this.container,
  45. p_text,
  46. this.mermaidIdx++,
  47. (graphDiv) => {
  48. this.fixSvgRelativeWidth(graphDiv.firstElementChild);
  49. this.processGraph(p_id, p_timeStamp, graphDiv);
  50. });
  51. } else if (p_lang === 'puml' || p_lang === 'plantuml') {
  52. let func = function(p_previewer, p_id, p_timeStamp) {
  53. let previewer = p_previewer;
  54. let id = p_id;
  55. let timeStamp = p_timeStamp;
  56. return function(p_format, p_data) {
  57. previewer.setGraphPreviewData(id, timeStamp, p_format, p_data, false, true);
  58. };
  59. };
  60. this.vxcore.getWorker('plantuml').renderText(p_text, func(this, p_id, p_timeStamp));
  61. return;
  62. } else if (p_lang === 'dot' || p_lang === 'graphviz') {
  63. let func = function(p_previewer, p_id, p_timeStamp) {
  64. let previewer = p_previewer;
  65. let id = p_id;
  66. let timeStamp = p_timeStamp;
  67. return function(p_svgNode) {
  68. previewer.setGraphPreviewData(id, timeStamp, 'svg', p_svgNode.outerHTML, false, true);
  69. };
  70. };
  71. this.vxcore.getWorker('graphviz').renderText(p_text, func(this, p_id, p_timeStamp));
  72. return;
  73. } else if (p_lang === 'mathjax') {
  74. this.renderMath(p_id, p_timeStamp, p_text, null);
  75. return;
  76. } else {
  77. this.setGraphPreviewData(p_id, p_timeStamp);
  78. }
  79. }
  80. // Interface 2.
  81. previewMath(p_id, p_timeStamp, p_text) {
  82. if (p_text.length == 0) {
  83. this.setMathPreviewData(p_id, p_timeStamp);
  84. return;
  85. }
  86. this.initOnFirstPreview();
  87. // Do we need to go through TexMath plugin? I don't think so.
  88. this.renderMath(p_id, p_timeStamp, p_text, this.setMathPreviewData.bind(this));
  89. }
  90. initOnFirstPreview() {
  91. if (this.firstPreview) {
  92. this.firstPreview = false;
  93. let contentStyle = window.getComputedStyle(this.vxcore.contentContainer);
  94. this.currentColor = contentStyle.getPropertyValue('color');
  95. console.log('currentColor', this.currentColor);
  96. }
  97. }
  98. renderMath(p_id, p_timeStamp, p_text, p_dataSetter) {
  99. let func = function(p_previewer, p_id, p_timeStamp) {
  100. let previewer = p_previewer;
  101. let id = p_id;
  102. let timeStamp = p_timeStamp;
  103. return function(p_svgNode) {
  104. previewer.fixSvgCurrentColor(p_svgNode);
  105. previewer.fixSvgRelativeWidth(p_svgNode);
  106. previewer.processSvgAsPng(id, timeStamp, p_svgNode, p_dataSetter);
  107. };
  108. };
  109. this.vxcore.getWorker('mathjax').renderText(this.container,
  110. p_text,
  111. func(this, p_id, p_timeStamp));
  112. }
  113. processGraph(p_id, p_timeStamp, p_graphDiv) {
  114. if (!p_graphDiv) {
  115. console.error('failed to preview graph', p_id, p_timeStamp);
  116. this.setGraphPreviewData(p_id, p_timeStamp);
  117. return;
  118. }
  119. this.container.removeChild(p_graphDiv);
  120. this.processSvgAsPng(p_id, p_timeStamp, p_graphDiv.firstElementChild);
  121. }
  122. processSvgAsPng(p_id, p_timeStamp, p_svgNode, p_dataSetter = null) {
  123. if (!p_dataSetter) {
  124. p_dataSetter = this.setGraphPreviewData.bind(this);
  125. }
  126. if (!p_svgNode) {
  127. console.warn('failed to preview graph', p_id, p_timeStamp);
  128. p_dataSetter(p_id, p_timeStamp);
  129. return;
  130. }
  131. this.scaleSvg(p_svgNode);
  132. SvgToImage.svgToImage(p_svgNode.outerHTML,
  133. { crossOrigin: 'Anonymous' },
  134. (p_err, p_image) => {
  135. if (p_err) {
  136. p_dataSetter(p_id, p_timeStamp);
  137. return;
  138. }
  139. let canvas = document.createElement('canvas');
  140. let ctx = canvas.getContext('2d');
  141. canvas.height = p_image.height;
  142. canvas.width = p_image.width;
  143. ctx.drawImage(p_image, 0, 0);
  144. let dataUrl = null;
  145. try {
  146. dataUrl = canvas.toDataURL();
  147. } catch (err) {
  148. // Tainted canvas may be caused by the <foreignObject> in SVG.
  149. console.error('failed to draw image on canvas', err);
  150. // Try simply using the SVG.
  151. p_dataSetter(p_id, p_timeStamp, 'svg', p_svgNode.outerHTML, false, false);
  152. return;
  153. }
  154. let png = dataUrl ? dataUrl.substring(dataUrl.indexOf(',') + 1) : '';
  155. p_dataSetter(p_id, p_timeStamp, 'png', png, true, false);
  156. });
  157. }
  158. // Fix SVG with width and height being '100%'.
  159. fixSvgRelativeWidth(p_svgNode) {
  160. if (!p_svgNode) {
  161. return;
  162. }
  163. if (p_svgNode.getAttribute('width').indexOf('%') != -1) {
  164. // Try maxWidth.
  165. if (p_svgNode.style.maxWidth && p_svgNode.style.maxWidth.endsWith('px')) {
  166. p_svgNode.setAttribute('width', p_svgNode.style.maxWidth);
  167. } else {
  168. // Set as window width.
  169. p_svgNode.setAttribute('width', Math.max(this.windowWidth - 100, 100) + 'px');
  170. }
  171. }
  172. }
  173. // Fix SVG with stroke="currentColor" and fill="currentColor".
  174. fixSvgCurrentColor(p_svgNode) {
  175. let currentColor = this.currentColor;
  176. if (currentColor && p_svgNode) {
  177. let nodes = p_svgNode.querySelectorAll("g[fill='currentColor']");
  178. for (let i = 0; i < nodes.length; ++i) {
  179. let node = nodes[i];
  180. if (node.getAttribute('stroke') === 'currentColor') {
  181. node.setAttribute('stroke', currentColor);
  182. }
  183. if (node.getAttribute('fill') === 'currentColor') {
  184. node.setAttribute('fill', currentColor);
  185. }
  186. }
  187. }
  188. }
  189. scaleSvg(p_svgNode) {
  190. let scaleFactor = window.devicePixelRatio;
  191. if (scaleFactor == 1 || !p_svgNode) {
  192. return;
  193. }
  194. if (p_svgNode.getAttribute('width').indexOf('%') == -1) {
  195. p_svgNode.width.baseVal.valueInSpecifiedUnits *= scaleFactor;
  196. }
  197. if (p_svgNode.getAttribute('height').indexOf('%') == -1) {
  198. p_svgNode.height.baseVal.valueInSpecifiedUnits *= scaleFactor;
  199. }
  200. }
  201. setGraphPreviewData(p_id, p_timeStamp, p_format = '', p_data = '', p_base64 = false, p_needScale = false) {
  202. let previewData = {
  203. id: p_id,
  204. timeStamp: p_timeStamp,
  205. format: p_format,
  206. data: p_data,
  207. base64: p_base64,
  208. needScale: p_needScale
  209. };
  210. this.vxcore.setGraphPreviewData(previewData);
  211. }
  212. setMathPreviewData(p_id, p_timeStamp, p_format = '', p_data = '', p_base64 = false, p_needScale = false) {
  213. let previewData = {
  214. id: p_id,
  215. timeStamp: p_timeStamp,
  216. format: p_format,
  217. data: p_data,
  218. base64: p_base64,
  219. needScale: p_needScale
  220. };
  221. this.vxcore.setMathPreviewData(previewData);
  222. }
  223. }