@cristianormazabal/triton-latex 0.1.13 → 0.1.15
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 +45 -0
- package/dist/cli.cjs +412 -36
- package/dist/cli.cjs.map +4 -4
- package/package.json +1 -1
- package/triton.sty +17 -2
package/README.md
CHANGED
|
@@ -104,6 +104,8 @@ by any character (here `|`) and an optional `\includegraphics` key list:
|
|
|
104
104
|
| `\tritonsetup{<opts>}` | Default `\includegraphics` options (default `width=\linewidth`). |
|
|
105
105
|
| `\tritoncli{<cmd>}` | CLI invocation (default `triton-latex`). |
|
|
106
106
|
| `\tritontheme{<name>}` | Theme preset passed to the CLI for inline renders. |
|
|
107
|
+
| `\tritonthemefile{<path>}` | Load an external `.triton-theme.json` by file path. |
|
|
108
|
+
| `\tritonthemesdir{<dir>}` | Scan a directory for `*.triton-theme.json` files. |
|
|
107
109
|
| `\tritonscale{<n>}` | Scale passed to the CLI (default `1`). |
|
|
108
110
|
| `\tritoncachedir{<dir>}` | Render cache directory (default `\jobname.triton-cache`). |
|
|
109
111
|
| `\triton[<opts>]{<name>}` | **Precompile fallback:** `\includegraphics` of `<dir>/<name>.pdf`. |
|
|
@@ -148,6 +150,49 @@ node dist/cli.cjs render diagram.mmd -o out.pdf --theme executive --scale 2
|
|
|
148
150
|
|
|
149
151
|
Installed as the `triton-latex` bin when the package is linked.
|
|
150
152
|
|
|
153
|
+
## External themes
|
|
154
|
+
|
|
155
|
+
Use a `.triton-theme.json` file to apply a custom colour palette and typography
|
|
156
|
+
to every inline diagram in your document.
|
|
157
|
+
|
|
158
|
+
### `.sty` macros
|
|
159
|
+
|
|
160
|
+
```latex
|
|
161
|
+
\tritonthemefile{.triton/themes/my-brand.triton-theme.json}
|
|
162
|
+
% — OR —
|
|
163
|
+
\tritonthemesdir{.triton/themes} % scan a directory, then use \tritontheme{name}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`\tritonthemefile{<path>}` passes `--theme-file <path>` to the CLI for every
|
|
167
|
+
inline `\begin{triton}` render in the document. `\tritonthemesdir{<dir>}` passes
|
|
168
|
+
`--themes-dir <dir>`; combine it with `\tritontheme{<name>}` to select a theme by
|
|
169
|
+
name from the discovered registry.
|
|
170
|
+
|
|
171
|
+
### CLI flags
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
# Load a specific theme file
|
|
175
|
+
node dist/cli.cjs render diagram.mmd -o out.pdf \
|
|
176
|
+
--theme-file .triton/themes/my-brand.triton-theme.json
|
|
177
|
+
|
|
178
|
+
# Scan a directory and select by name
|
|
179
|
+
node dist/cli.cjs render diagram.mmd -o out.pdf \
|
|
180
|
+
--themes-dir .triton/themes --theme my-brand
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### ⚠️ Cache caveat
|
|
184
|
+
|
|
185
|
+
The cache key includes the theme-file **path**, not its **content**. After editing
|
|
186
|
+
a `.triton-theme.json` in place, clear the cache before recompiling:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
latexmk -C # preferred
|
|
190
|
+
rm -r <jobname>.triton-cache # or manually
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
See [docs/external-themes.md](../docs/external-themes.md) for the full format
|
|
194
|
+
reference, field list, built-in preset names, and cross-host worked example.
|
|
195
|
+
|
|
151
196
|
## Overleaf / no-shell-escape fallback
|
|
152
197
|
|
|
153
198
|
Overleaf (and any compile with shell-escape disabled) can't run the CLI, so render
|
package/dist/cli.cjs
CHANGED
|
@@ -24,8 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_node_fs2 = require("node:fs");
|
|
28
|
+
var import_node_path3 = require("node:path");
|
|
29
|
+
var import_node_url = require("node:url");
|
|
29
30
|
|
|
30
31
|
// ../src/contracts/animations.ts
|
|
31
32
|
var CONNECTOR_ANIMATIONS = [
|
|
@@ -44097,6 +44098,211 @@ var svgRenderer = {
|
|
|
44097
44098
|
render: renderSVG
|
|
44098
44099
|
};
|
|
44099
44100
|
|
|
44101
|
+
// ../src/theme/validate.ts
|
|
44102
|
+
function isBuiltinThemeName(name) {
|
|
44103
|
+
return themePresetNames.includes(name);
|
|
44104
|
+
}
|
|
44105
|
+
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
44106
|
+
"name",
|
|
44107
|
+
"base",
|
|
44108
|
+
"palette",
|
|
44109
|
+
"typography",
|
|
44110
|
+
"spacing",
|
|
44111
|
+
"edges",
|
|
44112
|
+
"panel"
|
|
44113
|
+
]);
|
|
44114
|
+
var PALETTE_KEYS = /* @__PURE__ */ new Set([
|
|
44115
|
+
"primary",
|
|
44116
|
+
"secondary",
|
|
44117
|
+
"background",
|
|
44118
|
+
"surface",
|
|
44119
|
+
"border",
|
|
44120
|
+
"text",
|
|
44121
|
+
"textMuted",
|
|
44122
|
+
"success",
|
|
44123
|
+
"warning",
|
|
44124
|
+
"error"
|
|
44125
|
+
]);
|
|
44126
|
+
var TYPOGRAPHY_KEYS = /* @__PURE__ */ new Set([
|
|
44127
|
+
"fontFamily",
|
|
44128
|
+
"monoFamily",
|
|
44129
|
+
"baseFontSize",
|
|
44130
|
+
"titleFontSize",
|
|
44131
|
+
"smallFontSize",
|
|
44132
|
+
"lineHeight"
|
|
44133
|
+
]);
|
|
44134
|
+
var SPACING_KEYS = /* @__PURE__ */ new Set([
|
|
44135
|
+
"unit",
|
|
44136
|
+
"nodePadding",
|
|
44137
|
+
"nodeGap",
|
|
44138
|
+
"diagramMargin"
|
|
44139
|
+
]);
|
|
44140
|
+
var EDGES_KEYS = /* @__PURE__ */ new Set([
|
|
44141
|
+
"strokeWidth",
|
|
44142
|
+
"arrowSize",
|
|
44143
|
+
"labelFontSize",
|
|
44144
|
+
"curveTension"
|
|
44145
|
+
]);
|
|
44146
|
+
var PANEL_KEYS = /* @__PURE__ */ new Set([
|
|
44147
|
+
"titleAlign",
|
|
44148
|
+
"titlePosition",
|
|
44149
|
+
"titleChrome"
|
|
44150
|
+
]);
|
|
44151
|
+
var HEX_COLOR = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
44152
|
+
var SLUG = /^[a-z0-9-]+$/;
|
|
44153
|
+
var CSS_INJECTION = /url\s*\(|expression\s*\(/i;
|
|
44154
|
+
function isPlainObject(v) {
|
|
44155
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
44156
|
+
}
|
|
44157
|
+
function fail(message) {
|
|
44158
|
+
return err("THEME_VALIDATION_ERROR", message);
|
|
44159
|
+
}
|
|
44160
|
+
function validatePalette(obj) {
|
|
44161
|
+
if (!isPlainObject(obj)) return fail('"palette" must be a plain object');
|
|
44162
|
+
for (const key of Object.keys(obj)) {
|
|
44163
|
+
if (!PALETTE_KEYS.has(key)) return fail(`Unknown key in palette: "${key}"`);
|
|
44164
|
+
}
|
|
44165
|
+
for (const key of Object.keys(obj)) {
|
|
44166
|
+
const val = obj[key];
|
|
44167
|
+
if (typeof val !== "string" || !HEX_COLOR.test(val)) {
|
|
44168
|
+
return fail(`palette.${key} must be a CSS hex color (e.g. "#RGB" or "#RRGGBB"), got ${JSON.stringify(val)}`);
|
|
44169
|
+
}
|
|
44170
|
+
}
|
|
44171
|
+
return ok(obj);
|
|
44172
|
+
}
|
|
44173
|
+
function validateTypography(obj) {
|
|
44174
|
+
if (!isPlainObject(obj)) return fail('"typography" must be a plain object');
|
|
44175
|
+
for (const key of Object.keys(obj)) {
|
|
44176
|
+
if (!TYPOGRAPHY_KEYS.has(key)) return fail(`Unknown key in typography: "${key}"`);
|
|
44177
|
+
}
|
|
44178
|
+
const stringFields = ["fontFamily", "monoFamily"];
|
|
44179
|
+
for (const key of stringFields) {
|
|
44180
|
+
if (key in obj) {
|
|
44181
|
+
const val = obj[key];
|
|
44182
|
+
if (typeof val !== "string") return fail(`typography.${key} must be a string`);
|
|
44183
|
+
if (CSS_INJECTION.test(val)) return fail(`typography.${key} contains disallowed CSS expression`);
|
|
44184
|
+
}
|
|
44185
|
+
}
|
|
44186
|
+
const positiveFields = ["baseFontSize", "titleFontSize", "smallFontSize", "lineHeight"];
|
|
44187
|
+
for (const key of positiveFields) {
|
|
44188
|
+
if (key in obj) {
|
|
44189
|
+
const val = obj[key];
|
|
44190
|
+
if (typeof val !== "number" || val <= 0) {
|
|
44191
|
+
return fail(`typography.${key} must be a positive number, got ${JSON.stringify(val)}`);
|
|
44192
|
+
}
|
|
44193
|
+
}
|
|
44194
|
+
}
|
|
44195
|
+
return ok(obj);
|
|
44196
|
+
}
|
|
44197
|
+
function validateSpacing(obj) {
|
|
44198
|
+
if (!isPlainObject(obj)) return fail('"spacing" must be a plain object');
|
|
44199
|
+
for (const key of Object.keys(obj)) {
|
|
44200
|
+
if (!SPACING_KEYS.has(key)) return fail(`Unknown key in spacing: "${key}"`);
|
|
44201
|
+
}
|
|
44202
|
+
for (const key of Object.keys(obj)) {
|
|
44203
|
+
const val = obj[key];
|
|
44204
|
+
if (typeof val !== "number" || val < 0) {
|
|
44205
|
+
return fail(`spacing.${key} must be a non-negative number, got ${JSON.stringify(val)}`);
|
|
44206
|
+
}
|
|
44207
|
+
}
|
|
44208
|
+
return ok(obj);
|
|
44209
|
+
}
|
|
44210
|
+
function validateEdges(obj) {
|
|
44211
|
+
if (!isPlainObject(obj)) return fail('"edges" must be a plain object');
|
|
44212
|
+
for (const key of Object.keys(obj)) {
|
|
44213
|
+
if (!EDGES_KEYS.has(key)) return fail(`Unknown key in edges: "${key}"`);
|
|
44214
|
+
}
|
|
44215
|
+
const nonNegativeFields = ["strokeWidth", "arrowSize", "labelFontSize"];
|
|
44216
|
+
for (const key of nonNegativeFields) {
|
|
44217
|
+
if (key in obj) {
|
|
44218
|
+
const val = obj[key];
|
|
44219
|
+
if (typeof val !== "number" || val < 0) {
|
|
44220
|
+
return fail(`edges.${key} must be a non-negative number, got ${JSON.stringify(val)}`);
|
|
44221
|
+
}
|
|
44222
|
+
}
|
|
44223
|
+
}
|
|
44224
|
+
if ("curveTension" in obj) {
|
|
44225
|
+
const val = obj["curveTension"];
|
|
44226
|
+
if (typeof val !== "number" || val < 0 || val > 1) {
|
|
44227
|
+
return fail(`edges.curveTension must be a number between 0 and 1, got ${JSON.stringify(val)}`);
|
|
44228
|
+
}
|
|
44229
|
+
}
|
|
44230
|
+
return ok(obj);
|
|
44231
|
+
}
|
|
44232
|
+
function validatePanel(obj) {
|
|
44233
|
+
if (!isPlainObject(obj)) return fail('"panel" must be a plain object');
|
|
44234
|
+
for (const key of Object.keys(obj)) {
|
|
44235
|
+
if (!PANEL_KEYS.has(key)) return fail(`Unknown key in panel: "${key}"`);
|
|
44236
|
+
}
|
|
44237
|
+
if ("titleAlign" in obj) {
|
|
44238
|
+
const val = obj["titleAlign"];
|
|
44239
|
+
if (val !== "left" && val !== "center" && val !== "right") {
|
|
44240
|
+
return fail(`panel.titleAlign must be "left", "center", or "right", got ${JSON.stringify(val)}`);
|
|
44241
|
+
}
|
|
44242
|
+
}
|
|
44243
|
+
if ("titlePosition" in obj) {
|
|
44244
|
+
const val = obj["titlePosition"];
|
|
44245
|
+
if (val !== "inside" && val !== "on-border" && val !== "above") {
|
|
44246
|
+
return fail(`panel.titlePosition must be "inside", "on-border", or "above", got ${JSON.stringify(val)}`);
|
|
44247
|
+
}
|
|
44248
|
+
}
|
|
44249
|
+
if ("titleChrome" in obj) {
|
|
44250
|
+
const val = obj["titleChrome"];
|
|
44251
|
+
if (val !== "none" && val !== "box" && val !== "pill") {
|
|
44252
|
+
return fail(`panel.titleChrome must be "none", "box", or "pill", got ${JSON.stringify(val)}`);
|
|
44253
|
+
}
|
|
44254
|
+
}
|
|
44255
|
+
return ok(obj);
|
|
44256
|
+
}
|
|
44257
|
+
function validateThemeInput(json) {
|
|
44258
|
+
if (!isPlainObject(json)) {
|
|
44259
|
+
return fail(`ThemeInput must be a non-null plain object, got ${json === null ? "null" : Array.isArray(json) ? "array" : typeof json}`);
|
|
44260
|
+
}
|
|
44261
|
+
for (const key of Object.keys(json)) {
|
|
44262
|
+
if (!TOP_LEVEL_KEYS.has(key)) return fail(`Unknown top-level key: "${key}"`);
|
|
44263
|
+
}
|
|
44264
|
+
const result = {};
|
|
44265
|
+
if ("name" in json) {
|
|
44266
|
+
const v = json["name"];
|
|
44267
|
+
if (typeof v !== "string") return fail(`"name" must be a string, got ${JSON.stringify(v)}`);
|
|
44268
|
+
if (v.length < 1 || v.length > 64) return fail(`"name" must be 1\u201364 characters, got length ${v.length}`);
|
|
44269
|
+
if (!SLUG.test(v)) return fail(`"name" must match ^[a-z0-9-]+$, got ${JSON.stringify(v)}`);
|
|
44270
|
+
result["name"] = v;
|
|
44271
|
+
}
|
|
44272
|
+
if ("base" in json) {
|
|
44273
|
+
const v = json["base"];
|
|
44274
|
+
if (typeof v !== "string") return fail(`"base" must be a string, got ${JSON.stringify(v)}`);
|
|
44275
|
+
if (!isBuiltinThemeName(v)) return fail(`"base" must be a built-in preset name; "${v}" is not recognised`);
|
|
44276
|
+
result["base"] = v;
|
|
44277
|
+
}
|
|
44278
|
+
if ("palette" in json) {
|
|
44279
|
+
const r = validatePalette(json["palette"]);
|
|
44280
|
+
if (!r.ok) return r;
|
|
44281
|
+
result["palette"] = r.value;
|
|
44282
|
+
}
|
|
44283
|
+
if ("typography" in json) {
|
|
44284
|
+
const r = validateTypography(json["typography"]);
|
|
44285
|
+
if (!r.ok) return r;
|
|
44286
|
+
result["typography"] = r.value;
|
|
44287
|
+
}
|
|
44288
|
+
if ("spacing" in json) {
|
|
44289
|
+
const r = validateSpacing(json["spacing"]);
|
|
44290
|
+
if (!r.ok) return r;
|
|
44291
|
+
result["spacing"] = r.value;
|
|
44292
|
+
}
|
|
44293
|
+
if ("edges" in json) {
|
|
44294
|
+
const r = validateEdges(json["edges"]);
|
|
44295
|
+
if (!r.ok) return r;
|
|
44296
|
+
result["edges"] = r.value;
|
|
44297
|
+
}
|
|
44298
|
+
if ("panel" in json) {
|
|
44299
|
+
const r = validatePanel(json["panel"]);
|
|
44300
|
+
if (!r.ok) return r;
|
|
44301
|
+
result["panel"] = r.value;
|
|
44302
|
+
}
|
|
44303
|
+
return ok(result);
|
|
44304
|
+
}
|
|
44305
|
+
|
|
44100
44306
|
// ../src/frontend/index.ts
|
|
44101
44307
|
registerDiagram("flowchart", flowchart);
|
|
44102
44308
|
registerDiagram("timeline", timeline);
|
|
@@ -44229,11 +44435,144 @@ function svgToPdf(svg, opts2 = {}) {
|
|
|
44229
44435
|
});
|
|
44230
44436
|
}
|
|
44231
44437
|
|
|
44438
|
+
// src/theme-resolve.ts
|
|
44439
|
+
var import_node_path2 = require("node:path");
|
|
44440
|
+
|
|
44441
|
+
// ../src/theme/discover.ts
|
|
44442
|
+
var import_node_fs = require("node:fs");
|
|
44443
|
+
var import_node_path = require("node:path");
|
|
44444
|
+
var THEME_SUFFIX = ".triton-theme.json";
|
|
44445
|
+
var SLUG2 = /^[a-z0-9-]+$/;
|
|
44446
|
+
function deriveNameFromFilename(filename) {
|
|
44447
|
+
return (0, import_node_path.basename)(filename, THEME_SUFFIX);
|
|
44448
|
+
}
|
|
44449
|
+
function loadThemeFile(filePath) {
|
|
44450
|
+
let raw;
|
|
44451
|
+
try {
|
|
44452
|
+
raw = (0, import_node_fs.readFileSync)(filePath, "utf8");
|
|
44453
|
+
} catch (cause) {
|
|
44454
|
+
return err("THEME_VALIDATION_ERROR", `Cannot read theme file "${filePath}": ${String(cause)}`, cause);
|
|
44455
|
+
}
|
|
44456
|
+
let parsed;
|
|
44457
|
+
try {
|
|
44458
|
+
parsed = JSON.parse(raw);
|
|
44459
|
+
} catch (cause) {
|
|
44460
|
+
return err("THEME_VALIDATION_ERROR", `Invalid JSON in theme file "${filePath}": ${String(cause)}`, cause);
|
|
44461
|
+
}
|
|
44462
|
+
const validated = validateThemeInput(parsed);
|
|
44463
|
+
if (!validated.ok) {
|
|
44464
|
+
return err(
|
|
44465
|
+
"THEME_VALIDATION_ERROR",
|
|
44466
|
+
`Theme file "${filePath}" failed validation: ${validated.error.message}`,
|
|
44467
|
+
validated.error
|
|
44468
|
+
);
|
|
44469
|
+
}
|
|
44470
|
+
const input = validated.value;
|
|
44471
|
+
const baseName = parsed["base"] ?? "default";
|
|
44472
|
+
const base = getThemePreset(baseName);
|
|
44473
|
+
const resolved = resolveTheme(input, base);
|
|
44474
|
+
return ok(resolved);
|
|
44475
|
+
}
|
|
44476
|
+
function discoverThemes(dir) {
|
|
44477
|
+
const themes = /* @__PURE__ */ new Map();
|
|
44478
|
+
const warnings = [];
|
|
44479
|
+
let entries;
|
|
44480
|
+
try {
|
|
44481
|
+
entries = (0, import_node_fs.readdirSync)(dir);
|
|
44482
|
+
} catch (cause) {
|
|
44483
|
+
warnings.push(`Cannot read themes directory "${dir}": ${String(cause)}`);
|
|
44484
|
+
return { themes, warnings };
|
|
44485
|
+
}
|
|
44486
|
+
const themeFiles = entries.filter((e) => e.endsWith(THEME_SUFFIX));
|
|
44487
|
+
for (const filename of themeFiles) {
|
|
44488
|
+
const filePath = (0, import_node_path.join)(dir, filename);
|
|
44489
|
+
const result = loadThemeFile(filePath);
|
|
44490
|
+
if (!result.ok) {
|
|
44491
|
+
warnings.push(result.error.message);
|
|
44492
|
+
continue;
|
|
44493
|
+
}
|
|
44494
|
+
const resolved = result.value;
|
|
44495
|
+
const name = extractNameFromFile(filePath) ?? deriveNameFromFilename(filename);
|
|
44496
|
+
if (!SLUG2.test(name)) {
|
|
44497
|
+
warnings.push(`Theme file "${filename}": derived name "${name}" is not a valid slug (^[a-z0-9-]+$); skipped`);
|
|
44498
|
+
continue;
|
|
44499
|
+
}
|
|
44500
|
+
if (isBuiltinThemeName(name)) {
|
|
44501
|
+
warnings.push(`Theme "${name}" shadows a built-in preset; skipped`);
|
|
44502
|
+
continue;
|
|
44503
|
+
}
|
|
44504
|
+
if (themes.has(name)) {
|
|
44505
|
+
warnings.push(`Duplicate theme name "${name}": "${filename}" overrides an earlier definition`);
|
|
44506
|
+
}
|
|
44507
|
+
themes.set(name, resolved);
|
|
44508
|
+
}
|
|
44509
|
+
return { themes, warnings };
|
|
44510
|
+
}
|
|
44511
|
+
function extractNameFromFile(filePath) {
|
|
44512
|
+
try {
|
|
44513
|
+
const raw = (0, import_node_fs.readFileSync)(filePath, "utf8");
|
|
44514
|
+
const parsed = JSON.parse(raw);
|
|
44515
|
+
return typeof parsed["name"] === "string" ? parsed["name"] : null;
|
|
44516
|
+
} catch {
|
|
44517
|
+
return null;
|
|
44518
|
+
}
|
|
44519
|
+
}
|
|
44520
|
+
function findTritonThemesDir(startDir) {
|
|
44521
|
+
const MAX_LEVELS = 10;
|
|
44522
|
+
let current = (0, import_node_path.resolve)(startDir);
|
|
44523
|
+
for (let level = 0; level < MAX_LEVELS; level++) {
|
|
44524
|
+
const candidate = (0, import_node_path.join)(current, ".triton", "themes");
|
|
44525
|
+
try {
|
|
44526
|
+
if ((0, import_node_fs.existsSync)(candidate) && (0, import_node_fs.statSync)(candidate).isDirectory()) {
|
|
44527
|
+
return candidate;
|
|
44528
|
+
}
|
|
44529
|
+
} catch {
|
|
44530
|
+
}
|
|
44531
|
+
const parent = (0, import_node_path.resolve)((0, import_node_path.join)(current, ".."));
|
|
44532
|
+
if (parent === current) break;
|
|
44533
|
+
current = parent;
|
|
44534
|
+
}
|
|
44535
|
+
return void 0;
|
|
44536
|
+
}
|
|
44537
|
+
|
|
44538
|
+
// src/theme-resolve.ts
|
|
44539
|
+
function resolveCliTheme(args, inputDir) {
|
|
44540
|
+
if (args.themeFile) {
|
|
44541
|
+
const result = loadThemeFile((0, import_node_path2.resolve)(args.themeFile));
|
|
44542
|
+
if (!result.ok) {
|
|
44543
|
+
throw new Error(`--theme-file: ${result.error.message}`);
|
|
44544
|
+
}
|
|
44545
|
+
return result.value;
|
|
44546
|
+
}
|
|
44547
|
+
const registry4 = /* @__PURE__ */ new Map();
|
|
44548
|
+
const autoDir = findTritonThemesDir(inputDir);
|
|
44549
|
+
if (autoDir) {
|
|
44550
|
+
const { themes, warnings } = discoverThemes(autoDir);
|
|
44551
|
+
for (const [name, theme] of themes) registry4.set(name, theme);
|
|
44552
|
+
for (const w of warnings) console.error(`warning: ${w}`);
|
|
44553
|
+
}
|
|
44554
|
+
if (args.themesDir) {
|
|
44555
|
+
const { themes, warnings } = discoverThemes((0, import_node_path2.resolve)(args.themesDir));
|
|
44556
|
+
for (const [name, theme] of themes) registry4.set(name, theme);
|
|
44557
|
+
for (const w of warnings) console.error(`warning: ${w}`);
|
|
44558
|
+
}
|
|
44559
|
+
if (args.theme) {
|
|
44560
|
+
if (registry4.has(args.theme)) {
|
|
44561
|
+
return registry4.get(args.theme);
|
|
44562
|
+
}
|
|
44563
|
+
return getThemePreset(args.theme);
|
|
44564
|
+
}
|
|
44565
|
+
return void 0;
|
|
44566
|
+
}
|
|
44567
|
+
|
|
44232
44568
|
// src/cli.ts
|
|
44569
|
+
var import_meta = {};
|
|
44233
44570
|
function parseArgs(argv) {
|
|
44234
44571
|
const positionals = [];
|
|
44235
44572
|
let out;
|
|
44236
44573
|
let theme;
|
|
44574
|
+
let themeFile;
|
|
44575
|
+
let themesDir;
|
|
44237
44576
|
let scale = 1;
|
|
44238
44577
|
let help = false;
|
|
44239
44578
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -44246,6 +44585,12 @@ function parseArgs(argv) {
|
|
|
44246
44585
|
case "--theme":
|
|
44247
44586
|
theme = argv[++i];
|
|
44248
44587
|
break;
|
|
44588
|
+
case "--theme-file":
|
|
44589
|
+
themeFile = argv[++i];
|
|
44590
|
+
break;
|
|
44591
|
+
case "--themes-dir":
|
|
44592
|
+
themesDir = argv[++i];
|
|
44593
|
+
break;
|
|
44249
44594
|
case "--scale":
|
|
44250
44595
|
scale = Number(argv[++i]) || 1;
|
|
44251
44596
|
break;
|
|
@@ -44257,7 +44602,7 @@ function parseArgs(argv) {
|
|
|
44257
44602
|
positionals.push(a);
|
|
44258
44603
|
}
|
|
44259
44604
|
}
|
|
44260
|
-
return { command: positionals.shift(), positionals, out, theme, scale, help };
|
|
44605
|
+
return { command: positionals.shift(), positionals, out, theme, themeFile, themesDir, scale, help };
|
|
44261
44606
|
}
|
|
44262
44607
|
var USAGE = `triton-latex \u2014 render Triton diagrams to vector PDF for LaTeX.
|
|
44263
44608
|
|
|
@@ -44266,50 +44611,63 @@ Usage:
|
|
|
44266
44611
|
triton-latex render-dir <srcDir> -o <outDir> [options]
|
|
44267
44612
|
|
|
44268
44613
|
Options:
|
|
44269
|
-
-o, --out <path>
|
|
44270
|
-
--theme <name>
|
|
44271
|
-
|
|
44272
|
-
-
|
|
44614
|
+
-o, --out <path> Output file (render) or output directory (render-dir).
|
|
44615
|
+
--theme <name> Theme preset name (default, executive, minimal, \u2026) or
|
|
44616
|
+
a name from the discovered/loaded custom theme registry.
|
|
44617
|
+
--theme-file <path> Path to a .triton-theme.json file. Takes precedence over
|
|
44618
|
+
--theme and --themes-dir. Fails loudly on load error.
|
|
44619
|
+
--themes-dir <dir> Directory of .triton-theme.json files to register; merged
|
|
44620
|
+
on top of auto-discovered .triton/themes/ (overrides on
|
|
44621
|
+
name collision). Use with --theme <name>.
|
|
44622
|
+
--scale <number> Uniform scale for the PDF page box (default 1).
|
|
44623
|
+
-h, --help Show this help.
|
|
44624
|
+
|
|
44625
|
+
Theme resolution order:
|
|
44626
|
+
1. --theme-file <path> \u2192 load that exact file (errors are fatal)
|
|
44627
|
+
2. --themes-dir + auto-discovery (.triton/themes/ ancestor walk) \u2192 build registry
|
|
44628
|
+
--theme <name> \u2192 look up name in registry, then fall back to built-in preset
|
|
44629
|
+
3. No theme flags \u2192 core uses diagram frontmatter / default preset
|
|
44273
44630
|
|
|
44274
44631
|
Examples:
|
|
44275
44632
|
triton-latex render diagram.mmd -o figures/diagram.pdf
|
|
44276
|
-
triton-latex render diagram.mmd -o diagram.svg
|
|
44277
|
-
triton-latex render
|
|
44633
|
+
triton-latex render diagram.mmd -o diagram.svg # SVG pass-through
|
|
44634
|
+
triton-latex render diagram.mmd -o out.pdf --theme-file my.triton-theme.json
|
|
44635
|
+
triton-latex render diagram.mmd -o out.pdf --themes-dir .triton/themes --theme acme
|
|
44636
|
+
triton-latex render-dir diagrams/ -o figures/ # batch \u2192 *.pdf
|
|
44278
44637
|
`;
|
|
44279
44638
|
var DIAGRAM_EXTS = /* @__PURE__ */ new Set([".triton", ".mmd"]);
|
|
44280
|
-
function renderToSvg(source,
|
|
44281
|
-
const themeInput = themeName ? getThemePreset(themeName) : void 0;
|
|
44639
|
+
function renderToSvg(source, themeInput) {
|
|
44282
44640
|
const result = renderSync(source, themeInput);
|
|
44283
44641
|
if (!result.ok) {
|
|
44284
44642
|
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
44285
44643
|
}
|
|
44286
44644
|
return result.value;
|
|
44287
44645
|
}
|
|
44288
|
-
async function renderFile(inputPath, outPath,
|
|
44289
|
-
const source = (0,
|
|
44290
|
-
const svg = renderToSvg(source,
|
|
44291
|
-
(0,
|
|
44292
|
-
if ((0,
|
|
44293
|
-
(0,
|
|
44646
|
+
async function renderFile(inputPath, outPath, themeInput, scale) {
|
|
44647
|
+
const source = (0, import_node_fs2.readFileSync)(inputPath, "utf8");
|
|
44648
|
+
const svg = renderToSvg(source, themeInput);
|
|
44649
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)((0, import_node_path3.resolve)(outPath)), { recursive: true });
|
|
44650
|
+
if ((0, import_node_path3.extname)(outPath).toLowerCase() === ".svg") {
|
|
44651
|
+
(0, import_node_fs2.writeFileSync)(outPath, svg, "utf8");
|
|
44294
44652
|
return;
|
|
44295
44653
|
}
|
|
44296
44654
|
const pdf = await svgToPdf(svg, { scale });
|
|
44297
|
-
(0,
|
|
44655
|
+
(0, import_node_fs2.writeFileSync)(outPath, pdf);
|
|
44298
44656
|
}
|
|
44299
|
-
async function renderDir(srcDir, outDir,
|
|
44300
|
-
(0,
|
|
44301
|
-
const entries = (0,
|
|
44302
|
-
const full = (0,
|
|
44303
|
-
return (0,
|
|
44657
|
+
async function renderDir(srcDir, outDir, themeInput, scale) {
|
|
44658
|
+
(0, import_node_fs2.mkdirSync)(outDir, { recursive: true });
|
|
44659
|
+
const entries = (0, import_node_fs2.readdirSync)(srcDir).filter((f2) => {
|
|
44660
|
+
const full = (0, import_node_path3.join)(srcDir, f2);
|
|
44661
|
+
return (0, import_node_fs2.statSync)(full).isFile() && DIAGRAM_EXTS.has((0, import_node_path3.extname)(f2).toLowerCase());
|
|
44304
44662
|
});
|
|
44305
44663
|
let rendered = 0;
|
|
44306
44664
|
const failed = [];
|
|
44307
44665
|
for (const file of entries) {
|
|
44308
|
-
const inputPath = (0,
|
|
44309
|
-
const name = (0,
|
|
44310
|
-
const outPath = (0,
|
|
44666
|
+
const inputPath = (0, import_node_path3.join)(srcDir, file);
|
|
44667
|
+
const name = (0, import_node_path3.basename)(file, (0, import_node_path3.extname)(file));
|
|
44668
|
+
const outPath = (0, import_node_path3.join)(outDir, `${name}.pdf`);
|
|
44311
44669
|
try {
|
|
44312
|
-
await renderFile(inputPath, outPath,
|
|
44670
|
+
await renderFile(inputPath, outPath, themeInput, scale);
|
|
44313
44671
|
console.log(` \u2713 ${file} \u2192 ${name}.pdf`);
|
|
44314
44672
|
rendered++;
|
|
44315
44673
|
} catch (cause) {
|
|
@@ -44333,7 +44691,15 @@ async function main() {
|
|
|
44333
44691
|
console.error(USAGE);
|
|
44334
44692
|
return 1;
|
|
44335
44693
|
}
|
|
44336
|
-
|
|
44694
|
+
const inputPath = (0, import_node_path3.resolve)(input);
|
|
44695
|
+
let themeInput;
|
|
44696
|
+
try {
|
|
44697
|
+
themeInput = resolveCliTheme(args, (0, import_node_path3.dirname)(inputPath));
|
|
44698
|
+
} catch (e) {
|
|
44699
|
+
console.error(`error: ${e.message}`);
|
|
44700
|
+
return 1;
|
|
44701
|
+
}
|
|
44702
|
+
await renderFile(inputPath, (0, import_node_path3.resolve)(args.out), themeInput, args.scale);
|
|
44337
44703
|
console.log(`\u2713 ${input} \u2192 ${args.out}`);
|
|
44338
44704
|
return 0;
|
|
44339
44705
|
}
|
|
@@ -44344,10 +44710,18 @@ async function main() {
|
|
|
44344
44710
|
console.error(USAGE);
|
|
44345
44711
|
return 1;
|
|
44346
44712
|
}
|
|
44713
|
+
const resolvedSrcDir = (0, import_node_path3.resolve)(srcDir);
|
|
44714
|
+
let themeInput;
|
|
44715
|
+
try {
|
|
44716
|
+
themeInput = resolveCliTheme(args, resolvedSrcDir);
|
|
44717
|
+
} catch (e) {
|
|
44718
|
+
console.error(`error: ${e.message}`);
|
|
44719
|
+
return 1;
|
|
44720
|
+
}
|
|
44347
44721
|
const { rendered, failed } = await renderDir(
|
|
44348
|
-
|
|
44349
|
-
(0,
|
|
44350
|
-
|
|
44722
|
+
resolvedSrcDir,
|
|
44723
|
+
(0, import_node_path3.resolve)(args.out),
|
|
44724
|
+
themeInput,
|
|
44351
44725
|
args.scale
|
|
44352
44726
|
);
|
|
44353
44727
|
console.log(`
|
|
@@ -44359,9 +44733,11 @@ ${rendered} rendered, ${failed.length} failed.`);
|
|
|
44359
44733
|
console.error(USAGE);
|
|
44360
44734
|
return 1;
|
|
44361
44735
|
}
|
|
44362
|
-
|
|
44363
|
-
|
|
44364
|
-
|
|
44365
|
-
|
|
44366
|
-
|
|
44736
|
+
if (process.argv[1] === (0, import_node_url.fileURLToPath)(import_meta.url)) {
|
|
44737
|
+
main().then((code) => process.exit(code)).catch((cause) => {
|
|
44738
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
44739
|
+
console.error(`error: ${message}`);
|
|
44740
|
+
process.exit(1);
|
|
44741
|
+
});
|
|
44742
|
+
}
|
|
44367
44743
|
//# sourceMappingURL=cli.cjs.map
|