@lutrin/core 1.0.0 → 1.1.1
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 +15 -6
- package/package.json +4 -3
- package/src/cli.mjs +81 -2
- package/src/deck/assets.mjs +121 -13
- package/src/deck/browser.mjs +269 -0
- package/src/deck/layout.mjs +5 -2
- package/src/deck/marp.mjs +324 -0
- package/src/deck/mermaid-render.mjs +147 -0
- package/src/deck/parse.mjs +172 -3
- package/src/deck/validate.mjs +83 -22
- package/src/html/render.mjs +13 -3
- package/src/pptx/fonts.mjs +145 -7
- package/src/pptx/render.mjs +52 -14
- package/vendor/mermaid/LICENSE +21 -0
- package/vendor/mermaid/README.md +44 -0
- package/vendor/mermaid/mermaid.min.js +3587 -0
package/src/pptx/render.mjs
CHANGED
|
@@ -33,8 +33,9 @@ import {
|
|
|
33
33
|
import { ALERT_BLOCK_TYPES, runsToText } from '../deck/parse.mjs';
|
|
34
34
|
import {
|
|
35
35
|
fetchRemoteImage,
|
|
36
|
+
iconSvg,
|
|
37
|
+
ICON_RASTER_PX,
|
|
36
38
|
imageDims,
|
|
37
|
-
renderIcon,
|
|
38
39
|
renderMath,
|
|
39
40
|
renderMermaidCached,
|
|
40
41
|
rasterAvailable,
|
|
@@ -151,7 +152,12 @@ function addPara(slide, block, r) {
|
|
|
151
152
|
color: block.color ?? COLORS.neutralPrimary,
|
|
152
153
|
align: 'left',
|
|
153
154
|
valign: 'top',
|
|
154
|
-
|
|
155
|
+
// exact points, never a multiple: OOXML's spcPct multiplies the FONT'S
|
|
156
|
+
// own line metrics, so a kit font with tall ascenders rendered ~20%
|
|
157
|
+
// taller than blockHeight() measured and crowded whatever followed.
|
|
158
|
+
// spcPts pins the pitch to what the layout engine and the HTML (.para
|
|
159
|
+
// line-height 1.4) both assume, whatever font the kit ships.
|
|
160
|
+
lineSpacing: TYPE.body * LINE_HEIGHT,
|
|
155
161
|
});
|
|
156
162
|
}
|
|
157
163
|
|
|
@@ -168,8 +174,9 @@ function addHeading(slide, block, r) {
|
|
|
168
174
|
bold: true,
|
|
169
175
|
valign: 'top',
|
|
170
176
|
...(block.align ? { align: block.align } : {}),
|
|
171
|
-
// multi-line message: same line height as the CSS .slot-heading (1.3)
|
|
172
|
-
|
|
177
|
+
// multi-line message: same line height as the CSS .slot-heading (1.3),
|
|
178
|
+
// in exact points (spcPts) — see addPara on why never a multiple
|
|
179
|
+
...(block.size ? { lineSpacing: block.size * 1.3 } : {}),
|
|
173
180
|
});
|
|
174
181
|
}
|
|
175
182
|
|
|
@@ -194,6 +201,13 @@ function addBullets(slide, block, r) {
|
|
|
194
201
|
options: { fontFace: FONTS.body, color: block.color ?? COLORS.neutralPrimary },
|
|
195
202
|
});
|
|
196
203
|
}
|
|
204
|
+
if (it.level) {
|
|
205
|
+
// nested items: same size and pitch as the HTML (.bullets ul ul)
|
|
206
|
+
itemRuns.forEach((run) => {
|
|
207
|
+
run.options.fontSize = TYPE.bulletNested;
|
|
208
|
+
});
|
|
209
|
+
itemRuns[0].options.lineSpacing = TYPE.bulletNested * 1.3;
|
|
210
|
+
}
|
|
197
211
|
itemRuns[0] = {
|
|
198
212
|
...itemRuns[0],
|
|
199
213
|
options: {
|
|
@@ -222,8 +236,10 @@ function addBullets(slide, block, r) {
|
|
|
222
236
|
fontFace: FONTS.body,
|
|
223
237
|
color: block.color ?? COLORS.neutralPrimary,
|
|
224
238
|
valign: 'top',
|
|
225
|
-
|
|
226
|
-
|
|
239
|
+
// exact pitch and gap of the HTML (.bullets: line-height 1.3, li
|
|
240
|
+
// margin-bottom 6px) — spcPts + 4.5 pt (= 6 px); see addPara
|
|
241
|
+
lineSpacing: TYPE.bullet * 1.3,
|
|
242
|
+
paraSpaceAfter: 4.5,
|
|
227
243
|
});
|
|
228
244
|
}
|
|
229
245
|
|
|
@@ -259,7 +275,8 @@ function addCode(slide, block, r) {
|
|
|
259
275
|
h: px(r.h - 2 * SPACE.xs),
|
|
260
276
|
fontSize: TYPE.code,
|
|
261
277
|
valign: 'top',
|
|
262
|
-
|
|
278
|
+
// exact pitch of the HTML (.code line-height 1.3); see addPara
|
|
279
|
+
lineSpacing: TYPE.code * 1.3,
|
|
263
280
|
});
|
|
264
281
|
}
|
|
265
282
|
|
|
@@ -308,7 +325,13 @@ function addAlert(slide, block, r) {
|
|
|
308
325
|
const runs = [
|
|
309
326
|
{
|
|
310
327
|
text: sem.label,
|
|
311
|
-
options: {
|
|
328
|
+
options: {
|
|
329
|
+
bold: true,
|
|
330
|
+
fontSize: TYPE.small,
|
|
331
|
+
color: sem.text,
|
|
332
|
+
breakLine: true,
|
|
333
|
+
lineSpacing: TYPE.small * 1.3,
|
|
334
|
+
},
|
|
312
335
|
},
|
|
313
336
|
];
|
|
314
337
|
// outside ALERT_BLOCK_TYPES: ignored (height not reserved by blockHeight,
|
|
@@ -336,7 +359,9 @@ function addAlert(slide, block, r) {
|
|
|
336
359
|
fontSize: TYPE.body,
|
|
337
360
|
fontFace: FONTS.body,
|
|
338
361
|
valign: 'top',
|
|
339
|
-
|
|
362
|
+
// exact pitch of the HTML (.alert line-height 1.3); the label paragraph
|
|
363
|
+
// carries its own smaller pitch in its run options — see addPara
|
|
364
|
+
lineSpacing: TYPE.body * 1.3,
|
|
340
365
|
});
|
|
341
366
|
}
|
|
342
367
|
|
|
@@ -522,7 +547,8 @@ function addQuote(slide, block, r) {
|
|
|
522
547
|
color: COLORS.neutralPrimary,
|
|
523
548
|
fontFace: FONTS.body,
|
|
524
549
|
valign: 'middle',
|
|
525
|
-
|
|
550
|
+
// exact pitch of the HTML (.quote blockquote line-height 1.4); see addPara
|
|
551
|
+
lineSpacing: TYPE.quote * LINE_HEIGHT,
|
|
526
552
|
});
|
|
527
553
|
if (block.cite) {
|
|
528
554
|
slide.addText(`— ${block.cite}`, {
|
|
@@ -598,7 +624,7 @@ function addMermaid(slide, block, r, ctx) {
|
|
|
598
624
|
}
|
|
599
625
|
// faithful fallback: source shown as a code block plus a caption
|
|
600
626
|
addCode(slide, { type: 'code', lang: 'mermaid', source: block.source }, { ...r, h: r.h - 24 });
|
|
601
|
-
slide.addText('Mermaid diagram —
|
|
627
|
+
slide.addText('Mermaid diagram — run `lutrin setup-mermaid` for graphical rendering', {
|
|
602
628
|
x: px(r.x),
|
|
603
629
|
y: px(r.y + r.h - 22),
|
|
604
630
|
w: px(r.w),
|
|
@@ -922,6 +948,9 @@ function renderCover(pptx, scene) {
|
|
|
922
948
|
bold: true,
|
|
923
949
|
color: COLORS.neutralPrimary,
|
|
924
950
|
fontFace: FONTS.body,
|
|
951
|
+
// explicit: a paragraph without `algn` inherits the master's titleStyle,
|
|
952
|
+
// which PptxGenJS hard-codes centered — the HTML output is left-aligned
|
|
953
|
+
align: 'left',
|
|
925
954
|
valign: 'top',
|
|
926
955
|
});
|
|
927
956
|
if (LOGOS.cover && fs.existsSync(LOGOS.cover)) {
|
|
@@ -975,6 +1004,7 @@ function renderSection(pptx, scene) {
|
|
|
975
1004
|
bold: true,
|
|
976
1005
|
color: COLORS.ground,
|
|
977
1006
|
fontFace: FONTS.body,
|
|
1007
|
+
align: 'left', // same inheritance trap as the cover title
|
|
978
1008
|
valign: 'middle',
|
|
979
1009
|
});
|
|
980
1010
|
if (LOGOS.section && fs.existsSync(LOGOS.section)) {
|
|
@@ -1203,13 +1233,20 @@ async function renderDeckTo(scenes, meta, baseDir, outPath, tmp, opts = {}) {
|
|
|
1203
1233
|
const iconWarnings = new Array(iconBlocks.length);
|
|
1204
1234
|
await Promise.all(
|
|
1205
1235
|
iconBlocks.map(async (b, k) => {
|
|
1206
|
-
|
|
1207
|
-
|
|
1236
|
+
// two failures, two diagnostics: an icon whose SVG cannot be FOUND is a
|
|
1237
|
+
// deck problem (typo, package absent, offline), while an SVG found but
|
|
1238
|
+
// not RASTERIZED is the missing resvg binary — already reported, with
|
|
1239
|
+
// its remedy, by RASTER_UNAVAILABLE below. Conflating them sent Windows
|
|
1240
|
+
// users hunting for a network problem when their VSIX simply shipped
|
|
1241
|
+
// another platform's rasterizer.
|
|
1242
|
+
const svg = await iconSvg(b.name, { color: b.color });
|
|
1243
|
+
if (!svg) {
|
|
1208
1244
|
iconWarnings[k] =
|
|
1209
1245
|
`Icon "${iconLabel(b.name)}" not found — name unknown to Lucide, or the lucide-static package is absent and there is no network. The slide is rendered without it.`;
|
|
1210
1246
|
return;
|
|
1211
1247
|
}
|
|
1212
|
-
|
|
1248
|
+
const out = await svgToPng(svg, ICON_RASTER_PX);
|
|
1249
|
+
if (out) icons.set(b, writeTmpPng(tmp(), `icon-${k}-${iconSlug(b.name)}`, out.png));
|
|
1213
1250
|
}),
|
|
1214
1251
|
);
|
|
1215
1252
|
const assetWarnings = iconWarnings.filter(Boolean);
|
|
@@ -1310,6 +1347,7 @@ async function renderDeckTo(scenes, meta, baseDir, outPath, tmp, opts = {}) {
|
|
|
1310
1347
|
bold: true,
|
|
1311
1348
|
color: COLORS.neutralPrimary,
|
|
1312
1349
|
fontFace: FONTS.body,
|
|
1350
|
+
align: 'left', // same inheritance trap as the cover title
|
|
1313
1351
|
valign: 'middle',
|
|
1314
1352
|
},
|
|
1315
1353
|
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 - 2022 Knut Sveidqvist
|
|
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.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Vendored Mermaid
|
|
2
|
+
|
|
3
|
+
`mermaid.min.js` — the standalone UMD bundle of [Mermaid](https://mermaid.js.org),
|
|
4
|
+
copied verbatim from the npm package. MIT, see `LICENSE`.
|
|
5
|
+
|
|
6
|
+
| | |
|
|
7
|
+
|---|---|
|
|
8
|
+
| Version | 11.16.0 |
|
|
9
|
+
| Source | `mermaid@11.16.0/dist/mermaid.min.js` |
|
|
10
|
+
| SHA-256 | `74d7c46dabca328c2294733910a8aa1ed0c37451776e8d5295da38a2b758fb9b` |
|
|
11
|
+
|
|
12
|
+
## Why a copy in the repository rather than a dependency
|
|
13
|
+
|
|
14
|
+
Mermaid needs a browser: it measures the text it lays out, so it cannot render
|
|
15
|
+
without a layout engine. The two obvious ways to get one both cost far more
|
|
16
|
+
than this file:
|
|
17
|
+
|
|
18
|
+
- `@mermaid-js/mermaid-cli` pulls Puppeteer, which downloads a Chrome — 405 MB
|
|
19
|
+
of `node_modules` plus ~540 MB of browser, measured, on every install. That is
|
|
20
|
+
what made mermaid-cli an optional peer dependency nobody installs, and
|
|
21
|
+
therefore what made diagrams silently degrade to a text fallback on a fresh
|
|
22
|
+
machine.
|
|
23
|
+
- Depending on `mermaid` itself installs 83 MB, of which 56 MB are source maps.
|
|
24
|
+
We load exactly one file out of it.
|
|
25
|
+
|
|
26
|
+
So we take the one file. `browser.mjs` finds a browser already on the machine
|
|
27
|
+
and `mermaid-render.mjs` loads this bundle into it; `@resvg/resvg-js`, already a
|
|
28
|
+
dependency, turns the SVG into the PNG the .pptx needs.
|
|
29
|
+
|
|
30
|
+
## Upgrading
|
|
31
|
+
|
|
32
|
+
Version-bumping is a copy, and the two version markers must move together:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm pack mermaid@<version> # or npm i mermaid@<version> in a scratch dir
|
|
36
|
+
cp .../mermaid/dist/mermaid.min.js packages/core/vendor/mermaid/
|
|
37
|
+
shasum -a 256 packages/core/vendor/mermaid/mermaid.min.js
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then update the table above, the `@mermaid-js/mermaid-cli` peer range in
|
|
41
|
+
`packages/core/package.json` (kept in step so both rendering paths speak the
|
|
42
|
+
same Mermaid dialect), and `THIRD-PARTY-NOTICES.md`. `test/mermaid.test.mjs`
|
|
43
|
+
asserts the bundle is present and that its recorded SHA-256 matches, so a
|
|
44
|
+
half-finished upgrade fails the suite rather than shipping.
|