Skip to content

Commit e81cb89

Browse files
committed
support latex/katex
1 parent 8a28c80 commit e81cb89

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

src/components/mdx/Excalidraw.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
22
import LZString from 'lz-string';
33
import { parse } from 'toml';
44
import configRaw from './excalidraw.config.toml?raw';
5+
import katex from 'katex';
6+
import 'katex/dist/katex.min.css';
7+
import katexStyles from 'katex/dist/katex.min.css?raw';
58

69
const config = parse(configRaw);
710

@@ -90,6 +93,19 @@ export function Excalidraw({
9093

9194
const textContent = await res.text();
9295
let json;
96+
// Match lines like "hash: $$formula$$" for LaTeX extraction
97+
const latexMap: Record<string, string> = {};
98+
const latexLines = textContent.match(/^[a-f0-9]{40}: \$\$.*?\$\$/gm);
99+
if (latexLines) {
100+
latexLines.forEach(line => {
101+
const colonIndex = line.indexOf(':');
102+
if (colonIndex > 0) {
103+
const id = line.slice(0, colonIndex).trim();
104+
const formula = line.slice(colonIndex + 1).trim().replace(/^\$\$/, '').replace(/\$\$$/, '');
105+
latexMap[id] = formula;
106+
}
107+
});
108+
}
93109

94110
// Try parsing as standard JSON first
95111
try {
@@ -104,6 +120,43 @@ export function Excalidraw({
104120
const decompressed = LZString.decompressFromBase64(compressed);
105121
if (decompressed) {
106122
json = JSON.parse(decompressed);
123+
124+
// Populate json.files with LaTeX renders if we found any
125+
if (Object.keys(latexMap).length > 0) {
126+
json.files = json.files || {};
127+
for (const [id, formula] of Object.entries(latexMap)) {
128+
// Find corresponding image element to get its intended size
129+
const el = json.elements?.find((e: any) => e.fileId === id && !e.isDeleted);
130+
if (!el) continue;
131+
132+
try {
133+
const html = katex.renderToString(formula, { displayMode: true, throwOnError: false });
134+
// Create a self-contained SVG with inlined KaTeX CSS
135+
const svgString = `
136+
<svg xmlns="http://www.w3.org/2000/svg" width="${el.width}" height="${el.height}">
137+
<foreignObject width="100%" height="100%">
138+
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; overflow: hidden;">
139+
<style>
140+
${katexStyles}
141+
.katex-display { margin: 0; }
142+
.katex { font-size: 1.1em; }
143+
</style>
144+
${html}
145+
</div>
146+
</foreignObject>
147+
</svg>`.trim();
148+
const dataURL = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}`;
149+
json.files[id] = {
150+
mimeType: "image/svg+xml",
151+
id,
152+
dataURL,
153+
created: Date.now()
154+
};
155+
} catch (err) {
156+
console.error("KaTeX rendering failed for ID:", id, err);
157+
}
158+
}
159+
}
107160
} else {
108161
throw new Error("Failed to decompress Excalidraw data");
109162
}

0 commit comments

Comments
 (0)