pi-studio 0.5.9 → 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/index.ts +50 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.10] — 2026-03-14
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Studio preview/PDF math normalization is now more robust for model-emitted `\(...\)` / `\[...\]` math, including malformed mixed delimiters like `$\(...\)$`, optional spacing around those mixed delimiters, and multiline display-math line-break formatting that previously leaked raw/broken `$$` output into preview.
|
|
11
|
+
|
|
7
12
|
## [0.5.9] — 2026-03-13
|
|
8
13
|
|
|
9
14
|
### Fixed
|
package/index.ts
CHANGED
|
@@ -991,13 +991,58 @@ async function fetchLatestNpmVersion(packageName: string, timeoutMs = UPDATE_CHE
|
|
|
991
991
|
}
|
|
992
992
|
}
|
|
993
993
|
|
|
994
|
+
function isLikelyMathExpression(expr: string): boolean {
|
|
995
|
+
const content = expr.trim();
|
|
996
|
+
if (content.length === 0) return false;
|
|
997
|
+
|
|
998
|
+
if (/\\[a-zA-Z]+/.test(content)) return true; // LaTeX commands like \frac, \alpha
|
|
999
|
+
if (/[0-9]/.test(content)) return true;
|
|
1000
|
+
if (/[=+\-*/^_<>≤≥±×÷]/u.test(content)) return true;
|
|
1001
|
+
if (/[{}]/.test(content)) return true;
|
|
1002
|
+
if (/[α-ωΑ-Ω]/u.test(content)) return true;
|
|
1003
|
+
if (/^[A-Za-z]$/.test(content)) return true; // single-variable forms like \(x\)
|
|
1004
|
+
|
|
1005
|
+
// Plain words (e.g. escaped markdown like \[not a link\]) are not math.
|
|
1006
|
+
if (/^[A-Za-z][A-Za-z\s'".,:;!?-]*[A-Za-z]$/.test(content)) return false;
|
|
1007
|
+
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function collapseDisplayMathContent(expr: string): string {
|
|
1012
|
+
let content = expr.trim();
|
|
1013
|
+
if (content.includes("\\\\") || content.includes("\n")) {
|
|
1014
|
+
content = content.replace(/\\\\\s*/g, " ");
|
|
1015
|
+
content = content.replace(/\s*\n\s*/g, " ");
|
|
1016
|
+
content = content.replace(/\s{2,}/g, " ").trim();
|
|
1017
|
+
}
|
|
1018
|
+
return content;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
994
1021
|
function normalizeMathDelimitersInSegment(markdown: string): string {
|
|
995
|
-
let normalized = markdown.replace(
|
|
1022
|
+
let normalized = markdown.replace(/\$\s*\\\(([\s\S]*?)\\\)\s*\$/g, (match, expr: string) => {
|
|
1023
|
+
if (!isLikelyMathExpression(expr)) return match;
|
|
1024
|
+
const content = expr.trim();
|
|
1025
|
+
return content.length > 0 ? `\\(${content}\\)` : "\\(\\)";
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
normalized = normalized.replace(/\$\s*\\\[\s*([\s\S]*?)\s*\\\]\s*\$/g, (match, expr: string) => {
|
|
1029
|
+
if (!isLikelyMathExpression(expr)) return match;
|
|
1030
|
+
const content = collapseDisplayMathContent(expr);
|
|
1031
|
+
return content.length > 0 ? `\\[${content}\\]` : "\\[\\]";
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
normalized = normalized.replace(/\\\[\s*([\s\S]*?)\s*\\\]/g, (match, expr: string) => {
|
|
1035
|
+
if (!isLikelyMathExpression(expr)) return `[${expr.trim()}]`;
|
|
1036
|
+
const content = collapseDisplayMathContent(expr);
|
|
1037
|
+
return content.length > 0 ? `\\[${content}\\]` : "\\[\\]";
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
normalized = normalized.replace(/\\\(([\s\S]*?)\\\)/g, (match, expr: string) => {
|
|
1041
|
+
if (!isLikelyMathExpression(expr)) return `(${expr})`;
|
|
996
1042
|
const content = expr.trim();
|
|
997
|
-
return content.length > 0 ?
|
|
1043
|
+
return content.length > 0 ? `\\(${content}\\)` : "\\(\\)";
|
|
998
1044
|
});
|
|
999
1045
|
|
|
1000
|
-
normalized = normalized.replace(/\\\(([\s\S]*?)\\\)/g, (_match, expr: string) => `$${expr}$`);
|
|
1001
1046
|
return normalized;
|
|
1002
1047
|
}
|
|
1003
1048
|
|
|
@@ -1216,7 +1261,7 @@ async function preprocessStudioMermaidForPdf(markdown: string, workDir: string):
|
|
|
1216
1261
|
|
|
1217
1262
|
async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string): Promise<string> {
|
|
1218
1263
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
1219
|
-
const inputFormat = isLatex ? "latex" : "markdown+tex_math_dollars+autolink_bare_uris-raw_html";
|
|
1264
|
+
const inputFormat = isLatex ? "latex" : "markdown+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris-raw_html";
|
|
1220
1265
|
const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none"];
|
|
1221
1266
|
if (resourcePath) {
|
|
1222
1267
|
args.push(`--resource-path=${resourcePath}`);
|
|
@@ -1288,7 +1333,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
1288
1333
|
const pdfEngine = process.env.PANDOC_PDF_ENGINE?.trim() || "xelatex";
|
|
1289
1334
|
const inputFormat = isLatex
|
|
1290
1335
|
? "latex"
|
|
1291
|
-
: "markdown+tex_math_dollars+autolink_bare_uris+superscript+subscript-raw_html";
|
|
1336
|
+
: "markdown+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris+superscript+subscript-raw_html";
|
|
1292
1337
|
const normalizedMarkdown = isLatex ? markdown : normalizeObsidianImages(normalizeMathDelimiters(markdown));
|
|
1293
1338
|
|
|
1294
1339
|
const tempDir = join(tmpdir(), `pi-studio-pdf-${Date.now()}-${randomUUID()}`);
|