From f8a4488050781a53eb147034af9ac1dc1266746f Mon Sep 17 00:00:00 2001 From: NicolasNewman Date: Sat, 6 May 2023 11:33:23 +0900 Subject: [PATCH] feat(katex): added common functions for aiding in KaTeX rendering --- docs/config/setup/modules/defaultConfig.md | 2 +- packages/mermaid/src/config.type.ts | 1 + packages/mermaid/src/defaultConfig.ts | 11 ++++ .../mermaid/src/diagrams/common/common.ts | 58 +++++++++++++++++++ .../src/diagrams/flowchart/flowRenderer-v2.js | 20 ++----- .../src/diagrams/flowchart/flowRenderer.js | 28 ++++----- 6 files changed, 85 insertions(+), 35 deletions(-) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index ad8f90248..4024b0bc9 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -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) --- diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 545fdbbfb..622abd734 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -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 diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 9c6d6f46e..afa2241a1 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -131,6 +131,17 @@ const config: Partial = { * 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 diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 3b72e8718..f3d0eac30 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -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) : `
${text}
`).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(//g, '') + ); + } + return text.replace(/\$\$(.*)\$\$/g, (r, c) => 'MathML is unsupported in this environment.'); +}; + export default { getRows, sanitizeText, diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index a0b37e1d9..9156f7ae6 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -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(//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(//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;'; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index 2dc64ffcf..a578cb856 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -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) => ``) - .replace(/\$\$(.*)\$\$/g, (r, c) => - katex - .renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' }) - .replace(/\n/g, ' ') - .replace(//g, '') - ), + label: renderKatex( + vertexText.replace( + /fa[blrs]?:fa-[\w-]+/g, + (s) => `` + ) + ), }; 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 = `${edge.text - .replace(/fa[blrs]?:fa-[\w-]+/g, (s) => ``) - .replace(/\$\$(.*)\$\$/g, (r, c) => - katex - .renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' }) - .replace(/\n/g, ' ') - .replace(//g, '') - )}`; + }">${renderKatex( + edge.text.replace(/fa[blrs]?:fa-[\w-]+/g, (s) => ``) + )}`; } else { edgeData.labelType = 'text'; edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');