Files
openmaic/OpenMAIC/scripts/generate-video-export-katex.mjs
2026-08-16 14:58:47 +08:00

67 lines
2.9 KiB
JavaScript

/** Generate the offline KaTeX CSS/license module used by Quiz video export. */
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import prettier from 'prettier';
const require = createRequire(import.meta.url);
const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(here, '..');
const katexDir = path.dirname(require.resolve('katex/package.json'));
const katexVersion = require('katex/package.json').version;
const sourceCss = readFileSync(path.join(katexDir, 'dist', 'katex.min.css'), 'utf8');
const license = readFileSync(path.join(katexDir, 'LICENSE'), 'utf8');
const publicFontDir = path.join(root, 'public', 'vendor', 'video-export', 'fonts');
const publicFontBase = '/vendor/video-export/fonts';
const exportFontBase = 'assets/fonts';
mkdirSync(publicFontDir, { recursive: true });
const faces = [];
const assets = [];
for (const [, body] of sourceCss.matchAll(/@font-face\{([^}]*)\}/g)) {
const woff2 = body.match(/url\(fonts\/([^)]+\.woff2)\)/);
if (!woff2) continue;
const filename = woff2[1];
copyFileSync(path.join(katexDir, 'dist', 'fonts', filename), path.join(publicFontDir, filename));
const declaration = (pattern) => body.match(pattern)?.[0] ?? '';
faces.push(
`@font-face{${declaration(/font-family:[^;]+/)};${declaration(/font-style:[^;]+/)};${declaration(/font-weight:[^;]+/)};font-display:block;src:url("__OPENMAIC_QUIZ_FONT_BASE__/${filename}") format("woff2")}`,
);
assets.push({
path: `${exportFontBase}/${filename}`,
sourceUrl: `${publicFontBase}/${filename}`,
});
}
if (faces.length !== 20) {
throw new Error(`Expected 20 KaTeX WOFF2 faces, found ${faces.length}`);
}
const rules = sourceCss.replace(/@font-face\{[^}]*\}/g, '');
const cssTemplate = `${faces.join('')}\n${rules}`;
const contents = `/**
* GENERATED FILE — do not edit by hand.
* Complete KaTeX ${katexVersion} CSS with all 20 WOFF2 fonts emitted as
* deterministic project-relative binary assets.
* Regenerate with \`pnpm gen:video-export-katex\`.
*/
const KATEX_CSS_TEMPLATE = ${JSON.stringify(cssTemplate)};
export const KATEX_MEASUREMENT_CSS = KATEX_CSS_TEMPLATE.replaceAll(
'__OPENMAIC_QUIZ_FONT_BASE__',
${JSON.stringify(publicFontBase)},
);
export const KATEX_EXPORT_CSS = KATEX_CSS_TEMPLATE.replaceAll(
'__OPENMAIC_QUIZ_FONT_BASE__',
${JSON.stringify(exportFontBase)},
);
export const KATEX_FONT_ASSETS = ${JSON.stringify(assets)} as const;
export const KATEX_MIT_LICENSE = ${JSON.stringify(license)};
`;
const outFile = path.join(root, 'lib', 'video-export', 'emit-hyperframes', 'katex-assets.ts');
const config = await prettier.resolveConfig(outFile);
writeFileSync(outFile, await prettier.format(contents, { ...config, parser: 'typescript' }));
console.log(
`[video-export-katex] emitted ${faces.length} KaTeX ${katexVersion} WOFF2 assets and complete CSS`,
);