@bendyline/squisq 2.0.0 → 2.1.0

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 (85) hide show
  1. package/dist/{Doc-ylqWKDc_.d.ts → Doc-DynJleo-.d.ts} +159 -2
  2. package/dist/{ImageEditDoc-D828IXJ1.d.ts → ImageEditDoc-Ca4Tr9uN.d.ts} +1 -1
  3. package/dist/{chunk-PXLVXTLW.js → chunk-ILWDXLUK.js} +24 -6
  4. package/dist/chunk-ILWDXLUK.js.map +1 -0
  5. package/dist/{chunk-6PJRB2CF.js → chunk-JHZWYRSW.js} +570 -2
  6. package/dist/chunk-JHZWYRSW.js.map +1 -0
  7. package/dist/{chunk-GF3BVNLU.js → chunk-JI77MGAK.js} +195 -27
  8. package/dist/chunk-JI77MGAK.js.map +1 -0
  9. package/dist/{chunk-QVANWDPH.js → chunk-OTKVFVL6.js} +1403 -41
  10. package/dist/chunk-OTKVFVL6.js.map +1 -0
  11. package/dist/{chunk-LYNJEOAC.js → chunk-QUUPYOSP.js} +8 -2
  12. package/dist/chunk-QUUPYOSP.js.map +1 -0
  13. package/dist/chunk-VWCJ2WXC.js +215 -0
  14. package/dist/chunk-VWCJ2WXC.js.map +1 -0
  15. package/dist/{chunk-QTSJOY54.js → chunk-VYFRI7R6.js} +2 -2
  16. package/dist/{chunk-T3TI7GSZ.js → chunk-XNLB57UD.js} +2 -2
  17. package/dist/doc/index.d.ts +144 -5
  18. package/dist/doc/index.js +24 -4
  19. package/dist/generate/index.d.ts +1 -1
  20. package/dist/imageEdit/index.d.ts +3 -3
  21. package/dist/index.d.ts +6 -5
  22. package/dist/index.js +66 -8
  23. package/dist/jsonForm/index.d.ts +1 -1
  24. package/dist/jsonForm/index.js +4 -4
  25. package/dist/materializePageSection-DDzubQXo.d.ts +223 -0
  26. package/dist/narration/index.d.ts +1 -1
  27. package/dist/narration/index.js +3 -3
  28. package/dist/schemas/index.d.ts +30 -5
  29. package/dist/schemas/index.js +41 -3
  30. package/dist/{themeLibrary-RjVgLpUi.d.ts → themeLibrary-B72m0W1y.d.ts} +1 -1
  31. package/dist/transform/index.d.ts +8 -9
  32. package/dist/transform/index.js +1 -1
  33. package/package.json +1 -1
  34. package/src/__tests__/customThemesFrontmatter.test.ts +1 -0
  35. package/src/__tests__/markdown.test.ts +13 -4
  36. package/src/__tests__/markdownSanitize.test.ts +7 -5
  37. package/src/__tests__/materializeBlockLayers.test.ts +32 -0
  38. package/src/__tests__/materializePageSections.test.ts +404 -0
  39. package/src/__tests__/pageStyle.test.ts +258 -0
  40. package/src/__tests__/templateAnnotationParse.test.ts +13 -0
  41. package/src/doc/index.ts +34 -0
  42. package/src/doc/markdownToDoc.ts +3 -1
  43. package/src/doc/materializeBlockLayers.ts +170 -9
  44. package/src/doc/page/PageSection.ts +174 -0
  45. package/src/doc/page/materializePageSection.ts +616 -0
  46. package/src/doc/page/resolvePageBlock.ts +108 -0
  47. package/src/doc/page/sectionExtractors.ts +466 -0
  48. package/src/doc/pageCss.ts +628 -0
  49. package/src/doc/templateInputs.ts +1 -0
  50. package/src/doc/templates/__tests__/listBlock.test.ts +64 -0
  51. package/src/doc/templates/listBlock.ts +88 -30
  52. package/src/schemas/Doc.ts +22 -1
  53. package/src/schemas/PageStyle.ts +213 -0
  54. package/src/schemas/Theme.ts +7 -0
  55. package/src/schemas/index.ts +2 -0
  56. package/src/schemas/pageStyleDefaults.ts +204 -0
  57. package/src/schemas/themeCompile.ts +7 -0
  58. package/src/schemas/themeValidator.ts +183 -0
  59. package/src/schemas/themes/bold.json +29 -0
  60. package/src/schemas/themes/cinematic.json +32 -0
  61. package/src/schemas/themes/documentary.json +29 -0
  62. package/src/schemas/themes/gezellig.json +31 -1
  63. package/src/schemas/themes/magazine.json +37 -0
  64. package/src/schemas/themes/minimalist.json +29 -0
  65. package/src/schemas/themes/morning-light.json +32 -0
  66. package/src/schemas/themes/standard-dark.json +24 -0
  67. package/src/schemas/themes/standard.json +24 -0
  68. package/src/schemas/themes/tech-dark.json +42 -0
  69. package/src/schemas/themes/warm-earth.json +24 -0
  70. package/src/transform/registry.ts +19 -0
  71. package/src/transform/styles/dataDriven.ts +1 -0
  72. package/src/transform/styles/documentary.ts +1 -0
  73. package/src/transform/styles/magazine.ts +1 -0
  74. package/src/transform/styles/minimal.ts +1 -0
  75. package/src/transform/styles/narrative.ts +1 -0
  76. package/src/transform/types.ts +7 -0
  77. package/dist/chunk-6PJRB2CF.js.map +0 -1
  78. package/dist/chunk-FVJUUTEM.js +0 -74
  79. package/dist/chunk-FVJUUTEM.js.map +0 -1
  80. package/dist/chunk-GF3BVNLU.js.map +0 -1
  81. package/dist/chunk-LYNJEOAC.js.map +0 -1
  82. package/dist/chunk-PXLVXTLW.js.map +0 -1
  83. package/dist/chunk-QVANWDPH.js.map +0 -1
  84. /package/dist/{chunk-QTSJOY54.js.map → chunk-VYFRI7R6.js.map} +0 -0
  85. /package/dist/{chunk-T3TI7GSZ.js.map → chunk-XNLB57UD.js.map} +0 -0
@@ -30,7 +30,7 @@ import {
30
30
  resolveFontFamily,
31
31
  validateTheme,
32
32
  withAlpha
33
- } from "./chunk-6PJRB2CF.js";
33
+ } from "./chunk-JHZWYRSW.js";
34
34
  import {
35
35
  VIEWPORT_PRESETS,
36
36
  cloneData,
@@ -51,6 +51,7 @@ import {
51
51
  } from "./chunk-R2YZTCLO.js";
52
52
  import {
53
53
  extractPlainText,
54
+ readFrontmatterThemeId,
54
55
  walkMarkdownTree
55
56
  } from "./chunk-AMIR72JP.js";
56
57
  import {
@@ -1919,6 +1920,30 @@ function fullBleedQuote(input, context) {
1919
1920
  }
1920
1921
 
1921
1922
  // src/doc/templates/listBlock.ts
1923
+ var LIST_ITEM_LINE_HEIGHT = 1.2;
1924
+ var LIST_ITEM_GAP_PX = 18;
1925
+ function estimateWrappedLineCount(text, fontSize, maxWidth) {
1926
+ if (!text.trim()) return 1;
1927
+ const charsPerLine = Math.floor(maxWidth / (fontSize * 0.5));
1928
+ if (charsPerLine <= 0) return 1;
1929
+ let lineCount = 0;
1930
+ let currentLineLength = 0;
1931
+ for (const word of text.split(/\s+/)) {
1932
+ const testLineLength = currentLineLength ? currentLineLength + 1 + word.length : word.length;
1933
+ if (testLineLength <= charsPerLine) {
1934
+ currentLineLength = testLineLength;
1935
+ continue;
1936
+ }
1937
+ if (currentLineLength) lineCount += 1;
1938
+ let remainingLength = word.length;
1939
+ while (remainingLength > charsPerLine) {
1940
+ lineCount += 1;
1941
+ remainingLength -= charsPerLine;
1942
+ }
1943
+ currentLineLength = remainingLength;
1944
+ }
1945
+ return Math.max(1, lineCount + (currentLineLength ? 1 : 0));
1946
+ }
1922
1947
  function listBlock(input, context) {
1923
1948
  const { title, accentImage } = input;
1924
1949
  const items = Array.isArray(input.items) ? input.items : [];
@@ -1949,7 +1974,8 @@ function listBlock(input, context) {
1949
1974
  }
1950
1975
  const centerX = parseFloat(accentLayout.textCenterX);
1951
1976
  const widthPct = parseFloat(accentLayout.textWidth);
1952
- const leftX = Number.isFinite(centerX) && Number.isFinite(widthPct) ? `${centerX - widthPct / 2}%` : "8%";
1977
+ const leftPct = Number.isFinite(centerX) && Number.isFinite(widthPct) ? centerX - widthPct / 2 : 8;
1978
+ const leftX = `${leftPct}%`;
1953
1979
  const startY = title ? 34 : 26;
1954
1980
  if (title) {
1955
1981
  layers.push({
@@ -1974,14 +2000,43 @@ function listBlock(input, context) {
1974
2000
  animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1 })
1975
2001
  });
1976
2002
  }
1977
- const LIST_ITEM_BASE_PX = 34;
1978
- const LIST_ITEM_LINE_HEIGHT = 1.2;
1979
- const LIST_ITEM_GAP_PX = 18;
1980
- const DESIGN_HEIGHT_PX = 1080;
1981
- const spacing = (LIST_ITEM_BASE_PX * LIST_ITEM_LINE_HEIGHT + LIST_ITEM_GAP_PX) / DESIGN_HEIGHT_PX * 100;
2003
+ const textWidthPx = (Number.isFinite(widthPct) ? widthPct / 100 : 0.85) * context.viewport.width;
2004
+ const markerText = `${items.length}.`;
2005
+ const markerWidthPx = Math.max(itemFontSize, markerText.length * itemFontSize * 0.5);
2006
+ const markerGapPx = itemFontSize * 0.35;
2007
+ const bodyIndentPx = markerWidthPx + markerGapPx;
2008
+ const bodyLeftX = `${leftPct + bodyIndentPx / context.viewport.width * 100}%`;
2009
+ const bodyWidthPx = Math.max(itemFontSize, textWidthPx - bodyIndentPx);
2010
+ let itemY = startY;
1982
2011
  for (let i = 0; i < items.length; i++) {
1983
- const y = startY + spacing * i;
1984
- const itemText = `${i + 1}. ${items[i]}`;
2012
+ const itemText = items[i];
2013
+ const lineCount = estimateWrappedLineCount(itemText, itemFontSize, bodyWidthPx);
2014
+ const animation = themedEntrance(context, "text", {
2015
+ type: "fadeIn",
2016
+ duration: 0.8,
2017
+ delay: 0.3 + 0.3 * i
2018
+ });
2019
+ layers.push({
2020
+ type: "text",
2021
+ id: `item-${i}-marker`,
2022
+ content: {
2023
+ text: `${i + 1}.`,
2024
+ style: {
2025
+ fontSize: itemFontSize,
2026
+ fontFamily: getThemeFont(context, "body"),
2027
+ color: theme.colors.text,
2028
+ textAlign: "right",
2029
+ lineHeight: LIST_ITEM_LINE_HEIGHT,
2030
+ shadow: shouldUseShadow(context)
2031
+ }
2032
+ },
2033
+ position: {
2034
+ x: leftX,
2035
+ y: adjustY(`${itemY}%`, accentLayout),
2036
+ width: markerWidthPx
2037
+ },
2038
+ animation
2039
+ });
1985
2040
  layers.push({
1986
2041
  type: "text",
1987
2042
  id: `item-${i}`,
@@ -1992,21 +2047,19 @@ function listBlock(input, context) {
1992
2047
  fontFamily: getThemeFont(context, "body"),
1993
2048
  color: theme.colors.text,
1994
2049
  textAlign: "left",
1995
- lineHeight: 1.2,
2050
+ lineHeight: LIST_ITEM_LINE_HEIGHT,
1996
2051
  shadow: shouldUseShadow(context)
1997
2052
  }
1998
2053
  },
1999
2054
  position: {
2000
- x: leftX,
2001
- y: adjustY(`${y}%`, accentLayout),
2002
- width: accentLayout.textWidth
2055
+ x: bodyLeftX,
2056
+ y: adjustY(`${itemY}%`, accentLayout),
2057
+ width: bodyWidthPx
2003
2058
  },
2004
- animation: themedEntrance(context, "text", {
2005
- type: "fadeIn",
2006
- duration: 0.8,
2007
- delay: 0.3 + 0.3 * i
2008
- })
2059
+ animation
2009
2060
  });
2061
+ const itemHeightPx = lineCount * itemFontSize * LIST_ITEM_LINE_HEIGHT;
2062
+ itemY += (itemHeightPx + LIST_ITEM_GAP_PX) / context.viewport.height * 100;
2010
2063
  }
2011
2064
  return layers;
2012
2065
  }
@@ -5202,9 +5255,10 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
5202
5255
  const effectivePersistentLayers = options.persistentLayers === false ? void 0 : options.persistentLayers ?? theme.persistentLayers;
5203
5256
  const existingLayers = block.layers;
5204
5257
  if (existingLayers && existingLayers.length > 0 && !isTemplateBlock(block)) {
5258
+ const layers = appendMermaidLayers(cloneData(existingLayers), block, theme, viewport);
5205
5259
  return {
5206
5260
  layers: injectPersistentLayers(
5207
- cloneData(existingLayers),
5261
+ layers,
5208
5262
  block,
5209
5263
  theme,
5210
5264
  effectivePersistentLayers,
@@ -5214,15 +5268,16 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
5214
5268
  };
5215
5269
  }
5216
5270
  if (!isTemplateBlock(block)) {
5271
+ const layers = appendMermaidLayers([], block, theme, viewport);
5217
5272
  return {
5218
5273
  layers: injectPersistentLayers(
5219
- [],
5274
+ layers,
5220
5275
  block,
5221
5276
  theme,
5222
5277
  effectivePersistentLayers,
5223
5278
  runtime.expandedPersistentLayers
5224
5279
  ),
5225
- source: "empty"
5280
+ source: layers.length > 0 ? "rich-content" : "empty"
5226
5281
  };
5227
5282
  }
5228
5283
  const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport);
@@ -5230,9 +5285,10 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
5230
5285
  const execution = executeTemplateMaterialization(block, context, runtime.registry);
5231
5286
  if (execution.status === "ok") {
5232
5287
  const styledLayers = runtime.applyRenderStyle !== false ? applyRenderStyleToLayers(execution.layers, block, theme) : execution.layers;
5288
+ const layers = appendMermaidLayers(styledLayers, block, theme, viewport);
5233
5289
  return {
5234
5290
  layers: injectPersistentLayers(
5235
- styledLayers,
5291
+ layers,
5236
5292
  block,
5237
5293
  theme,
5238
5294
  effectivePersistentLayers,
@@ -5243,21 +5299,28 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
5243
5299
  }
5244
5300
  const diagnostic = toLayerMaterializationDiagnostic(block.template, execution);
5245
5301
  if ((options.failureMode ?? "fallback") === "empty") {
5302
+ const layers = appendMermaidLayers([], block, theme, viewport);
5246
5303
  return {
5247
5304
  layers: injectPersistentLayers(
5248
- [],
5305
+ layers,
5249
5306
  block,
5250
5307
  theme,
5251
5308
  effectivePersistentLayers,
5252
5309
  runtime.expandedPersistentLayers
5253
5310
  ),
5254
- source: "empty",
5311
+ source: layers.length > 0 ? "rich-content" : "empty",
5255
5312
  diagnostic
5256
5313
  };
5257
5314
  }
5315
+ const fallbackLayers = appendMermaidLayers(
5316
+ fallbackBlockLayers(block, context, diagnostic.message),
5317
+ block,
5318
+ theme,
5319
+ viewport
5320
+ );
5258
5321
  return {
5259
5322
  layers: injectPersistentLayers(
5260
- fallbackBlockLayers(block, context, diagnostic.message),
5323
+ fallbackLayers,
5261
5324
  block,
5262
5325
  theme,
5263
5326
  effectivePersistentLayers,
@@ -5267,6 +5330,109 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
5267
5330
  diagnostic
5268
5331
  };
5269
5332
  }
5333
+ function mermaidSources(block) {
5334
+ return (block.contents ?? []).filter(
5335
+ (node) => node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid"
5336
+ ).map((node) => node.value).filter((source) => source.trim().length > 0);
5337
+ }
5338
+ function resolvePositionValue(value, dimension) {
5339
+ if (value === void 0) return 0;
5340
+ if (typeof value === "number") return value;
5341
+ const percent = value.match(/^\s*(-?\d+(?:\.\d+)?)%\s*$/);
5342
+ if (percent) return Number(percent[1]) / 100 * dimension;
5343
+ const numeric = Number.parseFloat(value);
5344
+ return Number.isFinite(numeric) ? numeric : 0;
5345
+ }
5346
+ function positionRect(position, viewport, fallbackWidth, fallbackHeight) {
5347
+ const width = position.width ? resolvePositionValue(position.width, viewport.width) : fallbackWidth;
5348
+ const height = position.height ? resolvePositionValue(position.height, viewport.height) : fallbackHeight;
5349
+ let x = resolvePositionValue(position.x, viewport.width);
5350
+ let y = resolvePositionValue(position.y, viewport.height);
5351
+ const anchor = position.anchor ?? "top-left";
5352
+ if (anchor === "center") {
5353
+ x -= width / 2;
5354
+ y -= height / 2;
5355
+ } else {
5356
+ if (anchor.endsWith("right")) x -= width;
5357
+ if (anchor.startsWith("bottom")) y -= height;
5358
+ }
5359
+ return { x, y, width, height };
5360
+ }
5361
+ function overlapArea(a, b) {
5362
+ const width = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
5363
+ const height = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
5364
+ return width * height;
5365
+ }
5366
+ function mermaidRegion(layers, viewport) {
5367
+ const occupied = layers.filter((layer) => layer.type !== "shape" && layer.type !== "path" && layer.type !== "mermaid").map(
5368
+ (layer) => positionRect(
5369
+ layer.position,
5370
+ viewport,
5371
+ layer.type === "text" ? viewport.width * 0.45 : viewport.width * 0.5,
5372
+ layer.type === "text" ? viewport.height * 0.2 : viewport.height * 0.55
5373
+ )
5374
+ );
5375
+ if (occupied.length === 0) {
5376
+ return {
5377
+ x: viewport.width * 0.06,
5378
+ y: viewport.height * 0.08,
5379
+ width: viewport.width * 0.88,
5380
+ height: viewport.height * 0.84
5381
+ };
5382
+ }
5383
+ const candidates = [
5384
+ {
5385
+ x: viewport.width * 0.52,
5386
+ y: viewport.height * 0.12,
5387
+ width: viewport.width * 0.44,
5388
+ height: viewport.height * 0.76
5389
+ },
5390
+ {
5391
+ x: viewport.width * 0.04,
5392
+ y: viewport.height * 0.12,
5393
+ width: viewport.width * 0.44,
5394
+ height: viewport.height * 0.76
5395
+ },
5396
+ {
5397
+ x: viewport.width * 0.15,
5398
+ y: viewport.height * 0.53,
5399
+ width: viewport.width * 0.7,
5400
+ height: viewport.height * 0.41
5401
+ }
5402
+ ];
5403
+ return candidates.reduce((best, candidate) => {
5404
+ const score = occupied.reduce((sum, rect) => sum + overlapArea(candidate, rect), 0);
5405
+ const bestScore = occupied.reduce((sum, rect) => sum + overlapArea(best, rect), 0);
5406
+ return score < bestScore ? candidate : best;
5407
+ });
5408
+ }
5409
+ function appendMermaidLayers(layers, block, theme, viewport) {
5410
+ const sources = mermaidSources(block);
5411
+ if (sources.length === 0 || layers.some((layer) => layer.type === "mermaid")) return layers;
5412
+ const region = mermaidRegion(layers, viewport);
5413
+ const columns = sources.length === 1 ? 1 : 2;
5414
+ const rows = Math.ceil(sources.length / columns);
5415
+ const gap = Math.min(viewport.width, viewport.height) * 0.018;
5416
+ const width = (region.width - gap * (columns - 1)) / columns;
5417
+ const height = (region.height - gap * (rows - 1)) / rows;
5418
+ const mermaidLayers = sources.map((source, index) => ({
5419
+ id: `${block.id}-mermaid-${index + 1}`,
5420
+ type: "mermaid",
5421
+ position: {
5422
+ x: region.x + index % columns * (width + gap),
5423
+ y: region.y + Math.floor(index / columns) * (height + gap),
5424
+ width,
5425
+ height
5426
+ },
5427
+ content: {
5428
+ source,
5429
+ background: theme.colors.backgroundLight,
5430
+ foreground: theme.colors.text,
5431
+ padding: Math.max(12, Math.round(Math.min(width, height) * 0.025))
5432
+ }
5433
+ }));
5434
+ return [...layers, ...mermaidLayers];
5435
+ }
5270
5436
  function executeTemplateMaterialization(templateBlock, context, registry) {
5271
5437
  const safeTemplateBlock = cloneData(templateBlock);
5272
5438
  const safeContext = cloneData(context);
@@ -6099,7 +6265,7 @@ function findFirstList(contents) {
6099
6265
  // src/doc/templateInputs.ts
6100
6266
  function extractBodyPlainText(contents) {
6101
6267
  if (!contents || contents.length === 0) return "";
6102
- return contents.map((n) => extractPlainText(n)).join("\n").trim();
6268
+ return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
6103
6269
  }
6104
6270
  function extractListItems(contents) {
6105
6271
  if (!contents) return [];
@@ -7132,6 +7298,7 @@ function markdownToDoc(markdownDoc, options) {
7132
7298
  } : void 0;
7133
7299
  const customTemplates = readCustomTemplatesFromFrontmatter(markdownDoc.frontmatter);
7134
7300
  const customThemes = readCustomThemesFromFrontmatter(markdownDoc.frontmatter);
7301
+ const themeId = readFrontmatterThemeId(markdownDoc.frontmatter);
7135
7302
  const doc = {
7136
7303
  articleId,
7137
7304
  duration: currentTime,
@@ -7141,6 +7308,7 @@ function markdownToDoc(markdownDoc, options) {
7141
7308
  },
7142
7309
  ...captions ? { captions } : {},
7143
7310
  ...markdownDoc.frontmatter ? { frontmatter: markdownDoc.frontmatter } : {},
7311
+ ...themeId ? { themeId } : {},
7144
7312
  ...customTemplates ? { customTemplates } : {},
7145
7313
  ...customThemes ? { customThemes } : {},
7146
7314
  ...diagnostics.length > 0 ? { diagnostics } : {},
@@ -7842,4 +8010,4 @@ export {
7842
8010
  buildNarrationTimingJson,
7843
8011
  parseNarrationTimingJson
7844
8012
  };
7845
- //# sourceMappingURL=chunk-GF3BVNLU.js.map
8013
+ //# sourceMappingURL=chunk-JI77MGAK.js.map