@regmisatyam/retex 0.1.0 → 0.2.0
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 +189 -37
- package/dist/index-DJEasLJc.d.ts +492 -0
- package/dist/index-DXTDYNCI.d.cts +492 -0
- package/dist/index.cjs +1198 -591
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -358
- package/dist/index.d.ts +196 -358
- package/dist/index.js +1166 -592
- package/dist/index.js.map +1 -1
- package/dist/library.cjs +649 -0
- package/dist/library.cjs.map +1 -0
- package/dist/library.d.cts +2 -0
- package/dist/library.d.ts +2 -0
- package/dist/library.js +625 -0
- package/dist/library.js.map +1 -0
- package/dist/{react-v8gyKEAs.d.cts → react-B3tN1iWa.d.cts} +74 -38
- package/dist/{react-v8gyKEAs.d.ts → react-B3tN1iWa.d.ts} +74 -38
- package/dist/react.cjs +113 -74
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +113 -74
- package/dist/react.js.map +1 -1
- package/package.json +7 -1
package/dist/index.cjs
CHANGED
|
@@ -71,6 +71,8 @@ var DiagnosticCode = /* @__PURE__ */ ((DiagnosticCode2) => {
|
|
|
71
71
|
DiagnosticCode2["CommandOutsideContext"] = "RTX4001";
|
|
72
72
|
DiagnosticCode2["UnknownIcon"] = "RTX4002";
|
|
73
73
|
DiagnosticCode2["UnknownThemeColor"] = "RTX4003";
|
|
74
|
+
DiagnosticCode2["UnknownLibrary"] = "RTX4004";
|
|
75
|
+
DiagnosticCode2["LibraryRequired"] = "RTX4005";
|
|
74
76
|
DiagnosticCode2["UnsafeUrlBlocked"] = "RTX5001";
|
|
75
77
|
return DiagnosticCode2;
|
|
76
78
|
})(DiagnosticCode || {});
|
|
@@ -316,103 +318,6 @@ function parseKeyValArg(raw) {
|
|
|
316
318
|
return fields;
|
|
317
319
|
}
|
|
318
320
|
|
|
319
|
-
// src/security/sanitize.ts
|
|
320
|
-
function escapeHtml(input) {
|
|
321
|
-
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
322
|
-
}
|
|
323
|
-
function escapeAttribute(input) {
|
|
324
|
-
return escapeHtml(input);
|
|
325
|
-
}
|
|
326
|
-
var SAFE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:", "ftp:", "sms:"]);
|
|
327
|
-
var SCHEME_RE = /^([a-z][a-z0-9+.-]*):/i;
|
|
328
|
-
var CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/g;
|
|
329
|
-
function sanitizeUrl(input) {
|
|
330
|
-
const raw = String(input ?? "").trim();
|
|
331
|
-
const cleaned = raw.replace(CONTROL_CHARS_RE, "");
|
|
332
|
-
const match = SCHEME_RE.exec(cleaned);
|
|
333
|
-
if (!match) {
|
|
334
|
-
return { safe: cleaned, blocked: false };
|
|
335
|
-
}
|
|
336
|
-
const scheme = match[1].toLowerCase() + ":";
|
|
337
|
-
if (SAFE_PROTOCOLS.has(scheme)) {
|
|
338
|
-
return { safe: cleaned, blocked: false, scheme };
|
|
339
|
-
}
|
|
340
|
-
return { safe: "#", blocked: true, scheme };
|
|
341
|
-
}
|
|
342
|
-
function isSafeColor(value) {
|
|
343
|
-
const v = value.trim();
|
|
344
|
-
if (/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(v)) return true;
|
|
345
|
-
if (/^(rgb|rgba|hsl|hsla)\(\s*[0-9.,%\s/]+\)$/i.test(v)) return true;
|
|
346
|
-
return CSS_NAMED_COLORS.has(v.toLowerCase());
|
|
347
|
-
}
|
|
348
|
-
function isSafeDimension(value) {
|
|
349
|
-
return /^-?\d*\.?\d+(px|pt|em|rem|ex|ch|vw|vh|vmin|vmax|cm|mm|in|pc|%|fr)?$/.test(
|
|
350
|
-
value.trim()
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
function sanitizeStyleValue(value) {
|
|
354
|
-
const v = value.trim();
|
|
355
|
-
if (/[<>;{}]/.test(v) || /expression|url\s*\(|javascript:/i.test(v)) {
|
|
356
|
-
return void 0;
|
|
357
|
-
}
|
|
358
|
-
return v;
|
|
359
|
-
}
|
|
360
|
-
var CSS_NAMED_COLORS = /* @__PURE__ */ new Set([
|
|
361
|
-
"black",
|
|
362
|
-
"silver",
|
|
363
|
-
"gray",
|
|
364
|
-
"grey",
|
|
365
|
-
"white",
|
|
366
|
-
"maroon",
|
|
367
|
-
"red",
|
|
368
|
-
"purple",
|
|
369
|
-
"fuchsia",
|
|
370
|
-
"green",
|
|
371
|
-
"lime",
|
|
372
|
-
"olive",
|
|
373
|
-
"yellow",
|
|
374
|
-
"navy",
|
|
375
|
-
"blue",
|
|
376
|
-
"teal",
|
|
377
|
-
"aqua",
|
|
378
|
-
"cyan",
|
|
379
|
-
"magenta",
|
|
380
|
-
"orange",
|
|
381
|
-
"pink",
|
|
382
|
-
"brown",
|
|
383
|
-
"gold",
|
|
384
|
-
"indigo",
|
|
385
|
-
"violet",
|
|
386
|
-
"tan",
|
|
387
|
-
"beige",
|
|
388
|
-
"ivory",
|
|
389
|
-
"coral",
|
|
390
|
-
"salmon",
|
|
391
|
-
"khaki",
|
|
392
|
-
"crimson",
|
|
393
|
-
"turquoise",
|
|
394
|
-
"lavender",
|
|
395
|
-
"plum",
|
|
396
|
-
"orchid",
|
|
397
|
-
"slateblue",
|
|
398
|
-
"slategray",
|
|
399
|
-
"steelblue",
|
|
400
|
-
"skyblue",
|
|
401
|
-
"royalblue",
|
|
402
|
-
"midnightblue",
|
|
403
|
-
"darkblue",
|
|
404
|
-
"darkgreen",
|
|
405
|
-
"darkred",
|
|
406
|
-
"darkgray",
|
|
407
|
-
"darkgrey",
|
|
408
|
-
"lightgray",
|
|
409
|
-
"lightgrey",
|
|
410
|
-
"lightblue",
|
|
411
|
-
"transparent",
|
|
412
|
-
"currentcolor",
|
|
413
|
-
"inherit"
|
|
414
|
-
]);
|
|
415
|
-
|
|
416
321
|
// src/icons/icons.ts
|
|
417
322
|
var ICONS = {
|
|
418
323
|
github: {
|
|
@@ -493,10 +398,10 @@ function iconToSvg(name, opts = {}) {
|
|
|
493
398
|
const def = getIcon(name);
|
|
494
399
|
if (!def) return null;
|
|
495
400
|
const size = opts.size ?? "1em";
|
|
496
|
-
const
|
|
401
|
+
const dim2 = typeof size === "number" ? `${size}` : size;
|
|
497
402
|
const cls = opts.className ? ` class="${opts.className}"` : "";
|
|
498
403
|
const fillOrStroke = def.stroked ? 'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"' : 'fill="currentColor"';
|
|
499
|
-
return `<svg${cls} width="${
|
|
404
|
+
return `<svg${cls} width="${dim2}" height="${dim2}" viewBox="0 0 24 24" ${fillOrStroke} role="img" aria-hidden="true" focusable="false">${def.body}</svg>`;
|
|
500
405
|
}
|
|
501
406
|
|
|
502
407
|
// src/parser/builtins.ts
|
|
@@ -508,113 +413,12 @@ var mark = (name, type, summary) => ({
|
|
|
508
413
|
example: `\\${name}{...}`,
|
|
509
414
|
build: ({ args }) => ({ type, children: args[0]?.children ?? [] })
|
|
510
415
|
});
|
|
511
|
-
var scaleSwitch = (name, scale) => ({
|
|
512
|
-
name,
|
|
513
|
-
category: "switch",
|
|
514
|
-
scoped: true,
|
|
515
|
-
summary: `Set the font size to "${name}" for the rest of the group.`,
|
|
516
|
-
example: `{\\${name} ...}`,
|
|
517
|
-
build: ({ scope }) => ({ type: "fontscale", scale, children: scope })
|
|
518
|
-
});
|
|
519
416
|
var TYPOGRAPHY = [
|
|
520
417
|
mark("textbf", "bold", "Bold text."),
|
|
521
418
|
mark("textit", "italic", "Italic text."),
|
|
522
419
|
mark("emph", "italic", "Emphasized (italic) text."),
|
|
523
420
|
mark("underline", "underline", "Underlined text."),
|
|
524
421
|
mark("sout", "strike", "Struck-through text."),
|
|
525
|
-
{
|
|
526
|
-
name: "textcolor",
|
|
527
|
-
category: "inline",
|
|
528
|
-
args: [
|
|
529
|
-
{ kind: "string", name: "color", format: "color" },
|
|
530
|
-
{ kind: "content", name: "content" }
|
|
531
|
-
],
|
|
532
|
-
summary: "Color text with a hex/named color.",
|
|
533
|
-
example: "\\textcolor{#2563eb}{OpenAI}",
|
|
534
|
-
build: ({ args, utils, report }) => {
|
|
535
|
-
const color = utils.textOf(args[0]).trim();
|
|
536
|
-
if (color && !isSafeColor(color)) {
|
|
537
|
-
report({
|
|
538
|
-
severity: "warning" /* Warning */,
|
|
539
|
-
code: "RTX3001" /* InvalidColor */,
|
|
540
|
-
message: `"${color}" is not a recognized color; it will be ignored.`,
|
|
541
|
-
range: args[0]?.range ?? ZERO_RANGE
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
return {
|
|
545
|
-
type: "color",
|
|
546
|
-
color: isSafeColor(color) ? color : "",
|
|
547
|
-
children: args[1]?.children ?? []
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
name: "fontsize",
|
|
553
|
-
category: "inline",
|
|
554
|
-
args: [
|
|
555
|
-
{ kind: "string", name: "size", format: "dimension" },
|
|
556
|
-
{ kind: "content", name: "content" }
|
|
557
|
-
],
|
|
558
|
-
summary: "Set an explicit font size for the wrapped content.",
|
|
559
|
-
example: "\\fontsize{14pt}{Custom Size}",
|
|
560
|
-
build: ({ args, utils, report }) => {
|
|
561
|
-
const size = utils.textOf(args[0]).trim();
|
|
562
|
-
if (size && !isSafeDimension(size)) {
|
|
563
|
-
report({
|
|
564
|
-
severity: "warning" /* Warning */,
|
|
565
|
-
code: "RTX3003" /* InvalidDimension */,
|
|
566
|
-
message: `"${size}" is not a valid dimension.`,
|
|
567
|
-
range: args[0]?.range ?? ZERO_RANGE
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
return {
|
|
571
|
-
type: "fontsize",
|
|
572
|
-
size: isSafeDimension(size) ? size : "1em",
|
|
573
|
-
children: args[1]?.children ?? []
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
name: "fontfamily",
|
|
579
|
-
category: "inline",
|
|
580
|
-
args: [
|
|
581
|
-
{ kind: "string", name: "family" },
|
|
582
|
-
{ kind: "content", name: "content" }
|
|
583
|
-
],
|
|
584
|
-
summary: "Set the font family for the wrapped content.",
|
|
585
|
-
example: "\\fontfamily{Georgia}{Serif text}",
|
|
586
|
-
build: ({ args, utils }) => ({
|
|
587
|
-
type: "fontfamily",
|
|
588
|
-
family: utils.textOf(args[0]).trim() || "inherit",
|
|
589
|
-
children: args[1]?.children ?? []
|
|
590
|
-
})
|
|
591
|
-
},
|
|
592
|
-
{
|
|
593
|
-
name: "themecolor",
|
|
594
|
-
category: "inline",
|
|
595
|
-
args: [
|
|
596
|
-
{ kind: "string", name: "token" },
|
|
597
|
-
{ kind: "content", name: "content" }
|
|
598
|
-
],
|
|
599
|
-
summary: "Color text using a token from the active theme.",
|
|
600
|
-
example: "\\themecolor{primary}{Highlighted}",
|
|
601
|
-
build: ({ args, utils }) => ({
|
|
602
|
-
type: "themecolor",
|
|
603
|
-
token: utils.textOf(args[0]).trim() || "primary",
|
|
604
|
-
children: args[1]?.children ?? []
|
|
605
|
-
})
|
|
606
|
-
},
|
|
607
|
-
scaleSwitch("small", "small"),
|
|
608
|
-
scaleSwitch("large", "large"),
|
|
609
|
-
scaleSwitch("Large", "Large"),
|
|
610
|
-
scaleSwitch("Huge", "Huge"),
|
|
611
|
-
{
|
|
612
|
-
name: "normalsize",
|
|
613
|
-
category: "switch",
|
|
614
|
-
scoped: true,
|
|
615
|
-
summary: "Reset the font size to the document base size.",
|
|
616
|
-
build: ({ scope }) => ({ type: "fontscale", scale: "normal", children: scope })
|
|
617
|
-
},
|
|
618
422
|
{
|
|
619
423
|
name: "bfseries",
|
|
620
424
|
category: "switch",
|
|
@@ -628,16 +432,30 @@ var TYPOGRAPHY = [
|
|
|
628
432
|
scoped: true,
|
|
629
433
|
summary: "Switch to italic for the rest of the group.",
|
|
630
434
|
build: ({ scope }) => ({ type: "italic", children: scope })
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
var DIRECTIVES = [
|
|
438
|
+
{
|
|
439
|
+
name: "usepackage",
|
|
440
|
+
category: "meta",
|
|
441
|
+
args: [{ kind: "list", name: "libraries" }],
|
|
442
|
+
summary: "Import styling libraries (fonts, shapes, colors, modern, \u2026).",
|
|
443
|
+
documentation: "ReTeX is blank by default. `\\usepackage{...}` activates one or more libraries for this document so their commands and styling become available. Multiple libraries can be comma-separated.",
|
|
444
|
+
example: "\\usepackage{shapes, colors}",
|
|
445
|
+
build: ({ args }) => ({
|
|
446
|
+
type: "usepackage",
|
|
447
|
+
names: parseListArg(args[0]?.raw ?? "")
|
|
448
|
+
})
|
|
631
449
|
},
|
|
632
450
|
{
|
|
633
|
-
name: "
|
|
634
|
-
category: "
|
|
635
|
-
|
|
636
|
-
summary: "
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
451
|
+
name: "use",
|
|
452
|
+
category: "meta",
|
|
453
|
+
args: [{ kind: "list", name: "libraries" }],
|
|
454
|
+
summary: "Alias for \\usepackage \u2014 import styling libraries.",
|
|
455
|
+
example: "\\use{fonts}",
|
|
456
|
+
build: ({ args }) => ({
|
|
457
|
+
type: "usepackage",
|
|
458
|
+
names: parseListArg(args[0]?.raw ?? "")
|
|
641
459
|
})
|
|
642
460
|
}
|
|
643
461
|
];
|
|
@@ -821,42 +639,6 @@ var PRIMITIVES = [
|
|
|
821
639
|
summary: "An explicit line break.",
|
|
822
640
|
build: () => ({ type: "linebreak" })
|
|
823
641
|
},
|
|
824
|
-
{
|
|
825
|
-
name: "hrule",
|
|
826
|
-
category: "block",
|
|
827
|
-
summary: "A horizontal rule / divider.",
|
|
828
|
-
build: () => ({ type: "rule" })
|
|
829
|
-
},
|
|
830
|
-
{
|
|
831
|
-
name: "divider",
|
|
832
|
-
category: "block",
|
|
833
|
-
summary: "A horizontal rule / divider.",
|
|
834
|
-
build: () => ({ type: "rule" })
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
name: "vspace",
|
|
838
|
-
category: "block",
|
|
839
|
-
args: [{ kind: "string", name: "size", format: "dimension" }],
|
|
840
|
-
summary: "Vertical space.",
|
|
841
|
-
example: "\\vspace{1em}",
|
|
842
|
-
build: ({ args, utils }) => ({
|
|
843
|
-
type: "space",
|
|
844
|
-
axis: "vertical",
|
|
845
|
-
size: utils.textOf(args[0]).trim() || "1em"
|
|
846
|
-
})
|
|
847
|
-
},
|
|
848
|
-
{
|
|
849
|
-
name: "hspace",
|
|
850
|
-
category: "inline",
|
|
851
|
-
args: [{ kind: "string", name: "size", format: "dimension" }],
|
|
852
|
-
summary: "Horizontal space.",
|
|
853
|
-
example: "\\hspace{1em}",
|
|
854
|
-
build: ({ args, utils }) => ({
|
|
855
|
-
type: "space",
|
|
856
|
-
axis: "horizontal",
|
|
857
|
-
size: utils.textOf(args[0]).trim() || "1em"
|
|
858
|
-
})
|
|
859
|
-
},
|
|
860
642
|
// `\item` and `\column` are consumed by their environments; they are
|
|
861
643
|
// registered so they are recognized (and so the validator can flag misuse
|
|
862
644
|
// outside the right environment) but have no standalone builder.
|
|
@@ -874,6 +656,13 @@ var PRIMITIVES = [
|
|
|
874
656
|
example: "\\column{40%}"
|
|
875
657
|
}
|
|
876
658
|
];
|
|
659
|
+
function entryRange(e) {
|
|
660
|
+
const start = e.marker.range?.start;
|
|
661
|
+
if (!start) return void 0;
|
|
662
|
+
let end = e.marker.range?.end;
|
|
663
|
+
for (const n of e.content) if (n.range) end = n.range.end;
|
|
664
|
+
return end ? { start, end } : void 0;
|
|
665
|
+
}
|
|
877
666
|
var ENVIRONMENTS = [
|
|
878
667
|
{
|
|
879
668
|
name: "itemize",
|
|
@@ -885,7 +674,11 @@ var ENVIRONMENTS = [
|
|
|
885
674
|
type: "list",
|
|
886
675
|
kind: "itemize",
|
|
887
676
|
items: (entries ?? []).map(
|
|
888
|
-
(e) => ({
|
|
677
|
+
(e) => ({
|
|
678
|
+
type: "item",
|
|
679
|
+
children: e.content,
|
|
680
|
+
range: entryRange(e)
|
|
681
|
+
})
|
|
889
682
|
)
|
|
890
683
|
})
|
|
891
684
|
},
|
|
@@ -899,7 +692,11 @@ var ENVIRONMENTS = [
|
|
|
899
692
|
type: "list",
|
|
900
693
|
kind: "enumerate",
|
|
901
694
|
items: (entries ?? []).map(
|
|
902
|
-
(e) => ({
|
|
695
|
+
(e) => ({
|
|
696
|
+
type: "item",
|
|
697
|
+
children: e.content,
|
|
698
|
+
range: entryRange(e)
|
|
699
|
+
})
|
|
903
700
|
)
|
|
904
701
|
})
|
|
905
702
|
},
|
|
@@ -913,7 +710,7 @@ var ENVIRONMENTS = [
|
|
|
913
710
|
type: "columns",
|
|
914
711
|
columns: (entries ?? []).map((e) => {
|
|
915
712
|
const width = utils.textOf(e.marker.args[0]).trim() || "auto";
|
|
916
|
-
return { type: "column", width, children: e.content };
|
|
713
|
+
return { type: "column", width, children: e.content, range: entryRange(e) };
|
|
917
714
|
})
|
|
918
715
|
})
|
|
919
716
|
},
|
|
@@ -932,6 +729,7 @@ function createDefaultRegistry() {
|
|
|
932
729
|
const registry = new CommandRegistry();
|
|
933
730
|
for (const def of [
|
|
934
731
|
...TYPOGRAPHY,
|
|
732
|
+
...DIRECTIVES,
|
|
935
733
|
...LINKS,
|
|
936
734
|
...SECTIONS,
|
|
937
735
|
...CONTACT,
|
|
@@ -1017,25 +815,122 @@ function normalizeWhitespace(s) {
|
|
|
1017
815
|
return s.replace(/\s+/g, " ").trim();
|
|
1018
816
|
}
|
|
1019
817
|
|
|
1020
|
-
// src/
|
|
1021
|
-
function
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
|
|
818
|
+
// src/security/sanitize.ts
|
|
819
|
+
function escapeHtml(input) {
|
|
820
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
821
|
+
}
|
|
822
|
+
function escapeAttribute(input) {
|
|
823
|
+
return escapeHtml(input);
|
|
824
|
+
}
|
|
825
|
+
var SAFE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:", "ftp:", "sms:"]);
|
|
826
|
+
var SCHEME_RE = /^([a-z][a-z0-9+.-]*):/i;
|
|
827
|
+
var CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/g;
|
|
828
|
+
function sanitizeUrl(input) {
|
|
829
|
+
const raw = String(input ?? "").trim();
|
|
830
|
+
const cleaned = raw.replace(CONTROL_CHARS_RE, "");
|
|
831
|
+
const match = SCHEME_RE.exec(cleaned);
|
|
832
|
+
if (!match) {
|
|
833
|
+
return { safe: cleaned, blocked: false };
|
|
834
|
+
}
|
|
835
|
+
const scheme = match[1].toLowerCase() + ":";
|
|
836
|
+
if (SAFE_PROTOCOLS.has(scheme)) {
|
|
837
|
+
return { safe: cleaned, blocked: false, scheme };
|
|
838
|
+
}
|
|
839
|
+
return { safe: "#", blocked: true, scheme };
|
|
840
|
+
}
|
|
841
|
+
function isSafeColor(value) {
|
|
842
|
+
const v = value.trim();
|
|
843
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(v)) return true;
|
|
844
|
+
if (/^(rgb|rgba|hsl|hsla)\(\s*[0-9.,%\s/]+\)$/i.test(v)) return true;
|
|
845
|
+
return CSS_NAMED_COLORS.has(v.toLowerCase());
|
|
846
|
+
}
|
|
847
|
+
function isSafeDimension(value) {
|
|
848
|
+
return /^-?\d*\.?\d+(px|pt|em|rem|ex|ch|vw|vh|vmin|vmax|cm|mm|in|pc|%|fr)?$/.test(
|
|
849
|
+
value.trim()
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
function sanitizeStyleValue(value) {
|
|
853
|
+
const v = value.trim();
|
|
854
|
+
if (/[<>;{}]/.test(v) || /expression|url\s*\(|javascript:/i.test(v)) {
|
|
855
|
+
return void 0;
|
|
856
|
+
}
|
|
857
|
+
return v;
|
|
858
|
+
}
|
|
859
|
+
var CSS_NAMED_COLORS = /* @__PURE__ */ new Set([
|
|
860
|
+
"black",
|
|
861
|
+
"silver",
|
|
862
|
+
"gray",
|
|
863
|
+
"grey",
|
|
864
|
+
"white",
|
|
865
|
+
"maroon",
|
|
866
|
+
"red",
|
|
867
|
+
"purple",
|
|
868
|
+
"fuchsia",
|
|
869
|
+
"green",
|
|
870
|
+
"lime",
|
|
871
|
+
"olive",
|
|
872
|
+
"yellow",
|
|
873
|
+
"navy",
|
|
874
|
+
"blue",
|
|
875
|
+
"teal",
|
|
876
|
+
"aqua",
|
|
877
|
+
"cyan",
|
|
878
|
+
"magenta",
|
|
879
|
+
"orange",
|
|
880
|
+
"pink",
|
|
881
|
+
"brown",
|
|
882
|
+
"gold",
|
|
883
|
+
"indigo",
|
|
884
|
+
"violet",
|
|
885
|
+
"tan",
|
|
886
|
+
"beige",
|
|
887
|
+
"ivory",
|
|
888
|
+
"coral",
|
|
889
|
+
"salmon",
|
|
890
|
+
"khaki",
|
|
891
|
+
"crimson",
|
|
892
|
+
"turquoise",
|
|
893
|
+
"lavender",
|
|
894
|
+
"plum",
|
|
895
|
+
"orchid",
|
|
896
|
+
"slateblue",
|
|
897
|
+
"slategray",
|
|
898
|
+
"steelblue",
|
|
899
|
+
"skyblue",
|
|
900
|
+
"royalblue",
|
|
901
|
+
"midnightblue",
|
|
902
|
+
"darkblue",
|
|
903
|
+
"darkgreen",
|
|
904
|
+
"darkred",
|
|
905
|
+
"darkgray",
|
|
906
|
+
"darkgrey",
|
|
907
|
+
"lightgray",
|
|
908
|
+
"lightgrey",
|
|
909
|
+
"lightblue",
|
|
910
|
+
"transparent",
|
|
911
|
+
"currentcolor",
|
|
912
|
+
"inherit"
|
|
913
|
+
]);
|
|
914
|
+
|
|
915
|
+
// src/parser/suggest.ts
|
|
916
|
+
function levenshtein(a, b) {
|
|
917
|
+
const m = a.length;
|
|
918
|
+
const n = b.length;
|
|
919
|
+
if (m === 0) return n;
|
|
920
|
+
if (n === 0) return m;
|
|
921
|
+
let prev = new Array(n + 1);
|
|
922
|
+
let curr = new Array(n + 1);
|
|
923
|
+
for (let j = 0; j <= n; j++) prev[j] = j;
|
|
924
|
+
for (let i = 1; i <= m; i++) {
|
|
925
|
+
curr[0] = i;
|
|
926
|
+
for (let j = 1; j <= n; j++) {
|
|
927
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
928
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
929
|
+
}
|
|
930
|
+
[prev, curr] = [curr, prev];
|
|
931
|
+
}
|
|
932
|
+
return prev[n];
|
|
933
|
+
}
|
|
1039
934
|
function closestMatch(name, candidates) {
|
|
1040
935
|
let best;
|
|
1041
936
|
let bestDist = Infinity;
|
|
@@ -1506,231 +1401,688 @@ var Parser = class _Parser {
|
|
|
1506
1401
|
this.advance();
|
|
1507
1402
|
}
|
|
1508
1403
|
}
|
|
1509
|
-
skipTrivia() {
|
|
1510
|
-
while (this.at("Whitespace" /* Whitespace */) || this.at("Comment" /* Comment */) || this.at("ParBreak" /* ParBreak */)) {
|
|
1511
|
-
this.advance();
|
|
1404
|
+
skipTrivia() {
|
|
1405
|
+
while (this.at("Whitespace" /* Whitespace */) || this.at("Comment" /* Comment */) || this.at("ParBreak" /* ParBreak */)) {
|
|
1406
|
+
this.advance();
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
peek(k = 0) {
|
|
1410
|
+
return this.tokens[this.pos + k] ?? this.tokens[this.tokens.length - 1];
|
|
1411
|
+
}
|
|
1412
|
+
advance() {
|
|
1413
|
+
const t = this.peek();
|
|
1414
|
+
if (this.pos < this.tokens.length - 1) this.pos++;
|
|
1415
|
+
return t;
|
|
1416
|
+
}
|
|
1417
|
+
previousEnd() {
|
|
1418
|
+
return this.tokens[this.pos - 1]?.range.end ?? this.peek().range.start;
|
|
1419
|
+
}
|
|
1420
|
+
report(d) {
|
|
1421
|
+
this.diagnostics.push({ ...d, source: "parser" });
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
function parse(tokens, options) {
|
|
1425
|
+
return Parser.parse(tokens, options);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/validator/validator.ts
|
|
1429
|
+
var ZERO_RANGE3 = {
|
|
1430
|
+
start: { offset: 0, line: 1, column: 1 },
|
|
1431
|
+
end: { offset: 0, line: 1, column: 1 }
|
|
1432
|
+
};
|
|
1433
|
+
function validate(ast, options = {}) {
|
|
1434
|
+
const diagnostics = [];
|
|
1435
|
+
const report = (d) => {
|
|
1436
|
+
diagnostics.push({ ...d, source: "validator" });
|
|
1437
|
+
};
|
|
1438
|
+
walk(ast, {
|
|
1439
|
+
enter(node) {
|
|
1440
|
+
switch (node.type) {
|
|
1441
|
+
case "command":
|
|
1442
|
+
checkStrayCommand(node, report);
|
|
1443
|
+
break;
|
|
1444
|
+
case "section":
|
|
1445
|
+
if (node.title.trim() === "") {
|
|
1446
|
+
report({
|
|
1447
|
+
severity: "warning" /* Warning */,
|
|
1448
|
+
code: "RTX3006" /* EmptyArgument */,
|
|
1449
|
+
message: "Section heading is empty.",
|
|
1450
|
+
range: node.range ?? ZERO_RANGE3
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
break;
|
|
1454
|
+
case "contact":
|
|
1455
|
+
if (node.value.trim() === "") {
|
|
1456
|
+
report({
|
|
1457
|
+
severity: "warning" /* Warning */,
|
|
1458
|
+
code: "RTX3006" /* EmptyArgument */,
|
|
1459
|
+
message: `\\${node.field} is empty.`,
|
|
1460
|
+
range: node.range ?? ZERO_RANGE3
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
break;
|
|
1464
|
+
case "themecolor":
|
|
1465
|
+
if (options.theme?.colors && !(node.token in options.theme.colors)) {
|
|
1466
|
+
report({
|
|
1467
|
+
severity: "warning" /* Warning */,
|
|
1468
|
+
code: "RTX4003" /* UnknownThemeColor */,
|
|
1469
|
+
message: `Theme color "${node.token}" is not defined in theme "${options.theme.name}".`,
|
|
1470
|
+
range: node.range ?? ZERO_RANGE3
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
break;
|
|
1474
|
+
case "skills":
|
|
1475
|
+
if (node.items.length === 0) {
|
|
1476
|
+
report({
|
|
1477
|
+
severity: "info" /* Info */,
|
|
1478
|
+
code: "RTX3006" /* EmptyArgument */,
|
|
1479
|
+
message: "\\skills has no entries.",
|
|
1480
|
+
range: node.range ?? ZERO_RANGE3
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
return diagnostics;
|
|
1488
|
+
}
|
|
1489
|
+
function checkStrayCommand(node, report) {
|
|
1490
|
+
if (node.name === "item") {
|
|
1491
|
+
report({
|
|
1492
|
+
severity: "error" /* Error */,
|
|
1493
|
+
code: "RTX4001" /* CommandOutsideContext */,
|
|
1494
|
+
message: "\\item must appear inside an itemize or enumerate environment.",
|
|
1495
|
+
range: node.range ?? ZERO_RANGE3
|
|
1496
|
+
});
|
|
1497
|
+
} else if (node.name === "column") {
|
|
1498
|
+
report({
|
|
1499
|
+
severity: "error" /* Error */,
|
|
1500
|
+
code: "RTX4001" /* CommandOutsideContext */,
|
|
1501
|
+
message: "\\column must appear inside a columns environment.",
|
|
1502
|
+
range: node.range ?? ZERO_RANGE3
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/theme/default.ts
|
|
1508
|
+
var blankTheme = {
|
|
1509
|
+
name: "blank"
|
|
1510
|
+
};
|
|
1511
|
+
var defaultTheme = blankTheme;
|
|
1512
|
+
|
|
1513
|
+
// src/theme/themes.ts
|
|
1514
|
+
function resolveTheme(partial, base = defaultTheme) {
|
|
1515
|
+
return {
|
|
1516
|
+
name: partial?.name ?? base.name,
|
|
1517
|
+
colors: { ...base.colors, ...partial?.colors },
|
|
1518
|
+
fonts: { ...base.fonts, ...partial?.fonts },
|
|
1519
|
+
fontSizes: { ...base.fontSizes, ...partial?.fontSizes },
|
|
1520
|
+
spacing: { ...base.spacing, ...partial?.spacing },
|
|
1521
|
+
page: { ...base.page, ...partial?.page },
|
|
1522
|
+
sectionStyle: partial?.sectionStyle ?? base.sectionStyle,
|
|
1523
|
+
headings: { ...base.headings, ...partial?.headings }
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
var styledBase = {
|
|
1527
|
+
fonts: {
|
|
1528
|
+
heading: '"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
1529
|
+
body: '"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
1530
|
+
mono: '"JetBrains Mono", "SF Mono", "Fira Code", Consolas, monospace'
|
|
1531
|
+
},
|
|
1532
|
+
fontSizes: {
|
|
1533
|
+
base: "10.5pt",
|
|
1534
|
+
small: "9pt",
|
|
1535
|
+
large: "12pt",
|
|
1536
|
+
Large: "15pt",
|
|
1537
|
+
Huge: "22pt",
|
|
1538
|
+
name: "26pt",
|
|
1539
|
+
section: "13pt"
|
|
1540
|
+
},
|
|
1541
|
+
spacing: { unit: "4px", section: "1.1rem", item: "0.28rem", page: "0.55in" },
|
|
1542
|
+
page: { size: "Letter", margin: "0.55in", maxWidth: "8.5in" },
|
|
1543
|
+
headings: { transform: "uppercase", tracking: "0.06em", nameTracking: "-0.01em" }
|
|
1544
|
+
};
|
|
1545
|
+
var modernTheme = resolveTheme({
|
|
1546
|
+
...styledBase,
|
|
1547
|
+
name: "modern",
|
|
1548
|
+
colors: {
|
|
1549
|
+
primary: "#7c3aed",
|
|
1550
|
+
secondary: "#64748b",
|
|
1551
|
+
text: "#111827",
|
|
1552
|
+
muted: "#64748b",
|
|
1553
|
+
background: "#ffffff",
|
|
1554
|
+
border: "#e2e8f0",
|
|
1555
|
+
accent: "#7c3aed"
|
|
1556
|
+
},
|
|
1557
|
+
sectionStyle: "underline"
|
|
1558
|
+
});
|
|
1559
|
+
var classicTheme = resolveTheme({
|
|
1560
|
+
...styledBase,
|
|
1561
|
+
name: "classic",
|
|
1562
|
+
colors: {
|
|
1563
|
+
primary: "#111827",
|
|
1564
|
+
secondary: "#4b5563",
|
|
1565
|
+
text: "#1e293b",
|
|
1566
|
+
muted: "#4b5563",
|
|
1567
|
+
background: "#ffffff",
|
|
1568
|
+
border: "#cbd5e1",
|
|
1569
|
+
accent: "#111827"
|
|
1570
|
+
},
|
|
1571
|
+
fonts: {
|
|
1572
|
+
heading: 'Georgia, "Times New Roman", serif',
|
|
1573
|
+
body: 'Georgia, "Times New Roman", serif',
|
|
1574
|
+
mono: 'Consolas, "Courier New", monospace'
|
|
1575
|
+
},
|
|
1576
|
+
sectionStyle: "rule"
|
|
1577
|
+
});
|
|
1578
|
+
var compactTheme = resolveTheme({
|
|
1579
|
+
...styledBase,
|
|
1580
|
+
name: "compact",
|
|
1581
|
+
colors: {
|
|
1582
|
+
primary: "#2563eb",
|
|
1583
|
+
secondary: "#64748b",
|
|
1584
|
+
text: "#1e293b",
|
|
1585
|
+
muted: "#64748b",
|
|
1586
|
+
background: "#ffffff",
|
|
1587
|
+
border: "#e2e8f0",
|
|
1588
|
+
accent: "#2563eb"
|
|
1589
|
+
},
|
|
1590
|
+
fontSizes: {
|
|
1591
|
+
base: "9.5pt",
|
|
1592
|
+
small: "8pt",
|
|
1593
|
+
large: "11pt",
|
|
1594
|
+
Large: "13pt",
|
|
1595
|
+
Huge: "18pt",
|
|
1596
|
+
name: "20pt",
|
|
1597
|
+
section: "11pt"
|
|
1598
|
+
},
|
|
1599
|
+
spacing: { unit: "3px", section: "0.7rem", item: "0.18rem", page: "0.4in" },
|
|
1600
|
+
sectionStyle: "bar"
|
|
1601
|
+
});
|
|
1602
|
+
var themes = {
|
|
1603
|
+
blank: blankTheme,
|
|
1604
|
+
default: blankTheme,
|
|
1605
|
+
modern: modernTheme,
|
|
1606
|
+
classic: classicTheme,
|
|
1607
|
+
compact: compactTheme
|
|
1608
|
+
};
|
|
1609
|
+
function getTheme(name) {
|
|
1610
|
+
return themes[name] ?? defaultTheme;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// src/library/colors.ts
|
|
1614
|
+
var zero2 = { offset: 0, line: 1, column: 1 };
|
|
1615
|
+
var ZERO_RANGE4 = { start: zero2, end: zero2 };
|
|
1616
|
+
var colorCommands = [
|
|
1617
|
+
{
|
|
1618
|
+
name: "textcolor",
|
|
1619
|
+
category: "inline",
|
|
1620
|
+
args: [
|
|
1621
|
+
{ kind: "string", name: "color", format: "color" },
|
|
1622
|
+
{ kind: "content", name: "content" }
|
|
1623
|
+
],
|
|
1624
|
+
summary: "Color text with a hex/named color.",
|
|
1625
|
+
example: "\\textcolor{#2563eb}{OpenAI}",
|
|
1626
|
+
build: ({ args, utils, report }) => {
|
|
1627
|
+
const color2 = utils.textOf(args[0]).trim();
|
|
1628
|
+
if (color2 && !isSafeColor(color2)) {
|
|
1629
|
+
report({
|
|
1630
|
+
severity: "warning" /* Warning */,
|
|
1631
|
+
code: "RTX3001" /* InvalidColor */,
|
|
1632
|
+
message: `"${color2}" is not a recognized color; it will be ignored.`,
|
|
1633
|
+
range: args[0]?.range ?? ZERO_RANGE4
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
return {
|
|
1637
|
+
type: "color",
|
|
1638
|
+
color: isSafeColor(color2) ? color2 : "",
|
|
1639
|
+
children: args[1]?.children ?? []
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
name: "themecolor",
|
|
1645
|
+
category: "inline",
|
|
1646
|
+
args: [
|
|
1647
|
+
{ kind: "string", name: "token" },
|
|
1648
|
+
{ kind: "content", name: "content" }
|
|
1649
|
+
],
|
|
1650
|
+
summary: "Color text using a token from the active theme.",
|
|
1651
|
+
example: "\\themecolor{primary}{Highlighted}",
|
|
1652
|
+
build: ({ args, utils }) => ({
|
|
1653
|
+
type: "themecolor",
|
|
1654
|
+
token: utils.textOf(args[0]).trim() || "primary",
|
|
1655
|
+
children: args[1]?.children ?? []
|
|
1656
|
+
})
|
|
1657
|
+
}
|
|
1658
|
+
];
|
|
1659
|
+
var palettes = {
|
|
1660
|
+
slate: {
|
|
1661
|
+
primary: "#0f172a",
|
|
1662
|
+
secondary: "#475569",
|
|
1663
|
+
text: "#1e293b",
|
|
1664
|
+
muted: "#64748b",
|
|
1665
|
+
border: "#cbd5e1",
|
|
1666
|
+
accent: "#0f172a"
|
|
1667
|
+
},
|
|
1668
|
+
blue: {
|
|
1669
|
+
primary: "#2563eb",
|
|
1670
|
+
secondary: "#64748b",
|
|
1671
|
+
text: "#1e293b",
|
|
1672
|
+
muted: "#64748b",
|
|
1673
|
+
border: "#e2e8f0",
|
|
1674
|
+
accent: "#2563eb"
|
|
1675
|
+
},
|
|
1676
|
+
violet: {
|
|
1677
|
+
primary: "#7c3aed",
|
|
1678
|
+
secondary: "#6b7280",
|
|
1679
|
+
text: "#111827",
|
|
1680
|
+
muted: "#6b7280",
|
|
1681
|
+
border: "#e5e7eb",
|
|
1682
|
+
accent: "#7c3aed"
|
|
1683
|
+
},
|
|
1684
|
+
emerald: {
|
|
1685
|
+
primary: "#059669",
|
|
1686
|
+
secondary: "#6b7280",
|
|
1687
|
+
text: "#111827",
|
|
1688
|
+
muted: "#6b7280",
|
|
1689
|
+
border: "#d1fae5",
|
|
1690
|
+
accent: "#059669"
|
|
1691
|
+
},
|
|
1692
|
+
rose: {
|
|
1693
|
+
primary: "#e11d48",
|
|
1694
|
+
secondary: "#6b7280",
|
|
1695
|
+
text: "#111827",
|
|
1696
|
+
muted: "#6b7280",
|
|
1697
|
+
border: "#fecdd3",
|
|
1698
|
+
accent: "#e11d48"
|
|
1699
|
+
},
|
|
1700
|
+
amber: {
|
|
1701
|
+
primary: "#d97706",
|
|
1702
|
+
secondary: "#6b7280",
|
|
1703
|
+
text: "#111827",
|
|
1704
|
+
muted: "#6b7280",
|
|
1705
|
+
border: "#fde68a",
|
|
1706
|
+
accent: "#d97706"
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
var colorsLibrary = {
|
|
1710
|
+
name: "colors",
|
|
1711
|
+
summary: "Text color (\\textcolor, \\themecolor) and named palettes.",
|
|
1712
|
+
commands: colorCommands
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
// src/library/fonts.ts
|
|
1716
|
+
var zero3 = { offset: 0, line: 1, column: 1 };
|
|
1717
|
+
var ZERO_RANGE5 = { start: zero3, end: zero3 };
|
|
1718
|
+
var scaleSwitch = (name, scale) => ({
|
|
1719
|
+
name,
|
|
1720
|
+
category: "switch",
|
|
1721
|
+
scoped: true,
|
|
1722
|
+
summary: `Set the font size to "${name}" for the rest of the group.`,
|
|
1723
|
+
example: `{\\${name} ...}`,
|
|
1724
|
+
build: ({ scope }) => ({ type: "fontscale", scale, children: scope })
|
|
1725
|
+
});
|
|
1726
|
+
var fontCommands = [
|
|
1727
|
+
{
|
|
1728
|
+
name: "fontsize",
|
|
1729
|
+
category: "inline",
|
|
1730
|
+
args: [
|
|
1731
|
+
{ kind: "string", name: "size", format: "dimension" },
|
|
1732
|
+
{ kind: "content", name: "content" }
|
|
1733
|
+
],
|
|
1734
|
+
summary: "Set an explicit font size for the wrapped content.",
|
|
1735
|
+
example: "\\fontsize{14pt}{Custom Size}",
|
|
1736
|
+
build: ({ args, utils, report }) => {
|
|
1737
|
+
const size = utils.textOf(args[0]).trim();
|
|
1738
|
+
if (size && !isSafeDimension(size)) {
|
|
1739
|
+
report({
|
|
1740
|
+
severity: "warning" /* Warning */,
|
|
1741
|
+
code: "RTX3003" /* InvalidDimension */,
|
|
1742
|
+
message: `"${size}" is not a valid dimension.`,
|
|
1743
|
+
range: args[0]?.range ?? ZERO_RANGE5
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
return {
|
|
1747
|
+
type: "fontsize",
|
|
1748
|
+
size: isSafeDimension(size) ? size : "1em",
|
|
1749
|
+
children: args[1]?.children ?? []
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
name: "fontfamily",
|
|
1755
|
+
category: "inline",
|
|
1756
|
+
args: [
|
|
1757
|
+
{ kind: "string", name: "family" },
|
|
1758
|
+
{ kind: "content", name: "content" }
|
|
1759
|
+
],
|
|
1760
|
+
summary: "Set the font family for the wrapped content.",
|
|
1761
|
+
example: "\\fontfamily{Georgia}{Serif text}",
|
|
1762
|
+
build: ({ args, utils }) => ({
|
|
1763
|
+
type: "fontfamily",
|
|
1764
|
+
family: utils.textOf(args[0]).trim() || "inherit",
|
|
1765
|
+
children: args[1]?.children ?? []
|
|
1766
|
+
})
|
|
1767
|
+
},
|
|
1768
|
+
scaleSwitch("small", "small"),
|
|
1769
|
+
scaleSwitch("large", "large"),
|
|
1770
|
+
scaleSwitch("Large", "Large"),
|
|
1771
|
+
scaleSwitch("Huge", "Huge"),
|
|
1772
|
+
{
|
|
1773
|
+
name: "normalsize",
|
|
1774
|
+
category: "switch",
|
|
1775
|
+
scoped: true,
|
|
1776
|
+
summary: "Reset the font size to the document base size.",
|
|
1777
|
+
build: ({ scope }) => ({ type: "fontscale", scale: "normal", children: scope })
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
name: "ttfamily",
|
|
1781
|
+
category: "switch",
|
|
1782
|
+
scoped: true,
|
|
1783
|
+
summary: "Switch to a monospace font for the rest of the group.",
|
|
1784
|
+
build: ({ scope }) => ({
|
|
1785
|
+
type: "fontfamily",
|
|
1786
|
+
family: "monospace",
|
|
1787
|
+
children: scope
|
|
1788
|
+
})
|
|
1789
|
+
}
|
|
1790
|
+
];
|
|
1791
|
+
var fontStacks = {
|
|
1792
|
+
inter: '"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
1793
|
+
system: 'system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif',
|
|
1794
|
+
helvetica: '"Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
1795
|
+
georgia: 'Georgia, "Times New Roman", Times, serif',
|
|
1796
|
+
times: '"Times New Roman", Times, serif',
|
|
1797
|
+
garamond: '"EB Garamond", Garamond, Georgia, serif',
|
|
1798
|
+
spaceGrotesk: '"Space Grotesk", system-ui, sans-serif',
|
|
1799
|
+
sourceSerif: '"Source Serif 4", Georgia, serif',
|
|
1800
|
+
jetbrainsMono: '"JetBrains Mono", "SF Mono", "Fira Code", Consolas, monospace',
|
|
1801
|
+
firaCode: '"Fira Code", "JetBrains Mono", Consolas, monospace',
|
|
1802
|
+
ibmPlexMono: '"IBM Plex Mono", "SF Mono", Consolas, monospace'
|
|
1803
|
+
};
|
|
1804
|
+
function fontTheme(spec) {
|
|
1805
|
+
const resolve = (v) => v == null ? void 0 : fontStacks[v] ?? v;
|
|
1806
|
+
return {
|
|
1807
|
+
heading: resolve(spec.heading),
|
|
1808
|
+
body: resolve(spec.body),
|
|
1809
|
+
mono: resolve(spec.mono)
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
var fontsLibrary = {
|
|
1813
|
+
name: "fonts",
|
|
1814
|
+
summary: "Font family & size control (\\fontfamily, \\fontsize, \\small \u2026 \\Huge).",
|
|
1815
|
+
commands: fontCommands
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
// src/library/shapes.ts
|
|
1819
|
+
var rule = (name, style, summary) => ({
|
|
1820
|
+
name,
|
|
1821
|
+
category: "block",
|
|
1822
|
+
summary,
|
|
1823
|
+
example: `\\${name}`,
|
|
1824
|
+
build: () => style ? { type: "rule", style } : { type: "rule" }
|
|
1825
|
+
});
|
|
1826
|
+
var shapeCommands = [
|
|
1827
|
+
rule("hrule", "solid", "A horizontal rule / divider."),
|
|
1828
|
+
rule("divider", "solid", "A horizontal rule / divider."),
|
|
1829
|
+
rule("dashrule", "dashed", "A dashed horizontal rule."),
|
|
1830
|
+
rule("dotrule", "dotted", "A dotted horizontal rule."),
|
|
1831
|
+
rule("thickrule", "thick", "A thick horizontal rule."),
|
|
1832
|
+
rule("doublerule", "double", "A double horizontal rule."),
|
|
1833
|
+
{
|
|
1834
|
+
name: "vspace",
|
|
1835
|
+
category: "block",
|
|
1836
|
+
args: [{ kind: "string", name: "size", format: "dimension" }],
|
|
1837
|
+
summary: "Vertical space.",
|
|
1838
|
+
example: "\\vspace{1em}",
|
|
1839
|
+
build: ({ args, utils }) => ({
|
|
1840
|
+
type: "space",
|
|
1841
|
+
axis: "vertical",
|
|
1842
|
+
size: utils.textOf(args[0]).trim() || "1em"
|
|
1843
|
+
})
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
name: "hspace",
|
|
1847
|
+
category: "inline",
|
|
1848
|
+
args: [{ kind: "string", name: "size", format: "dimension" }],
|
|
1849
|
+
summary: "Horizontal space.",
|
|
1850
|
+
example: "\\hspace{1em}",
|
|
1851
|
+
build: ({ args, utils }) => ({
|
|
1852
|
+
type: "space",
|
|
1853
|
+
axis: "horizontal",
|
|
1854
|
+
size: utils.textOf(args[0]).trim() || "1em"
|
|
1855
|
+
})
|
|
1856
|
+
}
|
|
1857
|
+
];
|
|
1858
|
+
var shapesLibrary = {
|
|
1859
|
+
name: "shapes",
|
|
1860
|
+
summary: "Horizontal rules / dividers (\\hrule, \\dashrule, \u2026) and spacing.",
|
|
1861
|
+
commands: shapeCommands
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1864
|
+
// src/library/themes.ts
|
|
1865
|
+
var stylingCommands = [...colorCommands, ...fontCommands, ...shapeCommands];
|
|
1866
|
+
var modernLibrary = {
|
|
1867
|
+
name: "modern",
|
|
1868
|
+
summary: "A modern, accent-forward theme (violet, underlined headings).",
|
|
1869
|
+
theme: modernTheme,
|
|
1870
|
+
commands: stylingCommands
|
|
1871
|
+
};
|
|
1872
|
+
var classicLibrary = {
|
|
1873
|
+
name: "classic",
|
|
1874
|
+
summary: "A classic serif theme with ruled section headings.",
|
|
1875
|
+
theme: classicTheme,
|
|
1876
|
+
commands: stylingCommands
|
|
1877
|
+
};
|
|
1878
|
+
var compactLibrary = {
|
|
1879
|
+
name: "compact",
|
|
1880
|
+
summary: "A compact, single-accent theme maximizing content density.",
|
|
1881
|
+
theme: compactTheme,
|
|
1882
|
+
commands: stylingCommands
|
|
1883
|
+
};
|
|
1884
|
+
|
|
1885
|
+
// src/library/registry.ts
|
|
1886
|
+
var builtinLibraries = {
|
|
1887
|
+
colors: colorsLibrary,
|
|
1888
|
+
fonts: fontsLibrary,
|
|
1889
|
+
shapes: shapesLibrary,
|
|
1890
|
+
modern: modernLibrary,
|
|
1891
|
+
classic: classicLibrary,
|
|
1892
|
+
compact: compactLibrary
|
|
1893
|
+
};
|
|
1894
|
+
var ALIASES2 = {
|
|
1895
|
+
color: "colors",
|
|
1896
|
+
font: "fonts",
|
|
1897
|
+
shape: "shapes"
|
|
1898
|
+
};
|
|
1899
|
+
function resolveLibraryName(name) {
|
|
1900
|
+
const key = name.trim().toLowerCase();
|
|
1901
|
+
if (builtinLibraries[key]) return key;
|
|
1902
|
+
if (ALIASES2[key]) return ALIASES2[key];
|
|
1903
|
+
return void 0;
|
|
1904
|
+
}
|
|
1905
|
+
function getBuiltinLibrary(name) {
|
|
1906
|
+
const key = resolveLibraryName(name);
|
|
1907
|
+
return key ? builtinLibraries[key] : void 0;
|
|
1908
|
+
}
|
|
1909
|
+
function builtinLibraryNames() {
|
|
1910
|
+
return Object.keys(builtinLibraries);
|
|
1911
|
+
}
|
|
1912
|
+
var GATED_COMMANDS = (() => {
|
|
1913
|
+
const map = {};
|
|
1914
|
+
const add = (lib) => {
|
|
1915
|
+
for (const c of lib.commands ?? []) map[c.name] = lib.name;
|
|
1916
|
+
};
|
|
1917
|
+
add(colorsLibrary);
|
|
1918
|
+
add(fontsLibrary);
|
|
1919
|
+
add(shapesLibrary);
|
|
1920
|
+
return map;
|
|
1921
|
+
})();
|
|
1922
|
+
var dedupe = (names) => [...new Set(names)];
|
|
1923
|
+
function usedLibraryNamesFromTokens(tokens) {
|
|
1924
|
+
const names = [];
|
|
1925
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1926
|
+
const t = tokens[i];
|
|
1927
|
+
if (t.type !== "Command" /* Command */ || t.value !== "usepackage" && t.value !== "use") {
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
let j = i + 1;
|
|
1931
|
+
while (tokens[j] && (tokens[j].type === "Whitespace" /* Whitespace */ || tokens[j].type === "Comment" /* Comment */)) {
|
|
1932
|
+
j++;
|
|
1933
|
+
}
|
|
1934
|
+
if (!tokens[j] || tokens[j].type !== "LBrace" /* LBrace */) continue;
|
|
1935
|
+
j++;
|
|
1936
|
+
let raw = "";
|
|
1937
|
+
let depth = 0;
|
|
1938
|
+
while (tokens[j] && !(depth === 0 && tokens[j].type === "RBrace" /* RBrace */)) {
|
|
1939
|
+
const tj = tokens[j];
|
|
1940
|
+
if (tj.type === "LBrace" /* LBrace */) depth++;
|
|
1941
|
+
else if (tj.type === "RBrace" /* RBrace */) depth--;
|
|
1942
|
+
raw += tj.type === "Command" /* Command */ ? `\\${tj.value}` : tj.value;
|
|
1943
|
+
j++;
|
|
1944
|
+
}
|
|
1945
|
+
for (const part of raw.split(",")) {
|
|
1946
|
+
const name = part.trim();
|
|
1947
|
+
if (name) names.push(name);
|
|
1948
|
+
}
|
|
1949
|
+
i = j;
|
|
1950
|
+
}
|
|
1951
|
+
return dedupe(names);
|
|
1952
|
+
}
|
|
1953
|
+
function usedLibraryNamesFromAst(root) {
|
|
1954
|
+
const names = [];
|
|
1955
|
+
for (const node of collect(root, (n) => n.type === "usepackage")) {
|
|
1956
|
+
for (const name of node.names) {
|
|
1957
|
+
const trimmed = name.trim();
|
|
1958
|
+
if (trimmed) names.push(trimmed);
|
|
1512
1959
|
}
|
|
1513
1960
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1961
|
+
return dedupe(names);
|
|
1962
|
+
}
|
|
1963
|
+
function applyLibraries(base, names, lookup = getBuiltinLibrary) {
|
|
1964
|
+
const registry = base.clone();
|
|
1965
|
+
const libraries = [];
|
|
1966
|
+
const missing = [];
|
|
1967
|
+
for (const name of names) {
|
|
1968
|
+
const lib = lookup(name);
|
|
1969
|
+
if (!lib) {
|
|
1970
|
+
missing.push(name);
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
libraries.push(lib);
|
|
1974
|
+
for (const cmd of lib.commands ?? []) registry.registerCommand(cmd);
|
|
1975
|
+
for (const env of lib.environments ?? []) registry.registerEnvironment(env);
|
|
1524
1976
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1977
|
+
return { registry, libraries, missing };
|
|
1978
|
+
}
|
|
1979
|
+
function mergeLibraryTheme(base, libraries) {
|
|
1980
|
+
let theme = base;
|
|
1981
|
+
for (const lib of libraries) {
|
|
1982
|
+
if (lib.theme) theme = resolveTheme(lib.theme, theme);
|
|
1527
1983
|
}
|
|
1528
|
-
|
|
1529
|
-
function parse(tokens, options) {
|
|
1530
|
-
return Parser.parse(tokens, options);
|
|
1984
|
+
return theme;
|
|
1531
1985
|
}
|
|
1532
|
-
|
|
1533
|
-
// src/validator/validator.ts
|
|
1534
|
-
var ZERO_RANGE3 = {
|
|
1986
|
+
var ZERO_RANGE6 = {
|
|
1535
1987
|
start: { offset: 0, line: 1, column: 1 },
|
|
1536
1988
|
end: { offset: 0, line: 1, column: 1 }
|
|
1537
1989
|
};
|
|
1538
|
-
function
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
}
|
|
1558
|
-
break;
|
|
1559
|
-
case "contact":
|
|
1560
|
-
if (node.value.trim() === "") {
|
|
1561
|
-
report({
|
|
1562
|
-
severity: "warning" /* Warning */,
|
|
1563
|
-
code: "RTX3006" /* EmptyArgument */,
|
|
1564
|
-
message: `\\${node.field} is empty.`,
|
|
1565
|
-
range: node.range ?? ZERO_RANGE3
|
|
1566
|
-
});
|
|
1567
|
-
}
|
|
1568
|
-
break;
|
|
1569
|
-
case "themecolor":
|
|
1570
|
-
if (options.theme && !(node.token in options.theme.colors)) {
|
|
1571
|
-
report({
|
|
1572
|
-
severity: "warning" /* Warning */,
|
|
1573
|
-
code: "RTX4003" /* UnknownThemeColor */,
|
|
1574
|
-
message: `Theme color "${node.token}" is not defined in theme "${options.theme.name}".`,
|
|
1575
|
-
range: node.range ?? ZERO_RANGE3
|
|
1576
|
-
});
|
|
1577
|
-
}
|
|
1578
|
-
break;
|
|
1579
|
-
case "skills":
|
|
1580
|
-
if (node.items.length === 0) {
|
|
1581
|
-
report({
|
|
1582
|
-
severity: "info" /* Info */,
|
|
1583
|
-
code: "RTX3006" /* EmptyArgument */,
|
|
1584
|
-
message: "\\skills has no entries.",
|
|
1585
|
-
range: node.range ?? ZERO_RANGE3
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1588
|
-
break;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
});
|
|
1592
|
-
return diagnostics;
|
|
1593
|
-
}
|
|
1594
|
-
function checkStrayCommand(node, report) {
|
|
1595
|
-
if (node.name === "item") {
|
|
1596
|
-
report({
|
|
1597
|
-
severity: "error" /* Error */,
|
|
1598
|
-
code: "RTX4001" /* CommandOutsideContext */,
|
|
1599
|
-
message: "\\item must appear inside an itemize or enumerate environment.",
|
|
1600
|
-
range: node.range ?? ZERO_RANGE3
|
|
1601
|
-
});
|
|
1602
|
-
} else if (node.name === "column") {
|
|
1603
|
-
report({
|
|
1604
|
-
severity: "error" /* Error */,
|
|
1605
|
-
code: "RTX4001" /* CommandOutsideContext */,
|
|
1606
|
-
message: "\\column must appear inside a columns environment.",
|
|
1607
|
-
range: node.range ?? ZERO_RANGE3
|
|
1990
|
+
function libraryDiagnostics(ast, diagnostics, lookup = getBuiltinLibrary) {
|
|
1991
|
+
const gated = collect(
|
|
1992
|
+
ast,
|
|
1993
|
+
(n) => n.type === "command" && n.name in GATED_COMMANDS
|
|
1994
|
+
);
|
|
1995
|
+
const gatedOffsets = new Set(
|
|
1996
|
+
gated.map((n) => n.range?.start.offset).filter((o) => o != null)
|
|
1997
|
+
);
|
|
1998
|
+
const out = diagnostics.filter(
|
|
1999
|
+
(d) => !(d.code === "RTX2001" /* UnknownCommand */ && gatedOffsets.has(d.range.start.offset))
|
|
2000
|
+
);
|
|
2001
|
+
for (const node of gated) {
|
|
2002
|
+
const lib = GATED_COMMANDS[node.name];
|
|
2003
|
+
out.push({
|
|
2004
|
+
source: "parser",
|
|
2005
|
+
severity: "warning" /* Warning */,
|
|
2006
|
+
code: "RTX4005" /* LibraryRequired */,
|
|
2007
|
+
message: `\\${node.name} requires the "${lib}" library. Add \\usepackage{${lib}} to the preamble (or engine.use(${lib}Library)).`,
|
|
2008
|
+
range: node.range ?? ZERO_RANGE6
|
|
1608
2009
|
});
|
|
1609
2010
|
}
|
|
2011
|
+
for (const node of collect(ast, (n) => n.type === "usepackage")) {
|
|
2012
|
+
for (const name of node.names) {
|
|
2013
|
+
if (lookup(name)) continue;
|
|
2014
|
+
out.push({
|
|
2015
|
+
source: "parser",
|
|
2016
|
+
severity: "warning" /* Warning */,
|
|
2017
|
+
code: "RTX4004" /* UnknownLibrary */,
|
|
2018
|
+
message: `Unknown library "${name}". Available: ${builtinLibraryNames().join(", ")}.`,
|
|
2019
|
+
range: node.range ?? ZERO_RANGE6
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return out;
|
|
1610
2024
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
success: "#16a34a"
|
|
1624
|
-
},
|
|
1625
|
-
fonts: {
|
|
1626
|
-
heading: '"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
1627
|
-
body: '"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
1628
|
-
mono: '"JetBrains Mono", "SF Mono", "Fira Code", Consolas, monospace'
|
|
1629
|
-
},
|
|
1630
|
-
fontSizes: {
|
|
1631
|
-
base: "10.5pt",
|
|
1632
|
-
small: "9pt",
|
|
1633
|
-
large: "12pt",
|
|
1634
|
-
Large: "15pt",
|
|
1635
|
-
Huge: "22pt",
|
|
1636
|
-
name: "26pt",
|
|
1637
|
-
section: "13pt"
|
|
1638
|
-
},
|
|
1639
|
-
spacing: {
|
|
1640
|
-
unit: "4px",
|
|
1641
|
-
section: "1.1rem",
|
|
1642
|
-
item: "0.28rem",
|
|
1643
|
-
page: "0.55in"
|
|
1644
|
-
},
|
|
1645
|
-
page: {
|
|
1646
|
-
size: "Letter",
|
|
1647
|
-
margin: "0.55in",
|
|
1648
|
-
maxWidth: "8.5in"
|
|
1649
|
-
},
|
|
1650
|
-
sectionStyle: "rule"
|
|
1651
|
-
};
|
|
1652
|
-
|
|
1653
|
-
// src/theme/themes.ts
|
|
1654
|
-
function resolveTheme(partial, base = defaultTheme) {
|
|
1655
|
-
if (!partial) return base;
|
|
1656
|
-
return {
|
|
1657
|
-
name: partial.name ?? base.name,
|
|
1658
|
-
colors: { ...base.colors, ...partial.colors },
|
|
1659
|
-
fonts: { ...base.fonts, ...partial.fonts },
|
|
1660
|
-
fontSizes: { ...base.fontSizes, ...partial.fontSizes },
|
|
1661
|
-
spacing: { ...base.spacing, ...partial.spacing },
|
|
1662
|
-
page: { ...base.page, ...partial.page },
|
|
1663
|
-
sectionStyle: partial.sectionStyle ?? base.sectionStyle
|
|
1664
|
-
};
|
|
1665
|
-
}
|
|
1666
|
-
var modernTheme = resolveTheme({
|
|
1667
|
-
name: "modern",
|
|
1668
|
-
colors: { primary: "#7c3aed", accent: "#7c3aed", text: "#111827" },
|
|
1669
|
-
sectionStyle: "underline"
|
|
1670
|
-
});
|
|
1671
|
-
var classicTheme = resolveTheme({
|
|
1672
|
-
name: "classic",
|
|
1673
|
-
colors: { primary: "#111827", secondary: "#4b5563", accent: "#111827" },
|
|
1674
|
-
fonts: {
|
|
1675
|
-
heading: 'Georgia, "Times New Roman", serif',
|
|
1676
|
-
body: 'Georgia, "Times New Roman", serif',
|
|
1677
|
-
mono: 'Consolas, "Courier New", monospace'
|
|
1678
|
-
},
|
|
1679
|
-
sectionStyle: "rule"
|
|
1680
|
-
});
|
|
1681
|
-
var compactTheme = resolveTheme({
|
|
1682
|
-
name: "compact",
|
|
1683
|
-
fontSizes: {
|
|
1684
|
-
base: "9.5pt",
|
|
1685
|
-
small: "8pt",
|
|
1686
|
-
large: "11pt",
|
|
1687
|
-
Large: "13pt",
|
|
1688
|
-
Huge: "18pt",
|
|
1689
|
-
name: "20pt",
|
|
1690
|
-
section: "11pt"
|
|
1691
|
-
},
|
|
1692
|
-
spacing: { unit: "3px", section: "0.7rem", item: "0.18rem", page: "0.4in" },
|
|
1693
|
-
sectionStyle: "bar"
|
|
1694
|
-
});
|
|
1695
|
-
var themes = {
|
|
1696
|
-
default: defaultTheme,
|
|
1697
|
-
modern: modernTheme,
|
|
1698
|
-
classic: classicTheme,
|
|
1699
|
-
compact: compactTheme
|
|
1700
|
-
};
|
|
1701
|
-
function getTheme(name) {
|
|
1702
|
-
return themes[name] ?? defaultTheme;
|
|
2025
|
+
function collectLibraryOverrides(libraries) {
|
|
2026
|
+
const html = /* @__PURE__ */ new Map();
|
|
2027
|
+
const react = /* @__PURE__ */ new Map();
|
|
2028
|
+
for (const lib of libraries) {
|
|
2029
|
+
for (const cmd of lib.commands ?? []) {
|
|
2030
|
+
if (cmd.render?.html) html.set(`command:${cmd.name}`, cmd.render.html);
|
|
2031
|
+
if (cmd.render?.react) react.set(`command:${cmd.name}`, cmd.render.react);
|
|
2032
|
+
}
|
|
2033
|
+
for (const [k, fn] of Object.entries(lib.htmlRenderers ?? {})) html.set(k, fn);
|
|
2034
|
+
for (const [k, fn] of Object.entries(lib.reactRenderers ?? {})) react.set(k, fn);
|
|
2035
|
+
}
|
|
2036
|
+
return { html, react };
|
|
1703
2037
|
}
|
|
1704
2038
|
|
|
1705
2039
|
// src/renderers/css.ts
|
|
1706
2040
|
var tokenSafe = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2041
|
+
var has = (v) => v != null && v !== "";
|
|
2042
|
+
var color = (v, fallback) => has(v) && isSafeColor(v) ? v : fallback;
|
|
2043
|
+
var dim = (v, fallback) => has(v) && isSafeDimension(v) ? v : fallback;
|
|
2044
|
+
var val = (v, fallback) => has(v) ? sanitizeStyleValue(v) ?? fallback : fallback;
|
|
1707
2045
|
function themeToCss(theme, prefix = "retex") {
|
|
1708
2046
|
const p = prefix;
|
|
1709
|
-
const
|
|
2047
|
+
const c = theme.colors ?? {};
|
|
2048
|
+
const f = theme.fonts ?? {};
|
|
2049
|
+
const fs = theme.fontSizes ?? {};
|
|
2050
|
+
const sp = theme.spacing ?? {};
|
|
2051
|
+
const h = theme.headings ?? {};
|
|
2052
|
+
const colorVars = Object.entries(c).filter(([, v]) => has(v) && isSafeColor(v)).map(([k, v]) => ` --${p}-color-${tokenSafe(k)}: ${v};`).join("\n");
|
|
2053
|
+
const primary = color(c.primary, "");
|
|
2054
|
+
const skillBg = primary ? `color-mix(in srgb, ${primary} 12%, transparent)` : "transparent";
|
|
2055
|
+
const skillColor = primary || "inherit";
|
|
2056
|
+
const linkDecoration = primary ? "none" : "underline";
|
|
1710
2057
|
return `.${p}-resume {
|
|
1711
|
-
${colorVars}
|
|
1712
|
-
--${p}-
|
|
1713
|
-
--${p}-
|
|
1714
|
-
--${p}-
|
|
1715
|
-
--${p}-
|
|
1716
|
-
--${p}-
|
|
1717
|
-
--${p}-font-
|
|
1718
|
-
--${p}-font-
|
|
1719
|
-
--${p}-
|
|
1720
|
-
--${p}-fs-
|
|
1721
|
-
--${p}-fs-
|
|
1722
|
-
--${p}-fs-
|
|
1723
|
-
--${p}-fs-
|
|
1724
|
-
--${p}-fs-
|
|
1725
|
-
--${p}-fs-
|
|
1726
|
-
--${p}-
|
|
1727
|
-
--${p}-sp-
|
|
1728
|
-
--${p}-
|
|
2058
|
+
${colorVars ? colorVars + "\n" : ""} --${p}-primary: ${primary || "inherit"};
|
|
2059
|
+
--${p}-text: ${color(c.text, "inherit")};
|
|
2060
|
+
--${p}-muted: ${color(c.muted, "inherit")};
|
|
2061
|
+
--${p}-border: ${color(c.border, "currentColor")};
|
|
2062
|
+
--${p}-bg: ${color(c.background, "transparent")};
|
|
2063
|
+
--${p}-font-heading: ${val(f.heading, "inherit")};
|
|
2064
|
+
--${p}-font-body: ${val(f.body, "inherit")};
|
|
2065
|
+
--${p}-font-mono: ${val(f.mono, "ui-monospace, SFMono-Regular, Menlo, monospace")};
|
|
2066
|
+
--${p}-fs-base: ${dim(fs.base, "inherit")};
|
|
2067
|
+
--${p}-fs-small: ${dim(fs.small, "0.85em")};
|
|
2068
|
+
--${p}-fs-large: ${dim(fs.large, "1.2em")};
|
|
2069
|
+
--${p}-fs-Large: ${dim(fs.Large, "1.45em")};
|
|
2070
|
+
--${p}-fs-Huge: ${dim(fs.Huge, "2em")};
|
|
2071
|
+
--${p}-fs-name: ${dim(fs.name, "2em")};
|
|
2072
|
+
--${p}-fs-section: ${dim(fs.section, "1.25em")};
|
|
2073
|
+
--${p}-sp-section: ${dim(sp.section, "1.1rem")};
|
|
2074
|
+
--${p}-sp-item: ${dim(sp.item, "0.28rem")};
|
|
2075
|
+
--${p}-section-transform: ${val(h.transform, "none")};
|
|
2076
|
+
--${p}-section-tracking: ${val(h.tracking, "normal")};
|
|
2077
|
+
--${p}-name-tracking: ${val(h.nameTracking, "normal")};
|
|
2078
|
+
--${p}-link-decoration: ${linkDecoration};
|
|
2079
|
+
--${p}-skill-bg: ${skillBg};
|
|
2080
|
+
--${p}-skill-color: ${skillColor};
|
|
1729
2081
|
|
|
1730
2082
|
box-sizing: border-box;
|
|
1731
|
-
max-width: ${theme.page
|
|
2083
|
+
max-width: ${dim(theme.page?.maxWidth, "none")};
|
|
1732
2084
|
margin: 0 auto;
|
|
1733
|
-
padding: ${
|
|
2085
|
+
padding: ${dim(sp.page, "0")};
|
|
1734
2086
|
background: var(--${p}-bg);
|
|
1735
2087
|
color: var(--${p}-text);
|
|
1736
2088
|
font-family: var(--${p}-font-body);
|
|
@@ -1746,7 +2098,7 @@ ${colorVars}
|
|
|
1746
2098
|
font-weight: 700;
|
|
1747
2099
|
margin: 0 0 0.1em;
|
|
1748
2100
|
color: var(--${p}-text);
|
|
1749
|
-
letter-spacing: -
|
|
2101
|
+
letter-spacing: var(--${p}-name-tracking);
|
|
1750
2102
|
}
|
|
1751
2103
|
.${p}-title {
|
|
1752
2104
|
font-size: var(--${p}-fs-large);
|
|
@@ -1775,8 +2127,8 @@ ${colorVars}
|
|
|
1775
2127
|
font-family: var(--${p}-font-heading);
|
|
1776
2128
|
font-size: var(--${p}-fs-section);
|
|
1777
2129
|
font-weight: 700;
|
|
1778
|
-
text-transform:
|
|
1779
|
-
letter-spacing:
|
|
2130
|
+
text-transform: var(--${p}-section-transform);
|
|
2131
|
+
letter-spacing: var(--${p}-section-tracking);
|
|
1780
2132
|
color: var(--${p}-primary);
|
|
1781
2133
|
margin: 0 0 0.5em;
|
|
1782
2134
|
}
|
|
@@ -1819,8 +2171,8 @@ ${colorVars}
|
|
|
1819
2171
|
|
|
1820
2172
|
.${p}-skills { display: flex; flex-wrap: wrap; gap: 0.4rem; list-style: none; margin: 0.2rem 0; padding: 0; }
|
|
1821
2173
|
.${p}-skill {
|
|
1822
|
-
background:
|
|
1823
|
-
color: var(--${p}-
|
|
2174
|
+
background: var(--${p}-skill-bg);
|
|
2175
|
+
color: var(--${p}-skill-color);
|
|
1824
2176
|
border-radius: 4px;
|
|
1825
2177
|
padding: 0.12rem 0.5rem;
|
|
1826
2178
|
font-size: var(--${p}-fs-small);
|
|
@@ -1828,10 +2180,14 @@ ${colorVars}
|
|
|
1828
2180
|
white-space: nowrap;
|
|
1829
2181
|
}
|
|
1830
2182
|
|
|
1831
|
-
.${p}-link { color: var(--${p}-primary); text-decoration:
|
|
2183
|
+
.${p}-link { color: var(--${p}-primary); text-decoration: var(--${p}-link-decoration); }
|
|
1832
2184
|
.${p}-link:hover { text-decoration: underline; }
|
|
1833
2185
|
.${p}-icon { display: inline-flex; vertical-align: -0.125em; }
|
|
1834
2186
|
.${p}-rule { border: none; border-top: 1px solid var(--${p}-border); margin: 0.6rem 0; }
|
|
2187
|
+
.${p}-rule--dashed { border-top-style: dashed; }
|
|
2188
|
+
.${p}-rule--dotted { border-top-style: dotted; }
|
|
2189
|
+
.${p}-rule--thick { border-top-width: 3px; }
|
|
2190
|
+
.${p}-rule--double { border-top-style: double; border-top-width: 3px; }
|
|
1835
2191
|
.${p}-center { text-align: center; }
|
|
1836
2192
|
.${p}-scale-small { font-size: var(--${p}-fs-small); }
|
|
1837
2193
|
.${p}-scale-large { font-size: var(--${p}-fs-large); }
|
|
@@ -1889,7 +2245,7 @@ function splitPreamble(nodes) {
|
|
|
1889
2245
|
(n) => CONTACTISH.has(n.type) && n !== name && n !== title
|
|
1890
2246
|
);
|
|
1891
2247
|
const other = nodes.filter(
|
|
1892
|
-
(n) => !CONTACTISH.has(n.type) && n.type !== "parbreak" && !(n.type === "text" && n.value.trim() === "")
|
|
2248
|
+
(n) => !CONTACTISH.has(n.type) && n.type !== "parbreak" && n.type !== "usepackage" && !(n.type === "text" && n.value.trim() === "")
|
|
1893
2249
|
);
|
|
1894
2250
|
return { name, title, contacts, other };
|
|
1895
2251
|
}
|
|
@@ -1924,10 +2280,11 @@ function dateRange(start, end, date) {
|
|
|
1924
2280
|
var tokenSafe2 = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
1925
2281
|
var HtmlRenderer = class {
|
|
1926
2282
|
constructor(options = {}) {
|
|
1927
|
-
this.theme = options.theme
|
|
2283
|
+
this.theme = resolveTheme(options.theme);
|
|
1928
2284
|
this.prefix = options.classPrefix ?? "retex";
|
|
1929
2285
|
this.overrides = options.overrides ?? /* @__PURE__ */ new Map();
|
|
1930
2286
|
this.useHeader = options.header ?? true;
|
|
2287
|
+
this.sourceMap = options.sourceMap ?? false;
|
|
1931
2288
|
this.ctx = {
|
|
1932
2289
|
theme: this.theme,
|
|
1933
2290
|
classPrefix: this.prefix,
|
|
@@ -1975,8 +2332,8 @@ ${this.styles()}
|
|
|
1975
2332
|
}
|
|
1976
2333
|
/* --------------------------- structuring ---------------------------- */
|
|
1977
2334
|
renderSection(section, body) {
|
|
1978
|
-
const styleMod = ` ${this.cls("section")}--${this.theme.sectionStyle}
|
|
1979
|
-
return `<section class="${this.cls("section")}${styleMod}"><h2 class="${this.cls("section-title")}">${escapeHtml(section.title)}</h2><div class="${this.cls("section-body")}">${this.renderFlow(body)}</div></section>`;
|
|
2335
|
+
const styleMod = this.theme.sectionStyle ? ` ${this.cls("section")}--${this.theme.sectionStyle}` : "";
|
|
2336
|
+
return `<section class="${this.cls("section")}${styleMod}"${this.src(section)}><h2 class="${this.cls("section-title")}"${this.src(section, "section-title")}>${escapeHtml(section.title)}</h2><div class="${this.cls("section-body")}">${this.renderFlow(body)}</div></section>`;
|
|
1980
2337
|
}
|
|
1981
2338
|
renderPreamble(nodes) {
|
|
1982
2339
|
if (!this.useHeader) return this.renderFlow(nodes);
|
|
@@ -1985,8 +2342,10 @@ ${this.styles()}
|
|
|
1985
2342
|
let html = "";
|
|
1986
2343
|
if (hasHeader) {
|
|
1987
2344
|
html += `<header class="${this.cls("header")}">`;
|
|
1988
|
-
if (name)
|
|
1989
|
-
|
|
2345
|
+
if (name)
|
|
2346
|
+
html += `<h1 class="${this.cls("name")}"${this.src(name)}>${escapeHtml(name.value)}</h1>`;
|
|
2347
|
+
if (title)
|
|
2348
|
+
html += `<p class="${this.cls("title")}"${this.src(title)}>${escapeHtml(title.value)}</p>`;
|
|
1990
2349
|
if (contacts.length > 0) {
|
|
1991
2350
|
html += `<div class="${this.cls("contact")}">${contacts.map((c) => this.renderContactItem(c)).join("")}</div>`;
|
|
1992
2351
|
}
|
|
@@ -1997,35 +2356,37 @@ ${this.styles()}
|
|
|
1997
2356
|
}
|
|
1998
2357
|
renderContactItem(node) {
|
|
1999
2358
|
const item = this.cls("contact-item");
|
|
2359
|
+
const src = this.src(node);
|
|
2000
2360
|
if (node.type === "contact") {
|
|
2001
2361
|
switch (node.field) {
|
|
2002
2362
|
case "email":
|
|
2003
|
-
return this.contactLink(`mailto:${node.value}`, "email", node.value, item);
|
|
2363
|
+
return this.contactLink(`mailto:${node.value}`, "email", node.value, item, src);
|
|
2004
2364
|
case "phone":
|
|
2005
2365
|
return this.contactLink(
|
|
2006
2366
|
`tel:${node.value.replace(/[^\d+]/g, "")}`,
|
|
2007
2367
|
"phone",
|
|
2008
2368
|
node.value,
|
|
2009
|
-
item
|
|
2369
|
+
item,
|
|
2370
|
+
src
|
|
2010
2371
|
);
|
|
2011
2372
|
case "website":
|
|
2012
|
-
return this.contactLink(node.value, "website", node.value, item);
|
|
2373
|
+
return this.contactLink(node.value, "website", node.value, item, src);
|
|
2013
2374
|
case "location":
|
|
2014
|
-
return `<span class="${item}">${this.icon("location")}${escapeHtml(node.value)}</span>`;
|
|
2375
|
+
return `<span class="${item}"${src}>${this.icon("location")}${escapeHtml(node.value)}</span>`;
|
|
2015
2376
|
default:
|
|
2016
|
-
return `<span class="${item}">${escapeHtml(node.value)}</span>`;
|
|
2377
|
+
return `<span class="${item}"${src}>${escapeHtml(node.value)}</span>`;
|
|
2017
2378
|
}
|
|
2018
2379
|
}
|
|
2019
2380
|
if (node.type === "icon")
|
|
2020
|
-
return `<span class="${item}">${this.renderNode(node)}</span>`;
|
|
2381
|
+
return `<span class="${item}"${src}>${this.renderNode(node)}</span>`;
|
|
2021
2382
|
if (node.type === "link" || node.type === "url") {
|
|
2022
|
-
return `<span class="${item}">${this.renderNode(node)}</span>`;
|
|
2383
|
+
return `<span class="${item}"${src}>${this.renderNode(node)}</span>`;
|
|
2023
2384
|
}
|
|
2024
2385
|
return this.renderNode(node);
|
|
2025
2386
|
}
|
|
2026
|
-
contactLink(url, icon, label, cls) {
|
|
2387
|
+
contactLink(url, icon, label, cls, src = "") {
|
|
2027
2388
|
const { safe } = sanitizeUrl(url);
|
|
2028
|
-
return `<a class="${cls}" href="${escapeAttribute(safe)}">${this.icon(icon)}${escapeHtml(label)}</a>`;
|
|
2389
|
+
return `<a class="${cls}"${src} href="${escapeAttribute(safe)}">${this.icon(icon)}${escapeHtml(label)}</a>`;
|
|
2029
2390
|
}
|
|
2030
2391
|
/* ----------------------------- flow / inline ------------------------ */
|
|
2031
2392
|
/** Block-aware rendering: groups inline runs into `<p>`, renders blocks as-is. */
|
|
@@ -2034,7 +2395,10 @@ ${this.styles()}
|
|
|
2034
2395
|
let inline = [];
|
|
2035
2396
|
const flush = () => {
|
|
2036
2397
|
const html = this.renderInline(inline).trim();
|
|
2037
|
-
if (html)
|
|
2398
|
+
if (html)
|
|
2399
|
+
out.push(
|
|
2400
|
+
`<p class="${this.cls("para")}"${this.srcSpan(inline, "para")}>${html}</p>`
|
|
2401
|
+
);
|
|
2038
2402
|
inline = [];
|
|
2039
2403
|
};
|
|
2040
2404
|
for (const node of nodes) {
|
|
@@ -2062,68 +2426,76 @@ ${this.styles()}
|
|
|
2062
2426
|
return escapeHtml(node.value);
|
|
2063
2427
|
case "parbreak":
|
|
2064
2428
|
return "";
|
|
2429
|
+
case "usepackage":
|
|
2430
|
+
return "";
|
|
2065
2431
|
case "linebreak":
|
|
2066
2432
|
return "<br>";
|
|
2067
|
-
case "rule":
|
|
2068
|
-
|
|
2433
|
+
case "rule": {
|
|
2434
|
+
const variant = node.style && node.style !== "solid" ? ` ${this.cls(`rule--${node.style}`)}` : "";
|
|
2435
|
+
return `<hr class="${this.cls("rule")}${variant}"${this.src(node)}>`;
|
|
2436
|
+
}
|
|
2069
2437
|
case "space":
|
|
2070
|
-
return node.axis === "vertical" ? `<div style="height:${this.dim(node.size)}"></div>` : `<span style="display:inline-block;width:${this.dim(node.size)}"></span>`;
|
|
2438
|
+
return node.axis === "vertical" ? `<div style="height:${this.dim(node.size)}"${this.src(node)}></div>` : `<span style="display:inline-block;width:${this.dim(node.size)}"${this.src(node)}></span>`;
|
|
2071
2439
|
case "group":
|
|
2072
2440
|
return this.renderInline(node.children);
|
|
2073
2441
|
case "bold":
|
|
2074
|
-
return `<strong>${this.renderInline(node.children)}</strong>`;
|
|
2442
|
+
return `<strong${this.src(node)}>${this.renderInline(node.children)}</strong>`;
|
|
2075
2443
|
case "italic":
|
|
2076
|
-
return `<em>${this.renderInline(node.children)}</em>`;
|
|
2444
|
+
return `<em${this.src(node)}>${this.renderInline(node.children)}</em>`;
|
|
2077
2445
|
case "underline":
|
|
2078
|
-
return `<u>${this.renderInline(node.children)}</u>`;
|
|
2446
|
+
return `<u${this.src(node)}>${this.renderInline(node.children)}</u>`;
|
|
2079
2447
|
case "strike":
|
|
2080
|
-
return `<s>${this.renderInline(node.children)}</s>`;
|
|
2448
|
+
return `<s${this.src(node)}>${this.renderInline(node.children)}</s>`;
|
|
2081
2449
|
case "color": {
|
|
2082
2450
|
const inner = this.renderInline(node.children);
|
|
2083
2451
|
if (!node.color || !isSafeColor(node.color)) return inner;
|
|
2084
|
-
return `<span style="color:${node.color}">${inner}</span>`;
|
|
2452
|
+
return `<span style="color:${node.color}"${this.src(node)}>${inner}</span>`;
|
|
2085
2453
|
}
|
|
2086
2454
|
case "themecolor": {
|
|
2087
2455
|
const inner = this.renderInline(node.children);
|
|
2088
2456
|
const tok = tokenSafe2(node.token);
|
|
2089
|
-
return `<span style="color:var(--${this.prefix}-color-${tok}, currentColor)">${inner}</span>`;
|
|
2457
|
+
return `<span style="color:var(--${this.prefix}-color-${tok}, currentColor)"${this.src(node)}>${inner}</span>`;
|
|
2090
2458
|
}
|
|
2091
2459
|
case "fontsize": {
|
|
2092
2460
|
const inner = this.renderInline(node.children);
|
|
2093
2461
|
if (!isSafeDimension(node.size)) return inner;
|
|
2094
|
-
return `<span style="font-size:${node.size}">${inner}</span>`;
|
|
2462
|
+
return `<span style="font-size:${node.size}"${this.src(node)}>${inner}</span>`;
|
|
2095
2463
|
}
|
|
2096
2464
|
case "fontfamily": {
|
|
2097
2465
|
const inner = this.renderInline(node.children);
|
|
2098
2466
|
const family = node.family === "monospace" ? `var(--${this.prefix}-font-mono)` : sanitizeStyleValue(node.family);
|
|
2099
2467
|
if (!family) return inner;
|
|
2100
|
-
return `<span style="font-family:${family}">${inner}</span>`;
|
|
2468
|
+
return `<span style="font-family:${family}"${this.src(node)}>${inner}</span>`;
|
|
2101
2469
|
}
|
|
2102
2470
|
case "fontscale": {
|
|
2103
2471
|
const inner = this.renderInline(node.children);
|
|
2104
|
-
if (node.scale === "normal") return `<span>${inner}</span>`;
|
|
2105
|
-
return `<span class="${this.cls(`scale-${node.scale}`)}">${inner}</span>`;
|
|
2472
|
+
if (node.scale === "normal") return `<span${this.src(node)}>${inner}</span>`;
|
|
2473
|
+
return `<span class="${this.cls(`scale-${node.scale}`)}"${this.src(node)}>${inner}</span>`;
|
|
2106
2474
|
}
|
|
2107
2475
|
case "link":
|
|
2108
|
-
return this.renderLink(
|
|
2476
|
+
return this.renderLink(
|
|
2477
|
+
node.href,
|
|
2478
|
+
this.renderInline(node.children),
|
|
2479
|
+
this.src(node)
|
|
2480
|
+
);
|
|
2109
2481
|
case "url":
|
|
2110
|
-
return this.renderLink(node.href, escapeHtml(node.rawHref));
|
|
2482
|
+
return this.renderLink(node.href, escapeHtml(node.rawHref), this.src(node));
|
|
2111
2483
|
case "icon":
|
|
2112
2484
|
return this.icon(node.name);
|
|
2113
2485
|
case "section":
|
|
2114
|
-
return `<h${node.level + 1} class="${this.cls("subsection-title")}">${escapeHtml(node.title)}</h${node.level + 1}>`;
|
|
2486
|
+
return `<h${node.level + 1} class="${this.cls("subsection-title")}"${this.src(node)}>${escapeHtml(node.title)}</h${node.level + 1}>`;
|
|
2115
2487
|
case "list":
|
|
2116
2488
|
return this.renderList(node);
|
|
2117
2489
|
case "columns":
|
|
2118
2490
|
return this.renderColumns(node);
|
|
2119
2491
|
case "skills":
|
|
2120
|
-
return this.renderSkills(node
|
|
2492
|
+
return this.renderSkills(node);
|
|
2121
2493
|
case "job":
|
|
2122
|
-
return this.renderEntry(node
|
|
2494
|
+
return this.renderEntry(node, "job");
|
|
2123
2495
|
case "education":
|
|
2124
|
-
return this.renderEntry(node
|
|
2496
|
+
return this.renderEntry(node, "education");
|
|
2125
2497
|
case "project":
|
|
2126
|
-
return this.renderEntry(node
|
|
2498
|
+
return this.renderEntry(node, "project");
|
|
2127
2499
|
case "contact":
|
|
2128
2500
|
return this.renderContactItem(node);
|
|
2129
2501
|
case "command":
|
|
@@ -2134,33 +2506,36 @@ ${this.styles()}
|
|
|
2134
2506
|
return "";
|
|
2135
2507
|
}
|
|
2136
2508
|
}
|
|
2137
|
-
renderLink(href, label) {
|
|
2509
|
+
renderLink(href, label, src = "") {
|
|
2138
2510
|
const { safe } = sanitizeUrl(href);
|
|
2139
2511
|
const external = /^https?:/i.test(safe);
|
|
2140
2512
|
const rel = external ? ' target="_blank" rel="noopener noreferrer"' : "";
|
|
2141
|
-
return `<a class="${this.cls("link")}" href="${escapeAttribute(safe)}"${rel}>${label}</a>`;
|
|
2513
|
+
return `<a class="${this.cls("link")}"${src} href="${escapeAttribute(safe)}"${rel}>${label}</a>`;
|
|
2142
2514
|
}
|
|
2143
2515
|
renderList(node) {
|
|
2144
2516
|
const tag = node.kind === "enumerate" ? "ol" : "ul";
|
|
2145
|
-
const items = node.items.map(
|
|
2146
|
-
|
|
2517
|
+
const items = node.items.map(
|
|
2518
|
+
(item) => `<li${this.src(item)}>${this.renderInline(item.children).trim()}</li>`
|
|
2519
|
+
).join("");
|
|
2520
|
+
return `<${tag} class="${this.cls("list")}"${this.src(node)}>${items}</${tag}>`;
|
|
2147
2521
|
}
|
|
2148
2522
|
renderColumns(node) {
|
|
2149
2523
|
const cols = node.columns.map((col) => {
|
|
2150
2524
|
const basis = isSafeDimension(col.width) ? col.width : "auto";
|
|
2151
2525
|
const flex = basis === "auto" ? "flex:1 1 0" : `flex:0 0 ${basis}`;
|
|
2152
|
-
return `<div class="${this.cls("column")}" style="${flex}">${this.renderFlow(col.children)}</div>`;
|
|
2526
|
+
return `<div class="${this.cls("column")}" style="${flex}"${this.src(col)}>${this.renderFlow(col.children)}</div>`;
|
|
2153
2527
|
}).join("");
|
|
2154
|
-
return `<div class="${this.cls("columns")}">${cols}</div>`;
|
|
2528
|
+
return `<div class="${this.cls("columns")}"${this.src(node)}>${cols}</div>`;
|
|
2155
2529
|
}
|
|
2156
|
-
renderSkills(
|
|
2157
|
-
const lis = items.map((s) => `<li class="${this.cls("skill")}">${escapeHtml(s)}</li>`).join("");
|
|
2158
|
-
return `<ul class="${this.cls("skills")}">${lis}</ul>`;
|
|
2530
|
+
renderSkills(node) {
|
|
2531
|
+
const lis = node.items.map((s) => `<li class="${this.cls("skill")}">${escapeHtml(s)}</li>`).join("");
|
|
2532
|
+
return `<ul class="${this.cls("skills")}"${this.src(node)}>${lis}</ul>`;
|
|
2159
2533
|
}
|
|
2160
|
-
renderEntry(
|
|
2534
|
+
renderEntry(node, kind) {
|
|
2535
|
+
const { fields, children: body } = node;
|
|
2161
2536
|
const { title, subtitle, dates, location, url } = entryParts(fields, kind);
|
|
2162
2537
|
const titleHtml = url ? this.renderLink(sanitizeUrl(url).safe, escapeHtml(title)) : escapeHtml(title);
|
|
2163
|
-
let html = `<div class="${this.cls("entry")} ${this.cls(kind)}">`;
|
|
2538
|
+
let html = `<div class="${this.cls("entry")} ${this.cls(kind)}"${this.src(node)}>`;
|
|
2164
2539
|
html += `<div class="${this.cls("entry-row")}">`;
|
|
2165
2540
|
html += `<span class="${this.cls("entry-title")}">${titleHtml}</span>`;
|
|
2166
2541
|
if (dates)
|
|
@@ -2182,12 +2557,38 @@ ${this.styles()}
|
|
|
2182
2557
|
renderCommand(node) {
|
|
2183
2558
|
if (node.name === "center") {
|
|
2184
2559
|
const inner2 = node.args.flatMap((a) => a.children);
|
|
2185
|
-
return `<div class="${this.cls("center")}">${this.renderFlow(inner2)}</div>`;
|
|
2560
|
+
return `<div class="${this.cls("center")}"${this.src(node)}>${this.renderFlow(inner2)}</div>`;
|
|
2186
2561
|
}
|
|
2187
2562
|
const inner = node.args.flatMap((a) => a.children);
|
|
2188
2563
|
return inner.length > 0 ? this.renderInline(inner) : "";
|
|
2189
2564
|
}
|
|
2190
2565
|
/* ------------------------------ helpers ----------------------------- */
|
|
2566
|
+
/**
|
|
2567
|
+
* Source-position attributes for a node — `data-rtx-pos="start:end"` plus a
|
|
2568
|
+
* `data-rtx-type`. Returns an empty string unless `sourceMap` is enabled and
|
|
2569
|
+
* the node carries a range. Powers the live-preview two-way sync: a client
|
|
2570
|
+
* reads these to map a clicked element back to the exact source offsets (and,
|
|
2571
|
+
* in reverse, to find the element for a caret offset).
|
|
2572
|
+
*/
|
|
2573
|
+
src(node, type) {
|
|
2574
|
+
if (!this.sourceMap || !node?.range) return "";
|
|
2575
|
+
const kind = type ?? node.type;
|
|
2576
|
+
const kindAttr = kind ? ` data-rtx-type="${kind}"` : "";
|
|
2577
|
+
return ` data-rtx-pos="${node.range.start.offset}:${node.range.end.offset}"${kindAttr}`;
|
|
2578
|
+
}
|
|
2579
|
+
/** Like {@link src}, but spans a run of inline nodes (merges their ranges). */
|
|
2580
|
+
srcSpan(nodes, type) {
|
|
2581
|
+
if (!this.sourceMap) return "";
|
|
2582
|
+
let start = Infinity;
|
|
2583
|
+
let end = -Infinity;
|
|
2584
|
+
for (const n of nodes) {
|
|
2585
|
+
if (!n.range) continue;
|
|
2586
|
+
if (n.range.start.offset < start) start = n.range.start.offset;
|
|
2587
|
+
if (n.range.end.offset > end) end = n.range.end.offset;
|
|
2588
|
+
}
|
|
2589
|
+
if (end < 0) return "";
|
|
2590
|
+
return ` data-rtx-pos="${start}:${end}" data-rtx-type="${type}"`;
|
|
2591
|
+
}
|
|
2191
2592
|
icon(name) {
|
|
2192
2593
|
const svg = iconToSvg(name, { className: this.cls("icon") });
|
|
2193
2594
|
if (svg) return svg;
|
|
@@ -2229,8 +2630,12 @@ function renderJson(ast, opts = {}) {
|
|
|
2229
2630
|
}
|
|
2230
2631
|
|
|
2231
2632
|
// src/renderers/pdf.ts
|
|
2633
|
+
var pageSize = (v) => v && sanitizeStyleValue(v) || "Letter";
|
|
2634
|
+
var pageMargin = (v) => v && isSafeDimension(v) ? v : "0.5in";
|
|
2232
2635
|
function pageCss(theme) {
|
|
2233
|
-
|
|
2636
|
+
const size = pageSize(theme.page?.size);
|
|
2637
|
+
const margin = pageMargin(theme.page?.margin);
|
|
2638
|
+
return `@page { size: ${size}; margin: ${margin}; }
|
|
2234
2639
|
html, body { margin: 0; padding: 0; background: #fff; }
|
|
2235
2640
|
@media screen { body { background: #f1f5f9; padding: 24px; } .retex-resume { box-shadow: 0 1px 8px rgba(0,0,0,.12); } }`;
|
|
2236
2641
|
}
|
|
@@ -2265,16 +2670,13 @@ async function renderPdf(ast, options = {}) {
|
|
|
2265
2670
|
try {
|
|
2266
2671
|
const page = await browser.newPage();
|
|
2267
2672
|
await page.setContent(html, { waitUntil: "networkidle0" });
|
|
2673
|
+
const size = pageSize(theme.page?.size);
|
|
2674
|
+
const margin = pageMargin(theme.page?.margin);
|
|
2268
2675
|
return await page.pdf({
|
|
2269
2676
|
printBackground: options.printBackground ?? true,
|
|
2270
2677
|
preferCSSPageSize: true,
|
|
2271
|
-
format:
|
|
2272
|
-
margin: {
|
|
2273
|
-
top: theme.page.margin,
|
|
2274
|
-
bottom: theme.page.margin,
|
|
2275
|
-
left: theme.page.margin,
|
|
2276
|
-
right: theme.page.margin
|
|
2277
|
-
}
|
|
2678
|
+
format: size,
|
|
2679
|
+
margin: { top: margin, bottom: margin, left: margin, right: margin }
|
|
2278
2680
|
});
|
|
2279
2681
|
} finally {
|
|
2280
2682
|
await browser.close();
|
|
@@ -2304,7 +2706,7 @@ var ReactRenderer = class {
|
|
|
2304
2706
|
"ReactRenderer requires a `createElement` factory (e.g. React.createElement)."
|
|
2305
2707
|
);
|
|
2306
2708
|
}
|
|
2307
|
-
this.theme = options.theme
|
|
2709
|
+
this.theme = resolveTheme(options.theme);
|
|
2308
2710
|
this.prefix = options.classPrefix ?? "retex";
|
|
2309
2711
|
this.h = options.createElement;
|
|
2310
2712
|
this.Fragment = options.Fragment ?? "div";
|
|
@@ -2340,10 +2742,11 @@ var ReactRenderer = class {
|
|
|
2340
2742
|
}
|
|
2341
2743
|
/* --------------------------- structuring ---------------------------- */
|
|
2342
2744
|
renderSection(section, body) {
|
|
2745
|
+
const className = this.theme.sectionStyle ? `${this.cls("section")} ${this.cls("section")}--${this.theme.sectionStyle}` : this.cls("section");
|
|
2343
2746
|
return this.el(
|
|
2344
2747
|
"section",
|
|
2345
2748
|
{
|
|
2346
|
-
className
|
|
2749
|
+
className
|
|
2347
2750
|
},
|
|
2348
2751
|
[
|
|
2349
2752
|
this.el("h2", { className: this.cls("section-title") }, [section.title]),
|
|
@@ -2409,10 +2812,14 @@ var ReactRenderer = class {
|
|
|
2409
2812
|
return node.value;
|
|
2410
2813
|
case "parbreak":
|
|
2411
2814
|
return null;
|
|
2815
|
+
case "usepackage":
|
|
2816
|
+
return null;
|
|
2412
2817
|
case "linebreak":
|
|
2413
2818
|
return this.el("br", null, []);
|
|
2414
|
-
case "rule":
|
|
2415
|
-
|
|
2819
|
+
case "rule": {
|
|
2820
|
+
const variant = node.style && node.style !== "solid" ? ` ${this.cls(`rule--${node.style}`)}` : "";
|
|
2821
|
+
return this.el("hr", { className: `${this.cls("rule")}${variant}` }, []);
|
|
2822
|
+
}
|
|
2416
2823
|
case "space":
|
|
2417
2824
|
return node.axis === "vertical" ? this.el("div", { style: { height: this.dim(node.size) } }, []) : this.el(
|
|
2418
2825
|
"span",
|
|
@@ -2626,17 +3033,20 @@ function renderReact(ast, options) {
|
|
|
2626
3033
|
}
|
|
2627
3034
|
|
|
2628
3035
|
// src/engine.ts
|
|
2629
|
-
var
|
|
3036
|
+
var isFullTheme = (t) => typeof t?.name === "string" && t.name.length > 0;
|
|
2630
3037
|
var ReTeXEngine = class {
|
|
2631
3038
|
constructor(options = {}) {
|
|
2632
3039
|
this.htmlOverrides = /* @__PURE__ */ new Map();
|
|
2633
3040
|
this.reactOverrides = /* @__PURE__ */ new Map();
|
|
3041
|
+
/** Libraries available to `\usepackage` (built-ins are resolved separately). */
|
|
3042
|
+
this.customLibraries = /* @__PURE__ */ new Map();
|
|
2634
3043
|
this.cache = /* @__PURE__ */ new Map();
|
|
2635
3044
|
this.generation = 0;
|
|
2636
3045
|
this.registry = createDefaultRegistry();
|
|
2637
|
-
this.theme =
|
|
3046
|
+
this.theme = resolveTheme(options.theme);
|
|
2638
3047
|
this.prefix = options.classPrefix ?? "retex";
|
|
2639
3048
|
this.cacheSize = options.cacheSize ?? 64;
|
|
3049
|
+
for (const lib of options.libraries ?? []) this.provideLibrary(lib);
|
|
2640
3050
|
for (const plugin of options.plugins ?? []) this.use(plugin);
|
|
2641
3051
|
}
|
|
2642
3052
|
/* ----------------------------- plugins ------------------------------ */
|
|
@@ -2653,10 +3063,21 @@ var ReTeXEngine = class {
|
|
|
2653
3063
|
this.registerReactRenderer(key, fn);
|
|
2654
3064
|
}
|
|
2655
3065
|
if (plugin.theme) this.setTheme(plugin.theme);
|
|
3066
|
+
if (plugin.name) this.customLibraries.set(plugin.name.trim().toLowerCase(), plugin);
|
|
2656
3067
|
plugin.setup?.(this);
|
|
2657
3068
|
this.invalidate();
|
|
2658
3069
|
return this;
|
|
2659
3070
|
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Register a library so it can be activated with `\usepackage{name}`, without
|
|
3073
|
+
* installing it globally. Built-in libraries are always available; use this
|
|
3074
|
+
* for custom ones.
|
|
3075
|
+
*/
|
|
3076
|
+
provideLibrary(library) {
|
|
3077
|
+
this.customLibraries.set(library.name.trim().toLowerCase(), library);
|
|
3078
|
+
this.invalidate();
|
|
3079
|
+
return this;
|
|
3080
|
+
}
|
|
2660
3081
|
registerCommand(def) {
|
|
2661
3082
|
const { render, ...spec } = def;
|
|
2662
3083
|
this.registry.registerCommand(spec);
|
|
@@ -2684,7 +3105,7 @@ var ReTeXEngine = class {
|
|
|
2684
3105
|
}
|
|
2685
3106
|
/* ----------------------------- theming ------------------------------ */
|
|
2686
3107
|
setTheme(theme) {
|
|
2687
|
-
this.theme =
|
|
3108
|
+
this.theme = isFullTheme(theme) ? resolveTheme(theme, defaultTheme) : resolveTheme(theme, this.theme);
|
|
2688
3109
|
this.invalidate();
|
|
2689
3110
|
return this;
|
|
2690
3111
|
}
|
|
@@ -2705,11 +3126,22 @@ var ReTeXEngine = class {
|
|
|
2705
3126
|
const cached = this.cache.get(key);
|
|
2706
3127
|
if (cached) return cached;
|
|
2707
3128
|
const { tokens, diagnostics: lexDiags } = tokenize(source);
|
|
2708
|
-
const
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
3129
|
+
const usedNames = usedLibraryNamesFromTokens(tokens);
|
|
3130
|
+
const { registry, libraries } = applyLibraries(
|
|
3131
|
+
this.registry,
|
|
3132
|
+
usedNames,
|
|
3133
|
+
(n) => this.lookupLibrary(n)
|
|
3134
|
+
);
|
|
3135
|
+
const { ast, diagnostics: parseDiags } = new Parser(tokens, { registry }).parse();
|
|
3136
|
+
let diagnostics = libraryDiagnostics(
|
|
3137
|
+
ast,
|
|
3138
|
+
[...lexDiags, ...parseDiags],
|
|
3139
|
+
(n) => this.lookupLibrary(n)
|
|
3140
|
+
);
|
|
3141
|
+
if (runValidate) {
|
|
3142
|
+
const theme = mergeLibraryTheme(this.theme, libraries);
|
|
3143
|
+
diagnostics.push(...validate(ast, { theme }));
|
|
3144
|
+
}
|
|
2713
3145
|
const result = { source, tokens, ast, diagnostics };
|
|
2714
3146
|
this.remember(key, result);
|
|
2715
3147
|
return result;
|
|
@@ -2719,54 +3151,97 @@ var ReTeXEngine = class {
|
|
|
2719
3151
|
}
|
|
2720
3152
|
validate(input) {
|
|
2721
3153
|
if (typeof input === "string") return this.compile(input).diagnostics;
|
|
2722
|
-
return validate(input, { theme: this.theme });
|
|
3154
|
+
return validate(input, { theme: this.bundle(input).theme });
|
|
2723
3155
|
}
|
|
2724
3156
|
/* ----------------------------- renderers ---------------------------- */
|
|
2725
3157
|
toHtml(input, options = {}) {
|
|
2726
|
-
|
|
3158
|
+
const b = this.bundle(input);
|
|
3159
|
+
return new HtmlRenderer({
|
|
3160
|
+
theme: b.theme,
|
|
3161
|
+
classPrefix: this.prefix,
|
|
3162
|
+
overrides: b.htmlOverrides,
|
|
3163
|
+
...options
|
|
3164
|
+
}).render(b.ast);
|
|
2727
3165
|
}
|
|
2728
3166
|
toHtmlDocument(input, options = {}) {
|
|
2729
|
-
|
|
3167
|
+
const b = this.bundle(input);
|
|
3168
|
+
return new HtmlRenderer({
|
|
3169
|
+
theme: b.theme,
|
|
3170
|
+
classPrefix: this.prefix,
|
|
3171
|
+
overrides: b.htmlOverrides,
|
|
3172
|
+
...options
|
|
3173
|
+
}).renderDocument(b.ast, options.title);
|
|
2730
3174
|
}
|
|
2731
3175
|
toReact(input, options) {
|
|
3176
|
+
const b = this.bundle(input);
|
|
2732
3177
|
return new ReactRenderer({
|
|
2733
3178
|
...options,
|
|
2734
|
-
theme:
|
|
3179
|
+
theme: b.theme,
|
|
2735
3180
|
classPrefix: this.prefix,
|
|
2736
|
-
overrides:
|
|
2737
|
-
}).render(
|
|
3181
|
+
overrides: b.reactOverrides
|
|
3182
|
+
}).render(b.ast);
|
|
2738
3183
|
}
|
|
2739
3184
|
toJson(input, options) {
|
|
2740
3185
|
return renderJson(this.astOf(input), options);
|
|
2741
3186
|
}
|
|
2742
3187
|
toPrintHtml(input, options = {}) {
|
|
2743
|
-
|
|
3188
|
+
const b = this.bundle(input);
|
|
3189
|
+
return renderPrintHtml(b.ast, {
|
|
3190
|
+
theme: b.theme,
|
|
3191
|
+
classPrefix: this.prefix,
|
|
3192
|
+
overrides: b.htmlOverrides,
|
|
3193
|
+
...options
|
|
3194
|
+
});
|
|
2744
3195
|
}
|
|
2745
3196
|
toPdf(input, options = {}) {
|
|
2746
|
-
|
|
3197
|
+
const b = this.bundle(input);
|
|
3198
|
+
return renderPdf(b.ast, {
|
|
3199
|
+
theme: b.theme,
|
|
3200
|
+
classPrefix: this.prefix,
|
|
3201
|
+
overrides: b.htmlOverrides,
|
|
3202
|
+
...options
|
|
3203
|
+
});
|
|
2747
3204
|
}
|
|
2748
|
-
/**
|
|
2749
|
-
|
|
2750
|
-
|
|
3205
|
+
/**
|
|
3206
|
+
* The CSS stylesheet for the active theme. Pass the document (source or AST)
|
|
3207
|
+
* to fold in any libraries it imports via `\usepackage` (so the styles match
|
|
3208
|
+
* `toHtml(input)`); omit it to get the engine theme's stylesheet.
|
|
3209
|
+
*/
|
|
3210
|
+
styles(input) {
|
|
3211
|
+
const theme = input === void 0 ? this.theme : this.bundle(input).theme;
|
|
3212
|
+
return new HtmlRenderer({ theme, classPrefix: this.prefix }).styles();
|
|
2751
3213
|
}
|
|
2752
3214
|
/* ------------------------------ internals --------------------------- */
|
|
3215
|
+
/** Resolve a library name against custom libraries first, then built-ins. */
|
|
3216
|
+
lookupLibrary(name) {
|
|
3217
|
+
return this.customLibraries.get(name.trim().toLowerCase()) ?? getBuiltinLibrary(name);
|
|
3218
|
+
}
|
|
2753
3219
|
astOf(input) {
|
|
2754
3220
|
return typeof input === "string" ? this.compile(input, { validate: false }).ast : input;
|
|
2755
3221
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
3222
|
+
/**
|
|
3223
|
+
* Build the per-document render inputs: the AST, the effective theme (engine
|
|
3224
|
+
* theme + any libraries the document imports), and the merged render
|
|
3225
|
+
* overrides. Library use is recovered from `\usepackage` nodes in the AST, so
|
|
3226
|
+
* this works whether the input was a source string or a pre-parsed document.
|
|
3227
|
+
*/
|
|
3228
|
+
bundle(input) {
|
|
3229
|
+
const ast = this.astOf(input);
|
|
3230
|
+
const libraries = usedLibraryNamesFromAst(ast).map((n) => this.lookupLibrary(n)).filter((l) => !!l);
|
|
3231
|
+
if (libraries.length === 0) {
|
|
3232
|
+
return {
|
|
3233
|
+
ast,
|
|
3234
|
+
theme: this.theme,
|
|
3235
|
+
htmlOverrides: this.htmlOverrides,
|
|
3236
|
+
reactOverrides: this.reactOverrides
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
const { html, react } = collectLibraryOverrides(libraries);
|
|
2765
3240
|
return {
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
...
|
|
3241
|
+
ast,
|
|
3242
|
+
theme: mergeLibraryTheme(this.theme, libraries),
|
|
3243
|
+
htmlOverrides: new Map([...this.htmlOverrides, ...html]),
|
|
3244
|
+
reactOverrides: new Map([...this.reactOverrides, ...react])
|
|
2770
3245
|
};
|
|
2771
3246
|
}
|
|
2772
3247
|
invalidate() {
|
|
@@ -2873,7 +3348,7 @@ function printBlocks(nodes) {
|
|
|
2873
3348
|
for (const node of nodes) {
|
|
2874
3349
|
if (node.type === "parbreak") {
|
|
2875
3350
|
flush();
|
|
2876
|
-
} else if (isBlockNode(node) || node.type === "section" || node.type === "list" || node.type === "columns") {
|
|
3351
|
+
} else if (isBlockNode(node) || node.type === "section" || node.type === "list" || node.type === "columns" || node.type === "usepackage") {
|
|
2877
3352
|
flush();
|
|
2878
3353
|
out.push(printBlock(node));
|
|
2879
3354
|
} else {
|
|
@@ -2912,8 +3387,10 @@ ${indent}\\end{columns}`;
|
|
|
2912
3387
|
}
|
|
2913
3388
|
case "skills":
|
|
2914
3389
|
return `${indent}\\skills{${node.items.join(", ")}}`;
|
|
3390
|
+
case "usepackage":
|
|
3391
|
+
return `${indent}\\usepackage{${node.names.join(", ")}}`;
|
|
2915
3392
|
case "rule":
|
|
2916
|
-
return
|
|
3393
|
+
return indent + ruleCommand(node.style);
|
|
2917
3394
|
case "space":
|
|
2918
3395
|
return `${indent}\\${node.axis === "vertical" ? "vspace" : "hspace"}{${node.size}}`;
|
|
2919
3396
|
case "command":
|
|
@@ -2965,6 +3442,8 @@ function printNode(node) {
|
|
|
2965
3442
|
return `\\url{${node.rawHref}}`;
|
|
2966
3443
|
case "icon":
|
|
2967
3444
|
return `\\icon{${node.name}}`;
|
|
3445
|
+
case "usepackage":
|
|
3446
|
+
return `\\usepackage{${node.names.join(", ")}}`;
|
|
2968
3447
|
case "contact":
|
|
2969
3448
|
return `\\${node.field}{${node.value}}`;
|
|
2970
3449
|
case "space":
|
|
@@ -2978,6 +3457,20 @@ function printNode(node) {
|
|
|
2978
3457
|
return printBlock(node);
|
|
2979
3458
|
}
|
|
2980
3459
|
}
|
|
3460
|
+
function ruleCommand(style) {
|
|
3461
|
+
switch (style) {
|
|
3462
|
+
case "dashed":
|
|
3463
|
+
return "\\dashrule";
|
|
3464
|
+
case "dotted":
|
|
3465
|
+
return "\\dotrule";
|
|
3466
|
+
case "thick":
|
|
3467
|
+
return "\\thickrule";
|
|
3468
|
+
case "double":
|
|
3469
|
+
return "\\doublerule";
|
|
3470
|
+
default:
|
|
3471
|
+
return "\\hrule";
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
2981
3474
|
function indentLines(text, indent) {
|
|
2982
3475
|
return text.split("\n").map((line) => line ? indent + line : line).join("\n");
|
|
2983
3476
|
}
|
|
@@ -2985,22 +3478,34 @@ function indentLines(text, indent) {
|
|
|
2985
3478
|
// src/editor/editor.ts
|
|
2986
3479
|
var EditorService = class {
|
|
2987
3480
|
constructor(options = {}) {
|
|
3481
|
+
this.customLibraries = /* @__PURE__ */ new Map();
|
|
3482
|
+
this.lookupLibrary = (name) => this.customLibraries.get(name.trim().toLowerCase()) ?? getBuiltinLibrary(name);
|
|
2988
3483
|
this.registry = options.registry ?? createDefaultRegistry();
|
|
2989
3484
|
this.theme = options.theme;
|
|
3485
|
+
for (const lib of options.libraries ?? []) {
|
|
3486
|
+
this.customLibraries.set(lib.name.trim().toLowerCase(), lib);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
/** Build a registry that includes the libraries the source imports. */
|
|
3490
|
+
registryFor(tokens) {
|
|
3491
|
+
const names = usedLibraryNamesFromTokens(tokens);
|
|
3492
|
+
return applyLibraries(this.registry, names, this.lookupLibrary).registry;
|
|
2990
3493
|
}
|
|
2991
3494
|
/* ----------------------------- diagnostics -------------------------- */
|
|
2992
3495
|
getDiagnostics(source) {
|
|
2993
3496
|
const { tokens, diagnostics: lex } = tokenize(source);
|
|
2994
|
-
const
|
|
2995
|
-
|
|
2996
|
-
}).parse();
|
|
2997
|
-
|
|
3497
|
+
const names = usedLibraryNamesFromTokens(tokens);
|
|
3498
|
+
const { registry, libraries } = applyLibraries(this.registry, names, this.lookupLibrary);
|
|
3499
|
+
const { ast, diagnostics: parse2 } = new Parser(tokens, { registry }).parse();
|
|
3500
|
+
const diagnostics = libraryDiagnostics(ast, [...lex, ...parse2], this.lookupLibrary);
|
|
3501
|
+
const theme = this.theme ? mergeLibraryTheme(this.theme, libraries) : void 0;
|
|
3502
|
+
return [...diagnostics, ...validate(ast, theme ? { theme } : {})];
|
|
2998
3503
|
}
|
|
2999
3504
|
/* ------------------------------ inspect ----------------------------- */
|
|
3000
3505
|
/** Parse and return the AST for inspection / debugging. */
|
|
3001
3506
|
inspect(source) {
|
|
3002
3507
|
const { tokens } = tokenize(source);
|
|
3003
|
-
return new Parser(tokens, { registry: this.
|
|
3508
|
+
return new Parser(tokens, { registry: this.registryFor(tokens) }).parse().ast;
|
|
3004
3509
|
}
|
|
3005
3510
|
/* ------------------------------ format ------------------------------ */
|
|
3006
3511
|
format(source) {
|
|
@@ -3009,11 +3514,12 @@ var EditorService = class {
|
|
|
3009
3514
|
/* --------------------------- completion ----------------------------- */
|
|
3010
3515
|
getCompletions(source, offset) {
|
|
3011
3516
|
const before = source.slice(0, offset);
|
|
3517
|
+
const registry = this.registryFor(tokenize(source).tokens);
|
|
3012
3518
|
const envMatch = /\\(begin|end)\{([a-zA-Z*]*)$/.exec(before);
|
|
3013
3519
|
if (envMatch) {
|
|
3014
3520
|
const prefix = envMatch[2];
|
|
3015
3521
|
const start = offset - prefix.length;
|
|
3016
|
-
return
|
|
3522
|
+
return registry.allEnvironments().filter((e) => e.name.startsWith(prefix)).map((e) => this.environmentCompletion(e, this.rangeAt(source, start, offset)));
|
|
3017
3523
|
}
|
|
3018
3524
|
const fieldKeys = this.fieldContext(before);
|
|
3019
3525
|
if (fieldKeys) {
|
|
@@ -3028,7 +3534,18 @@ var EditorService = class {
|
|
|
3028
3534
|
if (cmdMatch) {
|
|
3029
3535
|
const prefix = cmdMatch[1];
|
|
3030
3536
|
const start = offset - prefix.length - 1;
|
|
3031
|
-
|
|
3537
|
+
const range2 = this.rangeAt(source, start, offset);
|
|
3538
|
+
const active = registry.allCommands().filter((c) => c.name.startsWith(prefix)).sort((a, b) => a.name.localeCompare(b.name)).map((c) => this.commandCompletion(c, range2));
|
|
3539
|
+
const present = new Set(registry.commandNames());
|
|
3540
|
+
const gated = Object.entries(GATED_COMMANDS).filter(([name]) => name.startsWith(prefix) && !present.has(name)).sort(([a], [b]) => a.localeCompare(b)).map(([name, lib]) => ({
|
|
3541
|
+
label: `\\${name}`,
|
|
3542
|
+
kind: "command",
|
|
3543
|
+
detail: `requires \\usepackage{${lib}}`,
|
|
3544
|
+
documentation: `\`\\${name}\` is provided by the **${lib}** library. Add \`\\usepackage{${lib}}\` to your preamble to use it.`,
|
|
3545
|
+
insertText: name,
|
|
3546
|
+
range: range2
|
|
3547
|
+
}));
|
|
3548
|
+
return [...active, ...gated];
|
|
3032
3549
|
}
|
|
3033
3550
|
return [];
|
|
3034
3551
|
}
|
|
@@ -3079,6 +3596,7 @@ var EditorService = class {
|
|
|
3079
3596
|
/* ------------------------------ hover ------------------------------- */
|
|
3080
3597
|
getHover(source, offset) {
|
|
3081
3598
|
const { tokens } = tokenize(source);
|
|
3599
|
+
const registry = this.registryFor(tokens);
|
|
3082
3600
|
const idx = tokens.findIndex(
|
|
3083
3601
|
(t) => t.type === "Command" /* Command */ && offset >= t.range.start.offset && offset <= t.range.end.offset
|
|
3084
3602
|
);
|
|
@@ -3086,7 +3604,7 @@ var EditorService = class {
|
|
|
3086
3604
|
const token = tokens[idx];
|
|
3087
3605
|
if (token.value === "begin" || token.value === "end") {
|
|
3088
3606
|
const name = this.readNameAfter(tokens, idx);
|
|
3089
|
-
const env = name ?
|
|
3607
|
+
const env = name ? registry.getEnvironment(name) : void 0;
|
|
3090
3608
|
if (env) {
|
|
3091
3609
|
return {
|
|
3092
3610
|
contents: this.environmentDoc(env),
|
|
@@ -3095,12 +3613,13 @@ var EditorService = class {
|
|
|
3095
3613
|
}
|
|
3096
3614
|
return null;
|
|
3097
3615
|
}
|
|
3098
|
-
const def =
|
|
3616
|
+
const def = registry.getCommand(token.value);
|
|
3099
3617
|
if (!def) {
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3618
|
+
const lib = GATED_COMMANDS[token.value];
|
|
3619
|
+
const contents = lib ? `\`\\${token.value}\` is provided by the **${lib}** library.
|
|
3620
|
+
|
|
3621
|
+
Add \`\\usepackage{${lib}}\` to your preamble to use it.` : `Unknown command \`\\${token.value}\``;
|
|
3622
|
+
return { contents, range: token.range };
|
|
3104
3623
|
}
|
|
3105
3624
|
return { contents: this.commandDoc(def), range: token.range };
|
|
3106
3625
|
}
|
|
@@ -3131,6 +3650,7 @@ ${def.example}
|
|
|
3131
3650
|
/* ------------------------ semantic highlighting --------------------- */
|
|
3132
3651
|
getSemanticTokens(source) {
|
|
3133
3652
|
const { tokens } = tokenize(source);
|
|
3653
|
+
const registry = this.registryFor(tokens);
|
|
3134
3654
|
const out = [];
|
|
3135
3655
|
for (let i = 0; i < tokens.length; i++) {
|
|
3136
3656
|
const t = tokens[i];
|
|
@@ -3140,7 +3660,7 @@ ${def.example}
|
|
|
3140
3660
|
out.push({ range: t.range, type: "environment", modifiers: [] });
|
|
3141
3661
|
break;
|
|
3142
3662
|
}
|
|
3143
|
-
const def =
|
|
3663
|
+
const def = registry.getCommand(t.value);
|
|
3144
3664
|
out.push({
|
|
3145
3665
|
range: t.range,
|
|
3146
3666
|
type: def ? "command" : "command-unknown",
|
|
@@ -3203,6 +3723,53 @@ ${def.example}
|
|
|
3203
3723
|
}
|
|
3204
3724
|
};
|
|
3205
3725
|
|
|
3726
|
+
// src/editor/sync.ts
|
|
3727
|
+
var SOURCE_POS_ATTR = "data-rtx-pos";
|
|
3728
|
+
var SOURCE_TYPE_ATTR = "data-rtx-type";
|
|
3729
|
+
function parseSourcePos(value) {
|
|
3730
|
+
if (!value) return null;
|
|
3731
|
+
const sep = value.indexOf(":");
|
|
3732
|
+
if (sep === -1) return null;
|
|
3733
|
+
const start = Number.parseInt(value.slice(0, sep), 10);
|
|
3734
|
+
const end = Number.parseInt(value.slice(sep + 1), 10);
|
|
3735
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null;
|
|
3736
|
+
return { start, end };
|
|
3737
|
+
}
|
|
3738
|
+
function readSourcePos(el) {
|
|
3739
|
+
if (!el) return null;
|
|
3740
|
+
return parseSourcePos(el.getAttribute(SOURCE_POS_ATTR));
|
|
3741
|
+
}
|
|
3742
|
+
function readSourceType(el) {
|
|
3743
|
+
return el?.getAttribute(SOURCE_TYPE_ATTR) ?? null;
|
|
3744
|
+
}
|
|
3745
|
+
function closestSourcePos(el) {
|
|
3746
|
+
if (!el) return null;
|
|
3747
|
+
const match = el.closest(`[${SOURCE_POS_ATTR}]`);
|
|
3748
|
+
return readSourcePos(match);
|
|
3749
|
+
}
|
|
3750
|
+
function spanLength(span) {
|
|
3751
|
+
return span.end - span.start;
|
|
3752
|
+
}
|
|
3753
|
+
function spanContains(span, offset) {
|
|
3754
|
+
return offset >= span.start && offset <= span.end;
|
|
3755
|
+
}
|
|
3756
|
+
function pickElementForOffset(elements, offset) {
|
|
3757
|
+
let best = null;
|
|
3758
|
+
let bestLen = Infinity;
|
|
3759
|
+
let bestStart = -1;
|
|
3760
|
+
for (const el of elements) {
|
|
3761
|
+
const span = readSourcePos(el);
|
|
3762
|
+
if (!span || !spanContains(span, offset)) continue;
|
|
3763
|
+
const len = spanLength(span);
|
|
3764
|
+
if (len < bestLen || len === bestLen && span.start > bestStart) {
|
|
3765
|
+
best = el;
|
|
3766
|
+
bestLen = len;
|
|
3767
|
+
bestStart = span.start;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
return best;
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3206
3773
|
// src/incremental/incremental.ts
|
|
3207
3774
|
var isLetter2 = (c) => c >= "a" && c <= "z" || c >= "A" && c <= "Z";
|
|
3208
3775
|
var IncrementalCompiler = class {
|
|
@@ -3214,6 +3781,11 @@ var IncrementalCompiler = class {
|
|
|
3214
3781
|
this.hits = 0;
|
|
3215
3782
|
this.misses = 0;
|
|
3216
3783
|
this.registry = options.registry ?? createDefaultRegistry();
|
|
3784
|
+
const custom = /* @__PURE__ */ new Map();
|
|
3785
|
+
for (const lib of options.libraries ?? []) {
|
|
3786
|
+
custom.set(lib.name.trim().toLowerCase(), lib);
|
|
3787
|
+
}
|
|
3788
|
+
this.lookupLibrary = (name) => custom.get(name.trim().toLowerCase()) ?? getBuiltinLibrary(name);
|
|
3217
3789
|
}
|
|
3218
3790
|
/** Clear all caches (e.g. after registering new commands). */
|
|
3219
3791
|
reset() {
|
|
@@ -3223,6 +3795,9 @@ var IncrementalCompiler = class {
|
|
|
3223
3795
|
compile(source) {
|
|
3224
3796
|
this.hits = 0;
|
|
3225
3797
|
this.misses = 0;
|
|
3798
|
+
const usedNames = usedLibraryNamesFromTokens(tokenize(source).tokens);
|
|
3799
|
+
const registry = applyLibraries(this.registry, usedNames, this.lookupLibrary).registry;
|
|
3800
|
+
const libsKey = usedNames.join(",");
|
|
3226
3801
|
const segments = splitBalancedSegments(source);
|
|
3227
3802
|
const children = [];
|
|
3228
3803
|
const diagnostics = [];
|
|
@@ -3231,22 +3806,21 @@ var IncrementalCompiler = class {
|
|
|
3231
3806
|
for (const seg of segments) {
|
|
3232
3807
|
const text = source.slice(seg.start, seg.end);
|
|
3233
3808
|
if (text.trim() === "") continue;
|
|
3234
|
-
const posKey = `${seg.start}:${seg.startLine}:${text}`;
|
|
3809
|
+
const posKey = `${libsKey}\0${seg.start}:${seg.startLine}:${text}`;
|
|
3235
3810
|
let resolved = this.positioned.get(posKey);
|
|
3236
3811
|
if (resolved) {
|
|
3237
3812
|
this.hits++;
|
|
3238
3813
|
} else {
|
|
3239
|
-
|
|
3814
|
+
const cacheKey = `${libsKey}\0${text}`;
|
|
3815
|
+
let parsed = this.parseCache.get(cacheKey);
|
|
3240
3816
|
if (parsed) {
|
|
3241
3817
|
this.hits++;
|
|
3242
3818
|
} else {
|
|
3243
3819
|
this.misses++;
|
|
3244
3820
|
const { tokens } = tokenize(text);
|
|
3245
|
-
const { ast: ast2, diagnostics: d } = new Parser(tokens, {
|
|
3246
|
-
registry: this.registry
|
|
3247
|
-
}).parse();
|
|
3821
|
+
const { ast: ast2, diagnostics: d } = new Parser(tokens, { registry }).parse();
|
|
3248
3822
|
parsed = { children: ast2.children, diagnostics: d };
|
|
3249
|
-
this.parseCache.set(
|
|
3823
|
+
this.parseCache.set(cacheKey, parsed);
|
|
3250
3824
|
}
|
|
3251
3825
|
const dOffset = seg.start;
|
|
3252
3826
|
const dLine = seg.startLine - 1;
|
|
@@ -3269,7 +3843,7 @@ var IncrementalCompiler = class {
|
|
|
3269
3843
|
};
|
|
3270
3844
|
return {
|
|
3271
3845
|
ast,
|
|
3272
|
-
diagnostics,
|
|
3846
|
+
diagnostics: libraryDiagnostics(ast, diagnostics, this.lookupLibrary),
|
|
3273
3847
|
stats: {
|
|
3274
3848
|
segments: segments.length,
|
|
3275
3849
|
cacheHits: this.hits,
|
|
@@ -3372,19 +3946,32 @@ exports.CommandRegistry = CommandRegistry;
|
|
|
3372
3946
|
exports.DiagnosticCode = DiagnosticCode;
|
|
3373
3947
|
exports.DiagnosticSeverity = DiagnosticSeverity;
|
|
3374
3948
|
exports.EditorService = EditorService;
|
|
3949
|
+
exports.GATED_COMMANDS = GATED_COMMANDS;
|
|
3375
3950
|
exports.HtmlRenderer = HtmlRenderer;
|
|
3376
3951
|
exports.IncrementalCompiler = IncrementalCompiler;
|
|
3377
3952
|
exports.Parser = Parser;
|
|
3378
3953
|
exports.ReTeXEngine = ReTeXEngine;
|
|
3379
3954
|
exports.ReactRenderer = ReactRenderer;
|
|
3955
|
+
exports.SOURCE_POS_ATTR = SOURCE_POS_ATTR;
|
|
3956
|
+
exports.SOURCE_TYPE_ATTR = SOURCE_TYPE_ATTR;
|
|
3380
3957
|
exports.SPECIAL_CHARS = SPECIAL_CHARS;
|
|
3381
3958
|
exports.TokenType = TokenType;
|
|
3382
3959
|
exports.Tokenizer = Tokenizer;
|
|
3960
|
+
exports.applyLibraries = applyLibraries;
|
|
3383
3961
|
exports.badgePlugin = badgePlugin;
|
|
3962
|
+
exports.blankTheme = blankTheme;
|
|
3963
|
+
exports.builtinLibraries = builtinLibraries;
|
|
3964
|
+
exports.builtinLibraryNames = builtinLibraryNames;
|
|
3384
3965
|
exports.childrenOf = childrenOf;
|
|
3966
|
+
exports.classicLibrary = classicLibrary;
|
|
3385
3967
|
exports.classicTheme = classicTheme;
|
|
3386
3968
|
exports.closestMatch = closestMatch;
|
|
3969
|
+
exports.closestSourcePos = closestSourcePos;
|
|
3387
3970
|
exports.collect = collect;
|
|
3971
|
+
exports.collectLibraryOverrides = collectLibraryOverrides;
|
|
3972
|
+
exports.colorCommands = colorCommands;
|
|
3973
|
+
exports.colorsLibrary = colorsLibrary;
|
|
3974
|
+
exports.compactLibrary = compactLibrary;
|
|
3388
3975
|
exports.compactTheme = compactTheme;
|
|
3389
3976
|
exports.createDefaultRegistry = createDefaultRegistry;
|
|
3390
3977
|
exports.createEngine = createEngine;
|
|
@@ -3394,6 +3981,11 @@ exports.entryParts = entryParts;
|
|
|
3394
3981
|
exports.escapeAttribute = escapeAttribute;
|
|
3395
3982
|
exports.escapeHtml = escapeHtml;
|
|
3396
3983
|
exports.flattenText = flattenText;
|
|
3984
|
+
exports.fontCommands = fontCommands;
|
|
3985
|
+
exports.fontStacks = fontStacks;
|
|
3986
|
+
exports.fontTheme = fontTheme;
|
|
3987
|
+
exports.fontsLibrary = fontsLibrary;
|
|
3988
|
+
exports.getBuiltinLibrary = getBuiltinLibrary;
|
|
3397
3989
|
exports.getIcon = getIcon;
|
|
3398
3990
|
exports.getTheme = getTheme;
|
|
3399
3991
|
exports.hasIcon = hasIcon;
|
|
@@ -3406,19 +3998,27 @@ exports.isParent = isParent;
|
|
|
3406
3998
|
exports.isSafeColor = isSafeColor;
|
|
3407
3999
|
exports.isSafeDimension = isSafeDimension;
|
|
3408
4000
|
exports.levenshtein = levenshtein;
|
|
4001
|
+
exports.libraryDiagnostics = libraryDiagnostics;
|
|
4002
|
+
exports.mergeLibraryTheme = mergeLibraryTheme;
|
|
3409
4003
|
exports.mergeRanges = mergeRanges;
|
|
4004
|
+
exports.modernLibrary = modernLibrary;
|
|
3410
4005
|
exports.modernTheme = modernTheme;
|
|
3411
4006
|
exports.nodePathAt = nodePathAt;
|
|
3412
4007
|
exports.normalizeWhitespace = normalizeWhitespace;
|
|
3413
4008
|
exports.pageCss = pageCss;
|
|
4009
|
+
exports.palettes = palettes;
|
|
3414
4010
|
exports.parse = parse;
|
|
3415
4011
|
exports.parseKeyValArg = parseKeyValArg;
|
|
3416
4012
|
exports.parseListArg = parseListArg;
|
|
4013
|
+
exports.parseSourcePos = parseSourcePos;
|
|
4014
|
+
exports.pickElementForOffset = pickElementForOffset;
|
|
3417
4015
|
exports.pos = pos;
|
|
3418
4016
|
exports.printDocument = printDocument;
|
|
3419
4017
|
exports.range = range;
|
|
3420
4018
|
exports.rangeContains = rangeContains;
|
|
3421
4019
|
exports.ratingPlugin = ratingPlugin;
|
|
4020
|
+
exports.readSourcePos = readSourcePos;
|
|
4021
|
+
exports.readSourceType = readSourceType;
|
|
3422
4022
|
exports.registerIcon = registerIcon;
|
|
3423
4023
|
exports.renderHtml = renderHtml;
|
|
3424
4024
|
exports.renderHtmlDocument = renderHtmlDocument;
|
|
@@ -3427,9 +4027,14 @@ exports.renderPdf = renderPdf;
|
|
|
3427
4027
|
exports.renderPrintHtml = renderPrintHtml;
|
|
3428
4028
|
exports.renderReact = renderReact;
|
|
3429
4029
|
exports.resolveIconName = resolveIconName;
|
|
4030
|
+
exports.resolveLibraryName = resolveLibraryName;
|
|
3430
4031
|
exports.resolveTheme = resolveTheme;
|
|
3431
4032
|
exports.sanitizeStyleValue = sanitizeStyleValue;
|
|
3432
4033
|
exports.sanitizeUrl = sanitizeUrl;
|
|
4034
|
+
exports.shapeCommands = shapeCommands;
|
|
4035
|
+
exports.shapesLibrary = shapesLibrary;
|
|
4036
|
+
exports.spanContains = spanContains;
|
|
4037
|
+
exports.spanLength = spanLength;
|
|
3433
4038
|
exports.splitBalancedSegments = splitBalancedSegments;
|
|
3434
4039
|
exports.splitPreamble = splitPreamble;
|
|
3435
4040
|
exports.splitTopLevel = splitTopLevel;
|
|
@@ -3438,6 +4043,8 @@ exports.themes = themes;
|
|
|
3438
4043
|
exports.toJsonTree = toJsonTree;
|
|
3439
4044
|
exports.toRegions = toRegions;
|
|
3440
4045
|
exports.tokenize = tokenize;
|
|
4046
|
+
exports.usedLibraryNamesFromAst = usedLibraryNamesFromAst;
|
|
4047
|
+
exports.usedLibraryNamesFromTokens = usedLibraryNamesFromTokens;
|
|
3441
4048
|
exports.validate = validate;
|
|
3442
4049
|
exports.walk = walk;
|
|
3443
4050
|
//# sourceMappingURL=index.cjs.map
|