@bendyline/squisq-cli 2.9.29 → 2.9.30

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 CHANGED
@@ -256,6 +256,27 @@ console.log(`Rendered ${result.frameCount} frames (${result.duration}s) → ${re
256
256
 
257
257
  The container's audio segments become the MP4's audio track; timed media clips and block videos found in the doc are embedded and mixed at their scheduled positions.
258
258
 
259
+ The capture page loads nothing from the network, so a theme font the rendering
260
+ machine lacks (any Google Fonts stack, such as warm-earth's `DM Sans` body)
261
+ renders in a fallback face. Pass the font files as `fonts`, one entry per file;
262
+ every renderer here (MP4, GIF, cover and dashboard PNG) accepts them:
263
+
264
+ ```ts
265
+ const fonts: RenderFontFace[] = [
266
+ { family: 'DM Sans', data: await readFile('DMSans-Variable.woff2'), weight: '100 1000' },
267
+ {
268
+ family: 'DM Sans',
269
+ data: await readFile('DMSans-VariableItalic.woff2'),
270
+ weight: '100 1000',
271
+ style: 'italic',
272
+ },
273
+ ];
274
+ await renderDocToMp4(doc, container, { outputPath: './output.mp4', fonts });
275
+ ```
276
+
277
+ `format` defaults to `'woff2'` (also `'woff'`, `'truetype'`, `'opentype'`), and
278
+ `unicodeRange` takes a CSS `unicode-range` for a file holding one subset.
279
+
259
280
  ### `renderDocToGif`
260
281
 
261
282
  ```ts
@@ -18,7 +18,7 @@ import {
18
18
  renderDocToMp4,
19
19
  resolveDashboardDimensions,
20
20
  validateDashboardImageDimensions
21
- } from "./chunk-KWE5OXEY.js";
21
+ } from "./chunk-6H22H2VX.js";
22
22
  import {
23
23
  GIF_EXPORT_DEFAULTS,
24
24
  framesToGifNative,
package/dist/api.d.ts CHANGED
@@ -2,8 +2,8 @@ import { Doc, MotionSpec } from '@bendyline/squisq/schemas';
2
2
  import { DashboardStyleId } from '@bendyline/squisq/doc';
3
3
  import { ContentContainer } from '@bendyline/squisq/storage';
4
4
  export { MemoryContentContainer } from '@bendyline/squisq/storage';
5
- import { VideoQuality, VideoOrientation, GifDither, DashboardResolutionId, VideoExportOptions } from '@bendyline/squisq-video';
6
- export { DASHBOARD_RESOLUTIONS, DEFAULT_DASHBOARD_RESOLUTION, DashboardResolutionId, DashboardResolutionPreset, GifDither, VideoOrientation, VideoQuality, resolveDashboardDimensions, validateDashboardImageDimensions } from '@bendyline/squisq-video';
5
+ import { VideoQuality, VideoOrientation, GifDither, DashboardResolutionId, VideoExportOptions, RenderFontFace } from '@bendyline/squisq-video';
6
+ export { DASHBOARD_RESOLUTIONS, DEFAULT_DASHBOARD_RESOLUTION, DashboardResolutionId, DashboardResolutionPreset, GifDither, RenderFontFace, VideoOrientation, VideoQuality, resolveDashboardDimensions, validateDashboardImageDimensions } from '@bendyline/squisq-video';
7
7
  import { FormatRegistry, FormatId, ConvertOptions, ConvertSource, ConversionResult, PreparedConversion } from '@bendyline/squisq-formats';
8
8
  export { ConversionError, ConversionErrorCode, ConversionErrorOptions, ConversionResult, ConvertOptions, ConvertSource, FormatDefinition, FormatId, FormatRegistry, NormalizedInput, PreparedConversion, PreparedExportOptions } from '@bendyline/squisq-formats';
9
9
  import { MarkdownDocument } from '@bendyline/squisq/markdown';
@@ -68,6 +68,8 @@ interface FrameManifest {
68
68
  animationsEnabled: boolean;
69
69
  coverPreRoll: number;
70
70
  captureFormat: CaptureFormat;
71
+ /** Digest of the embedded font files; absent when the render embedded none. */
72
+ fontsHash?: string;
71
73
  }
72
74
 
73
75
  /**
@@ -392,6 +394,12 @@ interface RenderDocToMp4Options {
392
394
  * callers get exactly the duration they ask for.
393
395
  */
394
396
  coverPreRoll?: number;
397
+ /**
398
+ * Font files to embed in the capture page. It loads nothing from the
399
+ * network, so a theme font the machine lacks (such as a Google Fonts stack)
400
+ * renders in a fallback face unless it is supplied here. Default: none.
401
+ */
402
+ fonts?: readonly RenderFontFace[];
395
403
  /**
396
404
  * Progress callback. Called with a phase name and 0-100 percentage.
397
405
  */
@@ -427,6 +435,8 @@ interface RenderDocToGifOptions {
427
435
  dither?: GifDither;
428
436
  /** Bayer strength when dither is bayer (0-5, default: 3). */
429
437
  bayerScale?: number;
438
+ /** Font files to embed (see {@link RenderDocToMp4Options.fonts}). */
439
+ fonts?: readonly RenderFontFace[];
430
440
  /** Progress callback. */
431
441
  onProgress?: (phase: string, percent: number) => void;
432
442
  }
@@ -488,6 +498,8 @@ interface RenderDashboardPngOptions {
488
498
  style?: DashboardStyleId;
489
499
  /** Host-supplied title fallback when the doc has no frontmatter title. */
490
500
  documentTitle?: string;
501
+ /** Font files to embed (see {@link RenderDocToMp4Options.fonts}). */
502
+ fonts?: readonly RenderFontFace[];
491
503
  onProgress?: (phase: string, percent: number) => void;
492
504
  }
493
505
  /** Result returned by {@link renderDocToDashboardPng}. */
@@ -505,6 +517,8 @@ interface RenderCoverPngOptions {
505
517
  width?: number;
506
518
  height?: number;
507
519
  animationsEnabled?: boolean;
520
+ /** Font files to embed (see {@link RenderDocToMp4Options.fonts}). */
521
+ fonts?: readonly RenderFontFace[];
508
522
  onProgress?: (phase: string, percent: number) => void;
509
523
  }
510
524
  /** Result returned by {@link renderDocCoverToPng}. */
package/dist/api.js CHANGED
@@ -469,6 +469,15 @@ function createFfmpegPipeSink(options) {
469
469
  function docRenderHash(doc) {
470
470
  return createHash("sha256").update(JSON.stringify(doc)).digest("hex");
471
471
  }
472
+ function fontsRenderHash(fonts) {
473
+ const hash = createHash("sha256");
474
+ for (const font of fonts) {
475
+ const { data, ...face } = font;
476
+ hash.update(JSON.stringify(face));
477
+ hash.update(data instanceof Uint8Array ? data : new Uint8Array(data));
478
+ }
479
+ return hash.digest("hex");
480
+ }
472
481
  var MANIFEST_NAME = "manifest.json";
473
482
  async function openDirectoryFrameStore(dir, manifest, resume) {
474
483
  await mkdir(dir, { recursive: true });
@@ -1133,6 +1142,7 @@ async function withRenderPage(doc, container, options, fn) {
1133
1142
  animationsEnabled,
1134
1143
  displayMode,
1135
1144
  dashboard,
1145
+ fonts,
1136
1146
  onProgress
1137
1147
  } = options;
1138
1148
  onProgress?.("collecting media", 0);
@@ -1179,6 +1189,7 @@ async function withRenderPage(doc, container, options, fn) {
1179
1189
  images.set(src, data);
1180
1190
  }
1181
1191
  }
1192
+ for (const font of fonts ?? []) budget.admit(`font:${font.family}`, font.data);
1182
1193
  onProgress?.("generating render HTML", 10);
1183
1194
  signal?.throwIfAborted();
1184
1195
  const playerBundle = await (selectStandalonePlayerVariant(doc) === "full" ? loadFullPlayerBundle() : loadPlayerBundle());
@@ -1193,7 +1204,8 @@ async function withRenderPage(doc, container, options, fn) {
1193
1204
  motion,
1194
1205
  animationsEnabled,
1195
1206
  displayMode,
1196
- dashboard
1207
+ dashboard,
1208
+ fonts
1197
1209
  });
1198
1210
  onProgress?.("launching browser", 15);
1199
1211
  signal?.throwIfAborted();
@@ -1359,6 +1371,7 @@ async function captureDocFrames(doc, container, options) {
1359
1371
  motion,
1360
1372
  coverPreRoll,
1361
1373
  animationsEnabled,
1374
+ fonts,
1362
1375
  onProgress,
1363
1376
  signal
1364
1377
  } = options;
@@ -1378,6 +1391,7 @@ async function captureDocFrames(doc, container, options) {
1378
1391
  captionStyle,
1379
1392
  motion,
1380
1393
  animationsEnabled,
1394
+ fonts,
1381
1395
  onProgress
1382
1396
  },
1383
1397
  async (session) => {
@@ -1429,7 +1443,8 @@ async function renderDocToMp4Piped(doc, container, options, render) {
1429
1443
  motion: motion === null ? null : JSON.stringify(motion),
1430
1444
  animationsEnabled,
1431
1445
  coverPreRoll,
1432
- captureFormat
1446
+ captureFormat,
1447
+ ...options.fonts?.length ? { fontsHash: fontsRenderHash(options.fonts) } : {}
1433
1448
  };
1434
1449
  const store = options.framesDir ? await openDirectoryFrameStore(
1435
1450
  resolvePath(options.framesDir),
@@ -1452,6 +1467,7 @@ async function renderDocToMp4Piped(doc, container, options, render) {
1452
1467
  captionStyle,
1453
1468
  motion,
1454
1469
  animationsEnabled,
1470
+ fonts: options.fonts,
1455
1471
  onProgress
1456
1472
  },
1457
1473
  async (session) => {
@@ -1547,6 +1563,7 @@ async function renderDocCoverToPng(doc, container, options = {}) {
1547
1563
  includeAudio: false,
1548
1564
  animationsEnabled: options.animationsEnabled ?? false,
1549
1565
  displayMode: "slideshow",
1566
+ fonts: options.fonts,
1550
1567
  onProgress: options.onProgress
1551
1568
  },
1552
1569
  async ({ page, renderAPI }) => {
@@ -1579,6 +1596,7 @@ async function renderDocToDashboardPng(doc, container, options = {}) {
1579
1596
  title,
1580
1597
  style,
1581
1598
  documentTitle,
1599
+ fonts,
1582
1600
  onProgress
1583
1601
  } = options;
1584
1602
  signal?.throwIfAborted();
@@ -1594,6 +1612,7 @@ async function renderDocToDashboardPng(doc, container, options = {}) {
1594
1612
  animationsEnabled: false,
1595
1613
  displayMode: "dashboard",
1596
1614
  dashboard: { layout, title, style, documentTitle },
1615
+ fonts,
1597
1616
  onProgress
1598
1617
  },
1599
1618
  async ({ page, renderAPI }) => {
@@ -1662,6 +1681,7 @@ async function renderDocToMp4(doc, container, options) {
1662
1681
  motion: options.motion ?? null,
1663
1682
  coverPreRoll: options.coverPreRoll ?? 0,
1664
1683
  animationsEnabled: options.animationsEnabled ?? true,
1684
+ fonts: options.fonts,
1665
1685
  onProgress: options.onProgress,
1666
1686
  signal: options.signal
1667
1687
  });
@@ -1726,6 +1746,7 @@ async function renderDocToGif(doc, container, options) {
1726
1746
  motion: options.motion ?? null,
1727
1747
  coverPreRoll: options.coverPreRoll ?? 0,
1728
1748
  animationsEnabled: options.animationsEnabled ?? false,
1749
+ fonts: options.fonts,
1729
1750
  onProgress: options.onProgress,
1730
1751
  signal: options.signal
1731
1752
  });
@@ -466,6 +466,15 @@ function createFfmpegPipeSink(options) {
466
466
  function docRenderHash(doc) {
467
467
  return createHash("sha256").update(JSON.stringify(doc)).digest("hex");
468
468
  }
469
+ function fontsRenderHash(fonts) {
470
+ const hash = createHash("sha256");
471
+ for (const font of fonts) {
472
+ const { data, ...face } = font;
473
+ hash.update(JSON.stringify(face));
474
+ hash.update(data instanceof Uint8Array ? data : new Uint8Array(data));
475
+ }
476
+ return hash.digest("hex");
477
+ }
469
478
  var MANIFEST_NAME = "manifest.json";
470
479
  async function openDirectoryFrameStore(dir, manifest, resume) {
471
480
  await mkdir(dir, { recursive: true });
@@ -543,7 +552,7 @@ function mp4Format() {
543
552
  const animationsEnabled = typeof mp4Opts.animationsEnabled === "boolean" ? mp4Opts.animationsEnabled : MP4_DEFAULTS.animationsEnabled;
544
553
  const outputPath = join2(tmpdir(), `squisq-mp4-${randomBytes(8).toString("hex")}.mp4`);
545
554
  try {
546
- const { renderDocToMp4: renderDocToMp42 } = await import("./api-6NVYOTM6.js");
555
+ const { renderDocToMp4: renderDocToMp42 } = await import("./api-775UOV72.js");
547
556
  await renderDocToMp42(input.doc, input.container, {
548
557
  outputPath,
549
558
  fps,
@@ -584,7 +593,7 @@ function gifFormat() {
584
593
  const defaultHeight = portrait ? GIF_DEFAULTS.width : GIF_DEFAULTS.height;
585
594
  const outputPath = join2(tmpdir(), `squisq-gif-${randomBytes(8).toString("hex")}.gif`);
586
595
  try {
587
- const { renderDocToGif: renderDocToGif2 } = await import("./api-6NVYOTM6.js");
596
+ const { renderDocToGif: renderDocToGif2 } = await import("./api-775UOV72.js");
588
597
  const result = await renderDocToGif2(input.doc, input.container, {
589
598
  outputPath,
590
599
  fps: typeof gifOpts.fps === "number" ? gifOpts.fps : GIF_DEFAULTS.fps,
@@ -627,7 +636,7 @@ function pngFormat() {
627
636
  async exportDoc(input, options) {
628
637
  options.signal?.throwIfAborted();
629
638
  const pngOpts = options.formatOptions?.png ?? {};
630
- const { renderDocToDashboardPng: renderDocToDashboardPng2 } = await import("./api-6NVYOTM6.js");
639
+ const { renderDocToDashboardPng: renderDocToDashboardPng2 } = await import("./api-775UOV72.js");
631
640
  const result = await renderDocToDashboardPng2(input.doc, input.container, {
632
641
  resolution: pngOpts.resolution,
633
642
  width: pngOpts.width,
@@ -1130,6 +1139,7 @@ async function withRenderPage(doc, container, options, fn) {
1130
1139
  animationsEnabled,
1131
1140
  displayMode,
1132
1141
  dashboard,
1142
+ fonts,
1133
1143
  onProgress
1134
1144
  } = options;
1135
1145
  onProgress?.("collecting media", 0);
@@ -1176,6 +1186,7 @@ async function withRenderPage(doc, container, options, fn) {
1176
1186
  images.set(src, data);
1177
1187
  }
1178
1188
  }
1189
+ for (const font of fonts ?? []) budget.admit(`font:${font.family}`, font.data);
1179
1190
  onProgress?.("generating render HTML", 10);
1180
1191
  signal?.throwIfAborted();
1181
1192
  const playerBundle = await (selectStandalonePlayerVariant(doc) === "full" ? loadFullPlayerBundle() : loadPlayerBundle());
@@ -1190,7 +1201,8 @@ async function withRenderPage(doc, container, options, fn) {
1190
1201
  motion,
1191
1202
  animationsEnabled,
1192
1203
  displayMode,
1193
- dashboard
1204
+ dashboard,
1205
+ fonts
1194
1206
  });
1195
1207
  onProgress?.("launching browser", 15);
1196
1208
  signal?.throwIfAborted();
@@ -1356,6 +1368,7 @@ async function captureDocFrames(doc, container, options) {
1356
1368
  motion,
1357
1369
  coverPreRoll,
1358
1370
  animationsEnabled,
1371
+ fonts,
1359
1372
  onProgress,
1360
1373
  signal
1361
1374
  } = options;
@@ -1375,6 +1388,7 @@ async function captureDocFrames(doc, container, options) {
1375
1388
  captionStyle,
1376
1389
  motion,
1377
1390
  animationsEnabled,
1391
+ fonts,
1378
1392
  onProgress
1379
1393
  },
1380
1394
  async (session) => {
@@ -1426,7 +1440,8 @@ async function renderDocToMp4Piped(doc, container, options, render) {
1426
1440
  motion: motion === null ? null : JSON.stringify(motion),
1427
1441
  animationsEnabled,
1428
1442
  coverPreRoll,
1429
- captureFormat
1443
+ captureFormat,
1444
+ ...options.fonts?.length ? { fontsHash: fontsRenderHash(options.fonts) } : {}
1430
1445
  };
1431
1446
  const store = options.framesDir ? await openDirectoryFrameStore(
1432
1447
  resolvePath(options.framesDir),
@@ -1449,6 +1464,7 @@ async function renderDocToMp4Piped(doc, container, options, render) {
1449
1464
  captionStyle,
1450
1465
  motion,
1451
1466
  animationsEnabled,
1467
+ fonts: options.fonts,
1452
1468
  onProgress
1453
1469
  },
1454
1470
  async (session) => {
@@ -1544,6 +1560,7 @@ async function renderDocCoverToPng(doc, container, options = {}) {
1544
1560
  includeAudio: false,
1545
1561
  animationsEnabled: options.animationsEnabled ?? false,
1546
1562
  displayMode: "slideshow",
1563
+ fonts: options.fonts,
1547
1564
  onProgress: options.onProgress
1548
1565
  },
1549
1566
  async ({ page, renderAPI }) => {
@@ -1576,6 +1593,7 @@ async function renderDocToDashboardPng(doc, container, options = {}) {
1576
1593
  title,
1577
1594
  style,
1578
1595
  documentTitle,
1596
+ fonts,
1579
1597
  onProgress
1580
1598
  } = options;
1581
1599
  signal?.throwIfAborted();
@@ -1591,6 +1609,7 @@ async function renderDocToDashboardPng(doc, container, options = {}) {
1591
1609
  animationsEnabled: false,
1592
1610
  displayMode: "dashboard",
1593
1611
  dashboard: { layout, title, style, documentTitle },
1612
+ fonts,
1594
1613
  onProgress
1595
1614
  },
1596
1615
  async ({ page, renderAPI }) => {
@@ -1659,6 +1678,7 @@ async function renderDocToMp4(doc, container, options) {
1659
1678
  motion: options.motion ?? null,
1660
1679
  coverPreRoll: options.coverPreRoll ?? 0,
1661
1680
  animationsEnabled: options.animationsEnabled ?? true,
1681
+ fonts: options.fonts,
1662
1682
  onProgress: options.onProgress,
1663
1683
  signal: options.signal
1664
1684
  });
@@ -1723,6 +1743,7 @@ async function renderDocToGif(doc, container, options) {
1723
1743
  motion: options.motion ?? null,
1724
1744
  coverPreRoll: options.coverPreRoll ?? 0,
1725
1745
  animationsEnabled: options.animationsEnabled ?? false,
1746
+ fonts: options.fonts,
1726
1747
  onProgress: options.onProgress,
1727
1748
  signal: options.signal
1728
1749
  });
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  renderDocToDashboardPng,
10
10
  renderDocToGif,
11
11
  renderDocToMp4
12
- } from "./chunk-KWE5OXEY.js";
12
+ } from "./chunk-6H22H2VX.js";
13
13
  import "./chunk-Y5XKUWJ5.js";
14
14
 
15
15
  // src/index.ts