starlight-cannoli-plugins 2.11.0 → 2.12.1
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/README.md
CHANGED
|
@@ -144,7 +144,7 @@ dvisvgm --version
|
|
|
144
144
|
- `svgOutputDir` (required): Directory where compiled SVG files are written. Must be inside `public/` so Astro serves them as static assets.
|
|
145
145
|
- `removeOrphanedSvgs` (optional, default: `false`): When `true`, SVG files that are no longer referenced by any `tex compile` block are deleted automatically. In dev mode, stale SVGs are removed immediately when a block is edited. On build, any remaining orphans are swept at the end.
|
|
146
146
|
- `texInputDirs` (optional): Directories added to the TeX input search path (`TEXINPUTS`), allowing `\input{}` and `\include{}` to resolve files from your project. Use a trailing `/` to search only that directory, or `//` to search it recursively. Multiple directories are supported.
|
|
147
|
-
- `tempOutputDir` (optional): When set, a JPEG copy of each compiled diagram is written to this directory, mirroring the folder structure of `src/content/docs/`. The filename format is `<originating-file>--<
|
|
147
|
+
- `tempOutputDir` (optional): When set, a JPEG copy of each compiled diagram is written to this directory, mirroring the folder structure of `src/content/docs/`. Only blocks that carry a `blockid=<n>` meta tag produce a JPEG — blocks without it are ignored. The filename format is `<originating-file>--<blockid>--<hash>.jpg` (e.g. `tex-test.md--5--abc123.jpg`). JPEGs are deleted automatically when their block is removed, its `blockid` changes, or its content changes. SVG output is unaffected — the JPEG is complementary and intended for local inspection.
|
|
148
148
|
|
|
149
149
|
```ts
|
|
150
150
|
astroLatexCompile({
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/plugins/astro-latex-compile/index.ts
|
|
2
|
-
import { existsSync as existsSync2 } from "fs";
|
|
3
2
|
import { rm } from "fs/promises";
|
|
4
3
|
import { join as join2, resolve as resolve2 } from "path";
|
|
5
4
|
import { visit, SKIP } from "unist-util-visit";
|
|
@@ -190,13 +189,13 @@ function execProcess(command, args, options) {
|
|
|
190
189
|
|
|
191
190
|
// src/plugins/astro-latex-compile/utils.ts
|
|
192
191
|
var CONTENT_ROOT = "src/content/docs/";
|
|
193
|
-
function computeJpgPath(tempOutputDir, filePath,
|
|
192
|
+
function computeJpgPath(tempOutputDir, filePath, blockId) {
|
|
194
193
|
const normalized = filePath.replace(/\\/g, "/");
|
|
195
194
|
const idx = normalized.indexOf(CONTENT_ROOT);
|
|
196
195
|
const relativePath = idx !== -1 ? normalized.slice(idx + CONTENT_ROOT.length) : basename(normalized);
|
|
197
196
|
const dir = dirname(relativePath);
|
|
198
197
|
const filename = basename(relativePath);
|
|
199
|
-
const jpgFilename = `${filename}--${
|
|
198
|
+
const jpgFilename = `${filename}--${blockId}.jpg`;
|
|
200
199
|
const base = resolve(tempOutputDir);
|
|
201
200
|
return dir === "." ? join(base, jpgFilename) : join(base, dir, jpgFilename);
|
|
202
201
|
}
|
|
@@ -204,6 +203,57 @@ async function writeJpgFromSvg(svgPath, jpgPath) {
|
|
|
204
203
|
mkdirSync(dirname(jpgPath), { recursive: true });
|
|
205
204
|
await sharp(svgPath).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
206
205
|
}
|
|
206
|
+
function stripAnsi(text) {
|
|
207
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
208
|
+
}
|
|
209
|
+
function escapeXml(text) {
|
|
210
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
211
|
+
}
|
|
212
|
+
function wrapText(text, maxChars) {
|
|
213
|
+
const lines = [];
|
|
214
|
+
for (const raw of text.split("\n")) {
|
|
215
|
+
if (raw.length <= maxChars) {
|
|
216
|
+
lines.push(raw);
|
|
217
|
+
} else {
|
|
218
|
+
let remaining = raw;
|
|
219
|
+
while (remaining.length > maxChars) {
|
|
220
|
+
lines.push(remaining.slice(0, maxChars));
|
|
221
|
+
remaining = remaining.slice(maxChars);
|
|
222
|
+
}
|
|
223
|
+
if (remaining) lines.push(remaining);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return lines;
|
|
227
|
+
}
|
|
228
|
+
async function writeJpgError(jpgPath, header, errorText) {
|
|
229
|
+
const MAX_LINES = 40;
|
|
230
|
+
const clean = stripAnsi(errorText);
|
|
231
|
+
const sourceIdx = clean.indexOf("LaTeX source:");
|
|
232
|
+
const summary = (sourceIdx !== -1 ? clean.slice(0, sourceIdx) : clean).trimEnd();
|
|
233
|
+
let lines = wrapText(summary, 90);
|
|
234
|
+
if (lines.length > MAX_LINES) {
|
|
235
|
+
lines = lines.slice(0, MAX_LINES);
|
|
236
|
+
lines.push("...");
|
|
237
|
+
}
|
|
238
|
+
const fontSize = 13;
|
|
239
|
+
const lineHeight = 18;
|
|
240
|
+
const padding = 20;
|
|
241
|
+
const headerHeight = 55;
|
|
242
|
+
const width = 800;
|
|
243
|
+
const height = padding * 2 + headerHeight + lines.length * lineHeight;
|
|
244
|
+
const textLines = lines.map(
|
|
245
|
+
(line, i) => `<text x="${padding}" y="${padding + headerHeight + i * lineHeight}" font-family="monospace" font-size="${fontSize}" fill="#333333">${escapeXml(line)}</text>`
|
|
246
|
+
).join("\n ");
|
|
247
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
248
|
+
<rect width="${width}" height="${height}" fill="white"/>
|
|
249
|
+
<rect x="0" y="0" width="${width}" height="4" fill="#cc0000"/>
|
|
250
|
+
<text x="${padding}" y="${padding + 20}" font-family="monospace" font-size="16" font-weight="bold" fill="#cc0000">LaTeX Compilation Error</text>
|
|
251
|
+
<text x="${padding}" y="${padding + 40}" font-family="monospace" font-size="12" fill="#666666">${escapeXml(header)}</text>
|
|
252
|
+
${textLines}
|
|
253
|
+
</svg>`;
|
|
254
|
+
mkdirSync(dirname(jpgPath), { recursive: true });
|
|
255
|
+
await sharp(Buffer.from(svg)).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
256
|
+
}
|
|
207
257
|
function hashLatexCode(code) {
|
|
208
258
|
const normalized = code.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("%")).filter(Boolean).join("\n").trim();
|
|
209
259
|
return createHash("md5").update(normalized).digest("hex").slice(0, 16);
|
|
@@ -315,9 +365,9 @@ function remarkLatexCompile(options) {
|
|
|
315
365
|
const filePath = file.path || "unknown";
|
|
316
366
|
const results = await Promise.all(
|
|
317
367
|
nodes.map(async ({ node, index, parent }) => {
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
const jpgPath = options.tempOutputDir &&
|
|
368
|
+
const lineNumberStr = node.position?.start.line ?? "?";
|
|
369
|
+
const blockId = new MetaOptions(node.meta ?? "").getInteger("blockid");
|
|
370
|
+
const jpgPath = options.tempOutputDir && blockId !== void 0 ? computeJpgPath(options.tempOutputDir, filePath, blockId) : null;
|
|
321
371
|
try {
|
|
322
372
|
const result = await compileLatexToSvg(
|
|
323
373
|
node.value,
|
|
@@ -330,7 +380,7 @@ function remarkLatexCompile(options) {
|
|
|
330
380
|
);
|
|
331
381
|
}
|
|
332
382
|
options._referencedHashes?.add(result.hash);
|
|
333
|
-
if (jpgPath
|
|
383
|
+
if (jpgPath) {
|
|
334
384
|
await writeJpgFromSvg(result.svgPath, jpgPath);
|
|
335
385
|
console.log(
|
|
336
386
|
`[remark-latex-compile] ${filePath}:${lineNumberStr}: wrote ${jpgPath}`
|
|
@@ -353,7 +403,11 @@ function remarkLatexCompile(options) {
|
|
|
353
403
|
${details}`
|
|
354
404
|
);
|
|
355
405
|
if (jpgPath) {
|
|
356
|
-
await
|
|
406
|
+
await writeJpgError(
|
|
407
|
+
jpgPath,
|
|
408
|
+
`${filePath}:${lineNumberStr}`,
|
|
409
|
+
errorMsg
|
|
410
|
+
);
|
|
357
411
|
}
|
|
358
412
|
return {
|
|
359
413
|
index,
|
|
@@ -361,7 +415,7 @@ ${details}`
|
|
|
361
415
|
result: null,
|
|
362
416
|
error: err,
|
|
363
417
|
hash: null,
|
|
364
|
-
jpgPath
|
|
418
|
+
jpgPath
|
|
365
419
|
};
|
|
366
420
|
}
|
|
367
421
|
})
|
package/dist/index.js
CHANGED
|
@@ -31,11 +31,11 @@ interface RemarkLatexCompileOptions {
|
|
|
31
31
|
texInputDirs?: string[];
|
|
32
32
|
/**
|
|
33
33
|
* When set, a JPEG copy of each compiled diagram is written here, mirroring
|
|
34
|
-
* the folder structure of `src/content/docs/`.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
34
|
+
* the folder structure of `src/content/docs/`. Only blocks that carry a
|
|
35
|
+
* `blockid=<n>` meta tag produce a JPEG. Filename format:
|
|
36
|
+
* `<originating-file>--<blockid>--<hash>.jpg`.
|
|
37
|
+
* JPEGs are deleted automatically when their block is removed or its content
|
|
38
|
+
* changes. Intended for local inspection, not for publishing.
|
|
39
39
|
*/
|
|
40
40
|
tempOutputDir?: string;
|
|
41
41
|
/**
|
package/package.json
CHANGED