import type { Editor, Element as CKElement, DocumentSelection, PositioningFunction } from 'ckeditor5'; import { BalloonPanelView, CKEditorError } from 'ckeditor5'; import type { KatexOptions, MathJax2, MathJax3 } from './typings-external.js'; export function getSelectedMathModelWidget( selection: DocumentSelection ): null | CKElement { const selectedElement = selection.getSelectedElement(); if ( selectedElement && ( selectedElement.is( 'element', 'mathtex-inline' ) || selectedElement.is( 'element', 'mathtex-display' ) ) ) { return selectedElement; } return null; } // Simple MathJax 3 version check export function isMathJaxVersion3( MathJax: unknown ): MathJax is MathJax3 { return ( MathJax != null && typeof MathJax == 'object' && 'version' in MathJax && typeof MathJax.version == 'string' && MathJax.version.split( '.' ).length === 3 && MathJax.version.split( '.' )[ 0 ] === '3' ); } // Simple MathJax 2 version check export function isMathJaxVersion2( MathJax: unknown ): MathJax is MathJax2 { return ( MathJax != null && typeof MathJax == 'object' && 'Hub' in MathJax ); } // Check if equation has delimiters. export function hasDelimiters( text: string ): RegExpMatchArray | null { return text.match( /^(\\\[.*?\\\]|\\\(.*?\\\))$/ ); } // Find delimiters count export function delimitersCounts( text: string ): number | undefined { return text.match( /(\\\[|\\\]|\\\(|\\\))/g )?.length; } // Extract delimiters and figure display mode for the model export function extractDelimiters( equation: string ): { equation: string; display: boolean; } { equation = equation.trim(); // Remove delimiters (e.g. \( \) or \[ \]) const hasInlineDelimiters = equation.includes( '\\(' ) && equation.includes( '\\)' ); const hasDisplayDelimiters = equation.includes( '\\[' ) && equation.includes( '\\]' ); if ( hasInlineDelimiters || hasDisplayDelimiters ) { equation = equation.substring( 2, equation.length - 2 ).trim(); } return { equation, display: hasDisplayDelimiters }; } export async function renderEquation( equation: string, element: HTMLElement, engine: | 'katex' | 'mathjax' | undefined | ( ( equation: string, element: HTMLElement, display: boolean, ) => void ) = 'katex', lazyLoad?: () => Promise, display = false, preview = false, previewUid = '', previewClassName: Array = [], katexRenderOptions: KatexOptions = {} ): Promise { if ( engine == 'mathjax' ) { if ( isMathJaxVersion3( MathJax ) ) { selectRenderMode( element, preview, previewUid, previewClassName, el => { renderMathJax3( equation, el, display, () => { if ( preview ) { el.style.visibility = 'visible'; } } ); } ); } else { selectRenderMode( element, preview, previewUid, previewClassName, el => { // Fixme: MathJax typesetting cause occasionally math processing error without asynchronous call window.setTimeout( () => { renderMathJax2( equation, el, display ); // Move and scale after rendering if ( preview && isMathJaxVersion2( MathJax ) ) { // eslint-disable-next-line new-cap MathJax.Hub.Queue( () => { el.style.visibility = 'visible'; } ); } } ); } ); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if ( engine === 'katex' && window.katex !== undefined ) { selectRenderMode( element, preview, previewUid, previewClassName, el => { if ( katex ) { katex.render( equation, el, { throwOnError: false, displayMode: display, ...katexRenderOptions } ); } if ( preview ) { el.style.visibility = 'visible'; } } ); } else if ( typeof engine === 'function' ) { engine( equation, element, display ); } else { if ( lazyLoad != null ) { try { window.CKEDITOR_MATH_LAZY_LOAD ??= lazyLoad(); element.innerHTML = equation; await window.CKEDITOR_MATH_LAZY_LOAD; await renderEquation( equation, element, engine, undefined, display, preview, previewUid, previewClassName, katexRenderOptions ); } catch ( err ) { element.innerHTML = equation; console.error( `math-tex-typesetting-lazy-load-failed: Lazy load failed: ${ String( err ) }` ); } } else { element.innerHTML = equation; console.warn( `math-tex-typesetting-missing: Missing the mathematical typesetting engine (${ String( engine ) }) for tex.` ); } } } export function getBalloonPositionData( editor: Editor ): { target: Range | HTMLElement; positions: Array; } { const view = editor.editing.view; const defaultPositions = BalloonPanelView.defaultPositions; const selectedElement = view.document.selection.getSelectedElement(); if ( selectedElement ) { return { target: view.domConverter.viewToDom( selectedElement ), positions: [ defaultPositions.southArrowNorth, defaultPositions.southArrowNorthWest, defaultPositions.southArrowNorthEast ] }; } else { const viewDocument = view.document; const firstRange = viewDocument.selection.getFirstRange(); if ( !firstRange ) { /** * Missing first range. * @error math-missing-range */ throw new CKEditorError( 'math-missing-range' ); } return { target: view.domConverter.viewRangeToDom( firstRange ), positions: [ defaultPositions.southArrowNorth, defaultPositions.southArrowNorthWest, defaultPositions.southArrowNorthEast ] }; } } function selectRenderMode( element: HTMLElement, preview: boolean, previewUid: string, previewClassName: Array, cb: ( previewEl: HTMLElement ) => void ) { if ( preview ) { createPreviewElement( element, previewUid, previewClassName, previewEl => { cb( previewEl ); } ); } else { cb( element ); } } function renderMathJax3( equation: string, element: HTMLElement, display: boolean, cb: () => void ) { let promiseFunction: undefined | ( ( input: string, options: { display: boolean } ) => Promise ) = undefined; if ( !isMathJaxVersion3( MathJax ) ) { return; } if ( MathJax.tex2chtmlPromise ) { promiseFunction = MathJax.tex2chtmlPromise; } else if ( MathJax.tex2svgPromise ) { promiseFunction = MathJax.tex2svgPromise; } if ( promiseFunction != null ) { void promiseFunction( equation, { display } ).then( ( node: Element ) => { if ( element.firstChild ) { element.removeChild( element.firstChild ); } element.appendChild( node ); cb(); } ); } } function renderMathJax2( equation: string, element: HTMLElement, display?: boolean ) { if ( isMathJaxVersion2( MathJax ) ) { if ( display ) { element.innerHTML = '\\[' + equation + '\\]'; } else { element.innerHTML = '\\(' + equation + '\\)'; } // eslint-disable-next-line MathJax.Hub.Queue(['Typeset', MathJax.Hub, element]); } } function createPreviewElement( element: HTMLElement, previewUid: string, previewClassName: Array, render: ( previewEl: HTMLElement ) => void ): void { const previewEl = getPreviewElement( element, previewUid, previewClassName ); render( previewEl ); } function getPreviewElement( element: HTMLElement, previewUid: string, previewClassName: Array ) { let previewEl = document.getElementById( previewUid ); // Create if not found if ( !previewEl ) { previewEl = document.createElement( 'div' ); previewEl.setAttribute( 'id', previewUid ); previewEl.classList.add( ...previewClassName ); previewEl.style.visibility = 'hidden'; element.appendChild( previewEl ); } return previewEl; }