@lutrin/core 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Julien Riel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # @lutrin/core — the compiler
2
+
3
+ The engine behind [Lutrin](../../README.md): enriched Markdown (a DSL) →
4
+ PowerPoint `.pptx` or standalone HTML, with the page layout decided by the
5
+ engine. It provides the `lutrin` CLI, and serves as a library for the editor
6
+ hosts (VS Code extension, Obsidian plugin).
7
+
8
+ Neither output format is privileged: both renderers consume the **same**
9
+ geometric **scene**, in pixels on a 1280 × 720 grid.
10
+
11
+ ## Installation
12
+
13
+ The package is not published on npm yet. From a clone of the repository:
14
+
15
+ ```bash
16
+ npm install # at the root of the monorepo
17
+ npx lutrin --help
18
+
19
+ npm link -w lutrin # to call "lutrin" from anywhere
20
+ ```
21
+
22
+ Node ≥ 22 (`engines.node`). The sources are executed as they are (ESM) — no
23
+ build step.
24
+
25
+ ## CLI
26
+
27
+ ```bash
28
+ lutrin build <deck.md> [-o output.pptx|output.html] [--kit <ref>] [--vendor-assets] [--force] [--verbose]
29
+ lutrin preview <deck.md> [--port 4321] [--kit <ref>]
30
+ lutrin validate <deck.md> [--json] [--kit <ref>]
31
+ lutrin vendor <deck.md> [--kit <ref>]
32
+ lutrin inspect <deck.md> [--kit <ref>]
33
+ lutrin config [--kit <ref>] [--unset]
34
+ lutrin kit install <file.deckkit|https://…> [--force] [--name <name>]
35
+ lutrin kit list | remove <name> | create <directory> [-o <file.deckkit>]
36
+ lutrin capabilities [<deck.md>] [--kit <ref>] [--json]
37
+ ```
38
+
39
+ The output format is deduced from the extension of `-o`. `--kit` takes
40
+ precedence over the deck's frontmatter; a bare name designates an installed
41
+ kit, everything else is a path resolved against the current directory.
42
+
43
+ **`build` does not deliver a deck in error.** If validation returns at least
44
+ one diagnostic of severity `error` — unknown directive, non-existent layout,
45
+ kit requested EXPLICITLY (`--kit` or the frontmatter's `kit:`) but
46
+ unresolvable — the command prints the errors, exits with **exit code 1** and
47
+ writes no file. `--force` compiles anyway, errors on screen, and exits with
48
+ code 0: that is for a draft one wants to see. A kit coming from an
49
+ **implicit** default (project, user, host) and not found stays a mere
50
+ warning, and does not prevent compilation.
51
+
52
+ Exit codes: `0` success; `1` error — invalid argument or flag, input file not
53
+ found, explicit kit unresolvable, deck carrying `error` diagnostics (except
54
+ `build --force`). `validate` exits with 1 as soon as one `error` remains;
55
+ `inspect`, `vendor` and `capabilities` do not open the deck's diagnostics and
56
+ exit with 0 — with the one reservation that an explicit unresolvable kit
57
+ stops them too, with 1.
58
+
59
+ The DSL is documented in [docs/dsl.md](../../docs/dsl.md). `lutrin
60
+ capabilities` reports as JSON what the **installed** engine actually supports
61
+ (layouts, parameters, directives, chart types, diagnostic codes): it is the
62
+ source to query rather than to guess at.
63
+
64
+ Its scope depends on what it is given:
65
+
66
+ | Form | What is published |
67
+ |---|---|
68
+ | `lutrin capabilities` | the **bare** engine — built-in layouts and the official catalog; `userLayouts` always empty |
69
+ | `lutrin capabilities <deck.md>` | in addition: the frontmatter's `kit:` honoured, its layouts, and the `layouts/*.json` next to the deck |
70
+ | `lutrin capabilities --kit <ref>` | the catalog of that brand, with the current directory serving as the base |
71
+
72
+ `--kit` takes precedence over the frontmatter's `kit:`, as it does for every
73
+ other command (`--theme` remains a deprecated alias). A kit requested
74
+ explicitly and unresolvable exits with **exit code 1** without writing
75
+ anything to stdout — never a generic catalog delivered in silence. A
76
+ `layouts/*.json` that could not be read produces a warning on **stderr**:
77
+ stdout stays pure JSON, usable by `jq`. `--json` is the explicit form of the
78
+ default, and has no effect.
79
+
80
+ ## API
81
+
82
+ The package is ESM and exposes subpath entry points:
83
+
84
+ ```js
85
+ import { compileHtml } from '@lutrin/core/html';
86
+
87
+ const { html, stats } = await compileHtml(markdown, { baseDir });
88
+ // { fragment: true } → { slides, css, fontsCss, … } for a webview
89
+ ```
90
+
91
+ | Subpath | Contents |
92
+ |---|---|
93
+ | `@lutrin/core`, `/html` | `renderDeckHtml`, `compileHtml` — standalone HTML or fragment |
94
+ | `/pptx` | `renderDeck` — writing the `.pptx` |
95
+ | `/parse` | `parseDeck` — Markdown → IR |
96
+ | `/layout` | `buildScenes`, `blockHeight`, the layout registry |
97
+ | `/validate` | `validateDeck`, `capabilities` |
98
+ | `/context` | `prepareDeckContext` — kit + layouts, to be called before `buildScenes` |
99
+ | `/theme` | `resolveTheme`, `applyTheme`, WCAG contrast |
100
+ | `/tokens` | the living design tokens |
101
+
102
+ Mandatory call order for a host: `parseDeck` →
103
+ `prepareDeckContext(meta, { baseDir, themePath, defaultTheme })` →
104
+ `buildScenes` → renderer. The tokens and the layout registry are module state
105
+ mutated in place; `prepareDeckContext` resets them on every compilation,
106
+ which makes a warm process (worker, preview server) safe between two decks.
107
+
108
+ ## The pipeline
109
+
110
+ ```text
111
+ Markdown → AST (markdown-it) → IR → layout engine → scene → renderer
112
+ ```
113
+
114
+ | File | Role |
115
+ |---|---|
116
+ | `src/deck/parse.mjs` | Markdown → IR (`deck → slides → sections → blocks`, with the source line) |
117
+ | `src/deck/layout.mjs` | layout inference, placement in regions, pagination; the layout registry |
118
+ | `src/deck/validate.mjs` | positioned diagnostics (`validateDeck`) and `capabilities()` |
119
+ | `src/deck/context.mjs` | the single insertion point for the kit and the layouts |
120
+ | `src/deck/theme.mjs` | resolution and validation of a theme, WCAG contrast |
121
+ | `src/deck/kit.mjs` | the `kit.json` manifest — reading, validation, internal paths |
122
+ | `src/deck/tokens.mjs` | design tokens of the generic design (mirror of `design/themes/default.json`) |
123
+ | `src/deck/chart.mjs` | `chart` blocks → SVG in the theme's style |
124
+ | `src/deck/assets.mjs` | remote images, Lucide icons, LaTeX, Mermaid (cache `~/.cache/lutrin/`) |
125
+ | `src/deck/highlight.mjs` | syntax highlighting of code blocks |
126
+ | `src/deck/suggest.mjs` | did you mean…? (edit distance) |
127
+ | `src/pptx/render.mjs` | scene → PptxGenJS |
128
+ | `src/pptx/fonts.mjs` | embedding the theme's TTFs into the `.pptx` |
129
+ | `src/pptx/anim.mjs` | native animations (`<p:timing>`, one effect per block type) |
130
+ | `src/pptx/morph.mjs` | Morph transition of "(cont.)" slides |
131
+ | `src/html/render.mjs` | scene → standalone HTML document (+ fragment mode) |
132
+ | `src/kit/archive.mjs` | `.deckkit` archives — package, download, install |
133
+ | `src/worker/worker.mjs` | IPC worker of the editor hosts (types in `protocol.d.ts`) |
134
+ | `src/vendor.mjs` | `lutrin vendor` — freezing the deck's external dependencies |
135
+ | `design/themes/default.json` | canonical mirror of the default theme, a template to copy |
136
+ | `design/layouts/*.json` | the catalog of the ten official layouts |
137
+
138
+ `src/deck/` is the core: it knows no output format and imports no backend
139
+ library — `test/boundary.test.mjs` verifies it.
140
+
141
+ ## Tests
142
+
143
+ ```bash
144
+ npm test -w @lutrin/core
145
+ UPDATE_GOLDEN=1 npm test # from the root, after an intended engine change
146
+ ```
147
+
148
+ A `node:test` harness, with no test dependency. See
149
+ [CONTRIBUTING.md](https://github.com/julien-riel/lutrin/blob/main/CONTRIBUTING.md).
150
+
151
+ ## License
152
+
153
+ MIT. Third-party dependencies:
154
+ [THIRD-PARTY-NOTICES.md](https://github.com/julien-riel/lutrin/blob/main/THIRD-PARTY-NOTICES.md).
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "before-after",
3
+ "base": "comparison",
4
+ "sections": { "min": 2, "max": 2 },
5
+ "description": "Current state, understated, on the left; target highlighted on the right."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "funnel",
3
+ "base": "layers",
4
+ "shape": "funnel",
5
+ "description": "Volumes narrowing step by step — conversion, request triage."
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "journey",
3
+ "base": "steps",
4
+ "description": "The path of a request or a user: steps joined by arrows."
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "key-message",
3
+ "base": "focus",
4
+ "description": "A single message: one large figure or key sentence, full frame, context underneath."
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "portfolio",
3
+ "base": "grid",
4
+ "cols": 3,
5
+ "sections": { "min": 2, "max": 6 },
6
+ "headed": true,
7
+ "description": "Projects or services as a mosaic — one header per card."
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "priority-matrix",
3
+ "base": "grid",
4
+ "sections": { "min": 4, "max": 4 },
5
+ "cols": 2,
6
+ "description": "A 2 × 2 effort / impact matrix — trade-offs in four quadrants."
7
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "pros-cons",
3
+ "base": "comparison",
4
+ "panels": ["success", "danger"],
5
+ "description": "Weighing a decision: arguments for (green panel) / against (red panel)."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "pyramid",
3
+ "base": "layers",
4
+ "shape": "pyramid",
5
+ "description": "A hierarchy from apex to foundations — bands growing wider."
6
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "risk-map",
3
+ "base": "grid",
4
+ "sections": { "min": 4, "max": 4 },
5
+ "cols": 2,
6
+ "kinds": ["success", "warning", "warning", "danger"],
7
+ "description": "Probability / severity: tinted quadrants, from green (low) to red (critical)."
8
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "roadmap",
3
+ "base": "timeline",
4
+ "orientation": "vertical",
5
+ "description": "Dated milestones of a plan, in a column — axis on the left, dates and content on the right."
6
+ }
@@ -0,0 +1,159 @@
1
+ {
2
+ "name": "Slate (generic default theme)",
3
+ "colors": {
4
+ "primary": "1D4ED8",
5
+ "primaryLighter": "60A5FA",
6
+ "primaryDarker": "1E3A8A",
7
+ "brand": "1D4ED8",
8
+ "brandBlack": "111827",
9
+ "neutralPrimary": "212529",
10
+ "neutralSecondary": "637381",
11
+ "neutralTertiary": "ADB2BD",
12
+ "neutralStroke": "CED4DA",
13
+ "underground1": "F8F9FA",
14
+ "underground2": "DEE2E6",
15
+ "ground": "FFFFFF",
16
+ "highlightLight": "EFF6FF",
17
+ "highlightStrong": "DBEAFE",
18
+ "informative": "0079C4",
19
+ "informativeDark": "004B7B",
20
+ "informativeLight": "E6F5F9",
21
+ "positive": "0DA566",
22
+ "positiveDark": "025D29",
23
+ "positiveLight": "E7F6F0",
24
+ "warning": "FFB833",
25
+ "warningDark": "6C4600",
26
+ "warningLight": "FEFAE6",
27
+ "negative": "D3310A",
28
+ "negativeDark": "851A00",
29
+ "negativeLight": "FFEBE6"
30
+ },
31
+ "fonts": {
32
+ "body": "Arial",
33
+ "mono": "Courier New"
34
+ },
35
+ "type": {
36
+ "coverTitle": 40,
37
+ "coverSubtitle": 20,
38
+ "sectionTitle": 32,
39
+ "slideTitle": 26,
40
+ "sectionHeading": 16,
41
+ "lead": 16,
42
+ "body": 14,
43
+ "bullet": 14,
44
+ "bulletNested": 13,
45
+ "small": 11,
46
+ "caption": 9,
47
+ "code": 11,
48
+ "metricValue": 44,
49
+ "metricLabel": 12,
50
+ "quote": 22,
51
+ "tableBody": 12,
52
+ "tableHeader": 12
53
+ },
54
+ "space": {
55
+ "xs": 8,
56
+ "sm": 16,
57
+ "md": 24,
58
+ "lg": 32,
59
+ "xl": 40,
60
+ "xxl": 48
61
+ },
62
+ "page": {
63
+ "margin": 48,
64
+ "gutter": 24,
65
+ "titleHeight": 96,
66
+ "footerHeight": 32
67
+ },
68
+ "rounded": {
69
+ "sm": 2,
70
+ "md": 4,
71
+ "lg": 8,
72
+ "pill": 64
73
+ },
74
+ "chrome": {
75
+ "cover": {
76
+ "barY": 280,
77
+ "barW": 96,
78
+ "barH": 6,
79
+ "titleY": 304,
80
+ "titleH": 120,
81
+ "subtitleY": 424,
82
+ "subtitleH": 72,
83
+ "bylineBottom": 80,
84
+ "bylineH": 32,
85
+ "logoH": 44
86
+ },
87
+ "section": {
88
+ "titleY": 288,
89
+ "titleH": 144,
90
+ "logoH": 32
91
+ },
92
+ "title": {
93
+ "accentW": 64,
94
+ "accentH": 4
95
+ },
96
+ "footer": {
97
+ "textW": 600,
98
+ "h": 24,
99
+ "numW": 64
100
+ }
101
+ },
102
+ "chartColors": [
103
+ "0A8A76",
104
+ "B87F00",
105
+ "0079C4",
106
+ "D3310A",
107
+ "005E99",
108
+ "8A5C00"
109
+ ],
110
+ "layerShades": [
111
+ {
112
+ "fill": "1E3A8A",
113
+ "ink": "FFFFFF"
114
+ },
115
+ {
116
+ "fill": "1D4ED8",
117
+ "ink": "FFFFFF"
118
+ },
119
+ {
120
+ "fill": "60A5FA",
121
+ "ink": "212529"
122
+ },
123
+ {
124
+ "fill": "DBEAFE",
125
+ "ink": "1E3A8A"
126
+ },
127
+ {
128
+ "fill": "EFF6FF",
129
+ "ink": "1E3A8A"
130
+ }
131
+ ],
132
+ "trendInk": {
133
+ "positive": "025D29",
134
+ "negative": "851A00",
135
+ "neutral": "637381"
136
+ },
137
+ "semantic": {
138
+ "info": {
139
+ "fill": "E6F5F9",
140
+ "text": "004B7B",
141
+ "label": "Info"
142
+ },
143
+ "success": {
144
+ "fill": "E7F6F0",
145
+ "text": "025D29",
146
+ "label": "Key point"
147
+ },
148
+ "warning": {
149
+ "fill": "FEFAE6",
150
+ "text": "6C4600",
151
+ "label": "Caution"
152
+ },
153
+ "danger": {
154
+ "fill": "FFEBE6",
155
+ "text": "851A00",
156
+ "label": "Important"
157
+ }
158
+ }
159
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@lutrin/core",
3
+ "version": "1.0.0",
4
+ "description": "Markdown → PowerPoint / HTML presentation compiler, themable: a generic default theme, organization brand kits distributed as .deckkit archives.",
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node --import ./test/setup.mjs --test \"test/*.test.mjs\""
8
+ },
9
+ "files": ["src", "design", "README.md"],
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "exports": {
14
+ ".": "./src/html/render.mjs",
15
+ "./cli": "./src/cli.mjs",
16
+ "./html": "./src/html/render.mjs",
17
+ "./parse": "./src/deck/parse.mjs",
18
+ "./layout": "./src/deck/layout.mjs",
19
+ "./pptx": "./src/pptx/render.mjs",
20
+ "./validate": "./src/deck/validate.mjs",
21
+ "./tokens": "./src/deck/tokens.mjs",
22
+ "./theme": "./src/deck/theme.mjs",
23
+ "./context": "./src/deck/context.mjs"
24
+ },
25
+ "dependencies": {
26
+ "@resvg/resvg-js": "^2.6.2",
27
+ "jszip": "^3.10.1",
28
+ "lucide-static": "^1.24.0",
29
+ "markdown-it": "^14.3.0",
30
+ "markdown-it-container": "^4.0.0",
31
+ "mathjax-full": "^3.2.1",
32
+ "pptxgenjs": "4.0.1"
33
+ },
34
+ "peerDependencies": {
35
+ "@mermaid-js/mermaid-cli": "^11.16.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@mermaid-js/mermaid-cli": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "keywords": ["markdown", "powerpoint", "pptx", "slides", "presentation", "deck", "html"],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/julien-riel/lutrin.git",
46
+ "directory": "packages/core"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/julien-riel/lutrin/issues"
50
+ },
51
+ "homepage": "https://github.com/julien-riel/lutrin#readme",
52
+ "engines": {
53
+ "node": ">=22"
54
+ },
55
+ "license": "MIT",
56
+ "author": "Julien Riel"
57
+ }