vnote/src/data/extra/web/js/graphpreviewer.js
2020-12-31 19:53:28 +08:00

238 lines
8.5 KiB
JavaScript

class GraphPreviewer {
constructor(p_vnotex, p_container) {
this.vnotex = p_vnotex;
// Preview will take place here.
this.container = p_container;
this.flowchartJsIdx = 0;
this.waveDromIdx = 0;
this.mermaidIdx = 0;
// Used to decide the width with 100% relative value.
this.windowWidth = 800;
this.firstPreview = true;
this.currentColor = null;
window.addEventListener(
'resize',
() => {
if (window.innerWidth > 0) {
this.windowWidth = window.innerWidth;
}
},
{ passive: true });
}
previewGraph(p_id, p_timeStamp, p_lang, p_text) {
if (p_text.length == 0) {
this.setGraphPreviewData(p_id, p_timeStamp);
return;
}
if (this.firstPreview) {
this.firstPreview = false;
let contentStyle = window.getComputedStyle(this.vnotex.contentContainer);
this.currentColor = contentStyle.getPropertyValue('color');
console.log('currentColor', this.currentColor);
}
if (p_lang === 'flow' || p_lang === 'flowchart') {
this.vnotex.getWorker('flowchartjs').renderText(this.container,
p_text,
this.flowchartJsIdx++,
(graphDiv) => {
this.processGraph(p_id, p_timeStamp, graphDiv);
});
} else if (p_lang === 'wavedrom') {
this.vnotex.getWorker('wavedrom').renderText(this.container,
p_text,
this.waveDromIdx++,
(graphDiv) => {
this.processGraph(p_id, p_timeStamp, graphDiv);
});
} else if (p_lang === 'mermaid') {
this.vnotex.getWorker('mermaid').renderText(this.container,
p_text,
this.mermaidIdx++,
(graphDiv) => {
this.fixSvgRelativeWidth(graphDiv.firstElementChild);
this.processGraph(p_id, p_timeStamp, graphDiv);
});
} else if (p_lang === 'puml' || p_lang === 'plantuml') {
let func = function(p_previewer, p_id, p_timeStamp) {
let previewer = p_previewer;
let id = p_id;
let timeStamp = p_timeStamp;
return function(p_format, p_data) {
previewer.setGraphPreviewData(id, timeStamp, p_format, p_data, false, true);
};
};
this.vnotex.getWorker('plantuml').renderText(p_text, func(this, p_id, p_timeStamp));
return;
} else if (p_lang === 'dot') {
let func = function(p_previewer, p_id, p_timeStamp) {
let previewer = p_previewer;
let id = p_id;
let timeStamp = p_timeStamp;
return function(p_svgNode) {
previewer.setGraphPreviewData(id, timeStamp, 'svg', p_svgNode.outerHTML, false, true);
};
};
this.vnotex.getWorker('graphviz').renderText(p_text, func(this, p_id, p_timeStamp));
return;
} else if (p_lang === 'mathjax') {
this.renderMath(p_id, p_timeStamp, p_text, null);
return;
} else {
this.setGraphPreviewData(p_id, p_timeStamp);
}
}
renderMath(p_id, p_timeStamp, p_text, p_dataSetter) {
let func = function(p_previewer, p_id, p_timeStamp) {
let previewer = p_previewer;
let id = p_id;
let timeStamp = p_timeStamp;
return function(p_svgNode) {
previewer.fixSvgCurrentColor(p_svgNode);
previewer.fixSvgRelativeWidth(p_svgNode);
previewer.processSvgAsPng(id, timeStamp, p_svgNode, p_dataSetter);
};
};
this.vnotex.getWorker('mathjax').renderText(this.container,
p_text,
func(this, p_id, p_timeStamp));
}
processGraph(p_id, p_timeStamp, p_graphDiv) {
if (!p_graphDiv) {
console.error('failed to preview graph', p_id, p_timeStamp);
this.setGraphPreviewData(p_id, p_timeStamp);
return;
}
this.container.removeChild(p_graphDiv);
this.processSvgAsPng(p_id, p_timeStamp, p_graphDiv.firstElementChild);
}
processSvgAsPng(p_id, p_timeStamp, p_svgNode, p_dataSetter = null) {
if (!p_dataSetter) {
p_dataSetter = this.setGraphPreviewData.bind(this);
}
if (!p_svgNode) {
console.error('failed to preview graph', p_id, p_timeStamp);
p_dataSetter(p_id, p_timeStamp);
return;
}
this.scaleSvg(p_svgNode);
SvgToImage.svgToImage(p_svgNode.outerHTML,
{ crossOrigin: 'Anonymous' },
(p_err, p_image) => {
if (p_err) {
p_dataSetter(p_id, p_timeStamp);
return;
}
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.height = p_image.height;
canvas.width = p_image.width;
ctx.drawImage(p_image, 0, 0);
let dataUrl = null;
try {
dataUrl = canvas.toDataURL();
} catch (err) {
// Tainted canvas may be caused by the <foreignObject> in SVG.
console.error('failed to draw image on canvas', err);
// Try simply using the SVG.
p_dataSetter(p_id, p_timeStamp, 'svg', p_svgNode.outerHTML, false, false);
return;
}
let png = dataUrl ? dataUrl.substring(dataUrl.indexOf(',') + 1) : '';
p_dataSetter(p_id, p_timeStamp, 'png', png, true, false);
});
}
previewMath(p_id, p_timeStamp, p_text) {
// Do we need to go through TexMath plugin? I don't think so.
this.renderMath(p_id, p_timeStamp, p_text, this.setMathPreviewData.bind(this));
}
// Fix SVG with width and height being '100%'.
fixSvgRelativeWidth(p_svgNode) {
if (p_svgNode.getAttribute('width').indexOf('%') != -1) {
// Try maxWidth.
if (p_svgNode.style.maxWidth && p_svgNode.style.maxWidth.endsWith('px')) {
p_svgNode.setAttribute('width', p_svgNode.style.maxWidth);
} else {
// Set as window width.
p_svgNode.setAttribute('width', Math.max(this.windowWidth - 100, 100) + 'px');
}
}
}
// Fix SVG with stroke="currentColor" and fill="currentColor".
fixSvgCurrentColor(p_svgNode) {
let currentColor = this.currentColor;
if (currentColor) {
let nodes = p_svgNode.querySelectorAll("g[fill='currentColor']");
for (let i = 0; i < nodes.length; ++i) {
let node = nodes[i];
if (node.getAttribute('stroke') === 'currentColor') {
node.setAttribute('stroke', currentColor);
}
if (node.getAttribute('fill') === 'currentColor') {
node.setAttribute('fill', currentColor);
}
}
}
}
scaleSvg(p_svgNode) {
let scaleFactor = window.devicePixelRatio;
if (scaleFactor == 1) {
return;
}
if (p_svgNode.getAttribute('width').indexOf('%') == -1) {
p_svgNode.width.baseVal.valueInSpecifiedUnits *= scaleFactor;
}
if (p_svgNode.getAttribute('height').indexOf('%') == -1) {
p_svgNode.height.baseVal.valueInSpecifiedUnits *= scaleFactor;
}
}
setGraphPreviewData(p_id, p_timeStamp, p_format = '', p_data = '', p_base64 = false, p_needScale = false) {
let previewData = {
id: p_id,
timeStamp: p_timeStamp,
format: p_format,
data: p_data,
base64: p_base64,
needScale: p_needScale
};
this.vnotex.setGraphPreviewData(previewData);
}
setMathPreviewData(p_id, p_timeStamp, p_format = '', p_data = '', p_base64 = false, p_needScale = false) {
let previewData = {
id: p_id,
timeStamp: p_timeStamp,
format: p_format,
data: p_data,
base64: p_base64,
needScale: p_needScale
};
this.vnotex.setMathPreviewData(previewData);
}
}