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