@@ -328,3 +328,86 @@ export function checkPyodideRuntime(fileVersion: number): RuntimeInfo {
328328 }
329329 }
330330}
331+
332+ export function prepLatexForMarkdown ( latex : string ) : string {
333+ // Dictionary mapping MathLive's text macros/escapes to Pandoc-safe equivalents
334+ const replacements : Record < string , string > = {
335+ '\\textbraceleft' : '\\{{}' ,
336+ '\\textbraceright' : '\\}{}' ,
337+ '\\lbrack' : '[' ,
338+ '\\rbrack' : ']' ,
339+ '\\!' : '!' ,
340+ '\\%' : '\\%{}' ,
341+ '\\&' : '\\&{}' ,
342+ '\\textasciicircum' : '\\textasciicircum{}' ,
343+ '\\#' : '\\#{}' ,
344+ '\\$' : '\\${}' ,
345+ '\\textasteriskcentered' : '*' ,
346+ '\\textasciitilde' : '\\textasciitilde{}' ,
347+ '\\_' : '\\_{}' ,
348+ '\\textbackslash' : '\\textbackslash{}'
349+ } ;
350+
351+ // Escape special regex characters in keys and join them for a single-pass replace
352+ const regexStr = Object . keys ( replacements )
353+ . map ( key => key . replace ( / [ - [ \] { } ( ) * + ? . , \\ ^ $ | # \s ] / g, '\\$&' ) )
354+ . join ( '|' ) ;
355+
356+ const replacementRegex = new RegExp ( regexStr , 'g' ) ;
357+
358+ let result = '' ;
359+ let currentIndex = 0 ;
360+ const textCommand = '\\text{' ;
361+
362+ while ( currentIndex < latex . length ) {
363+ // 1. Find the next \text{ command
364+ const textCommandIdx = latex . indexOf ( textCommand , currentIndex ) ;
365+
366+ // If no more \text{ commands exist, append the rest of the string and exit
367+ if ( textCommandIdx === - 1 ) {
368+ result += latex . slice ( currentIndex ) ;
369+ break ;
370+ }
371+
372+ // Append everything before and including "\text{" to the result
373+ const contentBefore = latex . slice ( currentIndex , textCommandIdx + textCommand . length ) ;
374+ result += contentBefore ;
375+ currentIndex = textCommandIdx + textCommand . length ;
376+
377+ // 2. Find the matching closing brace '}' for this \text{ block
378+ let braceCount = 1 ;
379+ let searchIndex = currentIndex ;
380+
381+ while ( searchIndex < latex . length && braceCount > 0 ) {
382+ const char = latex [ searchIndex ] ;
383+ const prevChar = latex [ searchIndex - 1 ] ;
384+
385+ // Only count braces that are not escaped
386+ if ( char === '{' && prevChar !== '\\' ) {
387+ braceCount ++ ;
388+ } else if ( char === '}' && prevChar !== '\\' ) {
389+ braceCount -- ;
390+ }
391+ searchIndex ++ ;
392+ }
393+
394+ // 3. Extract the inner text, apply replacements, and append
395+ if ( braceCount === 0 ) {
396+ // Successfully found the bounds of the \text{} block
397+ const innerText = latex . slice ( currentIndex , searchIndex - 1 ) ;
398+
399+ const sanitizedText = innerText . replace ( replacementRegex , match => {
400+ return replacements [ match ] ;
401+ } ) ;
402+
403+ result += sanitizedText + '}' ;
404+ currentIndex = searchIndex ;
405+ } else {
406+ // Fallback: If braces are unbalanced for some reason, just append and proceed
407+ result += latex . slice ( currentIndex , searchIndex ) ;
408+ currentIndex = searchIndex ;
409+ }
410+ }
411+
412+ return result . trim ( ) ;
413+ }
0 commit comments