diff --git a/src/extras/math-preview-panel.ts b/src/extras/math-preview-panel.ts index 864454071..6e9e6dde0 100644 --- a/src/extras/math-preview-panel.ts +++ b/src/extras/math-preview-panel.ts @@ -192,7 +192,7 @@ async function update(ev?: UpdateEvent) { if (vscode.workspace.getConfiguration('latex-workshop').get('mathpreviewpanel.cursor.enabled', false)) { await renderCursor(document, texMath) } - const result = await lw.preview.math.generateSVG(texMath, cachedMacros).catch(() => undefined) + const result = await lw.preview.math.tex2svg(texMath, cachedMacros).catch(() => undefined) if (!result) { return } diff --git a/src/preview/math.ts b/src/preview/math.ts index 9aa008227..25d335a15 100644 --- a/src/preview/math.ts +++ b/src/preview/math.ts @@ -5,13 +5,12 @@ import type { SupportedExtension } from 'mathjax-full' import type { IMathJaxWorker } from './math/mathjax' import { lw } from '../lw' import type { ReferenceItem, TeXMathEnv } from '../types' -import * as utils from '../utils/svg' import { getCurrentThemeLightness } from '../utils/theme' +import { stripComments } from '../utils/utils' import { renderCursor as renderCursorWorker } from './math/mathpreviewlib/cursorrenderer' -import { type ITextDocumentLike, TextDocumentLike } from './math/mathpreviewlib/textdocumentlike' +import { type ITextDocumentLike } from './math/mathpreviewlib/textdocumentlike' import { findMacros } from './math/mathpreviewlib/newcommandfinder' import { TeXMathEnvFinder } from './math/mathpreviewlib/texmathenvfinder' -import { MathPreviewUtils } from './math/mathpreviewlib/mathpreviewutils' const logger = lw.log('Preview', 'Math') @@ -19,13 +18,11 @@ export const math = { refreshMathColor, onRef, onTeX, - findRef, findTeX, findMath, - generateSVG, ref2svg, - renderCursor, - typeset + tex2svg, + renderCursor } const pool: workerpool.WorkerPool = workerpool.pool( @@ -51,13 +48,13 @@ async function onTeX(document: vscode.TextDocument, tex: TeXMathEnv, macros: str const configuration = vscode.workspace.getConfiguration('latex-workshop') const scale = configuration.get('hover.preview.scale') as number let s = await renderCursor(document, tex) - s = MathPreviewUtils.mathjaxify(s, tex.envname) - const typesetArg = macros + MathPreviewUtils.stripTeX(s, macros) + s = mathjaxify(s, tex.envname) + const typesetArg = macros + stripTeX(s, macros) const typesetOpts = { scale, color: foreColor } try { const xml = await typeset(typesetArg, typesetOpts) - const md = utils.svgToDataUrl(xml) - return new vscode.Hover(new vscode.MarkdownString(MathPreviewUtils.addDummyCodeBlock(`![equation](${md})`)), tex.range ) + const md = svg2DataUrl(xml) + return new vscode.Hover(new vscode.MarkdownString(addDummyCodeBlock(`![equation](${md})`)), tex.range ) } catch(e) { if (macros !== '') { logger.log(`Failed rendering MathJax ${typesetArg} . Try removing macro definitions.`) @@ -98,33 +95,39 @@ async function onRefMathJax(refData: ReferenceItem, ctoken?: vscode.Cancellation const link = vscode.Uri.parse('command:latex-workshop.synctexto').with({ query: JSON.stringify([line, refData.file]) }) const mdLink = new vscode.MarkdownString(`[View on pdf](${link})`) mdLink.isTrusted = true - return new vscode.Hover( [MathPreviewUtils.addDummyCodeBlock(`![equation](${md})`), mdLink], refData.math?.range ) + return new vscode.Hover( [addDummyCodeBlock(`![equation](${md})`), mdLink], refData.math?.range ) } async function ref2svg(refData: ReferenceItem, ctoken?: vscode.CancellationToken) { if (refData.math === undefined) { return '' } + const texMath = refData.math const configuration = vscode.workspace.getConfiguration('latex-workshop') - const scale = configuration.get('hover.preview.scale') as number const macros = await findMacros(ctoken) - let newTeXString: string + let texStr: string | undefined = undefined if (refData.prevIndex !== undefined && configuration.get('hover.ref.number.enabled') as boolean) { const tag = refData.prevIndex.refNumber - const texString = replaceLabelWithTag(refData.math.texString, refData.label, tag) - newTeXString = MathPreviewUtils.mathjaxify(texString, refData.math.envname, {stripLabel: false}) - } else { - newTeXString = MathPreviewUtils.mathjaxify(refData.math.texString, refData.math.envname) + const texString = replaceLabelWithTag(texMath.texString, refData.label, tag) + texStr = mathjaxify(texString, texMath.envname, {stripLabel: false}) } - const typesetArg = macros + MathPreviewUtils.stripTeX(newTeXString, macros) + const svg = await tex2svg(texMath, macros, texStr) + return svg.svgDataUrl +} + +async function tex2svg(tex: TeXMathEnv, macros?: string, texStr?: string) { + macros = macros ?? await findMacros() + const configuration = vscode.workspace.getConfiguration('latex-workshop') + const scale = configuration.get('hover.preview.scale') as number + texStr = texStr ?? mathjaxify(tex.texString, tex.envname) + texStr = macros + stripTeX(texStr, macros) try { - const xml = await lw.preview.math.typeset(typesetArg, { scale, color: foreColor }) - const svg = utils.svgToDataUrl(xml) - return svg + const xml = await typeset(texStr, {scale, color: foreColor}) + return { svgDataUrl: svg2DataUrl(xml), macros } } catch(e) { - logger.logError(`Failed rendering MathJax ${typesetArg} .`, e) + logger.logError(`Failed rendering MathJax ${texStr} .`, e) throw e } } @@ -163,15 +166,6 @@ function refNumberMessage(refData: Pick): string | u return } -async function generateSVG(tex: TeXMathEnv, macros?: string) { - macros = macros ?? await findMacros() - const configuration = vscode.workspace.getConfiguration('latex-workshop') - const scale = configuration.get('hover.preview.scale') as number - const s = MathPreviewUtils.mathjaxify(tex.texString, tex.envname) - const xml = await typeset(macros + MathPreviewUtils.stripTeX(s, macros), {scale, color: foreColor}) - return { svgDataUrl: utils.svgToDataUrl(xml), macros } -} - function refreshMathColor() { foreColor = getCurrentThemeLightness() === 'light' ? '#000000' : '#ffffff' } @@ -188,15 +182,51 @@ function findTeX(document: ITextDocumentLike, position: vscode.Position): TeXMat return TeXMathEnvFinder.findHoverOnTex(document, position) } -function findRef( - refData: Pick, - token: string -) { - const document = TextDocumentLike.load(refData.file) - const position = refData.position - return TeXMathEnvFinder.findHoverOnRef(document, position, refData, token) -} - function findMath(document: ITextDocumentLike, position: vscode.Position): TeXMathEnv | undefined { return TeXMathEnvFinder.findMathEnvIncludingPosition(document, position) } + +function addDummyCodeBlock(md: string): string { + // We need a dummy code block in hover to make the width of hover larger. + const dummyCodeBlock = '```\n```' + return dummyCodeBlock + '\n' + md + '\n' + dummyCodeBlock +} + +function stripTeX(tex: string, macros: string): string { + // First remove math env declaration + if (tex.startsWith('$$') && tex.endsWith('$$')) { + tex = tex.slice(2, tex.length - 2) + } else if (tex.startsWith('$') && tex.endsWith('$')) { + tex = tex.slice(1, tex.length - 1) + } else if (tex.startsWith('\\(') && tex.endsWith('\\)')) { + tex = tex.slice(2, tex.length - 2) + } else if (tex.startsWith('\\[') && tex.endsWith('\\]')) { + tex = tex.slice(2, tex.length - 2) + } + // Then remove the star variant of new macros + [...macros.matchAll(/\\newcommand\{(.*?)\}/g)].forEach(match => { + tex = tex.replaceAll(match[1] + '*', match[1]) + }) + return tex +} + +function mathjaxify(tex: string, envname: string, opt = { stripLabel: true }): string { + // remove TeX comments + let s = stripComments(tex) + // remove \label{...} + if (opt.stripLabel) { + s = s.replace(/\\label\{.*?\}/g, '') + } + if (envname.match(/^(aligned|alignedat|array|Bmatrix|bmatrix|cases|CD|gathered|matrix|pmatrix|smallmatrix|split|subarray|Vmatrix|vmatrix)$/)) { + s = '\\begin{equation}' + s + '\\end{equation}' + } + return s +} + +function svg2DataUrl(xml: string): string { + // We have to call encodeURIComponent and unescape because SVG can includes non-ASCII characters. + // We have to encode them before converting them to base64. + const svg64 = Buffer.from(unescape(encodeURIComponent(xml)), 'binary').toString('base64') + const b64Start = 'data:image/svg+xml;base64,' + return b64Start + svg64 +} diff --git a/src/preview/math/mathpreviewlib/mathpreviewutils.ts b/src/preview/math/mathpreviewlib/mathpreviewutils.ts deleted file mode 100644 index 84e2db0ad..000000000 --- a/src/preview/math/mathpreviewlib/mathpreviewutils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { stripComments } from '../../../utils/utils' - -export class MathPreviewUtils { - static addDummyCodeBlock(md: string): string { - // We need a dummy code block in hover to make the width of hover larger. - const dummyCodeBlock = '```\n```' - return dummyCodeBlock + '\n' + md + '\n' + dummyCodeBlock - } - - static stripTeX(tex: string, macros: string): string { - // First remove math env declaration - if (tex.startsWith('$$') && tex.endsWith('$$')) { - tex = tex.slice(2, tex.length - 2) - } else if (tex.startsWith('$') && tex.endsWith('$')) { - tex = tex.slice(1, tex.length - 1) - } else if (tex.startsWith('\\(') && tex.endsWith('\\)')) { - tex = tex.slice(2, tex.length - 2) - } else if (tex.startsWith('\\[') && tex.endsWith('\\]')) { - tex = tex.slice(2, tex.length - 2) - } - // Then remove the star variant of new macros - [...macros.matchAll(/\\newcommand\{(.*?)\}/g)].forEach(match => { - tex = tex.replaceAll(match[1] + '*', match[1]) - }) - return tex - } - - static mathjaxify(tex: string, envname: string, opt = { stripLabel: true }): string { - // remove TeX comments - let s = stripComments(tex) - // remove \label{...} - if (opt.stripLabel) { - s = s.replace(/\\label\{.*?\}/g, '') - } - if (envname.match(/^(aligned|alignedat|array|Bmatrix|bmatrix|cases|CD|gathered|matrix|pmatrix|smallmatrix|split|subarray|Vmatrix|vmatrix)$/)) { - s = '\\begin{equation}' + s + '\\end{equation}' - } - return s - } -} diff --git a/src/preview/math/mathpreviewlib/texmathenvfinder.ts b/src/preview/math/mathpreviewlib/texmathenvfinder.ts index 8b21b387e..31e5fd1b3 100644 --- a/src/preview/math/mathpreviewlib/texmathenvfinder.ts +++ b/src/preview/math/mathpreviewlib/texmathenvfinder.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode' -import type { ReferenceItem, TeXMathEnv } from '../../../types' +import type { TeXMathEnv } from '../../../types' import * as utils from '../../../utils/utils' -import { type ITextDocumentLike, TextDocumentLike } from './textdocumentlike' +import { type ITextDocumentLike } from './textdocumentlike' const ENV_NAMES = [ 'align', 'align\\*', 'alignat', 'alignat\\*', 'aligned', 'alignedat', 'array', 'Bmatrix', 'bmatrix', 'cases', 'CD', 'eqnarray', 'eqnarray\\*', 'equation', 'equation\\*', 'flalign', 'flalign\\*', 'gather', 'gather\\*', 'gathered', 'matrix', 'multline', 'multline\\*', 'pmatrix', 'smallmatrix', 'split', 'subarray', 'Vmatrix', 'vmatrix' @@ -27,36 +27,6 @@ export class TeXMathEnvFinder { return TeXMathEnvFinder.findHoverOnInline(document, position) } - static findHoverOnRef( - document: ITextDocumentLike, - position: vscode.Position, - refData: Pick, - token: string, - ): TeXMathEnv | undefined { - const limit = vscode.workspace.getConfiguration('latex-workshop').get('hover.preview.maxLines') as number - const docOfRef = TextDocumentLike.load(refData.file) - const envBeginPatMathMode = new RegExp(`\\\\begin\\{(${MATH_ENV_NAMES.join('|')})\\}`) - const l = docOfRef.lineAt(refData.position.line).text - const pat = new RegExp('\\\\label\\{' + utils.escapeRegExp(token) + '\\}') - const m = l.match(pat) - if (m && m.index !== undefined) { - const labelPos = new vscode.Position(refData.position.line, m.index) - const beginPos = TeXMathEnvFinder.findBeginPair(docOfRef, envBeginPatMathMode, labelPos, limit) - if (beginPos) { - const t = TeXMathEnvFinder.findHoverOnTex(docOfRef, beginPos) - if (t) { - const beginEndRange = t.range - const refRange = document.getWordRangeAtPosition(position, /\S+?\{.*?\}/) - if (refRange && beginEndRange.contains(labelPos)) { - t.range = refRange - return t - } - } - } - } - return - } - static findMathEnvIncludingPosition(document: ITextDocumentLike, position: vscode.Position): TeXMathEnv | undefined { const limit = vscode.workspace.getConfiguration('latex-workshop').get('hover.preview.maxLines') as number const envNamePatMathMode = new RegExp(`(${MATH_ENV_NAMES.join('|')})`) diff --git a/src/utils/svg.ts b/src/utils/svg.ts deleted file mode 100644 index 229da91e5..000000000 --- a/src/utils/svg.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function svgToDataUrl(xml: string): string { - // We have to call encodeURIComponent and unescape because SVG can includes non-ASCII characters. - // We have to encode them before converting them to base64. - const svg64 = Buffer.from(unescape(encodeURIComponent(xml)), 'binary').toString('base64') - const b64Start = 'data:image/svg+xml;base64,' - return b64Start + svg64 -}