@bendyline/squisq-video 2.4.18 → 2.4.20

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
@@ -233,7 +233,8 @@ declare function computeAudioTimeline(doc: Doc, coverPreRoll?: number, options?:
233
233
  * Render HTML Generation for Video Frame Capture
234
234
  *
235
235
  * Generates a self-contained HTML document that loads the SquisqPlayer standalone
236
- * bundle in renderMode, embedding all images and audio as base64 data URIs.
236
+ * bundle in renderMode, embedding all images and audio as base64 data URIs, and
237
+ * any font files the host supplies (`fonts`) as `@font-face` rules.
237
238
  *
238
239
  * The generated page mounts one standalone player whose instance handle is
239
240
  * available through `SquisqPlayer.getHandle(root)`. Headless callers use that
@@ -242,6 +243,27 @@ declare function computeAudioTimeline(doc: Doc, coverPreRoll?: number, options?:
242
243
  * Browser-pure: uses only btoa() and Uint8Array — no Node.js APIs.
243
244
  */
244
245
 
246
+ /**
247
+ * A font face embedded in the render page. Capture pages load nothing from the
248
+ * network, so a theme font the rendering machine lacks (any Google Fonts stack,
249
+ * such as `dm-sans`) falls back to a system face unless its file is supplied.
250
+ * One entry per file: a family split into unicode-range subsets, or into
251
+ * upright and italic files, takes one entry for each.
252
+ */
253
+ interface RenderFontFace {
254
+ /** CSS family name as the theme's font stack spells it, e.g. `DM Sans`. */
255
+ family: string;
256
+ /** The font file's bytes (an ArrayBuffer, or a Uint8Array such as a Node Buffer). */
257
+ data: ArrayBuffer | Uint8Array;
258
+ /** File format (default 'woff2'). */
259
+ format?: 'woff2' | 'woff' | 'truetype' | 'opentype';
260
+ /** CSS `font-weight`: one weight, or a variable font's range such as `100 1000` (default `normal`). */
261
+ weight?: number | string;
262
+ /** CSS `font-style` (default 'normal'). */
263
+ style?: 'normal' | 'italic';
264
+ /** CSS `unicode-range`, when the file holds one subset of the family. */
265
+ unicodeRange?: string;
266
+ }
245
267
  interface RenderHtmlOptions {
246
268
  /** The IIFE player bundle source code (from PLAYER_BUNDLE) */
247
269
  playerScript: string;
@@ -286,6 +308,11 @@ interface RenderHtmlOptions {
286
308
  * single-frame image capture.
287
309
  */
288
310
  displayMode?: 'slideshow' | 'dashboard';
311
+ /**
312
+ * Font files to embed as `@font-face` rules (default none). Faces load
313
+ * before the player mounts, so `document.fonts.ready` covers them.
314
+ */
315
+ fonts?: readonly RenderFontFace[];
289
316
  /** Dashboard-mode options (ignored unless `displayMode` is 'dashboard'). */
290
317
  dashboard?: {
291
318
  /** Layout id or 'auto'. Overrides doc frontmatter. */
@@ -531,4 +558,4 @@ declare function frameTimeSeconds(index: number, fps: number): number;
531
558
  /** Number of frames needed to cover `durationSeconds` at `fps` (partial frames round up). */
532
559
  declare function frameCountFor(durationSeconds: number, fps: number): number;
533
560
 
534
- export { type AudioTimelineClip, type ComputeAudioTimelineOptions, DASHBOARD_RESOLUTIONS, DEFAULT_DASHBOARD_RESOLUTION, type DashboardResolutionId, type DashboardResolutionPreset, type EncoderResult, FFMPEG_WASM_SETUP_HINT, type FfmpegWasmLoadConfig, type GifDither, type GifFilterOptions, type GifOutputOptions, MAX_DASHBOARD_IMAGE_DIMENSION, MAX_DASHBOARD_IMAGE_PIXELS, MIN_DASHBOARD_IMAGE_DIMENSION, ORIENTATION_DIMENSIONS, QUALITY_PRESETS, type QualityPreset, type RenderHtmlOptions, type ResolveDashboardDimensionsInput, type ResolvedDashboardDimensions, type VideoExportOptions, type VideoOrientation, type VideoQuality, audioBitrateArg, bitrateForQuality, computeAudioTimeline, dashboardFamilyForDimensions, ffmpegAudioMuxArgs, ffmpegGifFilterGraph, ffmpegGifOutputArgs, ffmpegGifPaletteApplicationArgs, ffmpegGifPaletteApplicationGraph, ffmpegGifPaletteGenerationFilter, ffmpegVideoQualityArgs, frameCountFor, frameTimeSeconds, framesToMp4Wasm, generateRenderHtml, resolveDashboardDimensions, resolveDimensions, resolveFfmpegWasmLoad, validateDashboardImageDimensions, validateVideoExportOptions };
561
+ export { type AudioTimelineClip, type ComputeAudioTimelineOptions, DASHBOARD_RESOLUTIONS, DEFAULT_DASHBOARD_RESOLUTION, type DashboardResolutionId, type DashboardResolutionPreset, type EncoderResult, FFMPEG_WASM_SETUP_HINT, type FfmpegWasmLoadConfig, type GifDither, type GifFilterOptions, type GifOutputOptions, MAX_DASHBOARD_IMAGE_DIMENSION, MAX_DASHBOARD_IMAGE_PIXELS, MIN_DASHBOARD_IMAGE_DIMENSION, ORIENTATION_DIMENSIONS, QUALITY_PRESETS, type QualityPreset, type RenderFontFace, type RenderHtmlOptions, type ResolveDashboardDimensionsInput, type ResolvedDashboardDimensions, type VideoExportOptions, type VideoOrientation, type VideoQuality, audioBitrateArg, bitrateForQuality, computeAudioTimeline, dashboardFamilyForDimensions, ffmpegAudioMuxArgs, ffmpegGifFilterGraph, ffmpegGifOutputArgs, ffmpegGifPaletteApplicationArgs, ffmpegGifPaletteApplicationGraph, ffmpegGifPaletteGenerationFilter, ffmpegVideoQualityArgs, frameCountFor, frameTimeSeconds, framesToMp4Wasm, generateRenderHtml, resolveDashboardDimensions, resolveDimensions, resolveFfmpegWasmLoad, validateDashboardImageDimensions, validateVideoExportOptions };
package/dist/index.js CHANGED
@@ -174,7 +174,7 @@ function inferMimeType(filename) {
174
174
  }
175
175
  var BINARY_STRING_CHUNK = 32768;
176
176
  function arrayBufferToDataUrl(buffer, mimeType) {
177
- const bytes = new Uint8Array(buffer);
177
+ const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
178
178
  const parts = [];
179
179
  for (let i = 0; i < bytes.length; i += BINARY_STRING_CHUNK) {
180
180
  parts.push(String.fromCharCode(...bytes.subarray(i, i + BINARY_STRING_CHUNK)));
@@ -187,6 +187,37 @@ function escapeForScript(str) {
187
187
  function escapeHtml(str) {
188
188
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
189
189
  }
190
+ var FONT_MIME = {
191
+ woff2: "font/woff2",
192
+ woff: "font/woff",
193
+ truetype: "font/ttf",
194
+ opentype: "font/otf"
195
+ };
196
+ var FONT_FAMILY = /^[\p{L}\p{N} _.-]+$/u;
197
+ var FONT_WEIGHT = /^(?:normal|bold|\d{1,4}(?: \d{1,4})?)$/;
198
+ var UNICODE_RANGE = /^U\+[0-9A-F?]{1,6}(?:-[0-9A-F]{1,6})?(?:, ?U\+[0-9A-F?]{1,6}(?:-[0-9A-F]{1,6})?)*$/i;
199
+ function fontFaceCss(fonts) {
200
+ return fonts.map((font) => {
201
+ const family = font.family.trim();
202
+ const format = font.format ?? "woff2";
203
+ const weight = String(font.weight ?? "normal").trim();
204
+ const style = font.style ?? "normal";
205
+ if (!FONT_FAMILY.test(family)) throw new Error(`Invalid font family name: ${font.family}`);
206
+ if (!(format in FONT_MIME)) throw new Error(`Unsupported font format: ${format}`);
207
+ if (!FONT_WEIGHT.test(weight)) {
208
+ throw new Error(`Invalid font weight for ${family}: ${weight}`);
209
+ }
210
+ if (style !== "normal" && style !== "italic") {
211
+ throw new Error(`Invalid font style for ${family}: ${String(style)}`);
212
+ }
213
+ if (font.unicodeRange !== void 0 && !UNICODE_RANGE.test(font.unicodeRange.trim())) {
214
+ throw new Error(`Invalid unicode-range for ${family}: ${font.unicodeRange}`);
215
+ }
216
+ const src = arrayBufferToDataUrl(font.data, FONT_MIME[format]);
217
+ const range = font.unicodeRange ? `;unicode-range:${font.unicodeRange.trim()}` : "";
218
+ return `@font-face{font-family:"${family}";src:url(${src}) format("${format}");font-weight:${weight};font-style:${style};font-display:block${range}}`;
219
+ }).join("\n");
220
+ }
190
221
  function generateRenderHtml(doc, options) {
191
222
  const {
192
223
  playerScript,
@@ -204,8 +235,12 @@ function generateRenderHtml(doc, options) {
204
235
  animationsEnabled = true,
205
236
  buildsEnabled = true,
206
237
  displayMode = "slideshow",
207
- dashboard
238
+ dashboard,
239
+ fonts = []
208
240
  } = options;
241
+ const fontCss = fonts.length > 0 ? `
242
+ ${fontFaceCss(fonts)}` : "";
243
+ const fontLoad = fonts.length > 0 ? "\n if (document.fonts) document.fonts.forEach(function (face) { face.load().catch(function () {}); });" : "";
209
244
  const imageMap = {};
210
245
  if (images) {
211
246
  for (const [path, buffer] of images.entries()) {
@@ -248,7 +283,7 @@ function generateRenderHtml(doc, options) {
248
283
  <style>
249
284
  *,*::before,*::after{box-sizing:border-box}
250
285
  html,body{margin:0;padding:0;width:${width}px;height:${height}px;overflow:hidden;background:#000}
251
- #squisq-root{width:${width}px;height:${height}px;display:flex;align-items:center;justify-content:center}
286
+ #squisq-root{width:${width}px;height:${height}px;display:flex;align-items:center;justify-content:center}${fontCss}
252
287
  </style>
253
288
  </head>
254
289
  <body>
@@ -259,7 +294,7 @@ html,body{margin:0;padding:0;width:${width}px;height:${height}px;overflow:hidden
259
294
  var doc = JSON.parse(${JSON.stringify(docJson)});
260
295
  var images = JSON.parse(${JSON.stringify(imageMapJson)});
261
296
  var audio = ${audioMapJson === "null" ? "null" : "JSON.parse(" + JSON.stringify(audioMapJson) + ")"};
262
- var root = document.getElementById("squisq-root");
297
+ var root = document.getElementById("squisq-root");${fontLoad}
263
298
  SquisqPlayer.mount(root, doc, {
264
299
  mode: ${JSON.stringify(displayMode === "dashboard" ? "dashboard" : "slideshow")},
265
300
  images: images,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-video",
3
- "version": "2.4.18",
3
+ "version": "2.4.20",
4
4
  "description": "Cross-runtime video and animated-GIF helpers with browser-based ffmpeg.wasm encoding for Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -48,7 +48,7 @@
48
48
  "typecheck": "tsc --noEmit"
49
49
  },
50
50
  "dependencies": {
51
- "@bendyline/squisq": "2.13.12",
51
+ "@bendyline/squisq": "2.13.14",
52
52
  "@ffmpeg/ffmpeg": "0.12.15",
53
53
  "@ffmpeg/util": "0.12.2"
54
54
  },