pi-studio 0.9.46 → 0.9.48

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.
@@ -0,0 +1,122 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ const STUDIO_LATEX_LOCAL_STYLE_MAX_BYTES = 2_000_000;
5
+ const STUDIO_LATEX_PACKAGE_PATTERN = /\\(usepackage|RequirePackage)(\s*(?:\[[^\]]*\]\s*)?)\{([^{}]+)\}/g;
6
+ const STUDIO_LATEX_PACKAGE_NAME_PATTERN = /^[A-Za-z0-9._/+:-]+$/;
7
+
8
+ function stripStudioLatexStyleComments(source) {
9
+ return String(source || "")
10
+ .replace(/\r\n/g, "\n")
11
+ .split("\n")
12
+ .map((line) => {
13
+ let out = "";
14
+ let backslashRun = 0;
15
+ for (const ch of line) {
16
+ if (ch === "%" && backslashRun % 2 === 0) break;
17
+ out += ch;
18
+ if (ch === "\\") backslashRun += 1;
19
+ else backslashRun = 0;
20
+ }
21
+ return out;
22
+ })
23
+ .join("\n");
24
+ }
25
+
26
+ export function doesStudioLatexStyleOverrideDocumentStartup(source) {
27
+ const uncommented = stripStudioLatexStyleComments(source);
28
+ return [
29
+ /\\(?:renewcommand|newcommand|providecommand)\s*\*?\s*(?:\{\s*\\document\s*\}|\\document\b)/,
30
+ /\\(?:def|gdef|edef|xdef)\s*\\document(?:\s|#|\{)/,
31
+ /\\let\s*\\document\s*(?:=\s*)?\\?[A-Za-z@]+/,
32
+ /\\(?:renewenvironment|newenvironment)\s*\*?\s*\{\s*document\s*\}/,
33
+ ].some((pattern) => pattern.test(uncommented));
34
+ }
35
+
36
+ function resolveStudioLocalLatexStyle(packageName, baseDir) {
37
+ const normalizedName = String(packageName || "").trim();
38
+ if (!normalizedName || !STUDIO_LATEX_PACKAGE_NAME_PATTERN.test(normalizedName)) return null;
39
+ const relativeStylePath = normalizedName.toLowerCase().endsWith(".sty")
40
+ ? normalizedName
41
+ : `${normalizedName}.sty`;
42
+ const stylePath = resolve(baseDir, relativeStylePath);
43
+ try {
44
+ const info = statSync(stylePath);
45
+ if (!info.isFile() || info.size > STUDIO_LATEX_LOCAL_STYLE_MAX_BYTES) return null;
46
+ return stylePath;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function isStudioPandocIncompatibleLocalStyle(packageName, baseDir) {
53
+ const stylePath = resolveStudioLocalLatexStyle(packageName, baseDir);
54
+ if (!stylePath) return null;
55
+ try {
56
+ const styleSource = readFileSync(stylePath, "utf8");
57
+ return doesStudioLatexStyleOverrideDocumentStartup(styleSource) ? stylePath : null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ function isStudioLatexSourceOffsetCommented(source, offset) {
64
+ const lineStart = source.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
65
+ let backslashRun = 0;
66
+ for (let index = lineStart; index < offset; index += 1) {
67
+ const ch = source[index];
68
+ if (ch === "%" && backslashRun % 2 === 0) return true;
69
+ if (ch === "\\") backslashRun += 1;
70
+ else backslashRun = 0;
71
+ }
72
+ return false;
73
+ }
74
+
75
+ function findStudioLatexDocumentStart(source) {
76
+ const pattern = /\\begin\s*\{document\}/g;
77
+ for (;;) {
78
+ const match = pattern.exec(source);
79
+ if (!match) return source.length;
80
+ if (!isStudioLatexSourceOffsetCommented(source, match.index)) return match.index;
81
+ }
82
+ }
83
+
84
+ export function prepareStudioLatexForPandoc(source, baseDir) {
85
+ const input = String(source || "");
86
+ const normalizedBaseDir = typeof baseDir === "string" ? baseDir.trim() : "";
87
+ if (!input || !normalizedBaseDir) {
88
+ return { source: input, omittedPackages: [] };
89
+ }
90
+
91
+ const omittedPackages = [];
92
+ const documentStart = findStudioLatexDocumentStart(input);
93
+ const preparedSource = input.replace(
94
+ STUDIO_LATEX_PACKAGE_PATTERN,
95
+ (match, command, optionText, packageList, offset) => {
96
+ if (offset >= documentStart || isStudioLatexSourceOffsetCommented(input, offset)) return match;
97
+ const packageNames = String(packageList || "")
98
+ .split(",")
99
+ .map((name) => name.trim())
100
+ .filter(Boolean);
101
+ if (packageNames.length === 0) return match;
102
+
103
+ const retainedPackages = [];
104
+ for (const packageName of packageNames) {
105
+ const stylePath = isStudioPandocIncompatibleLocalStyle(packageName, normalizedBaseDir);
106
+ if (!stylePath) {
107
+ retainedPackages.push(packageName);
108
+ continue;
109
+ }
110
+ if (!omittedPackages.some((entry) => entry.name === packageName && entry.path === stylePath)) {
111
+ omittedPackages.push({ name: packageName, path: stylePath });
112
+ }
113
+ }
114
+
115
+ if (retainedPackages.length === packageNames.length) return match;
116
+ if (retainedPackages.length === 0) return "\\relax{}";
117
+ return `\\${command}${optionText || ""}{${retainedPackages.join(",")}}`;
118
+ },
119
+ );
120
+
121
+ return { source: preparedSource, omittedPackages };
122
+ }
@@ -0,0 +1,34 @@
1
+ export function createStudioPandocHtmlResourceFlagResolver(probeHelp) {
2
+ if (typeof probeHelp !== "function") {
3
+ throw new TypeError("A Pandoc capability probe function is required.");
4
+ }
5
+
6
+ const cache = new Map();
7
+
8
+ async function getResourceFlag(pandocCommand) {
9
+ const command = String(pandocCommand || "pandoc");
10
+ let cached = cache.get(command);
11
+ if (!cached) {
12
+ cached = Promise.resolve()
13
+ .then(() => probeHelp(command))
14
+ .then((helpText) => String(helpText || "").includes("--embed-resources")
15
+ ? "--embed-resources"
16
+ : "--self-contained");
17
+ cache.set(command, cached);
18
+ void cached.catch(() => {
19
+ if (cache.get(command) === cached) cache.delete(command);
20
+ });
21
+ }
22
+ return cached;
23
+ }
24
+
25
+ return async function resolveStudioPandocHtmlResourceFlag(pandocCommand) {
26
+ try {
27
+ return await getResourceFlag(pandocCommand);
28
+ } catch {
29
+ // Old and current Pandoc versions accept --self-contained. The actual
30
+ // render can now succeed or report the executable's useful error.
31
+ return "--self-contained";
32
+ }
33
+ };
34
+ }