feat(katex): added common functions for aiding in KaTeX rendering

This commit is contained in:
NicolasNewman 2023-05-06 11:33:23 +09:00
parent e0e038d223
commit f8a4488050
6 changed files with 85 additions and 35 deletions

View File

@ -14,7 +14,7 @@
#### Defined in
[defaultConfig.ts:2115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2115)
[defaultConfig.ts:2126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2126)
---

View File

@ -34,6 +34,7 @@ export interface MermaidConfig {
dompurifyConfig?: DOMPurify.Config;
wrap?: boolean;
fontSize?: number;
legacyMathML?: boolean;
}
// TODO: More configs needs to be moved in here

View File

@ -131,6 +131,17 @@ const config: Partial<MermaidConfig> = {
* Default value: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize']
*/
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'],
/**
* This option specifies if Mermaid can expected the dependnet to include KaTeX stylesheets for browsers
* without their own MathML implementation. If this option is disabled and MathML is not supported, the math
* equations are replaced with a warning. If this option is enabled and MathML is not supported, Mermaid will
* fall back to legacy rendering for KaTeX.
*
* **Notes**:
*
* Default value: false
*/
legacyMathML: false,
/**
* This option controls if the generated ids of nodes in the SVG are generated randomly or based
* on a seed. If set to false, the IDs are generated based on the current date and thus are not

View File

@ -1,4 +1,6 @@
import DOMPurify from 'dompurify';
// @ts-ignore @types/katex does not work
import katex from 'katex';
import { MermaidConfig } from '../../config.type.js';
/**
@ -170,6 +172,62 @@ export const parseGenericTypes = function (text: string): string {
}
};
// TODO: find a better method for detecting support. This interface was added in the MathML 4 spec.
// Firefox versions between [4,71] (0.47%) and Safari versions between [5,13.4] (0.17%) don't have this interface implemented but MathML is supported
export const isMathMLSupported = window.MathMLElement !== undefined;
export const katexRegex = /\$\$(.*)\$\$/g;
/**
* Whether or not a text has KaTeX delimiters
*
* @param text - The text to test
* @returns Whether or not the text has KaTeX delimiters
*/
export const hasKatex = (text: string): boolean => (text.match(katexRegex)?.length ?? 0) > 0;
/**
* Computes the minimum dimensions needed to display a div contianing MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns Object containing {width, height}
*/
export const calculateMathMLDimensions = (text: string, config: MermaidConfig) => {
text = renderKatex(text, config).split(lineBreakRegex).map((text) => hasKatex(text) ? renderKatex(text, config) : `<div>${text}</div>`).join('');
const divElem = document.createElement('div')
divElem.innerHTML = text;
divElem.id = 'katex-temp';
divElem.style.visibility = 'hidden';
divElem.style.position = 'absolute';
divElem.style.top = '0';
const body = document.querySelector('body');
body?.insertAdjacentElement('beforeend', divElem);
const dim = {width: divElem.clientWidth, height: divElem.clientHeight};
divElem.remove();
return dim;
}
/**
* Attempts to render and return the KaTeX portion of a string with MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns String containing MathML if KaTeX is supported, or an error message if it is not and stylesheets aren't present
*/
export const renderKatex = (text: string, config: MermaidConfig): string => {
if (isMathMLSupported || (!isMathMLSupported && config.legacyMathML)) {
return text.replace(/\$\$(.*)\$\$/g, (r, c) =>
katex
.renderToString(c, { throwOnError: true, displayMode: true, output: isMathMLSupported ? 'mathml' : 'htmlAndMathml' })
.replace(/\n/g, ' ')
.replace(/<annotation.*<\/annotation>/g, '')
);
}
return text.replace(/\$\$(.*)\$\$/g, (r, c) => 'MathML is unsupported in this environment.');
};
export default {
getRows,
sanitizeText,

View File

@ -1,6 +1,5 @@
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { select, curveLinear, selectAll } from 'd3';
import katex from 'katex';
import flowDb from './flowDb.js';
import { getConfig } from '../../config.js';
@ -9,7 +8,7 @@ import utils from '../../utils.js';
import { render } from '../../dagre-wrapper/index.js';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { log } from '../../logger.js';
import common, { evaluate } from '../common/common.js';
import common, { evaluate, renderKatex } from '../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
@ -144,12 +143,8 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
default:
_shape = 'rect';
}
const labelText = vertexText.replace(/\$\$(.*)\$\$/g, (r, c) =>
katex
.renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
.replace(/\n/g, ' ')
.replace(/<annotation.*<\/annotation>/g, '')
);
const labelText = renderKatex(vertexText, getConfig());
// Add the node
g.setNode(vertex.id, {
labelStyle: styles.labelStyle,
@ -323,14 +318,7 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.labelpos = 'c';
}
edgeData.labelType = edge.labelType;
edgeData.label = edge.text
.replace(common.lineBreakRegex, '\n')
.replace(/\$\$(.*)\$\$/g, (r, c) =>
katex
.renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
.replace(/\n/g, ' ')
.replace(/<annotation.*<\/annotation>/g, '')
);
edgeData.label = renderKatex(edge.text.replace(common.lineBreakRegex, '\n')), getConfig();
if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';

View File

@ -1,12 +1,11 @@
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { select, curveLinear, selectAll } from 'd3';
import katex from 'katex';
import { getConfig } from '../../config.js';
import { render as Render } from 'dagre-d3-es';
import { applyStyle } from 'dagre-d3-es/src/dagre-js/util.js';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { log } from '../../logger.js';
import common, { evaluate } from '../common/common.js';
import common, { evaluate, renderKatex } from '../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import flowChartShapes from './flowChartShapes.js';
@ -58,14 +57,12 @@ export const addVertices = function (vert, g, svgId, root, _doc, diagObj) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = {
label: vertexText
.replace(/fa[blrs]?:fa-[\w-]+/g, (s) => `<i class='${s.replace(':', ' ')}'></i>`)
.replace(/\$\$(.*)\$\$/g, (r, c) =>
katex
.renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
.replace(/\n/g, ' ')
.replace(/<annotation.*<\/annotation>/g, '')
),
label: renderKatex(
vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
)
),
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
@ -244,14 +241,9 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.labelType = 'html';
edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
edgeData.labelStyle
}">${edge.text
.replace(/fa[blrs]?:fa-[\w-]+/g, (s) => `<i class='${s.replace(':', ' ')}'></i>`)
.replace(/\$\$(.*)\$\$/g, (r, c) =>
katex
.renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
.replace(/\n/g, ' ')
.replace(/<annotation.*<\/annotation>/g, '')
)}</span>`;
}">${renderKatex(
edge.text.replace(/fa[blrs]?:fa-[\w-]+/g, (s) => `<i class='${s.replace(':', ' ')}'></i>`)
)}</span>`;
} else {
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');