@bendyline/squisq 2.11.13 → 2.11.15

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.
Files changed (33) hide show
  1. package/README.md +20 -0
  2. package/dist/{Doc-BWtfp7dT.d.ts → Doc-C-cv1g4G.d.ts} +8 -0
  3. package/dist/{ImageEditDoc-CYRQK8so.d.ts → ImageEditDoc-3J4PRw8N.d.ts} +1 -1
  4. package/dist/{chunk-S4427XKY.js → chunk-4IQ2DHCI.js} +83 -10
  5. package/dist/{chunk-EWO2VLOS.js → chunk-4P557IPH.js} +284 -14
  6. package/dist/{chunk-X2TDC4E5.js → chunk-GQWIUBJU.js} +2 -2
  7. package/dist/{chunk-RLBHQSUG.js → chunk-UVQPK6A3.js} +123 -0
  8. package/dist/{chunk-GIYWXYCK.js → chunk-XSOIWNZL.js} +8 -0
  9. package/dist/{chunk-42IU2SJY.js → chunk-YPI2VJSB.js} +190 -45
  10. package/dist/{chunk-BR7ZMDWW.js → chunk-Z35TG4WR.js} +11 -6
  11. package/dist/doc/index.d.ts +17 -7
  12. package/dist/doc/index.js +4 -3
  13. package/dist/fence/index.d.ts +1 -1
  14. package/dist/generate/index.d.ts +1 -1
  15. package/dist/imageEdit/index.d.ts +3 -3
  16. package/dist/index.d.ts +6 -6
  17. package/dist/index.js +12 -8
  18. package/dist/jsonForm/index.d.ts +1 -1
  19. package/dist/markdown/index.d.ts +12 -3
  20. package/dist/markdown/index.js +3 -1
  21. package/dist/{materializePageSection-Db7pABI0.d.ts → materializePageSection-Dmy_-AUQ.d.ts} +1 -1
  22. package/dist/mediaEdit/index.d.ts +2 -2
  23. package/dist/narration/index.d.ts +28 -8
  24. package/dist/narration/index.js +5 -3
  25. package/dist/schemas/index.d.ts +4 -4
  26. package/dist/storage/index.js +1 -1
  27. package/dist/{themeLibrary-Bphz1CRp.d.ts → themeLibrary-CROfw3YK.d.ts} +1 -1
  28. package/dist/timing/index.d.ts +1 -1
  29. package/dist/timing/index.js +1 -1
  30. package/dist/transform/index.d.ts +8 -2
  31. package/dist/transform/index.js +2 -2
  32. package/package.json +1 -1
  33. /package/dist/{chunk-44BJ42GT.js → chunk-4OQFLZ6G.js} +0 -0
@@ -393,6 +393,103 @@ function tooLarge(maxBytes) {
393
393
  );
394
394
  }
395
395
 
396
+ // src/markdown/condenseMarkdown.ts
397
+ function cellSource(source) {
398
+ let text = trimPadding(source);
399
+ if (text.startsWith("|")) text = text.slice(1);
400
+ if (text.endsWith("|")) {
401
+ let backslashes = 0;
402
+ for (let i = text.length - 2; i >= 0 && text[i] === "\\"; i--) backslashes++;
403
+ if (backslashes % 2 === 0) text = text.slice(0, -1);
404
+ }
405
+ return trimPadding(text);
406
+ }
407
+ function trimPadding(source) {
408
+ let start = 0;
409
+ let end = source.length;
410
+ while (start < end && (source[start] === " " || source[start] === " ")) start++;
411
+ while (end > start && (source[end - 1] === " " || source[end - 1] === " ")) end--;
412
+ return source.slice(start, end);
413
+ }
414
+ function tableEdits(source, table) {
415
+ const edits = [];
416
+ for (const row of table.children) {
417
+ const start2 = row.position?.start.offset;
418
+ const end2 = row.position?.end.offset;
419
+ if (start2 === void 0 || end2 === void 0) throw new Error("Table row has no source span");
420
+ let boundary = start2;
421
+ const cells2 = [];
422
+ for (const cell of row.children) {
423
+ const a = cell.position?.start.offset;
424
+ const b = cell.position?.end.offset;
425
+ if (a !== boundary || b === void 0 || b < a) {
426
+ throw new Error("Table cell spans do not cover their row");
427
+ }
428
+ cells2.push(cellSource(source.slice(a, b)));
429
+ boundary = b;
430
+ }
431
+ if (boundary !== end2) throw new Error("Table cell spans do not cover their row");
432
+ edits.push({ start: start2, end: end2, text: `| ${cells2.join(" | ")} |` });
433
+ }
434
+ const headerEnd = table.children[0]?.position?.end.offset;
435
+ if (headerEnd === void 0) throw new Error("Table has no header span");
436
+ const start = source.indexOf("\n", headerEnd) + 1;
437
+ const newline = source.indexOf("\n", start);
438
+ let end = newline === -1 ? source.length : newline;
439
+ if (source[end - 1] === "\r") end--;
440
+ const divider = source.slice(start, end);
441
+ const prefix = /^[ \t>]*/.exec(divider)?.[0] ?? "";
442
+ if (start === 0 || !/^[|:\- \t]+$/.test(divider.slice(prefix.length))) {
443
+ throw new Error("Table has no recognizable delimiter row");
444
+ }
445
+ const columns = table.children[0]?.children.length ?? 0;
446
+ const cells = Array.from({ length: columns }, (_, i) => {
447
+ const align = table.align?.[i];
448
+ return `${align === "left" || align === "center" ? ":" : ""}---${align === "right" || align === "center" ? ":" : ""}`;
449
+ });
450
+ edits.push({ start: start + prefix.length, end, text: `| ${cells.join(" | ")} |` });
451
+ return edits;
452
+ }
453
+ function condenseMarkdownSource(source) {
454
+ const { frontmatter, body } = splitFrontmatterBlock(source);
455
+ const options = { frontmatter: false, parseHtml: false };
456
+ const document = parseMarkdown(body, options);
457
+ const edits = [];
458
+ const visit = (node) => {
459
+ if (node.type === "table") {
460
+ edits.push(...tableEdits(body, node));
461
+ return;
462
+ }
463
+ if (node.type === "thematicBreak") {
464
+ const start = node.position?.start.offset;
465
+ const end = node.position?.end.offset;
466
+ if (start !== void 0 && end !== void 0) {
467
+ const marker = body.slice(start, end).match(/[-*_]/)?.[0];
468
+ if (marker) edits.push({ start, end, text: marker.repeat(3) });
469
+ }
470
+ }
471
+ for (const child of getChildren(node)) visit(child);
472
+ };
473
+ visit(document);
474
+ const parts = [];
475
+ let boundary = 0;
476
+ let changed = false;
477
+ for (const edit of edits.sort((a, b) => a.start - b.start)) {
478
+ if (edit.start < boundary) throw new Error("Overlapping Markdown condensation edits");
479
+ parts.push(body.slice(boundary, edit.start), edit.text);
480
+ changed || (changed = body.slice(edit.start, edit.end) !== edit.text);
481
+ boundary = edit.end;
482
+ }
483
+ if (!changed) return source;
484
+ parts.push(body.slice(boundary));
485
+ const output = parts.join("");
486
+ const withoutPositions = (key, value) => key === "position" ? void 0 : value;
487
+ if (JSON.stringify(document, withoutPositions) !== JSON.stringify(parseMarkdown(output, options), withoutPositions)) {
488
+ throw new Error("Condensed Markdown no longer parses to an equivalent document");
489
+ }
490
+ return (frontmatter ?? "") + output;
491
+ }
492
+
396
493
  // src/markdown/sourceTransforms.ts
397
494
  var DEFAULT_WRAP_WIDTH = 80;
398
495
  var MIN_WRAP_WIDTH = 20;
@@ -861,6 +958,25 @@ ${cleanedBody}` : fm;
861
958
  edits: [{ start: 0, end: source.length, text: output }]
862
959
  };
863
960
  }
961
+ function condenseTransform(source, options) {
962
+ try {
963
+ const output = condenseMarkdownSource(source);
964
+ if (output === source) return noopResult(source, false);
965
+ return {
966
+ output,
967
+ changed: true,
968
+ degraded: false,
969
+ edits: [{ start: 0, end: source.length, text: output }]
970
+ };
971
+ } catch (err) {
972
+ return degrade(
973
+ "condense",
974
+ source,
975
+ options?.strict,
976
+ err instanceof Error ? err.message : String(err)
977
+ );
978
+ }
979
+ }
864
980
  var MARKDOWN_SOURCE_TRANSFORMS = Object.freeze([
865
981
  {
866
982
  id: "unwrap",
@@ -879,6 +995,12 @@ var MARKDOWN_SOURCE_TRANSFORMS = Object.freeze([
879
995
  label: "Clean up formatting",
880
996
  description: "Re-serialize through the canonical house style: bullets, emphasis, headings, table padding, spacing.",
881
997
  apply: (source, options) => cleanupTransform(source, options)
998
+ },
999
+ {
1000
+ id: "condense",
1001
+ label: "Condense formatting",
1002
+ description: "Compact table padding and decorative dividers while preserving all document content.",
1003
+ apply: (source, options) => condenseTransform(source, options)
882
1004
  }
883
1005
  ]);
884
1006
  function applyMarkdownSourceTransform(id, source, options) {
@@ -1144,6 +1266,7 @@ export {
1144
1266
  ResourcePolicyError,
1145
1267
  isResourceUrlAllowed,
1146
1268
  fetchResourceBytes,
1269
+ condenseMarkdownSource,
1147
1270
  DEFAULT_WRAP_WIDTH,
1148
1271
  MARKDOWN_SOURCE_TRANSFORMS,
1149
1272
  applyMarkdownSourceTransform,
@@ -348,6 +348,8 @@ function createMediaProviderFromContainer(container) {
348
348
  const generation = generations.get(relativePath) ?? 0;
349
349
  const work = (async () => {
350
350
  const data = await container.readFile(relativePath);
351
+ if (disposed) return relativePath;
352
+ if ((generations.get(relativePath) ?? 0) !== generation) return resolveUrl(relativePath);
351
353
  const entries = data ? await container.listFiles() : [];
352
354
  if (disposed) return relativePath;
353
355
  if ((generations.get(relativePath) ?? 0) !== generation) return resolveUrl(relativePath);
@@ -376,6 +378,7 @@ function createMediaProviderFromContainer(container) {
376
378
  },
377
379
  async addMedia(name, data, mimeType) {
378
380
  invalidate(name);
381
+ const generation = generations.get(name);
379
382
  let buffer;
380
383
  if (data instanceof Blob) {
381
384
  buffer = await data.arrayBuffer();
@@ -383,7 +386,12 @@ function createMediaProviderFromContainer(container) {
383
386
  buffer = data;
384
387
  }
385
388
  await container.writeFile(name, buffer, mimeType);
389
+ const canCacheUpload = generations.get(name) === generation;
386
390
  invalidate(name);
391
+ if (!disposed && canCacheUpload) {
392
+ const bytes = ArrayBuffer.isView(buffer) ? new Uint8Array(buffer).slice().buffer : buffer;
393
+ blobUrlCache.set(name, URL.createObjectURL(new Blob([bytes], { type: mimeType })));
394
+ }
387
395
  return name;
388
396
  },
389
397
  async removeMedia(relativePath) {
@@ -2153,6 +2153,7 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
2153
2153
 
2154
2154
  // src/doc/templates/contentBlock.ts
2155
2155
  function bodyNodes(context) {
2156
+ if (context.block?.summaryMode === "headings-and-features") return [];
2156
2157
  return (context.block?.contents ?? []).filter(
2157
2158
  // Mermaid source is already materialized as a visual layer. Repeating the
2158
2159
  // fence text as prose makes the loss-averse default noisy without
@@ -3310,6 +3311,41 @@ function imageWithCaption(input, context) {
3310
3311
  return layers;
3311
3312
  }
3312
3313
 
3314
+ // src/doc/templates/featureMediaSlot.ts
3315
+ function featureMediaSlot(side, {
3316
+ stack,
3317
+ summaryFeature,
3318
+ sized = false
3319
+ }) {
3320
+ if (summaryFeature) {
3321
+ return {
3322
+ position: {
3323
+ x: stack ? "5%" : side === "left" ? "5%" : "35%",
3324
+ y: "5%",
3325
+ width: stack ? "90%" : "60%",
3326
+ height: stack ? "70%" : "90%"
3327
+ },
3328
+ fit: "contain"
3329
+ };
3330
+ }
3331
+ if (stack) {
3332
+ return {
3333
+ position: { x: "0", y: "0", width: "100%", height: "50%" },
3334
+ fit: sized ? "contain" : "cover"
3335
+ };
3336
+ }
3337
+ if (sized) {
3338
+ return {
3339
+ position: { x: side === "left" ? "5%" : "55%", y: "5%", width: "40%", height: "90%" },
3340
+ fit: "contain"
3341
+ };
3342
+ }
3343
+ return {
3344
+ position: { x: side === "left" ? "0" : "50%", y: "0", width: "50%", height: "100%" },
3345
+ fit: "cover"
3346
+ };
3347
+ }
3348
+
3313
3349
  // src/doc/templates/featureBlock.ts
3314
3350
  var TITLE_LINE_HEIGHT3 = 1.2;
3315
3351
  var BODY_LINE_HEIGHT = 1.5;
@@ -3375,6 +3411,7 @@ function buildFeatureLayers(input, context, side) {
3375
3411
  const body = input.body?.trim() ?? "";
3376
3412
  const { theme, layout, viewport } = context;
3377
3413
  const treatment = themedImageTreatment(context, input.imageTreatment);
3414
+ const summaryFeature = input.summaryMode === "headings-and-features" && !body;
3378
3415
  const sized = (imageWidth ?? 0) > 0 || (imageHeight ?? 0) > 0;
3379
3416
  const stack = layout.stackColumns;
3380
3417
  const titleFont = getThemeFont(context, "title");
@@ -3383,39 +3420,15 @@ function buildFeatureLayers(input, context, side) {
3383
3420
  const titleMinSize = themedFontSize(30, context, true);
3384
3421
  const bodyBaseSize = themedFontSize(24, context, false);
3385
3422
  const bodyMinSize = themedFontSize(18, context, false);
3386
- let imgX;
3387
- let imgY;
3388
- let imgW;
3389
- let imgH;
3390
- let imageFit;
3391
- if (stack) {
3392
- imgX = "0";
3393
- imgY = "0";
3394
- imgW = "100%";
3395
- imgH = "50%";
3396
- imageFit = sized ? "contain" : "cover";
3397
- } else if (sized) {
3398
- const halfStart = side === "left" ? 5 : 55;
3399
- imgX = `${halfStart}%`;
3400
- imgY = "5%";
3401
- imgW = "40%";
3402
- imgH = "90%";
3403
- imageFit = "contain";
3404
- } else {
3405
- imgX = side === "left" ? "0" : "50%";
3406
- imgY = "0";
3407
- imgW = "50%";
3408
- imgH = "100%";
3409
- imageFit = "cover";
3410
- }
3423
+ const mediaSlot = featureMediaSlot(side, { stack, summaryFeature, sized });
3411
3424
  const COLUMN_INSET = 4;
3412
- const textColumnWidth = stack ? 90 : 42;
3413
- const textXPct = stack ? (100 - textColumnWidth) / 2 : side === "left" ? 50 + COLUMN_INSET : COLUMN_INSET + 2;
3425
+ const textColumnWidth = stack ? 90 : summaryFeature ? 26 : 42;
3426
+ const textXPct = summaryFeature && !stack ? side === "left" ? 69 : 5 : stack ? (100 - textColumnWidth) / 2 : side === "left" ? 50 + COLUMN_INSET : COLUMN_INSET + 2;
3414
3427
  const textX = `${textXPct}%`;
3415
3428
  const columnWidthPx = textColumnWidth / 100 * viewport.width;
3416
3429
  const textAnchor = "top-left";
3417
3430
  const textAlign = "left";
3418
- const bandTop = (stack ? 0.56 : 0.08) * viewport.height;
3431
+ const bandTop = (stack ? summaryFeature ? 0.79 : 0.56 : 0.08) * viewport.height;
3419
3432
  const bandBottom = (stack ? 0.92 : 0.9) * viewport.height;
3420
3433
  const bandHeight = bandBottom - bandTop;
3421
3434
  const titleWrapWidth = title ? fitWrapWidth(title, titleBaseSize, columnWidthPx, titleFont) : 0;
@@ -3424,7 +3437,7 @@ function buildFeatureLayers(input, context, side) {
3424
3437
  baseFontSize: titleBaseSize,
3425
3438
  minFontSize: titleMinSize,
3426
3439
  maxWidthPx: titleWrapWidth,
3427
- maxHeightPx: bandHeight * 0.45,
3440
+ maxHeightPx: bandHeight * (summaryFeature ? 1 : 0.45),
3428
3441
  lineHeight: TITLE_LINE_HEIGHT3
3429
3442
  }) : null;
3430
3443
  const titleLines = titleFit ? Math.min(
@@ -3464,10 +3477,10 @@ function buildFeatureLayers(input, context, side) {
3464
3477
  content: {
3465
3478
  src: imageSrc,
3466
3479
  alt: imageAlt ?? title,
3467
- fit: imageFit,
3480
+ fit: mediaSlot.fit,
3468
3481
  ...treatment ? { treatment } : {}
3469
3482
  },
3470
- position: { x: imgX, y: imgY, width: imgW, height: imgH }
3483
+ position: mediaSlot.position
3471
3484
  });
3472
3485
  }
3473
3486
  if (title && titleFit) {
@@ -4902,7 +4915,7 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
4902
4915
  unconsumedMedia: "retain-native-layout"
4903
4916
  },
4904
4917
  leftFeature: {
4905
- summary: "Requires a primary image in the left feature cell; text occupies the right cell.",
4918
+ summary: "Primary imagery or embedded visuals fill the left feature cell; text occupies the right cell.",
4906
4919
  noMedia: "unsupported",
4907
4920
  ownership: "required-native",
4908
4921
  nativeLayout: "feature-left",
@@ -4910,7 +4923,7 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
4910
4923
  unconsumedMedia: "retain-native-layout"
4911
4924
  },
4912
4925
  rightFeature: {
4913
- summary: "Requires a primary image in the right feature cell; text occupies the left cell.",
4926
+ summary: "Primary imagery or embedded visuals fill the right feature cell; text occupies the left cell.",
4914
4927
  noMedia: "unsupported",
4915
4928
  ownership: "required-native",
4916
4929
  nativeLayout: "feature-right",
@@ -5141,6 +5154,93 @@ function wrappedLineCount2(text, fontSize, maxWidth) {
5141
5154
  }
5142
5155
 
5143
5156
  // src/doc/richMediaLayout.ts
5157
+ function containedFeatureArea(region, aspects) {
5158
+ const count = aspects.length;
5159
+ const columns = Math.min(
5160
+ count,
5161
+ Math.max(1, Math.ceil(Math.sqrt(count * region.width / region.height)))
5162
+ );
5163
+ const rows = Math.ceil(count / columns);
5164
+ const width = region.width / columns;
5165
+ const height = region.height / rows;
5166
+ return aspects.reduce((area, aspect) => {
5167
+ const ratio = aspect && Number.isFinite(aspect) && aspect > 0 ? aspect : 16 / 9;
5168
+ const containedWidth = Math.min(width, height * ratio);
5169
+ return area + containedWidth * (containedWidth / ratio);
5170
+ }, 0);
5171
+ }
5172
+ function resolveFeatureSummaryLayout(layers, viewport, aspectRatios) {
5173
+ const title = layers.find((layer) => layer.type === "text" && layer.id === "title");
5174
+ if (!title || title.type !== "text") {
5175
+ return { layers, mediaRect: rect(viewport, 0.045, 0.045, 0.91, 0.91), framed: false };
5176
+ }
5177
+ const unit = Math.min(viewport.width, viewport.height);
5178
+ const margin = unit * 0.045;
5179
+ const gap = unit * 0.035;
5180
+ const baseFontSize = Math.min(title.content.style.fontSize, unit * 0.056);
5181
+ const minFontSize = Math.min(baseFontSize, unit * 0.026);
5182
+ const lineHeight = 1.2;
5183
+ const topWidth = viewport.width - margin * 2;
5184
+ const topFit = fitProse({
5185
+ text: title.content.text,
5186
+ baseFontSize,
5187
+ minFontSize,
5188
+ maxWidthPx: topWidth,
5189
+ maxHeightPx: viewport.height * 0.26,
5190
+ lineHeight
5191
+ });
5192
+ const mediaTop = margin + topFit.heightPx + gap;
5193
+ const topMedia = {
5194
+ x: margin,
5195
+ y: mediaTop,
5196
+ width: topWidth,
5197
+ height: viewport.height - margin - mediaTop
5198
+ };
5199
+ const sideWidth = viewport.width * 0.25;
5200
+ const sideMedia = {
5201
+ x: margin + sideWidth + gap,
5202
+ y: margin,
5203
+ width: viewport.width - margin * 2 - sideWidth - gap,
5204
+ height: viewport.height - margin * 2
5205
+ };
5206
+ const side = containedFeatureArea(sideMedia, aspectRatios) > containedFeatureArea(topMedia, aspectRatios) * 1.04;
5207
+ const titleWidth = side ? sideWidth : topWidth;
5208
+ const titleFit = side ? fitProse({
5209
+ text: title.content.text,
5210
+ baseFontSize,
5211
+ minFontSize,
5212
+ maxWidthPx: titleWidth,
5213
+ maxHeightPx: sideMedia.height,
5214
+ lineHeight
5215
+ }) : topFit;
5216
+ const titleLayer = {
5217
+ ...title,
5218
+ position: {
5219
+ ...title.position,
5220
+ x: side ? margin : viewport.width / 2,
5221
+ y: side ? viewport.height / 2 - titleFit.heightPx / 2 : margin + titleFit.heightPx / 2,
5222
+ width: titleWidth,
5223
+ height: titleFit.heightPx,
5224
+ anchor: side ? "top-left" : "center"
5225
+ },
5226
+ content: {
5227
+ ...title.content,
5228
+ style: {
5229
+ ...title.content.style,
5230
+ fontSize: titleFit.fontSize,
5231
+ lineHeight,
5232
+ textAlign: side ? "left" : "center",
5233
+ maxLines: titleFit.maxLines,
5234
+ shrinkToFit: true
5235
+ }
5236
+ }
5237
+ };
5238
+ return {
5239
+ layers: [...layers.filter((layer) => isFullViewportBackground(layer, viewport)), titleLayer],
5240
+ mediaRect: side ? sideMedia : topMedia,
5241
+ framed: false
5242
+ };
5243
+ }
5144
5244
  function rect(viewport, x, y, width, height) {
5145
5245
  return {
5146
5246
  x: viewport.width * x,
@@ -5553,12 +5653,31 @@ function leastObstructedMediaRect(layers, viewport) {
5553
5653
  return score < bestScore ? candidate : best;
5554
5654
  });
5555
5655
  }
5556
- function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount, aspectRatios) {
5656
+ function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount, aspectRatios, featureOptions = {}) {
5557
5657
  const policy = getBlockMediaLayoutPolicy(template);
5558
5658
  const shape = mediaShape(mediaCount, aspectRatios);
5559
5659
  const alreadyOwnsMedia = layers.some(
5560
5660
  (layer) => layer.type === "image" || layer.type === "video" || layer.type === "map" || layer.type === "mermaid"
5561
5661
  );
5662
+ if (!alreadyOwnsMedia && (policy?.nativeLayout === "feature-left" || policy?.nativeLayout === "feature-right")) {
5663
+ const { position } = featureMediaSlot(
5664
+ policy.nativeLayout === "feature-left" ? "left" : "right",
5665
+ {
5666
+ stack: featureOptions.stackColumns ?? getViewportOrientation(viewport) === "portrait",
5667
+ summaryFeature: featureOptions.summaryFeature ?? false
5668
+ }
5669
+ );
5670
+ return {
5671
+ layers,
5672
+ mediaRect: {
5673
+ x: resolvePositionValue(position.x, viewport.width),
5674
+ y: resolvePositionValue(position.y, viewport.height),
5675
+ width: resolvePositionValue(position.width, viewport.width),
5676
+ height: resolvePositionValue(position.height, viewport.height)
5677
+ },
5678
+ framed: false
5679
+ };
5680
+ }
5562
5681
  const retainNativeLayout = policy?.unconsumedMedia === "retain-native-layout" || policy?.unconsumedMedia === "reserve-when-no-native-media" && alreadyOwnsMedia;
5563
5682
  if (policy?.additionalMediaLayout === "overlay-inset" && retainNativeLayout) {
5564
5683
  return {
@@ -9638,7 +9757,8 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
9638
9757
  cloneData(existingLayers),
9639
9758
  block,
9640
9759
  theme,
9641
- viewport
9760
+ viewport,
9761
+ options.mediaAspectRatios
9642
9762
  );
9643
9763
  return {
9644
9764
  layers: injectPersistentLayers(
@@ -9652,7 +9772,13 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
9652
9772
  };
9653
9773
  }
9654
9774
  if (!isTemplateBlock(block)) {
9655
- const layers = appendRichContentLayers([], block, theme, viewport);
9775
+ const layers = appendRichContentLayers(
9776
+ [],
9777
+ block,
9778
+ theme,
9779
+ viewport,
9780
+ options.mediaAspectRatios
9781
+ );
9656
9782
  return {
9657
9783
  layers: injectPersistentLayers(
9658
9784
  layers,
@@ -9669,7 +9795,13 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
9669
9795
  const execution = executeTemplateMaterialization(block, context, runtime.registry);
9670
9796
  if (execution.status === "ok") {
9671
9797
  const styledLayers = runtime.applyRenderStyle !== false ? applyRenderStyleToLayers(execution.layers, block, theme) : execution.layers;
9672
- const layers = appendRichContentLayers(styledLayers, block, theme, viewport);
9798
+ const layers = appendRichContentLayers(
9799
+ styledLayers,
9800
+ block,
9801
+ theme,
9802
+ viewport,
9803
+ options.mediaAspectRatios
9804
+ );
9673
9805
  return {
9674
9806
  layers: injectPersistentLayers(
9675
9807
  layers,
@@ -9940,16 +10072,25 @@ function applyEmbeddedVideoTiming(layers, block) {
9940
10072
  };
9941
10073
  });
9942
10074
  }
9943
- function appendRichContentLayers(layers, block, theme, viewport) {
10075
+ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRatios) {
9944
10076
  const timedLayers = applyEmbeddedVideoTiming(layers, block);
9945
10077
  const items = collectRichMediaItems(timedLayers, block);
9946
10078
  if (items.length === 0) return timedLayers;
9947
- const layout = resolveSupplementalMediaLayout(
10079
+ const aspects = items.map((item) => {
10080
+ const key = item.kind === "image" || item.kind === "video" ? item.src : item.kind === "mermaid" ? item.source : void 0;
10081
+ const intrinsic = key ? mediaAspectRatios?.[key] : void 0;
10082
+ return intrinsic && Number.isFinite(intrinsic) && intrinsic > 0 ? intrinsic : item.aspectRatio;
10083
+ });
10084
+ const layout = block.summaryLayout === "feature" ? resolveFeatureSummaryLayout(timedLayers, viewport, aspects) : resolveSupplementalMediaLayout(
9948
10085
  timedLayers,
9949
10086
  block.template ? resolveTemplateName(block.template) : void 0,
9950
10087
  viewport,
9951
10088
  items.length,
9952
- items.map((item) => item.aspectRatio)
10089
+ aspects,
10090
+ {
10091
+ summaryFeature: block.summaryMode === "headings-and-features" && !timedLayers.some((layer) => layer.id === "feature-body"),
10092
+ stackColumns: createTemplateContext(theme, 0, 1, viewport).layout.stackColumns
10093
+ }
9953
10094
  );
9954
10095
  const cells = gridCells(layout.mediaRect, items.length, viewport, layout.framed);
9955
10096
  const counters = {
@@ -10669,7 +10810,7 @@ function expandCoverBlock(config, context, template = "cover") {
10669
10810
  }
10670
10811
 
10671
10812
  // src/doc/templates/index.ts
10672
- function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic, motion) {
10813
+ function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic, motion, mediaAspectRatios) {
10673
10814
  const materialized = materializeBlockLayersWithRuntime(
10674
10815
  sourceBlock,
10675
10816
  {
@@ -10679,7 +10820,8 @@ function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme,
10679
10820
  blockIndex,
10680
10821
  totalBlocks,
10681
10822
  failureMode,
10682
- motion
10823
+ motion,
10824
+ mediaAspectRatios
10683
10825
  },
10684
10826
  { registry, expandedPersistentLayers }
10685
10827
  );
@@ -10739,7 +10881,8 @@ function expandDocBlocks(blocks, options = {}) {
10739
10881
  registry,
10740
10882
  failureMode,
10741
10883
  onDiagnostic,
10742
- motion
10884
+ motion,
10885
+ opts.mediaAspectRatios
10743
10886
  );
10744
10887
  expandedBlock.startTime = currentTime;
10745
10888
  currentTime += expandedBlock.duration;
@@ -10772,7 +10915,8 @@ function expandDocBlocks(blocks, options = {}) {
10772
10915
  registry,
10773
10916
  failureMode,
10774
10917
  onDiagnostic,
10775
- motion
10918
+ motion,
10919
+ opts.mediaAspectRatios
10776
10920
  );
10777
10921
  expandedBlock.startTime = offsetTime;
10778
10922
  offsetTime += expandedBlock.duration;
@@ -10811,7 +10955,8 @@ function expandDocBlocks(blocks, options = {}) {
10811
10955
  registry,
10812
10956
  failureMode,
10813
10957
  onDiagnostic,
10814
- motion
10958
+ motion,
10959
+ opts.mediaAspectRatios
10815
10960
  );
10816
10961
  const templateBlock = block;
10817
10962
  expandedInfos.push({
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  flattenRenderableBlocks,
3
3
  getBlockBodyText
4
- } from "./chunk-42IU2SJY.js";
4
+ } from "./chunk-YPI2VJSB.js";
5
5
  import {
6
6
  estimateSpokenWordCount
7
7
  } from "./chunk-GHOPHGOK.js";
@@ -216,6 +216,14 @@ function wordIndexAtTime(words, tSec) {
216
216
  }
217
217
 
218
218
  // src/narration/sidecar.ts
219
+ function resolveNarrationTimingGenerator(options) {
220
+ const baseWpm = options?.generator?.baseWpm ?? options?.baseWpm;
221
+ return {
222
+ name: options?.generator?.name ?? "squisq-teleprompter",
223
+ method: options?.generator?.method ?? "dsp-align",
224
+ ...baseWpm !== void 0 ? { baseWpm } : {}
225
+ };
226
+ }
219
227
  function buildNarrationTimingJson(script, alignment, durationSec, options) {
220
228
  const bookmarks = alignment.words.map((word) => {
221
229
  const token = script.tokens[word.tokenIndex];
@@ -246,11 +254,7 @@ function buildNarrationTimingJson(script, alignment, durationSec, options) {
246
254
  // (JSON has no NaN) and be dropped on parse anyway — omitting it here
247
255
  // keeps the emitted sidecar honest rather than carrying a dead field.
248
256
  ...finiteNumber(options?.cameraOffsetSec) ? { cameraOffsetSec: options.cameraOffsetSec } : {},
249
- generator: {
250
- name: "squisq-teleprompter",
251
- method: "dsp-align",
252
- ...options?.baseWpm !== void 0 ? { baseWpm: options.baseWpm } : {}
253
- }
257
+ generator: resolveNarrationTimingGenerator(options)
254
258
  };
255
259
  }
256
260
  function finiteNonNegative(value) {
@@ -327,6 +331,7 @@ export {
327
331
  wordPosAtExpectedSyllables,
328
332
  wordIndexAtChar,
329
333
  wordIndexAtTime,
334
+ resolveNarrationTimingGenerator,
330
335
  buildNarrationTimingJson,
331
336
  parseNarrationTimingJson
332
337
  };