@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/dist/index.d.ts CHANGED
@@ -1,226 +1,7 @@
1
- import { S as SourceRange, A as ArgumentNode, N as Node, C as CommandNode, T as Theme, D as DocumentNode, a as ContactNode, b as SectionNode, F as FieldMap, R as ReactRenderFn, P as PartialTheme, c as ReactRenderOptions } from './react-v8gyKEAs.js';
2
- export { d as ColorNode, e as ColumnNode, f as ColumnsNode, g as ContactField, E as EducationNode, h as ErrorNode, i as FontFamilyNode, j as FontScale, k as FontScaleNode, l as FontSizeNode, G as GroupNode, I as IconNode, J as JobNode, m as JsxFactory, L as LineBreakNode, n as LinkNode, o as ListItemNode, p as ListKind, q as ListNode, M as MarkNode, r as MarkType, s as NodeBase, t as NodeMap, u as NodeType, v as ParBreakNode, w as ParentNode, x as Position, y as ProjectNode, z as ReactRenderContext, B as ReactRenderer, H as RuleNode, K as SkillsNode, O as SpaceNode, Q as TextNode, U as ThemeColorNode, V as ThemeColors, W as ThemeFontSizes, X as ThemeFonts, Y as ThemePage, Z as ThemeSpacing, _ as UrlNode, $ as isNode, a0 as isParent, a1 as mergeRanges, a2 as pos, a3 as range, a4 as rangeContains, a5 as renderReact } from './react-v8gyKEAs.js';
3
-
4
- /**
5
- * Token kinds produced by the {@link Tokenizer}.
6
- *
7
- * ReTeX intentionally diverges from TeX in a few places that matter for
8
- * resumes:
9
- * - `%` is a *literal* percent sign (so `Reduced latency by 40%` and
10
- * `\column{40%}` work as written) rather than a comment marker.
11
- * - Comments use `%%` to end-of-line, which never collides with percentages.
12
- */
13
- declare enum TokenType {
14
- /** A command such as `\section`, `\textbf`, `\item`. The lexeme excludes the backslash. */
15
- Command = "Command",
16
- /** `{` — begin group / argument. */
17
- LBrace = "LBrace",
18
- /** `}` — end group / argument. */
19
- RBrace = "RBrace",
20
- /** `[` — begin optional argument. */
21
- LBracket = "LBracket",
22
- /** `]` — end optional argument. */
23
- RBracket = "RBracket",
24
- /** Run of literal text. */
25
- Text = "Text",
26
- /** Run of inline whitespace (spaces/tabs, single newline). */
27
- Whitespace = "Whitespace",
28
- /** A blank line — paragraph break. */
29
- ParBreak = "ParBreak",
30
- /** `\\` — explicit line break. */
31
- LineBreak = "LineBreak",
32
- /** `%% ...` comment (stripped from output, surfaced to editor tooling). */
33
- Comment = "Comment",
34
- /** End of input. */
35
- EOF = "EOF"
36
- }
37
- /** A lexical token with full source provenance. */
38
- interface Token {
39
- type: TokenType;
40
- /** The exact source text of the token (backslash stripped for commands). */
41
- value: string;
42
- range: SourceRange;
43
- }
44
- /** Reserved single characters that the tokenizer treats specially. */
45
- declare const SPECIAL_CHARS: Set<string>;
46
-
47
- /** Severity levels, ordered the same way LSP orders them. */
48
- declare enum DiagnosticSeverity {
49
- Error = "error",
50
- Warning = "warning",
51
- Info = "info",
52
- Hint = "hint"
53
- }
54
- /**
55
- * Stable, machine-readable diagnostic codes. Editors can key quick-fixes and
56
- * documentation links off these rather than the (localizable) message text.
57
- */
58
- declare enum DiagnosticCode {
59
- UnexpectedToken = "RTX1001",
60
- UnterminatedGroup = "RTX1002",
61
- UnterminatedArgument = "RTX1003",
62
- UnexpectedEOF = "RTX1004",
63
- MismatchedBrace = "RTX1005",
64
- UnknownCommand = "RTX2001",
65
- UnknownEnvironment = "RTX2002",
66
- MissingRequiredArgument = "RTX2003",
67
- TooManyArguments = "RTX2004",
68
- MissingEnvironmentEnd = "RTX2005",
69
- MismatchedEnvironment = "RTX2006",
70
- InvalidColor = "RTX3001",
71
- InvalidUrl = "RTX3002",
72
- InvalidDimension = "RTX3003",
73
- MissingRequiredField = "RTX3004",
74
- UnknownField = "RTX3005",
75
- EmptyArgument = "RTX3006",
76
- CommandOutsideContext = "RTX4001",
77
- UnknownIcon = "RTX4002",
78
- UnknownThemeColor = "RTX4003",
79
- UnsafeUrlBlocked = "RTX5001"
80
- }
81
- /** Optional quick-fix an editor can apply. */
82
- interface QuickFix {
83
- title: string;
84
- /** Replacement text for {@link Diagnostic.range}. */
85
- replacement: string;
86
- range?: SourceRange;
87
- }
88
- /** A single diagnostic emitted by any stage of the pipeline. */
89
- interface Diagnostic {
90
- severity: DiagnosticSeverity;
91
- code: DiagnosticCode;
92
- message: string;
93
- range: SourceRange;
94
- /** Stage that produced the diagnostic, for filtering. */
95
- source: "tokenizer" | "parser" | "validator" | "security";
96
- /** Optional editor quick-fixes. */
97
- fixes?: QuickFix[];
98
- }
99
-
100
- /**
101
- * How a single argument should be consumed by the parser.
102
- *
103
- * - `content` — recursively parse markup (nested commands, text).
104
- * - `string` — verbatim text; no command expansion (URLs, colors, sizes).
105
- * - `keyval` — `key=value, key=value` map.
106
- * - `list` — comma-separated list of trimmed strings.
107
- */
108
- type ArgKind = "content" | "string" | "keyval" | "list";
109
- interface ArgSpec {
110
- kind: ArgKind;
111
- /** When true the argument may be absent (no diagnostic if missing). */
112
- optional?: boolean;
113
- /**
114
- * Delimiter pair. `"brace"` (default) → `{...}`; `"bracket"` → `[...]`.
115
- * An optional brace argument (e.g. an entry body) is still written with
116
- * `{...}` but may be omitted.
117
- */
118
- delimiter?: "brace" | "bracket";
119
- /** Human-readable name surfaced in hover docs / diagnostics. */
120
- name?: string;
121
- /** Validation hint for `string` args (drives format checks). */
122
- format?: "color" | "url" | "dimension" | "text";
123
- }
124
- /**
125
- * Semantic category of a command. Drives default rendering and where the
126
- * command is legal.
127
- *
128
- * - `inline` — flows within text (`\textbf`).
129
- * - `block` — starts a block (`\section`, `\job`).
130
- * - `switch` — a state switch that applies to the rest of its group
131
- * (`\large`, `\bfseries`-style).
132
- * - `meta` — document metadata that produces no inline flow on its own.
133
- */
134
- type CommandCategory = "inline" | "block" | "switch" | "meta";
135
- /**
136
- * A factory the parser calls once arguments are consumed, to build the AST
137
- * node for a command. Returning `null` drops the command from the tree.
138
- */
139
- type NodeBuilder = (ctx: BuildContext) => Node | Node[] | null;
140
- interface BuildContext {
141
- name: string;
142
- args: ArgumentNode[];
143
- /** For switches: the trailing content the switch applies to. */
144
- scope: Node[];
145
- /** Emit a diagnostic from within a builder. */
146
- report: (d: Omit<Diagnostic, "source">) => void;
147
- /** Utilities exposed to plugin authors. */
148
- utils: BuilderUtils;
149
- }
150
- interface BuilderUtils {
151
- /** Flatten an argument's children into a plain string (best-effort). */
152
- textOf(arg: ArgumentNode | undefined): string;
153
- /** Sanitize a URL; returns `"#"` (and reports) when unsafe. */
154
- safeUrl(url: string): string;
155
- }
156
- /**
157
- * The contract for a command. Used by the parser (argument signature, scope
158
- * behavior) and the renderers (when no native node exists, renderers consult
159
- * the command's per-target `render` map).
160
- */
161
- interface CommandDefinition {
162
- name: string;
163
- category?: CommandCategory;
164
- /** Argument signature, in order. */
165
- args?: ArgSpec[];
166
- /**
167
- * `true` for switches that swallow the remainder of their enclosing group
168
- * as scoped content (`\large`, `\itshape`).
169
- */
170
- scoped?: boolean;
171
- /**
172
- * Builds the AST node. When omitted, the parser emits a generic
173
- * {@link CommandNode} carrying the parsed arguments.
174
- */
175
- build?: NodeBuilder;
176
- /** One-line summary for hover docs / completion detail. */
177
- summary?: string;
178
- /** Longer documentation (markdown) for hover. */
179
- documentation?: string;
180
- /** Example snippet shown in completion / hover. */
181
- example?: string;
182
- }
183
- /** Environment (`\begin{x} … \end{x}`) contract. */
184
- interface EnvironmentDefinition {
185
- name: string;
186
- /**
187
- * Command that introduces each entry inside the environment, e.g. `item`
188
- * for `itemize`, `column` for `columns`. The parser uses this to slice
189
- * the body into entries.
190
- */
191
- itemCommand?: string;
192
- /** Commands legal as direct children (for validation). `*` allows any. */
193
- allowedChildren?: string[];
194
- /** Build the environment node from its parsed body. */
195
- build?: (ctx: EnvBuildContext) => Node | Node[] | null;
196
- summary?: string;
197
- documentation?: string;
198
- example?: string;
199
- }
200
- /**
201
- * One entry of an `itemCommand`-delimited environment. For `itemize`, the
202
- * marker command is `\item`; for `columns`, it is `\column{width}`.
203
- */
204
- interface EnvEntry {
205
- /** The marker command that introduced the entry, with its parsed arguments. */
206
- marker: CommandNode;
207
- /** Content nodes belonging to this entry (up to the next marker / `\end`). */
208
- content: Node[];
209
- }
210
- interface EnvBuildContext {
211
- name: string;
212
- /** Parsed body nodes of the environment (always present). */
213
- body: Node[];
214
- /**
215
- * When the environment declares an `itemCommand`, the body sliced into
216
- * entries at each marker. `undefined` for plain block environments.
217
- */
218
- entries?: EnvEntry[];
219
- /** Optional `[...]` arguments after `\begin{env}`. */
220
- options: ArgumentNode[];
221
- report: (d: Omit<Diagnostic, "source">) => void;
222
- utils: BuilderUtils;
223
- }
1
+ import { T as Token, D as Diagnostic, H as HtmlRenderFn, R as ReTeXPlugin, a as ReTeXLibrary, P as PluginHost, E as EngineCommand, b as EnvironmentDefinition, I as IconDefinition, C as CommandRegistry } from './index-DJEasLJc.js';
2
+ export { A as AppliedLibraries, c as ArgKind, d as ArgSpec, B as BLOCK_TYPES, e as BuildContext, f as BuilderUtils, g as CommandCategory, h as CommandDefinition, i as DiagnosticCode, j as DiagnosticSeverity, k as EnvBuildContext, l as EnvEntry, G as GATED_COMMANDS, m as HtmlRenderContext, L as LibraryLookup, N as NodeBuilder, Q as QuickFix, S as SPECIAL_CHARS, n as TokenType, o as applyLibraries, p as builtinLibraries, q as builtinLibraryNames, r as classicLibrary, s as collectLibraryOverrides, t as colorCommands, u as colorsLibrary, v as compactLibrary, w as fontCommands, x as fontStacks, y as fontTheme, z as fontsLibrary, F as getBuiltinLibrary, J as getIcon, K as hasIcon, M as iconNames, O as iconToSvg, U as isBlockNode, V as libraryDiagnostics, W as mergeLibraryTheme, X as modernLibrary, Y as palettes, Z as registerIcon, _ as resolveIconName, $ as resolveLibraryName, a0 as shapeCommands, a1 as shapesLibrary, a2 as usedLibraryNamesFromAst, a3 as usedLibraryNamesFromTokens } from './index-DJEasLJc.js';
3
+ import { T as Theme, D as DocumentNode, N as Node, C as ContactNode, S as SectionNode, F as FieldMap, P as PartialTheme, R as ReactRenderFn, a as ReactRenderOptions, b as SourceRange } from './react-B3tN1iWa.js';
4
+ export { A as ArgumentNode, c as ColorNode, d as ColumnNode, e as ColumnsNode, f as CommandNode, g as ContactField, E as EducationNode, h as ErrorNode, i as FontFamilyNode, j as FontScale, k as FontScaleNode, l as FontSizeNode, G as GroupNode, I as IconNode, J as JobNode, m as JsxFactory, L as LineBreakNode, n as LinkNode, o as ListItemNode, p as ListKind, q as ListNode, M as MarkNode, r as MarkType, s as NodeBase, t as NodeMap, u as NodeType, v as ParBreakNode, w as ParentNode, x as Position, y as ProjectNode, z as ReactRenderContext, B as ReactRenderer, H as RuleNode, K as SkillsNode, O as SpaceNode, Q as TextNode, U as ThemeColorNode, V as ThemeColors, W as ThemeFontSizes, X as ThemeFonts, Y as ThemeHeadings, Z as ThemePage, _ as ThemeSpacing, $ as UrlNode, a0 as UsePackageNode, a1 as isNode, a2 as isParent, a3 as mergeRanges, a4 as pos, a5 as range, a6 as rangeContains, a7 as renderReact } from './react-B3tN1iWa.js';
224
5
 
225
6
  interface TokenizeResult {
226
7
  tokens: Token[];
@@ -262,86 +43,22 @@ declare class Tokenizer {
262
43
  declare function tokenize(source: string): TokenizeResult;
263
44
 
264
45
  /**
265
- * The command/environment registry. The parser consults it for argument
266
- * signatures and node builders; the plugin system mutates it. A registry can
267
- * be cloned cheaply so an engine instance never mutates shared global state.
268
- */
269
- declare class CommandRegistry {
270
- private commands;
271
- private environments;
272
- registerCommand(def: CommandDefinition): this;
273
- registerEnvironment(def: EnvironmentDefinition): this;
274
- getCommand(name: string): CommandDefinition | undefined;
275
- getEnvironment(name: string): EnvironmentDefinition | undefined;
276
- hasCommand(name: string): boolean;
277
- hasEnvironment(name: string): boolean;
278
- commandNames(): string[];
279
- environmentNames(): string[];
280
- allCommands(): CommandDefinition[];
281
- allEnvironments(): EnvironmentDefinition[];
282
- /** Shallow-clone the registry (definitions are shared, maps are copied). */
283
- clone(): CommandRegistry;
284
- }
285
-
286
- /**
287
- * Icon registry. Icons are stored as the inner markup of a `0 0 24 24` SVG and
288
- * use `currentColor` so they inherit surrounding text color. The set is
289
- * intentionally small and extensible via {@link registerIcon} / plugins.
46
+ * Generate the stylesheet for a rendered resume from a {@link Theme}.
290
47
  *
291
- * Glyphs are simplified, original representations (not vendor logo artwork) so
292
- * the package carries no third-party asset licensing.
293
- */
294
- interface IconDefinition {
295
- /** Inner SVG markup (paths/shapes) with a `0 0 24 24` viewBox. */
296
- body: string;
297
- /** Whether shapes are stroked (true) or filled (false). */
298
- stroked?: boolean;
299
- /** Aliases that resolve to this icon. */
300
- aliases?: string[];
301
- }
302
- /** Resolve a (possibly aliased) icon name to its canonical key. */
303
- declare function resolveIconName(name: string): string | undefined;
304
- declare function hasIcon(name: string): boolean;
305
- declare function getIcon(name: string): IconDefinition | undefined;
306
- /** Register (or override) an icon at runtime. Used by plugins. */
307
- declare function registerIcon(name: string, def: IconDefinition): void;
308
- /** All canonical icon names, for completion / docs. */
309
- declare function iconNames(): string[];
310
- /**
311
- * Render an icon to an inline SVG string. Returns `null` for unknown icons so
312
- * callers can emit a diagnostic and a graceful fallback.
313
- */
314
- declare function iconToSvg(name: string, opts?: {
315
- size?: number | string;
316
- className?: string;
317
- }): string | null;
318
-
319
- /**
320
- * Generate the stylesheet for a rendered resume from a {@link Theme}. Theme
321
- * values become CSS custom properties so they can be overridden at runtime,
322
- * and every theme color is exposed as `--retex-color-<token>` for use by
323
- * `\themecolor`.
48
+ * Themes are **blank by default**: any field may be `undefined`. The structural
49
+ * layout (flex rows, list indents, spacing rhythm) is always emitted, while all
50
+ * *decoration* (accent colors, fonts, uppercased headings, skill chips, section
51
+ * rules) is driven by CSS custom properties whose fallbacks are neutral. So an
52
+ * unstyled theme produces a clean, browser-default document, and importing a
53
+ * library/theme simply fills in those variables.
54
+ *
55
+ * Every embedded theme value is vetted before it reaches the stylesheet
56
+ * (`isSafeColor` / `isSafeDimension` / `sanitizeStyleValue`), so even a
57
+ * user-supplied theme cannot inject CSS. Each color is also exposed as
58
+ * `--retex-color-<token>` for use by `\themecolor`.
324
59
  */
325
60
  declare function themeToCss(theme: Theme, prefix?: string): string;
326
61
 
327
- /** Node types that participate in block (vertical) flow. */
328
- declare const BLOCK_TYPES: Set<string>;
329
- declare function isBlockNode(node: Node): boolean;
330
- /**
331
- * Plugin HTML renderer. Receives the node, the active context, and a callback
332
- * to recursively render child nodes to HTML.
333
- */
334
- type HtmlRenderFn = (node: Node, ctx: HtmlRenderContext, renderChildren: (nodes: Node[]) => string) => string;
335
- interface HtmlRenderContext {
336
- theme: Theme;
337
- classPrefix: string;
338
- /** Override renderers keyed by node `type` or `command:<name>`. */
339
- overrides: Map<string, HtmlRenderFn>;
340
- /** Render arbitrary inline/block nodes to an HTML string. */
341
- renderNodes: (nodes: Node[]) => string;
342
- cls: (name: string) => string;
343
- }
344
-
345
62
  interface HtmlRenderOptions {
346
63
  theme?: Theme;
347
64
  /** CSS class prefix. Default `"retex"`. */
@@ -350,6 +67,14 @@ interface HtmlRenderOptions {
350
67
  overrides?: Map<string, HtmlRenderFn>;
351
68
  /** Group leading contact commands into a `<header>`. Default `true`. */
352
69
  header?: boolean;
70
+ /**
71
+ * Emit source-position attributes (`data-rtx-pos="start:end"` and
72
+ * `data-rtx-type`) on rendered elements so a live editor can map preview
73
+ * clicks back to the source and vice-versa (Overleaf-style two-way sync).
74
+ * Offsets are zero-based UTF-16 code-unit offsets into the original source.
75
+ * Default `false` — final/exported HTML stays clean and unannotated.
76
+ */
77
+ sourceMap?: boolean;
353
78
  }
354
79
  /**
355
80
  * The HTML renderer. Produces clean, semantic, fully-escaped HTML. Every text
@@ -361,6 +86,7 @@ declare class HtmlRenderer {
361
86
  private readonly prefix;
362
87
  private readonly overrides;
363
88
  private readonly useHeader;
89
+ private readonly sourceMap;
364
90
  private readonly ctx;
365
91
  constructor(options?: HtmlRenderOptions);
366
92
  /** Render the resume body as an HTML fragment (no `<html>`/`<style>`). */
@@ -383,6 +109,16 @@ declare class HtmlRenderer {
383
109
  private renderSkills;
384
110
  private renderEntry;
385
111
  private renderCommand;
112
+ /**
113
+ * Source-position attributes for a node — `data-rtx-pos="start:end"` plus a
114
+ * `data-rtx-type`. Returns an empty string unless `sourceMap` is enabled and
115
+ * the node carries a range. Powers the live-preview two-way sync: a client
116
+ * reads these to map a clicked element back to the exact source offsets (and,
117
+ * in reverse, to find the element for a caret offset).
118
+ */
119
+ private src;
120
+ /** Like {@link src}, but spans a run of inline nodes (merges their ranges). */
121
+ private srcSpan;
386
122
  private icon;
387
123
  private dim;
388
124
  private cls;
@@ -481,50 +217,15 @@ interface EntryParts {
481
217
  declare function entryParts(fields: FieldMap, kind: string): EntryParts;
482
218
  declare function dateRange(start?: string, end?: string, date?: string): string;
483
219
 
484
- /**
485
- * A command definition extended with optional per-target render functions.
486
- * Passing `render.html` is shorthand for registering an HTML override keyed by
487
- * `command:<name>` — the common case for a plugin command with no custom AST
488
- * node (e.g. `\badge{...}`).
489
- */
490
- interface EngineCommand extends CommandDefinition {
491
- render?: {
492
- html?: HtmlRenderFn;
493
- react?: ReactRenderFn;
494
- };
495
- }
496
- /**
497
- * A ReTeX plugin. Everything is optional; a plugin may contribute commands,
498
- * environments, icons, renderers, theme overrides, or run arbitrary setup
499
- * against the engine. Plugins never touch global state — they mutate only the
500
- * engine instance they are installed on.
501
- */
502
- interface ReTeXPlugin {
503
- name: string;
504
- commands?: EngineCommand[];
505
- environments?: EnvironmentDefinition[];
506
- icons?: Record<string, IconDefinition>;
507
- /** HTML overrides keyed by node `type` or `command:<name>`. */
508
- htmlRenderers?: Record<string, HtmlRenderFn>;
509
- /** React overrides keyed by node `type` or `command:<name>`. */
510
- reactRenderers?: Record<string, ReactRenderFn>;
511
- /** A theme patch applied when the plugin is installed. */
512
- theme?: PartialTheme;
513
- /** Imperative hook for advanced setup. */
514
- setup?: (engine: PluginHost) => void;
515
- }
516
- /** The subset of the engine surface a plugin's `setup` hook may use. */
517
- interface PluginHost {
518
- registerCommand(def: EngineCommand): PluginHost;
519
- registerEnvironment(def: EnvironmentDefinition): PluginHost;
520
- registerHtmlRenderer(key: string, fn: HtmlRenderFn): PluginHost;
521
- registerReactRenderer(key: string, fn: ReactRenderFn): PluginHost;
522
- registerIcon(name: string, def: IconDefinition): PluginHost;
523
- }
524
-
525
220
  interface EngineOptions {
526
221
  theme?: Theme | PartialTheme;
527
222
  plugins?: ReTeXPlugin[];
223
+ /**
224
+ * Libraries to make available to `\usepackage` *without* activating them
225
+ * globally. Built-in libraries (`fonts`, `shapes`, `colors`, `modern`, …) are
226
+ * always available; use this to register your own.
227
+ */
228
+ libraries?: ReTeXLibrary[];
528
229
  classPrefix?: string;
529
230
  /** Max number of compiled documents to cache. Default 64. */
530
231
  cacheSize?: number;
@@ -541,13 +242,15 @@ interface CompileOptions {
541
242
  }
542
243
  /**
543
244
  * The ReTeX engine: the single entry point that wires the tokenizer, parser,
544
- * validator, renderers, theming, plugins, and caching together.
245
+ * validator, renderers, theming, libraries, plugins, and caching together.
246
+ *
247
+ * ReTeX is blank by default. Styling is opt-in via **libraries**, activated
248
+ * either from the document (`\usepackage{colors}`) or from code
249
+ * (`engine.use(colorsLibrary)`).
545
250
  *
546
251
  * ```ts
547
- * const engine = new ReTeXEngine({ theme: { colors: { primary: "#7c3aed" } } });
548
- * engine.registerCommand({ name: "badge", args: [{ kind: "content" }],
549
- * render: { html: (n, ctx, kids) => `<span class="badge">${kids((n as any).args[0].children)}</span>` } });
550
- * const html = engine.toHtml(source);
252
+ * const engine = new ReTeXEngine();
253
+ * engine.toHtml("\\usepackage{colors}\n\\textcolor{#7c3aed}{Hi}");
551
254
  * ```
552
255
  */
553
256
  declare class ReTeXEngine implements PluginHost {
@@ -556,11 +259,19 @@ declare class ReTeXEngine implements PluginHost {
556
259
  private readonly prefix;
557
260
  private readonly htmlOverrides;
558
261
  private readonly reactOverrides;
262
+ /** Libraries available to `\usepackage` (built-ins are resolved separately). */
263
+ private readonly customLibraries;
559
264
  private readonly cache;
560
265
  private readonly cacheSize;
561
266
  private generation;
562
267
  constructor(options?: EngineOptions);
563
268
  use(plugin: ReTeXPlugin): this;
269
+ /**
270
+ * Register a library so it can be activated with `\usepackage{name}`, without
271
+ * installing it globally. Built-in libraries are always available; use this
272
+ * for custom ones.
273
+ */
274
+ provideLibrary(library: ReTeXLibrary): this;
564
275
  registerCommand(def: EngineCommand): this;
565
276
  registerEnvironment(def: EnvironmentDefinition): this;
566
277
  registerHtmlRenderer(key: string, fn: HtmlRenderFn): this;
@@ -582,11 +293,22 @@ declare class ReTeXEngine implements PluginHost {
582
293
  toJson(input: string | DocumentNode, options?: JsonRenderOptions): string;
583
294
  toPrintHtml(input: string | DocumentNode, options?: PrintOptions): string;
584
295
  toPdf(input: string | DocumentNode, options?: PdfOptions): Promise<Uint8Array>;
585
- /** The CSS stylesheet for the active theme. */
586
- styles(): string;
296
+ /**
297
+ * The CSS stylesheet for the active theme. Pass the document (source or AST)
298
+ * to fold in any libraries it imports via `\usepackage` (so the styles match
299
+ * `toHtml(input)`); omit it to get the engine theme's stylesheet.
300
+ */
301
+ styles(input?: string | DocumentNode): string;
302
+ /** Resolve a library name against custom libraries first, then built-ins. */
303
+ private lookupLibrary;
587
304
  private astOf;
588
- private htmlRenderer;
589
- private printOptions;
305
+ /**
306
+ * Build the per-document render inputs: the AST, the effective theme (engine
307
+ * theme + any libraries the document imports), and the merged render
308
+ * overrides. Library use is recovered from `\usepackage` nodes in the AST, so
309
+ * this works whether the input was a source string or a pre-parsed document.
310
+ */
311
+ private bundle;
590
312
  private invalidate;
591
313
  private remember;
592
314
  }
@@ -666,9 +388,13 @@ declare class Parser {
666
388
  declare function parse(tokens: Token[], options?: ParserOptions): ParseResult;
667
389
 
668
390
  /**
669
- * Register every built-in command and environment into a fresh registry.
670
- * The engine calls this once per instance so plugins can extend a private
671
- * copy without touching global state.
391
+ * Register every built-in (core) command and environment into a fresh registry.
392
+ * The core is intentionally minimal: document structure, basic emphasis, links,
393
+ * sections, entries, icons, and the `\usepackage` directive. Visual styling
394
+ * (color, fonts, shapes) is added by libraries — see `src/library`.
395
+ *
396
+ * The engine calls this once per instance so plugins/libraries can extend a
397
+ * private copy without touching global state.
672
398
  */
673
399
  declare function createDefaultRegistry(): CommandRegistry;
674
400
 
@@ -729,12 +455,32 @@ declare function flattenText(input: Node | Node[]): string;
729
455
  declare function normalizeWhitespace(s: string): string;
730
456
 
731
457
  /**
732
- * The default ReTeX theme a clean, ATS-friendly look that reads well both on
733
- * screen and in print. All other themes are partial overrides of this one.
458
+ * The default ReTeX theme is **blank** it defines no colors, fonts, sizes, or
459
+ * section decoration. A document rendered with it is a clean, browser-default
460
+ * page: black text, the user agent's default font, plain section headings, and
461
+ * no accent colors or rules. This is "just simple editing".
462
+ *
463
+ * Visual styling is opt-in. Import a library to populate this theme:
464
+ *
465
+ * - `\usepackage{fonts}` / `engine.use(fontsLibrary)` — fonts & sizes
466
+ * - `\usepackage{shapes}` / `engine.use(shapesLibrary)` — rules, dividers, spacing
467
+ * - `\usepackage{colors}` / `engine.use(colorsLibrary)` — text color & palettes
468
+ * - `\usepackage{modern}` / `engine.use(modernTheme)` — a full styled look
469
+ *
470
+ * Every renderer treats theme values as optional and substitutes a neutral,
471
+ * unstyled fallback when a field is absent, so the blank theme always produces
472
+ * a readable document.
734
473
  */
474
+ declare const blankTheme: Theme;
475
+ /** Alias kept for backwards compatibility — the default theme is the blank one. */
735
476
  declare const defaultTheme: Theme;
736
477
 
737
- /** Deep-merge a partial theme over a base theme (sub-objects merged shallowly). */
478
+ /**
479
+ * Deep-merge a partial theme over a base theme (sub-objects merged shallowly).
480
+ * The result always has defined (possibly empty) sub-objects, so consumers can
481
+ * safely read `theme.colors`, `theme.fonts`, … without null checks even when
482
+ * the base is the blank theme.
483
+ */
738
484
  declare function resolveTheme(partial?: PartialTheme, base?: Theme): Theme;
739
485
  /** A modern, accent-forward theme with underlined section headings. */
740
486
  declare const modernTheme: Theme;
@@ -742,9 +488,13 @@ declare const modernTheme: Theme;
742
488
  declare const classicTheme: Theme;
743
489
  /** A compact, single-accent theme that maximizes content density. */
744
490
  declare const compactTheme: Theme;
745
- /** Built-in theme presets keyed by name. */
491
+ /**
492
+ * Built-in theme presets keyed by name. `default` and `blank` both map to the
493
+ * unstyled theme; the styled presets are opt-in (import them, or activate them
494
+ * with `\usepackage{modern}` etc.).
495
+ */
746
496
  declare const themes: Record<string, Theme>;
747
- /** Look up a preset theme by name, falling back to the default. */
497
+ /** Look up a preset theme by name, falling back to the blank default. */
748
498
  declare function getTheme(name: string): Theme;
749
499
 
750
500
  /**
@@ -832,6 +582,11 @@ interface SemanticToken {
832
582
  interface EditorServiceOptions {
833
583
  registry?: CommandRegistry;
834
584
  theme?: Theme;
585
+ /**
586
+ * Custom libraries addressable via `\usepackage{name}`. Built-in libraries
587
+ * (`fonts`, `shapes`, `colors`, `modern`, …) are always available.
588
+ */
589
+ libraries?: ReTeXLibrary[];
835
590
  }
836
591
  /**
837
592
  * Editor integration surface: everything a code editor (Monaco, CodeMirror, an
@@ -842,7 +597,11 @@ interface EditorServiceOptions {
842
597
  declare class EditorService {
843
598
  private readonly registry;
844
599
  private readonly theme?;
600
+ private readonly customLibraries;
845
601
  constructor(options?: EditorServiceOptions);
602
+ private lookupLibrary;
603
+ /** Build a registry that includes the libraries the source imports. */
604
+ private registryFor;
846
605
  getDiagnostics(source: string): Diagnostic[];
847
606
  /** Parse and return the AST for inspection / debugging. */
848
607
  inspect(source: string): DocumentNode;
@@ -868,6 +627,83 @@ declare class EditorService {
868
627
  */
869
628
  declare function printDocument(ast: DocumentNode): string;
870
629
 
630
+ /**
631
+ * Two-way preview sync — the editor-agnostic half of ReTeX's Overleaf-style
632
+ * "click the preview, jump to the source" feature (and its reverse).
633
+ *
634
+ * The {@link HtmlRenderer} `sourceMap` option stamps every meaningful element
635
+ * with a `data-rtx-pos="start:end"` attribute (zero-based UTF-16 offsets into
636
+ * the source) and a `data-rtx-type`. These helpers are the small, dependency-
637
+ * free glue a host editor uses on top of those attributes:
638
+ *
639
+ * - **Preview → code** (reverse sync): on a click in the preview, read the
640
+ * nearest annotated ancestor's span and select/scroll to it in the editor.
641
+ *
642
+ * ```ts
643
+ * preview.addEventListener("click", (e) => {
644
+ * const span = closestSourcePos(e.target as Element);
645
+ * if (span) {
646
+ * textarea.focus();
647
+ * textarea.setSelectionRange(span.start, span.end);
648
+ * }
649
+ * });
650
+ * ```
651
+ *
652
+ * - **Code → preview** (forward sync): on caret move, find the tightest
653
+ * element whose span contains the caret offset and highlight it.
654
+ *
655
+ * ```ts
656
+ * const el = pickElementForOffset(
657
+ * preview.querySelectorAll(`[${SOURCE_POS_ATTR}]`),
658
+ * textarea.selectionStart,
659
+ * );
660
+ * el?.scrollIntoView({ block: "center" });
661
+ * ```
662
+ *
663
+ * Every function is pure and works with any object exposing `getAttribute`
664
+ * (real DOM elements, or test doubles), so this module never touches `document`
665
+ * and stays usable outside the browser.
666
+ */
667
+ /** Attribute carrying a `"start:end"` source-offset span. */
668
+ declare const SOURCE_POS_ATTR = "data-rtx-pos";
669
+ /** Attribute carrying the originating AST node type (e.g. `"section"`). */
670
+ declare const SOURCE_TYPE_ATTR = "data-rtx-type";
671
+ /** A half-open `[start, end)` span of UTF-16 offsets into the source text. */
672
+ interface SourceSpan {
673
+ start: number;
674
+ end: number;
675
+ }
676
+ /** The minimal shape these helpers need from a preview element. */
677
+ interface AttrElement {
678
+ getAttribute(name: string): string | null;
679
+ }
680
+ /** An element that can also walk up to its nearest matching ancestor. */
681
+ interface ClosestElement extends AttrElement {
682
+ closest(selectors: string): (AttrElement & ClosestElement) | null;
683
+ }
684
+ /** Parse a `"start:end"` attribute value into a {@link SourceSpan}. */
685
+ declare function parseSourcePos(value: string | null | undefined): SourceSpan | null;
686
+ /** Read the {@link SOURCE_POS_ATTR} span off a single element. */
687
+ declare function readSourcePos(el: AttrElement | null | undefined): SourceSpan | null;
688
+ /** Read the {@link SOURCE_TYPE_ATTR} off a single element. */
689
+ declare function readSourceType(el: AttrElement | null | undefined): string | null;
690
+ /**
691
+ * Reverse sync: from a clicked element, walk up to the nearest annotated
692
+ * ancestor and return its span. Uses the DOM `closest` when available.
693
+ */
694
+ declare function closestSourcePos(el: ClosestElement | null | undefined): SourceSpan | null;
695
+ /** Width of a span; used to prefer the most specific (smallest) match. */
696
+ declare function spanLength(span: SourceSpan): number;
697
+ /** Whether `offset` falls within `span` (both ends inclusive). */
698
+ declare function spanContains(span: SourceSpan, offset: number): boolean;
699
+ /**
700
+ * Forward sync: from a set of annotated elements, return the single tightest
701
+ * one whose span contains `offset` — i.e. the most specific element under the
702
+ * caret. Ties on width are broken toward the later span (the deepest/innermost
703
+ * element rendered). Returns `null` when no span contains the offset.
704
+ */
705
+ declare function pickElementForOffset<T extends AttrElement>(elements: Iterable<T>, offset: number): T | null;
706
+
871
707
  interface Segment {
872
708
  start: number;
873
709
  end: number;
@@ -895,6 +731,7 @@ interface IncrementalResult {
895
731
  */
896
732
  declare class IncrementalCompiler {
897
733
  private readonly registry;
734
+ private readonly lookupLibrary;
898
735
  /** Position-independent parse cache, keyed by exact block text. */
899
736
  private readonly parseCache;
900
737
  /** Position-resolved cache, keyed by `offset:line:text` (already shifted). */
@@ -903,6 +740,7 @@ declare class IncrementalCompiler {
903
740
  private misses;
904
741
  constructor(options?: {
905
742
  registry?: CommandRegistry;
743
+ libraries?: ReTeXLibrary[];
906
744
  });
907
745
  /** Clear all caches (e.g. after registering new commands). */
908
746
  reset(): void;
@@ -913,4 +751,4 @@ declare function splitBalancedSegments(source: string): Segment[];
913
751
  /** Are braces and `\begin`/`\end` balanced in `text`? (Escapes are skipped.) */
914
752
  declare function isBalanced(text: string): boolean;
915
753
 
916
- export { type ArgKind, type ArgSpec, ArgumentNode, BLOCK_TYPES, type BuildContext, type BuilderUtils, type CommandCategory, type CommandDefinition, CommandNode, CommandRegistry, type CompileOptions, type CompileResult, type CompletionItem, type CompletionKind, ContactNode, type Diagnostic, DiagnosticCode, DiagnosticSeverity, DocumentNode, EditorService, type EditorServiceOptions, type EngineCommand, type EngineOptions, type EntryParts, type EnvBuildContext, type EnvEntry, type EnvironmentDefinition, FieldMap, type HeadlessBrowser, type HeadlessPage, type HoverInfo, type HtmlRenderContext, type HtmlRenderFn, type HtmlRenderOptions, HtmlRenderer, type IconDefinition, IncrementalCompiler, type IncrementalResult, type IncrementalStats, type JsonRenderOptions, Node, type NodeBuilder, type ParseResult, Parser, type ParserOptions, PartialTheme, type PdfOptions, type PluginHost, type PreambleParts, type PrintOptions, type QuickFix, ReTeXEngine, type ReTeXPlugin, ReactRenderFn, ReactRenderOptions, type Region, SPECIAL_CHARS, SectionNode, type SemanticToken, type SemanticTokenType, SourceRange, Theme, type Token, TokenType, type TokenizeResult, Tokenizer, type UrlSanitizeResult, type ValidateOptions, type VisitOptions, badgePlugin, childrenOf, classicTheme, closestMatch, collect, compactTheme, createDefaultRegistry, createEngine, dateRange, defaultTheme, entryParts, escapeAttribute, escapeHtml, flattenText, getIcon, getTheme, hasIcon, iconNames, iconToSvg, isBalanced, isBlockNode, isSafeColor, isSafeDimension, levenshtein, modernTheme, nodePathAt, normalizeWhitespace, pageCss, parse, parseKeyValArg, parseListArg, printDocument, ratingPlugin, registerIcon, renderHtml, renderHtmlDocument, renderJson, renderPdf, renderPrintHtml, resolveIconName, resolveTheme, sanitizeStyleValue, sanitizeUrl, splitBalancedSegments, splitPreamble, splitTopLevel, themeToCss, themes, toJsonTree, toRegions, tokenize, validate, walk };
754
+ export { type AttrElement, type ClosestElement, CommandRegistry, type CompileOptions, type CompileResult, type CompletionItem, type CompletionKind, ContactNode, Diagnostic, DocumentNode, EditorService, type EditorServiceOptions, EngineCommand, type EngineOptions, type EntryParts, EnvironmentDefinition, FieldMap, type HeadlessBrowser, type HeadlessPage, type HoverInfo, HtmlRenderFn, type HtmlRenderOptions, HtmlRenderer, IconDefinition, IncrementalCompiler, type IncrementalResult, type IncrementalStats, type JsonRenderOptions, Node, type ParseResult, Parser, type ParserOptions, PartialTheme, type PdfOptions, PluginHost, type PreambleParts, type PrintOptions, ReTeXEngine, ReTeXLibrary, ReTeXPlugin, ReactRenderFn, ReactRenderOptions, type Region, SOURCE_POS_ATTR, SOURCE_TYPE_ATTR, SectionNode, type SemanticToken, type SemanticTokenType, SourceRange, type SourceSpan, Theme, Token, type TokenizeResult, Tokenizer, type UrlSanitizeResult, type ValidateOptions, type VisitOptions, badgePlugin, blankTheme, childrenOf, classicTheme, closestMatch, closestSourcePos, collect, compactTheme, createDefaultRegistry, createEngine, dateRange, defaultTheme, entryParts, escapeAttribute, escapeHtml, flattenText, getTheme, isBalanced, isSafeColor, isSafeDimension, levenshtein, modernTheme, nodePathAt, normalizeWhitespace, pageCss, parse, parseKeyValArg, parseListArg, parseSourcePos, pickElementForOffset, printDocument, ratingPlugin, readSourcePos, readSourceType, renderHtml, renderHtmlDocument, renderJson, renderPdf, renderPrintHtml, resolveTheme, sanitizeStyleValue, sanitizeUrl, spanContains, spanLength, splitBalancedSegments, splitPreamble, splitTopLevel, themeToCss, themes, toJsonTree, toRegions, tokenize, validate, walk };