mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-09 00:59:37 +02:00
feat(katex): added common functions for aiding in KaTeX rendering
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -34,6 +34,7 @@ export interface MermaidConfig {
|
|||||||
dompurifyConfig?: DOMPurify.Config;
|
dompurifyConfig?: DOMPurify.Config;
|
||||||
wrap?: boolean;
|
wrap?: boolean;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
|
legacyMathML?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: More configs needs to be moved in here
|
// TODO: More configs needs to be moved in here
|
||||||
|
@@ -131,6 +131,17 @@ const config: Partial<MermaidConfig> = {
|
|||||||
* Default value: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize']
|
* Default value: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize']
|
||||||
*/
|
*/
|
||||||
secure: ['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
|
* 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
|
* on a seed. If set to false, the IDs are generated based on the current date and thus are not
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
|
// @ts-ignore @types/katex does not work
|
||||||
|
import katex from 'katex';
|
||||||
import { MermaidConfig } from '../../config.type.js';
|
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 {
|
export default {
|
||||||
getRows,
|
getRows,
|
||||||
sanitizeText,
|
sanitizeText,
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
||||||
import { select, curveLinear, selectAll } from 'd3';
|
import { select, curveLinear, selectAll } from 'd3';
|
||||||
import katex from 'katex';
|
|
||||||
|
|
||||||
import flowDb from './flowDb.js';
|
import flowDb from './flowDb.js';
|
||||||
import { getConfig } from '../../config.js';
|
import { getConfig } from '../../config.js';
|
||||||
@@ -9,7 +8,7 @@ import utils from '../../utils.js';
|
|||||||
import { render } from '../../dagre-wrapper/index.js';
|
import { render } from '../../dagre-wrapper/index.js';
|
||||||
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
|
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
|
||||||
import { log } from '../../logger.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 { interpolateToCurve, getStylesFromArray } from '../../utils.js';
|
||||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||||
|
|
||||||
@@ -144,12 +143,8 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
|
|||||||
default:
|
default:
|
||||||
_shape = 'rect';
|
_shape = 'rect';
|
||||||
}
|
}
|
||||||
const labelText = vertexText.replace(/\$\$(.*)\$\$/g, (r, c) =>
|
const labelText = renderKatex(vertexText, getConfig());
|
||||||
katex
|
|
||||||
.renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
|
|
||||||
.replace(/\n/g, ' ')
|
|
||||||
.replace(/<annotation.*<\/annotation>/g, '')
|
|
||||||
);
|
|
||||||
// Add the node
|
// Add the node
|
||||||
g.setNode(vertex.id, {
|
g.setNode(vertex.id, {
|
||||||
labelStyle: styles.labelStyle,
|
labelStyle: styles.labelStyle,
|
||||||
@@ -323,14 +318,7 @@ export const addEdges = function (edges, g, diagObj) {
|
|||||||
edgeData.labelpos = 'c';
|
edgeData.labelpos = 'c';
|
||||||
}
|
}
|
||||||
edgeData.labelType = edge.labelType;
|
edgeData.labelType = edge.labelType;
|
||||||
edgeData.label = edge.text
|
edgeData.label = renderKatex(edge.text.replace(common.lineBreakRegex, '\n')), getConfig();
|
||||||
.replace(common.lineBreakRegex, '\n')
|
|
||||||
.replace(/\$\$(.*)\$\$/g, (r, c) =>
|
|
||||||
katex
|
|
||||||
.renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
|
|
||||||
.replace(/\n/g, ' ')
|
|
||||||
.replace(/<annotation.*<\/annotation>/g, '')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (edge.style === undefined) {
|
if (edge.style === undefined) {
|
||||||
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
|
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
||||||
import { select, curveLinear, selectAll } from 'd3';
|
import { select, curveLinear, selectAll } from 'd3';
|
||||||
import katex from 'katex';
|
|
||||||
import { getConfig } from '../../config.js';
|
import { getConfig } from '../../config.js';
|
||||||
import { render as Render } from 'dagre-d3-es';
|
import { render as Render } from 'dagre-d3-es';
|
||||||
import { applyStyle } from 'dagre-d3-es/src/dagre-js/util.js';
|
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 { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
|
||||||
import { log } from '../../logger.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 { interpolateToCurve, getStylesFromArray } from '../../utils.js';
|
||||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||||
import flowChartShapes from './flowChartShapes.js';
|
import flowChartShapes from './flowChartShapes.js';
|
||||||
@@ -58,13 +57,11 @@ export const addVertices = function (vert, g, svgId, root, _doc, diagObj) {
|
|||||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||||
const node = {
|
const node = {
|
||||||
label: vertexText
|
label: renderKatex(
|
||||||
.replace(/fa[blrs]?:fa-[\w-]+/g, (s) => `<i class='${s.replace(':', ' ')}'></i>`)
|
vertexText.replace(
|
||||||
.replace(/\$\$(.*)\$\$/g, (r, c) =>
|
/fa[blrs]?:fa-[\w-]+/g,
|
||||||
katex
|
(s) => `<i class='${s.replace(':', ' ')}'></i>`
|
||||||
.renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
|
)
|
||||||
.replace(/\n/g, ' ')
|
|
||||||
.replace(/<annotation.*<\/annotation>/g, '')
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
vertexNode = addHtmlLabel(svg, node).node();
|
vertexNode = addHtmlLabel(svg, node).node();
|
||||||
@@ -244,13 +241,8 @@ export const addEdges = function (edges, g, diagObj) {
|
|||||||
edgeData.labelType = 'html';
|
edgeData.labelType = 'html';
|
||||||
edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
|
edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
|
||||||
edgeData.labelStyle
|
edgeData.labelStyle
|
||||||
}">${edge.text
|
}">${renderKatex(
|
||||||
.replace(/fa[blrs]?:fa-[\w-]+/g, (s) => `<i class='${s.replace(':', ' ')}'></i>`)
|
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>`;
|
)}</span>`;
|
||||||
} else {
|
} else {
|
||||||
edgeData.labelType = 'text';
|
edgeData.labelType = 'text';
|
||||||
|
Reference in New Issue
Block a user