@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.
@@ -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
- lineSpacingMultiple: LINE_HEIGHT,
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
- ...(block.size ? { lineSpacingMultiple: 1.3 } : {}),
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
- lineSpacingMultiple: 1.25,
226
- paraSpaceAfter: 6,
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
- lineSpacingMultiple: 1.25,
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: { bold: true, fontSize: TYPE.small, color: sem.text, breakLine: true },
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
- lineSpacingMultiple: 1.3,
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
- lineSpacingMultiple: LINE_HEIGHT,
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 — install @mermaid-js/mermaid-cli for graphical rendering', {
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
- const out = await renderIcon(b.name, { color: b.color });
1207
- if (!out) {
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
- icons.set(b, writeTmpPng(tmp(), `icon-${k}-${iconSlug(b.name)}`, out.png));
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.