@hirokisakabe/pom 9.1.1 → 10.0.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 (73) hide show
  1. package/dist/buildContext.js +2 -6
  2. package/dist/buildContext.js.map +1 -1
  3. package/dist/buildPptx.d.ts +2 -1
  4. package/dist/buildPptx.d.ts.map +1 -1
  5. package/dist/buildPptx.js +4 -9
  6. package/dist/buildPptx.js.map +1 -1
  7. package/dist/calcYogaLayout/calcYogaLayout.js +1 -1
  8. package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
  9. package/dist/index.d.ts +2 -1
  10. package/dist/registry/definitions/icon.js +1 -1
  11. package/dist/registry/definitions/list.js.map +1 -1
  12. package/dist/renderPptx/nodes/chart.js +172 -47
  13. package/dist/renderPptx/nodes/chart.js.map +1 -1
  14. package/dist/renderPptx/nodes/flow.js +98 -90
  15. package/dist/renderPptx/nodes/flow.js.map +1 -1
  16. package/dist/renderPptx/nodes/icon.js +36 -24
  17. package/dist/renderPptx/nodes/icon.js.map +1 -1
  18. package/dist/renderPptx/nodes/image.js +5 -22
  19. package/dist/renderPptx/nodes/image.js.map +1 -1
  20. package/dist/renderPptx/nodes/list.js +94 -111
  21. package/dist/renderPptx/nodes/list.js.map +1 -1
  22. package/dist/renderPptx/nodes/matrix.js +95 -70
  23. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  24. package/dist/renderPptx/nodes/processArrow.js +74 -35
  25. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  26. package/dist/renderPptx/nodes/pyramid.js +37 -16
  27. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  28. package/dist/renderPptx/nodes/shape.js +66 -37
  29. package/dist/renderPptx/nodes/shape.js.map +1 -1
  30. package/dist/renderPptx/nodes/svg.js +2 -5
  31. package/dist/renderPptx/nodes/svg.js.map +1 -1
  32. package/dist/renderPptx/nodes/table.js +89 -59
  33. package/dist/renderPptx/nodes/table.js.map +1 -1
  34. package/dist/renderPptx/nodes/text.js +1 -18
  35. package/dist/renderPptx/nodes/text.js.map +1 -1
  36. package/dist/renderPptx/nodes/timeline.js +114 -74
  37. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  38. package/dist/renderPptx/nodes/tree.js +81 -79
  39. package/dist/renderPptx/nodes/tree.js.map +1 -1
  40. package/dist/renderPptx/pptxAuthoring.js +381 -0
  41. package/dist/renderPptx/pptxAuthoring.js.map +1 -0
  42. package/dist/renderPptx/renderPptx.js +158 -128
  43. package/dist/renderPptx/renderPptx.js.map +1 -1
  44. package/dist/renderPptx/textOptions.js +7 -8
  45. package/dist/renderPptx/textOptions.js.map +1 -1
  46. package/dist/renderPptx/units.js +3 -3
  47. package/dist/renderPptx/units.js.map +1 -1
  48. package/dist/renderPptx/utils/backgroundBorder.js +70 -48
  49. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  50. package/dist/renderPptx/utils/contentArea.js +1 -1
  51. package/dist/renderPptx/utils/contentArea.js.map +1 -1
  52. package/dist/renderPptx/utils/glimpsePicture.js +111 -0
  53. package/dist/renderPptx/utils/glimpsePicture.js.map +1 -0
  54. package/dist/renderPptx/utils/glimpseShape.js +71 -0
  55. package/dist/renderPptx/utils/glimpseShape.js.map +1 -0
  56. package/dist/renderPptx/utils/glimpseTextBox.js +107 -0
  57. package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -0
  58. package/dist/renderPptx/utils/straightLine.js +33 -15
  59. package/dist/renderPptx/utils/straightLine.js.map +1 -1
  60. package/dist/renderPptx/utils/visualStyle.js +3 -56
  61. package/dist/renderPptx/utils/visualStyle.js.map +1 -1
  62. package/dist/renderPptx/writablePptx.d.ts +23 -0
  63. package/dist/renderPptx/writablePptx.d.ts.map +1 -0
  64. package/dist/renderPptx/writablePptx.js +74 -0
  65. package/dist/renderPptx/writablePptx.js.map +1 -0
  66. package/dist/shared/measureImage.js.map +1 -1
  67. package/package.json +4 -5
  68. package/dist/renderPptx/glimpseTextBoxes.js +0 -347
  69. package/dist/renderPptx/glimpseTextBoxes.js.map +0 -1
  70. package/dist/renderPptx/glowEffects.js +0 -157
  71. package/dist/renderPptx/glowEffects.js.map +0 -1
  72. package/dist/renderPptx/gradientFills.js +0 -154
  73. package/dist/renderPptx/gradientFills.js.map +0 -1
@@ -1,7 +1,5 @@
1
- import { GradientFillRegistry } from "./renderPptx/gradientFills.js";
1
+ import { PptxAuthoringRegistry } from "./renderPptx/pptxAuthoring.js";
2
2
  import { DiagnosticCollector } from "./diagnostics.js";
3
- import { GlowEffectRegistry } from "./renderPptx/glowEffects.js";
4
- import { GlimpseTextBoxRegistry } from "./renderPptx/glimpseTextBoxes.js";
5
3
  //#region src/buildContext.ts
6
4
  function createBuildContext(textMeasurementMode = "auto") {
7
5
  return {
@@ -10,9 +8,7 @@ function createBuildContext(textMeasurementMode = "auto") {
10
8
  imageDataCache: /* @__PURE__ */ new Map(),
11
9
  iconRasterCache: /* @__PURE__ */ new Map(),
12
10
  diagnostics: new DiagnosticCollector(),
13
- gradientFills: new GradientFillRegistry(),
14
- glowEffects: new GlowEffectRegistry(),
15
- glimpseTextBoxes: new GlimpseTextBoxRegistry()
11
+ pptxAuthoring: new PptxAuthoringRegistry()
16
12
  };
17
13
  }
18
14
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"buildContext.js","names":[],"sources":["../src/buildContext.ts"],"sourcesContent":["import type { TextMeasurementMode } from \"./calcYogaLayout/measureText.ts\";\nimport { DiagnosticCollector } from \"./diagnostics.ts\";\nimport { GlowEffectRegistry } from \"./renderPptx/glowEffects.ts\";\nimport { GradientFillRegistry } from \"./renderPptx/gradientFills.ts\";\nimport { GlimpseTextBoxRegistry } from \"./renderPptx/glimpseTextBoxes.ts\";\n\nexport interface BuildContext {\n textMeasurementMode: TextMeasurementMode;\n imageSizeCache: Map<string, { widthPx: number; heightPx: number }>;\n imageDataCache: Map<string, string>;\n iconRasterCache: Map<string, string>;\n diagnostics: DiagnosticCollector;\n gradientFills: GradientFillRegistry;\n glowEffects: GlowEffectRegistry;\n glimpseTextBoxes: GlimpseTextBoxRegistry;\n}\n\nexport function createBuildContext(\n textMeasurementMode: TextMeasurementMode = \"auto\",\n): BuildContext {\n return {\n textMeasurementMode,\n imageSizeCache: new Map(),\n imageDataCache: new Map(),\n iconRasterCache: new Map(),\n diagnostics: new DiagnosticCollector(),\n gradientFills: new GradientFillRegistry(),\n glowEffects: new GlowEffectRegistry(),\n glimpseTextBoxes: new GlimpseTextBoxRegistry(),\n };\n}\n"],"mappings":";;;;;AAiBA,SAAgB,mBACd,sBAA2C,QAC7B;CACd,OAAO;EACL;EACA,gCAAgB,IAAI,IAAI;EACxB,gCAAgB,IAAI,IAAI;EACxB,iCAAiB,IAAI,IAAI;EACzB,aAAa,IAAI,oBAAoB;EACrC,eAAe,IAAI,qBAAqB;EACxC,aAAa,IAAI,mBAAmB;EACpC,kBAAkB,IAAI,uBAAuB;CAC/C;AACF"}
1
+ {"version":3,"file":"buildContext.js","names":[],"sources":["../src/buildContext.ts"],"sourcesContent":["import type { TextMeasurementMode } from \"./calcYogaLayout/measureText.ts\";\nimport { DiagnosticCollector } from \"./diagnostics.ts\";\nimport { PptxAuthoringRegistry } from \"./renderPptx/pptxAuthoring.ts\";\n\nexport interface BuildContext {\n textMeasurementMode: TextMeasurementMode;\n imageSizeCache: Map<string, { widthPx: number; heightPx: number }>;\n imageDataCache: Map<string, string>;\n iconRasterCache: Map<string, string>;\n diagnostics: DiagnosticCollector;\n pptxAuthoring: PptxAuthoringRegistry;\n}\n\nexport function createBuildContext(\n textMeasurementMode: TextMeasurementMode = \"auto\",\n): BuildContext {\n return {\n textMeasurementMode,\n imageSizeCache: new Map(),\n imageDataCache: new Map(),\n iconRasterCache: new Map(),\n diagnostics: new DiagnosticCollector(),\n pptxAuthoring: new PptxAuthoringRegistry(),\n };\n}\n"],"mappings":";;;AAaA,SAAgB,mBACd,sBAA2C,QAC7B;CACd,OAAO;EACL;EACA,gCAAgB,IAAI,IAAI;EACxB,gCAAgB,IAAI,IAAI;EACxB,iCAAiB,IAAI,IAAI;EACzB,aAAa,IAAI,oBAAoB;EACrC,eAAe,IAAI,sBAAsB;CAC3C;AACF"}
@@ -1,10 +1,11 @@
1
1
  import { SlideMasterOptions } from "./types.js";
2
2
  import { TextMeasurementMode } from "./calcYogaLayout/measureText.js";
3
3
  import { Diagnostic } from "./diagnostics.js";
4
+ import { WritablePptx } from "./renderPptx/writablePptx.js";
4
5
 
5
6
  //#region src/buildPptx.d.ts
6
7
  interface BuildPptxResult {
7
- pptx: import("pptxgenjs").default;
8
+ pptx: WritablePptx;
8
9
  diagnostics: Diagnostic[];
9
10
  }
10
11
  declare function buildPptx(xml: string, slideSize: {
@@ -1 +1 @@
1
- {"version":3,"file":"buildPptx.d.ts","names":[],"sources":["../src/buildPptx.ts"],"mappings":";;;;;UAqBiB,eAAA;EACf,IAAA,sBAA0B,OAAA;EAC1B,WAAA,EAAa,UAAU;AAAA;AAAA,iBAGH,SAAA,CACpB,GAAA,UACA,SAAA;EAAa,CAAA;EAAW,CAAA;AAAA,GACxB,OAAA;EACE,MAAA,GAAS,kBAAA;EACT,UAAA,GAAa,WAAA,GAAc,UAAA;EAC3B,eAAA,GAAkB,mBAAA;EAClB,OAAA;EACA,MAAA;AAAA,IAED,OAAA,CAAQ,eAAA"}
1
+ {"version":3,"file":"buildPptx.d.ts","names":[],"sources":["../src/buildPptx.ts"],"mappings":";;;;;;UAoBiB,eAAA;EACf,IAAA,EAAM,YAAA;EACN,WAAA,EAAa,UAAU;AAAA;AAAA,iBAGH,SAAA,CACpB,GAAA,UACA,SAAA;EAAa,CAAA;EAAW,CAAA;AAAA,GACxB,OAAA;EACE,MAAA,GAAS,kBAAA;EACT,UAAA,GAAa,WAAA,GAAc,UAAA;EAC3B,eAAA,GAAkB,mBAAA;EAClB,OAAA;EACA,MAAA;AAAA,IAED,OAAA,CAAQ,eAAA"}
package/dist/buildPptx.js CHANGED
@@ -1,10 +1,8 @@
1
+ import { prefetchImageSize } from "./shared/measureImage.js";
1
2
  import { freeYogaTree } from "./shared/freeYogaTree.js";
2
- import { patchPptxWriteForGradientFills } from "./renderPptx/gradientFills.js";
3
3
  import { calcYogaLayout } from "./calcYogaLayout/calcYogaLayout.js";
4
4
  import { autoFitSlide } from "./autoFit/autoFit.js";
5
5
  import { DiagnosticsError } from "./diagnostics.js";
6
- import { patchPptxWriteForGlowEffects } from "./renderPptx/glowEffects.js";
7
- import { patchPptxWriteForGlimpseTextBoxes } from "./renderPptx/glimpseTextBoxes.js";
8
6
  import { createBuildContext } from "./buildContext.js";
9
7
  import { extractLayoutResults } from "./calcYogaLayout/types.js";
10
8
  import { parseMasterPptx } from "./parseMasterPptx.js";
@@ -15,8 +13,6 @@ import { validatePositioned } from "./validatePositioned/validatePositioned.js";
15
13
  //#region src/buildPptx.ts
16
14
  async function buildPptx(xml, slideSize, options) {
17
15
  const ctx = createBuildContext(options?.textMeasurement ?? "auto");
18
- ctx.gradientFills.reserveColors(xml);
19
- if (options?.master) ctx.gradientFills.reserveColors(JSON.stringify(options.master));
20
16
  const nodes = parseXml(xml);
21
17
  const positionedPages = [];
22
18
  for (const [slideIndex, node] of nodes.entries()) {
@@ -44,10 +40,9 @@ async function buildPptx(xml, slideSize, options) {
44
40
  const message = e instanceof Error ? e.message : "Unknown error parsing masterPptx";
45
41
  ctx.diagnostics.add("MASTER_PPTX_PARSE_FAILED", message);
46
42
  }
47
- const pptx = await renderPptx(positionedPages, slideSize, ctx, master);
48
- patchPptxWriteForGradientFills(pptx, ctx.gradientFills);
49
- patchPptxWriteForGlimpseTextBoxes(pptx, ctx.glimpseTextBoxes);
50
- patchPptxWriteForGlowEffects(pptx, ctx.glowEffects);
43
+ const masterImageSources = [...new Set([master?.background && "image" in master.background ? master.background.image : void 0, ...master?.objects?.filter((object) => object.type === "image").map((object) => object.src) ?? []].filter((source) => Boolean(source?.startsWith("https://") || source?.startsWith("http://"))))];
44
+ await Promise.all(masterImageSources.map((source) => prefetchImageSize(source, ctx.imageSizeCache, ctx.imageDataCache, ctx.diagnostics)));
45
+ const pptx = renderPptx(positionedPages, slideSize, ctx, master);
51
46
  const diagnostics = ctx.diagnostics.items;
52
47
  if (options?.strict && diagnostics.length > 0) throw new DiagnosticsError(diagnostics);
53
48
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"buildPptx.js","names":[],"sources":["../src/buildPptx.ts"],"sourcesContent":["import { autoFitSlide } from \"./autoFit/autoFit.ts\";\nimport { createBuildContext } from \"./buildContext.ts\";\nimport { calcYogaLayout } from \"./calcYogaLayout/calcYogaLayout.ts\";\nimport type { TextMeasurementMode } from \"./calcYogaLayout/measureText.ts\";\nimport type { YogaNodeMap } from \"./calcYogaLayout/types.ts\";\nimport { extractLayoutResults } from \"./calcYogaLayout/types.ts\";\nimport type { Diagnostic } from \"./diagnostics.ts\";\nimport { DiagnosticsError } from \"./diagnostics.ts\";\nimport { parseMasterPptx } from \"./parseMasterPptx.ts\";\nimport { parseXml } from \"./parseXml/parseXml.ts\";\nimport { patchPptxWriteForGlowEffects } from \"./renderPptx/glowEffects.ts\";\nimport { patchPptxWriteForGlimpseTextBoxes } from \"./renderPptx/glimpseTextBoxes.ts\";\nimport { patchPptxWriteForGradientFills } from \"./renderPptx/gradientFills.ts\";\nimport { renderPptx } from \"./renderPptx/renderPptx.ts\";\nimport { freeYogaTree } from \"./shared/freeYogaTree.ts\";\nimport { toPositioned } from \"./toPositioned/toPositioned.ts\";\nimport { PositionedNode, SlideMasterOptions } from \"./types.ts\";\nimport { validatePositioned } from \"./validatePositioned/validatePositioned.ts\";\n\nexport type { TextMeasurementMode };\n\nexport interface BuildPptxResult {\n pptx: import(\"pptxgenjs\").default;\n diagnostics: Diagnostic[];\n}\n\nexport async function buildPptx(\n xml: string,\n slideSize: { w: number; h: number },\n options?: {\n master?: SlideMasterOptions;\n masterPptx?: ArrayBuffer | Uint8Array;\n textMeasurement?: TextMeasurementMode;\n autoFit?: boolean;\n strict?: boolean;\n },\n): Promise<BuildPptxResult> {\n const ctx = createBuildContext(options?.textMeasurement ?? \"auto\");\n\n // グラデーション後処理のマーカー色がユーザー指定色と衝突しないよう、\n // 入力 XML / master オプション中に現れる色を予約しておく\n ctx.gradientFills.reserveColors(xml);\n if (options?.master) {\n ctx.gradientFills.reserveColors(JSON.stringify(options.master));\n }\n\n const nodes = parseXml(xml);\n const positionedPages: PositionedNode[] = [];\n\n for (const [slideIndex, node] of nodes.entries()) {\n let map: YogaNodeMap | undefined;\n try {\n if (options?.autoFit !== false) {\n map = await autoFitSlide(node, slideSize, ctx);\n } else {\n map = await calcYogaLayout(node, slideSize, ctx);\n }\n const layoutMap = extractLayoutResults(map);\n const positioned = await toPositioned(node, ctx, layoutMap);\n validatePositioned(positioned, slideSize, ctx, slideIndex);\n positionedPages.push(positioned);\n } finally {\n if (map) freeYogaTree(map);\n }\n }\n\n // masterPptx から背景を抽出し、master オプションにマージ\n let master = options?.master;\n if (options?.masterPptx) {\n try {\n const bg = await parseMasterPptx(options.masterPptx);\n if (bg) {\n if (master) {\n // 明示的に background が指定されていない場合のみ、masterPptx の背景を使用\n if (!master.background) {\n master = { ...master, background: bg };\n }\n } else {\n master = { background: bg };\n }\n }\n } catch (e) {\n const message =\n e instanceof Error ? e.message : \"Unknown error parsing masterPptx\";\n ctx.diagnostics.add(\"MASTER_PPTX_PARSE_FAILED\", message);\n }\n }\n\n const pptx = await renderPptx(positionedPages, slideSize, ctx, master);\n\n // backgroundGradient 使用時は write/writeFile gradFill 置換の後処理を仕込む\n patchPptxWriteForGradientFills(pptx, ctx.gradientFills);\n\n // Text primitive glimpse writer text box XML marker shape を置換する\n patchPptxWriteForGlimpseTextBoxes(pptx, ctx.glimpseTextBoxes);\n\n // Shape / Icon の glow 指定がある場合は write/writeFile に effectLst 挿入の\n // 後処理を仕込む (gradientFills patch 後にチェーンする)\n patchPptxWriteForGlowEffects(pptx, ctx.glowEffects);\n\n const diagnostics = ctx.diagnostics.items;\n\n if (options?.strict && diagnostics.length > 0) {\n throw new DiagnosticsError(diagnostics);\n }\n\n return { pptx, diagnostics };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,eAAsB,UACpB,KACA,WACA,SAO0B;CAC1B,MAAM,MAAM,mBAAmB,SAAS,mBAAmB,MAAM;CAIjE,IAAI,cAAc,cAAc,GAAG;CACnC,IAAI,SAAS,QACX,IAAI,cAAc,cAAc,KAAK,UAAU,QAAQ,MAAM,CAAC;CAGhE,MAAM,QAAQ,SAAS,GAAG;CAC1B,MAAM,kBAAoC,CAAC;CAE3C,KAAK,MAAM,CAAC,YAAY,SAAS,MAAM,QAAQ,GAAG;EAChD,IAAI;EACJ,IAAI;GACF,IAAI,SAAS,YAAY,OACvB,MAAM,MAAM,aAAa,MAAM,WAAW,GAAG;QAE7C,MAAM,MAAM,eAAe,MAAM,WAAW,GAAG;GAGjD,MAAM,aAAa,MAAM,aAAa,MAAM,KAD1B,qBAAqB,GACkB,CAAC;GAC1D,mBAAmB,YAAY,WAAW,KAAK,UAAU;GACzD,gBAAgB,KAAK,UAAU;EACjC,UAAU;GACR,IAAI,KAAK,aAAa,GAAG;EAC3B;CACF;CAGA,IAAI,SAAS,SAAS;CACtB,IAAI,SAAS,YACX,IAAI;EACF,MAAM,KAAK,MAAM,gBAAgB,QAAQ,UAAU;EACnD,IAAI,IACF,IAAI;OAEE,CAAC,OAAO,YACV,SAAS;IAAE,GAAG;IAAQ,YAAY;GAAG;EAAA,OAGvC,SAAS,EAAE,YAAY,GAAG;CAGhC,SAAS,GAAG;EACV,MAAM,UACJ,aAAa,QAAQ,EAAE,UAAU;EACnC,IAAI,YAAY,IAAI,4BAA4B,OAAO;CACzD;CAGF,MAAM,OAAO,MAAM,WAAW,iBAAiB,WAAW,KAAK,MAAM;CAGrE,+BAA+B,MAAM,IAAI,aAAa;CAGtD,kCAAkC,MAAM,IAAI,gBAAgB;CAI5D,6BAA6B,MAAM,IAAI,WAAW;CAElD,MAAM,cAAc,IAAI,YAAY;CAEpC,IAAI,SAAS,UAAU,YAAY,SAAS,GAC1C,MAAM,IAAI,iBAAiB,WAAW;CAGxC,OAAO;EAAE;EAAM;CAAY;AAC7B"}
1
+ {"version":3,"file":"buildPptx.js","names":[],"sources":["../src/buildPptx.ts"],"sourcesContent":["import { autoFitSlide } from \"./autoFit/autoFit.ts\";\nimport { createBuildContext } from \"./buildContext.ts\";\nimport { calcYogaLayout } from \"./calcYogaLayout/calcYogaLayout.ts\";\nimport type { TextMeasurementMode } from \"./calcYogaLayout/measureText.ts\";\nimport type { YogaNodeMap } from \"./calcYogaLayout/types.ts\";\nimport { extractLayoutResults } from \"./calcYogaLayout/types.ts\";\nimport type { Diagnostic } from \"./diagnostics.ts\";\nimport { DiagnosticsError } from \"./diagnostics.ts\";\nimport { parseMasterPptx } from \"./parseMasterPptx.ts\";\nimport { parseXml } from \"./parseXml/parseXml.ts\";\nimport { renderPptx } from \"./renderPptx/renderPptx.ts\";\nimport type { WritablePptx } from \"./renderPptx/writablePptx.ts\";\nimport { freeYogaTree } from \"./shared/freeYogaTree.ts\";\nimport { prefetchImageSize } from \"./shared/measureImage.ts\";\nimport { toPositioned } from \"./toPositioned/toPositioned.ts\";\nimport { PositionedNode, SlideMasterOptions } from \"./types.ts\";\nimport { validatePositioned } from \"./validatePositioned/validatePositioned.ts\";\n\nexport type { TextMeasurementMode };\n\nexport interface BuildPptxResult {\n pptx: WritablePptx;\n diagnostics: Diagnostic[];\n}\n\nexport async function buildPptx(\n xml: string,\n slideSize: { w: number; h: number },\n options?: {\n master?: SlideMasterOptions;\n masterPptx?: ArrayBuffer | Uint8Array;\n textMeasurement?: TextMeasurementMode;\n autoFit?: boolean;\n strict?: boolean;\n },\n): Promise<BuildPptxResult> {\n const ctx = createBuildContext(options?.textMeasurement ?? \"auto\");\n\n const nodes = parseXml(xml);\n const positionedPages: PositionedNode[] = [];\n\n for (const [slideIndex, node] of nodes.entries()) {\n let map: YogaNodeMap | undefined;\n try {\n if (options?.autoFit !== false) {\n map = await autoFitSlide(node, slideSize, ctx);\n } else {\n map = await calcYogaLayout(node, slideSize, ctx);\n }\n const layoutMap = extractLayoutResults(map);\n const positioned = await toPositioned(node, ctx, layoutMap);\n validatePositioned(positioned, slideSize, ctx, slideIndex);\n positionedPages.push(positioned);\n } finally {\n if (map) freeYogaTree(map);\n }\n }\n\n // masterPptx から背景を抽出し、master オプションにマージ\n let master = options?.master;\n if (options?.masterPptx) {\n try {\n const bg = await parseMasterPptx(options.masterPptx);\n if (bg) {\n if (master) {\n // 明示的に background が指定されていない場合のみ、masterPptx の背景を使用\n if (!master.background) {\n master = { ...master, background: bg };\n }\n } else {\n master = { background: bg };\n }\n }\n } catch (e) {\n const message =\n e instanceof Error ? e.message : \"Unknown error parsing masterPptx\";\n ctx.diagnostics.add(\"MASTER_PPTX_PARSE_FAILED\", message);\n }\n }\n\n const masterImageSources = [\n ...new Set(\n [\n master?.background && \"image\" in master.background\n ? master.background.image\n : undefined,\n ...(master?.objects\n ?.filter((object) => object.type === \"image\")\n .map((object) => object.src) ?? []),\n ].filter((source): source is string =>\n Boolean(\n source?.startsWith(\"https://\") || source?.startsWith(\"http://\"),\n ),\n ),\n ),\n ];\n await Promise.all(\n masterImageSources.map((source) =>\n prefetchImageSize(\n source,\n ctx.imageSizeCache,\n ctx.imageDataCache,\n ctx.diagnostics,\n ),\n ),\n );\n\n const pptx = renderPptx(positionedPages, slideSize, ctx, master);\n\n const diagnostics = ctx.diagnostics.items;\n\n if (options?.strict && diagnostics.length > 0) {\n throw new DiagnosticsError(diagnostics);\n }\n\n return { pptx, diagnostics };\n}\n"],"mappings":";;;;;;;;;;;;;AAyBA,eAAsB,UACpB,KACA,WACA,SAO0B;CAC1B,MAAM,MAAM,mBAAmB,SAAS,mBAAmB,MAAM;CAEjE,MAAM,QAAQ,SAAS,GAAG;CAC1B,MAAM,kBAAoC,CAAC;CAE3C,KAAK,MAAM,CAAC,YAAY,SAAS,MAAM,QAAQ,GAAG;EAChD,IAAI;EACJ,IAAI;GACF,IAAI,SAAS,YAAY,OACvB,MAAM,MAAM,aAAa,MAAM,WAAW,GAAG;QAE7C,MAAM,MAAM,eAAe,MAAM,WAAW,GAAG;GAGjD,MAAM,aAAa,MAAM,aAAa,MAAM,KAD1B,qBAAqB,GACkB,CAAC;GAC1D,mBAAmB,YAAY,WAAW,KAAK,UAAU;GACzD,gBAAgB,KAAK,UAAU;EACjC,UAAU;GACR,IAAI,KAAK,aAAa,GAAG;EAC3B;CACF;CAGA,IAAI,SAAS,SAAS;CACtB,IAAI,SAAS,YACX,IAAI;EACF,MAAM,KAAK,MAAM,gBAAgB,QAAQ,UAAU;EACnD,IAAI,IACF,IAAI;OAEE,CAAC,OAAO,YACV,SAAS;IAAE,GAAG;IAAQ,YAAY;GAAG;EAAA,OAGvC,SAAS,EAAE,YAAY,GAAG;CAGhC,SAAS,GAAG;EACV,MAAM,UACJ,aAAa,QAAQ,EAAE,UAAU;EACnC,IAAI,YAAY,IAAI,4BAA4B,OAAO;CACzD;CAGF,MAAM,qBAAqB,CACzB,GAAG,IAAI,IACL,CACE,QAAQ,cAAc,WAAW,OAAO,aACpC,OAAO,WAAW,QAClB,KAAA,GACJ,GAAI,QAAQ,SACR,QAAQ,WAAW,OAAO,SAAS,OAAO,CAAC,CAC5C,KAAK,WAAW,OAAO,GAAG,KAAK,CAAC,CACrC,CAAC,CAAC,QAAQ,WACR,QACE,QAAQ,WAAW,UAAU,KAAK,QAAQ,WAAW,SAAS,CAChE,CACF,CACF,CACF;CACA,MAAM,QAAQ,IACZ,mBAAmB,KAAK,WACtB,kBACE,QACA,IAAI,gBACJ,IAAI,gBACJ,IAAI,WACN,CACF,CACF;CAEA,MAAM,OAAO,WAAW,iBAAiB,WAAW,KAAK,MAAM;CAE/D,MAAM,cAAc,IAAI,YAAY;CAEpC,IAAI,SAAS,UAAU,YAAY,SAAS,GAC1C,MAAM,IAAI,iBAAiB,WAAW;CAGxC,OAAO;EAAE;EAAM;CAAY;AAC7B"}
@@ -35,7 +35,7 @@ async function calcYogaLayout(root, slideSize, ctx) {
35
35
  * POMNode ツリー内のすべての画像のサイズを事前取得する
36
36
  */
37
37
  async function prefetchAllImageSizes(node, ctx) {
38
- const imageSources = collectImageSources(node);
38
+ const imageSources = [...new Set(collectImageSources(node))];
39
39
  await Promise.all(imageSources.map((src) => prefetchImageSize(src, ctx.imageSizeCache, ctx.imageDataCache, ctx.diagnostics)));
40
40
  }
41
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"calcYogaLayout.js","names":[],"sources":["../../src/calcYogaLayout/calcYogaLayout.ts"],"sourcesContent":["import type { POMNode, AlignItems } from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { YogaNodeMap } from \"./types.ts\";\nimport { Node as YogaNode } from \"yoga-layout\";\nimport { loadYoga } from \"yoga-layout/load\";\nimport { prefetchImageSize } from \"../shared/measureImage.ts\";\nimport { freeYogaTree } from \"../shared/freeYogaTree.ts\";\nimport { resolveBoxSpacing } from \"../shared/boxSpacing.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\n\n/**\n * POMNode ツリーを Yoga でレイアウト計算する\n * POMNode と YogaNode の対応を YogaNodeMap として返す\n *\n * @param root 入力 POMNode ツリーのルート\n * @param slideSize スライド全体のサイズ(px)\n * @param ctx BuildContext\n * @returns YogaNodeMap(POMNode → YogaNode のマッピング)\n */\nexport async function calcYogaLayout(\n root: POMNode,\n slideSize: { w: number; h: number },\n ctx: BuildContext,\n): Promise<YogaNodeMap> {\n const Yoga = await getYoga();\n\n // 事前に全画像のサイズを取得(HTTPS対応のため)\n await prefetchAllImageSizes(root, ctx);\n\n const map: YogaNodeMap = new Map();\n\n try {\n const rootYoga = Yoga.Node.create();\n map.set(root, rootYoga);\n\n await buildPomWithYogaTree(root, rootYoga, ctx, map);\n\n // スライド全体サイズを指定\n rootYoga.setWidth(slideSize.w);\n rootYoga.setHeight(slideSize.h);\n\n rootYoga.calculateLayout(slideSize.w, slideSize.h, Yoga.DIRECTION_LTR);\n } catch (e) {\n // 途中で失敗した場合、作成済みの YogaNode を解放してから再 throw\n freeYogaTree(map);\n throw e;\n }\n\n return map;\n}\n\n/**\n * POMNode ツリー内のすべての画像のサイズを事前取得する\n */\nasync function prefetchAllImageSizes(\n node: POMNode,\n ctx: BuildContext,\n): Promise<void> {\n const imageSources = collectImageSources(node);\n await Promise.all(\n imageSources.map((src) =>\n prefetchImageSize(\n src,\n ctx.imageSizeCache,\n ctx.imageDataCache,\n ctx.diagnostics,\n ),\n ),\n );\n}\n\n/**\n * POMNode ツリー内のすべての画像のsrcを収集する\n */\nfunction collectImageSources(node: POMNode): string[] {\n const sources: string[] = [];\n\n function traverse(n: POMNode) {\n // backgroundImage の src を収集(全ノード共通)\n if (n.backgroundImage) {\n sources.push(n.backgroundImage.src);\n }\n\n const def = getNodeDef(n.type);\n\n // ノード固有の画像ソース収集\n if (def.collectImageSources) {\n sources.push(...def.collectImageSources(n));\n }\n\n // 子要素の再帰\n if (def.category === \"multi-child\" || def.category === \"absolute-child\") {\n const containerNode = n as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n traverse(child);\n }\n }\n }\n\n traverse(node);\n return sources;\n}\n\n/**\n * Yogaシングルトン\n */\nlet yogaP: Promise<Yoga> | null = null;\ntype Yoga = Awaited<ReturnType<typeof loadYoga>>;\nasync function getYoga(): Promise<Yoga> {\n if (yogaP === null) yogaP = loadYoga();\n return yogaP;\n}\n\n/**\n * ノードが交差軸方向(幅)で確定サイズを持つかを判定する\n * - 明示的な w がある場合は確定\n * - alignSelf で stretch 以外が指定されている場合は不確定\n * - 親の alignItems で stretch(デフォルト)以外が指定されている場合は不確定\n */\nfunction nodeHasDefiniteWidth(node: POMNode, parentNode?: POMNode): boolean {\n // 明示的な幅がある\n if (node.w !== undefined) return true;\n\n // alignSelf で stretch 以外が明示されている場合は不確定\n if (\n node.alignSelf !== undefined &&\n node.alignSelf !== \"stretch\" &&\n node.alignSelf !== \"auto\"\n ) {\n return false;\n }\n\n // 親がいない場合(ルートノード)は確定\n if (!parentNode) return true;\n\n // 親の alignItems を取得(VStack/HStack のみ持つ)\n let parentAlignItems: AlignItems | undefined;\n if (parentNode.type === \"vstack\") {\n parentAlignItems = parentNode.alignItems;\n } else if (parentNode.type === \"hstack\") {\n parentAlignItems = parentNode.alignItems;\n }\n\n // VStack(column 方向)の子の場合、交差軸は水平方向\n // alignItems が stretch(デフォルト)なら子は親幅に伸長される\n if (parentNode.type === \"vstack\" || parentNode.type === \"layer\") {\n return parentAlignItems === undefined || parentAlignItems === \"stretch\";\n }\n\n // HStack の子の場合、幅は主軸方向で flex により決まるため確定とみなす\n if (parentNode.type === \"hstack\") {\n return true;\n }\n\n return true;\n}\n\n/**\n * POMNode ツリーを再帰的に走査し、YogaNode ツリーを構築する\n */\nasync function buildPomWithYogaTree(\n node: POMNode,\n parentYoga: YogaNode,\n ctx: BuildContext,\n map: YogaNodeMap,\n parentNode?: POMNode,\n grandparentNode?: POMNode,\n) {\n const yoga = await getYoga();\n\n const yn = yoga.Node.create();\n map.set(node, yn); // 対応する YogaNode をマップに登録\n\n await applyStyleToYogaNode(node, yn, ctx);\n\n // HStack/VStack の子要素に flexShrink=1 をデフォルト設定(CSS Flexbox と同じ挙動)\n // 主軸方向で %サイズ + gap がある場合の overflow を防ぐ\n // アイコンは固定サイズのコンテンツなので shrink させない\n if (parentNode?.type === \"hstack\" || parentNode?.type === \"vstack\") {\n yn.setFlexShrink(node.type === \"icon\" ? 0 : 1);\n }\n\n // HStack の子要素で幅が指定されていない場合、grow の比率で余白を配分\n // (grow 未指定は 1 として扱い均等分割)\n // テーブルは setMeasureFunc でカラム幅合計を返すため除外\n // アイコンは固定サイズのコンテンツなので除外\n if (\n parentNode?.type === \"hstack\" &&\n node.w === undefined &&\n node.type !== \"table\" &&\n node.type !== \"icon\"\n ) {\n yn.setFlexGrow(node.grow ?? 1);\n // HStack が確定幅を持つ場合のみ flexBasis=0 で均等分割\n // HStack が auto-sized(親の alignItems が center/start/end 等)の場合、\n // flexBasis=0 だと子要素の自然な幅が失われてレイアウトが崩れるため、\n // flexBasis=auto(デフォルト)のまま維持する\n if (nodeHasDefiniteWidth(parentNode, grandparentNode)) {\n yn.setFlexBasis(0);\n }\n }\n\n parentYoga.insertChild(yn, parentYoga.getChildCount());\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n await buildPomWithYogaTree(child, yn, ctx, map, node, parentNode);\n }\n break;\n }\n case \"leaf\":\n // 子要素なし\n break;\n }\n}\n\n/**\n * node のスタイルを YogaNode に適用する\n */\nasync function applyStyleToYogaNode(\n node: POMNode,\n yn: YogaNode,\n ctx: BuildContext,\n) {\n const yoga = await getYoga();\n\n // デフォルト: 縦並び\n yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);\n\n // width\n if (node.w !== undefined) {\n if (typeof node.w === \"number\") {\n yn.setWidth(node.w);\n } else if (node.w === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.w.endsWith(\"%\")) {\n const percent = parseFloat(node.w);\n yn.setWidthPercent(percent);\n }\n }\n\n // height\n if (node.h !== undefined) {\n if (typeof node.h === \"number\") {\n yn.setHeight(node.h);\n } else if (node.h === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.h.endsWith(\"%\")) {\n const percent = parseFloat(node.h);\n yn.setHeightPercent(percent);\n }\n }\n\n // flex-grow(w=\"max\" / h=\"max\" の setFlexGrow(1) より優先)\n if (node.grow !== undefined) {\n yn.setFlexGrow(node.grow);\n }\n\n // min/max constraints\n if (node.minW !== undefined) {\n yn.setMinWidth(node.minW);\n }\n if (node.maxW !== undefined) {\n yn.setMaxWidth(node.maxW);\n }\n if (node.minH !== undefined) {\n yn.setMinHeight(node.minH);\n }\n if (node.maxH !== undefined) {\n yn.setMaxHeight(node.maxH);\n }\n\n // padding(yoga の未設定 edge は 0 扱いのため、解決済みの 0 を明示設定しても等価)\n if (node.padding !== undefined) {\n const padding = resolveBoxSpacing(node.padding);\n yn.setPadding(yoga.EDGE_TOP, padding.top);\n yn.setPadding(yoga.EDGE_RIGHT, padding.right);\n yn.setPadding(yoga.EDGE_BOTTOM, padding.bottom);\n yn.setPadding(yoga.EDGE_LEFT, padding.left);\n }\n\n // margin\n if (node.margin !== undefined) {\n const margin = resolveBoxSpacing(node.margin);\n yn.setMargin(yoga.EDGE_TOP, margin.top);\n yn.setMargin(yoga.EDGE_RIGHT, margin.right);\n yn.setMargin(yoga.EDGE_BOTTOM, margin.bottom);\n yn.setMargin(yoga.EDGE_LEFT, margin.left);\n }\n\n // position\n if (node.position === \"absolute\") {\n yn.setPositionType(yoga.POSITION_TYPE_ABSOLUTE);\n }\n if (node.top !== undefined) {\n yn.setPosition(yoga.EDGE_TOP, node.top);\n }\n if (node.right !== undefined) {\n yn.setPosition(yoga.EDGE_RIGHT, node.right);\n }\n if (node.bottom !== undefined) {\n yn.setPosition(yoga.EDGE_BOTTOM, node.bottom);\n }\n if (node.left !== undefined) {\n yn.setPosition(yoga.EDGE_LEFT, node.left);\n }\n\n // alignSelf\n if (node.alignSelf !== undefined) {\n switch (node.alignSelf) {\n case \"auto\":\n yn.setAlignSelf(yoga.ALIGN_AUTO);\n break;\n case \"start\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_START);\n break;\n case \"center\":\n yn.setAlignSelf(yoga.ALIGN_CENTER);\n break;\n case \"end\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_END);\n break;\n case \"stretch\":\n yn.setAlignSelf(yoga.ALIGN_STRETCH);\n break;\n }\n }\n\n // ノード固有のスタイル適用(measureFunc 等)\n const def = getNodeDef(node.type);\n if (def.applyYogaStyle) {\n await def.applyYogaStyle(node, yn, yoga, ctx);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"calcYogaLayout.js","names":[],"sources":["../../src/calcYogaLayout/calcYogaLayout.ts"],"sourcesContent":["import type { POMNode, AlignItems } from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { YogaNodeMap } from \"./types.ts\";\nimport { Node as YogaNode } from \"yoga-layout\";\nimport { loadYoga } from \"yoga-layout/load\";\nimport { prefetchImageSize } from \"../shared/measureImage.ts\";\nimport { freeYogaTree } from \"../shared/freeYogaTree.ts\";\nimport { resolveBoxSpacing } from \"../shared/boxSpacing.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\n\n/**\n * POMNode ツリーを Yoga でレイアウト計算する\n * POMNode と YogaNode の対応を YogaNodeMap として返す\n *\n * @param root 入力 POMNode ツリーのルート\n * @param slideSize スライド全体のサイズ(px)\n * @param ctx BuildContext\n * @returns YogaNodeMap(POMNode → YogaNode のマッピング)\n */\nexport async function calcYogaLayout(\n root: POMNode,\n slideSize: { w: number; h: number },\n ctx: BuildContext,\n): Promise<YogaNodeMap> {\n const Yoga = await getYoga();\n\n // 事前に全画像のサイズを取得(HTTPS対応のため)\n await prefetchAllImageSizes(root, ctx);\n\n const map: YogaNodeMap = new Map();\n\n try {\n const rootYoga = Yoga.Node.create();\n map.set(root, rootYoga);\n\n await buildPomWithYogaTree(root, rootYoga, ctx, map);\n\n // スライド全体サイズを指定\n rootYoga.setWidth(slideSize.w);\n rootYoga.setHeight(slideSize.h);\n\n rootYoga.calculateLayout(slideSize.w, slideSize.h, Yoga.DIRECTION_LTR);\n } catch (e) {\n // 途中で失敗した場合、作成済みの YogaNode を解放してから再 throw\n freeYogaTree(map);\n throw e;\n }\n\n return map;\n}\n\n/**\n * POMNode ツリー内のすべての画像のサイズを事前取得する\n */\nasync function prefetchAllImageSizes(\n node: POMNode,\n ctx: BuildContext,\n): Promise<void> {\n const imageSources = [...new Set(collectImageSources(node))];\n await Promise.all(\n imageSources.map((src) =>\n prefetchImageSize(\n src,\n ctx.imageSizeCache,\n ctx.imageDataCache,\n ctx.diagnostics,\n ),\n ),\n );\n}\n\n/**\n * POMNode ツリー内のすべての画像のsrcを収集する\n */\nfunction collectImageSources(node: POMNode): string[] {\n const sources: string[] = [];\n\n function traverse(n: POMNode) {\n // backgroundImage の src を収集(全ノード共通)\n if (n.backgroundImage) {\n sources.push(n.backgroundImage.src);\n }\n\n const def = getNodeDef(n.type);\n\n // ノード固有の画像ソース収集\n if (def.collectImageSources) {\n sources.push(...def.collectImageSources(n));\n }\n\n // 子要素の再帰\n if (def.category === \"multi-child\" || def.category === \"absolute-child\") {\n const containerNode = n as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n traverse(child);\n }\n }\n }\n\n traverse(node);\n return sources;\n}\n\n/**\n * Yogaシングルトン\n */\nlet yogaP: Promise<Yoga> | null = null;\ntype Yoga = Awaited<ReturnType<typeof loadYoga>>;\nasync function getYoga(): Promise<Yoga> {\n if (yogaP === null) yogaP = loadYoga();\n return yogaP;\n}\n\n/**\n * ノードが交差軸方向(幅)で確定サイズを持つかを判定する\n * - 明示的な w がある場合は確定\n * - alignSelf で stretch 以外が指定されている場合は不確定\n * - 親の alignItems で stretch(デフォルト)以外が指定されている場合は不確定\n */\nfunction nodeHasDefiniteWidth(node: POMNode, parentNode?: POMNode): boolean {\n // 明示的な幅がある\n if (node.w !== undefined) return true;\n\n // alignSelf で stretch 以外が明示されている場合は不確定\n if (\n node.alignSelf !== undefined &&\n node.alignSelf !== \"stretch\" &&\n node.alignSelf !== \"auto\"\n ) {\n return false;\n }\n\n // 親がいない場合(ルートノード)は確定\n if (!parentNode) return true;\n\n // 親の alignItems を取得(VStack/HStack のみ持つ)\n let parentAlignItems: AlignItems | undefined;\n if (parentNode.type === \"vstack\") {\n parentAlignItems = parentNode.alignItems;\n } else if (parentNode.type === \"hstack\") {\n parentAlignItems = parentNode.alignItems;\n }\n\n // VStack(column 方向)の子の場合、交差軸は水平方向\n // alignItems が stretch(デフォルト)なら子は親幅に伸長される\n if (parentNode.type === \"vstack\" || parentNode.type === \"layer\") {\n return parentAlignItems === undefined || parentAlignItems === \"stretch\";\n }\n\n // HStack の子の場合、幅は主軸方向で flex により決まるため確定とみなす\n if (parentNode.type === \"hstack\") {\n return true;\n }\n\n return true;\n}\n\n/**\n * POMNode ツリーを再帰的に走査し、YogaNode ツリーを構築する\n */\nasync function buildPomWithYogaTree(\n node: POMNode,\n parentYoga: YogaNode,\n ctx: BuildContext,\n map: YogaNodeMap,\n parentNode?: POMNode,\n grandparentNode?: POMNode,\n) {\n const yoga = await getYoga();\n\n const yn = yoga.Node.create();\n map.set(node, yn); // 対応する YogaNode をマップに登録\n\n await applyStyleToYogaNode(node, yn, ctx);\n\n // HStack/VStack の子要素に flexShrink=1 をデフォルト設定(CSS Flexbox と同じ挙動)\n // 主軸方向で %サイズ + gap がある場合の overflow を防ぐ\n // アイコンは固定サイズのコンテンツなので shrink させない\n if (parentNode?.type === \"hstack\" || parentNode?.type === \"vstack\") {\n yn.setFlexShrink(node.type === \"icon\" ? 0 : 1);\n }\n\n // HStack の子要素で幅が指定されていない場合、grow の比率で余白を配分\n // (grow 未指定は 1 として扱い均等分割)\n // テーブルは setMeasureFunc でカラム幅合計を返すため除外\n // アイコンは固定サイズのコンテンツなので除外\n if (\n parentNode?.type === \"hstack\" &&\n node.w === undefined &&\n node.type !== \"table\" &&\n node.type !== \"icon\"\n ) {\n yn.setFlexGrow(node.grow ?? 1);\n // HStack が確定幅を持つ場合のみ flexBasis=0 で均等分割\n // HStack が auto-sized(親の alignItems が center/start/end 等)の場合、\n // flexBasis=0 だと子要素の自然な幅が失われてレイアウトが崩れるため、\n // flexBasis=auto(デフォルト)のまま維持する\n if (nodeHasDefiniteWidth(parentNode, grandparentNode)) {\n yn.setFlexBasis(0);\n }\n }\n\n parentYoga.insertChild(yn, parentYoga.getChildCount());\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n await buildPomWithYogaTree(child, yn, ctx, map, node, parentNode);\n }\n break;\n }\n case \"leaf\":\n // 子要素なし\n break;\n }\n}\n\n/**\n * node のスタイルを YogaNode に適用する\n */\nasync function applyStyleToYogaNode(\n node: POMNode,\n yn: YogaNode,\n ctx: BuildContext,\n) {\n const yoga = await getYoga();\n\n // デフォルト: 縦並び\n yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);\n\n // width\n if (node.w !== undefined) {\n if (typeof node.w === \"number\") {\n yn.setWidth(node.w);\n } else if (node.w === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.w.endsWith(\"%\")) {\n const percent = parseFloat(node.w);\n yn.setWidthPercent(percent);\n }\n }\n\n // height\n if (node.h !== undefined) {\n if (typeof node.h === \"number\") {\n yn.setHeight(node.h);\n } else if (node.h === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.h.endsWith(\"%\")) {\n const percent = parseFloat(node.h);\n yn.setHeightPercent(percent);\n }\n }\n\n // flex-grow(w=\"max\" / h=\"max\" の setFlexGrow(1) より優先)\n if (node.grow !== undefined) {\n yn.setFlexGrow(node.grow);\n }\n\n // min/max constraints\n if (node.minW !== undefined) {\n yn.setMinWidth(node.minW);\n }\n if (node.maxW !== undefined) {\n yn.setMaxWidth(node.maxW);\n }\n if (node.minH !== undefined) {\n yn.setMinHeight(node.minH);\n }\n if (node.maxH !== undefined) {\n yn.setMaxHeight(node.maxH);\n }\n\n // padding(yoga の未設定 edge は 0 扱いのため、解決済みの 0 を明示設定しても等価)\n if (node.padding !== undefined) {\n const padding = resolveBoxSpacing(node.padding);\n yn.setPadding(yoga.EDGE_TOP, padding.top);\n yn.setPadding(yoga.EDGE_RIGHT, padding.right);\n yn.setPadding(yoga.EDGE_BOTTOM, padding.bottom);\n yn.setPadding(yoga.EDGE_LEFT, padding.left);\n }\n\n // margin\n if (node.margin !== undefined) {\n const margin = resolveBoxSpacing(node.margin);\n yn.setMargin(yoga.EDGE_TOP, margin.top);\n yn.setMargin(yoga.EDGE_RIGHT, margin.right);\n yn.setMargin(yoga.EDGE_BOTTOM, margin.bottom);\n yn.setMargin(yoga.EDGE_LEFT, margin.left);\n }\n\n // position\n if (node.position === \"absolute\") {\n yn.setPositionType(yoga.POSITION_TYPE_ABSOLUTE);\n }\n if (node.top !== undefined) {\n yn.setPosition(yoga.EDGE_TOP, node.top);\n }\n if (node.right !== undefined) {\n yn.setPosition(yoga.EDGE_RIGHT, node.right);\n }\n if (node.bottom !== undefined) {\n yn.setPosition(yoga.EDGE_BOTTOM, node.bottom);\n }\n if (node.left !== undefined) {\n yn.setPosition(yoga.EDGE_LEFT, node.left);\n }\n\n // alignSelf\n if (node.alignSelf !== undefined) {\n switch (node.alignSelf) {\n case \"auto\":\n yn.setAlignSelf(yoga.ALIGN_AUTO);\n break;\n case \"start\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_START);\n break;\n case \"center\":\n yn.setAlignSelf(yoga.ALIGN_CENTER);\n break;\n case \"end\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_END);\n break;\n case \"stretch\":\n yn.setAlignSelf(yoga.ALIGN_STRETCH);\n break;\n }\n }\n\n // ノード固有のスタイル適用(measureFunc 等)\n const def = getNodeDef(node.type);\n if (def.applyYogaStyle) {\n await def.applyYogaStyle(node, yn, yoga, ctx);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,eAAsB,eACpB,MACA,WACA,KACsB;CACtB,MAAM,OAAO,MAAM,QAAQ;CAG3B,MAAM,sBAAsB,MAAM,GAAG;CAErC,MAAM,sBAAmB,IAAI,IAAI;CAEjC,IAAI;EACF,MAAM,WAAW,KAAK,KAAK,OAAO;EAClC,IAAI,IAAI,MAAM,QAAQ;EAEtB,MAAM,qBAAqB,MAAM,UAAU,KAAK,GAAG;EAGnD,SAAS,SAAS,UAAU,CAAC;EAC7B,SAAS,UAAU,UAAU,CAAC;EAE9B,SAAS,gBAAgB,UAAU,GAAG,UAAU,GAAG,KAAK,aAAa;CACvE,SAAS,GAAG;EAEV,aAAa,GAAG;EAChB,MAAM;CACR;CAEA,OAAO;AACT;;;;AAKA,eAAe,sBACb,MACA,KACe;CACf,MAAM,eAAe,CAAC,GAAG,IAAI,IAAI,oBAAoB,IAAI,CAAC,CAAC;CAC3D,MAAM,QAAQ,IACZ,aAAa,KAAK,QAChB,kBACE,KACA,IAAI,gBACJ,IAAI,gBACJ,IAAI,WACN,CACF,CACF;AACF;;;;AAKA,SAAS,oBAAoB,MAAyB;CACpD,MAAM,UAAoB,CAAC;CAE3B,SAAS,SAAS,GAAY;EAE5B,IAAI,EAAE,iBACJ,QAAQ,KAAK,EAAE,gBAAgB,GAAG;EAGpC,MAAM,MAAM,WAAW,EAAE,IAAI;EAG7B,IAAI,IAAI,qBACN,QAAQ,KAAK,GAAG,IAAI,oBAAoB,CAAC,CAAC;EAI5C,IAAI,IAAI,aAAa,iBAAiB,IAAI,aAAa,kBAAkB;GACvE,MAAM,gBAAgB;GAItB,KAAK,MAAM,SAAS,cAAc,UAChC,SAAS,KAAK;EAElB;CACF;CAEA,SAAS,IAAI;CACb,OAAO;AACT;;;;AAKA,IAAI,QAA8B;AAElC,eAAe,UAAyB;CACtC,IAAI,UAAU,MAAM,QAAQ,SAAS;CACrC,OAAO;AACT;;;;;;;AAQA,SAAS,qBAAqB,MAAe,YAA+B;CAE1E,IAAI,KAAK,MAAM,KAAA,GAAW,OAAO;CAGjC,IACE,KAAK,cAAc,KAAA,KACnB,KAAK,cAAc,aACnB,KAAK,cAAc,QAEnB,OAAO;CAIT,IAAI,CAAC,YAAY,OAAO;CAGxB,IAAI;CACJ,IAAI,WAAW,SAAS,UACtB,mBAAmB,WAAW;MACzB,IAAI,WAAW,SAAS,UAC7B,mBAAmB,WAAW;CAKhC,IAAI,WAAW,SAAS,YAAY,WAAW,SAAS,SACtD,OAAO,qBAAqB,KAAA,KAAa,qBAAqB;CAIhE,IAAI,WAAW,SAAS,UACtB,OAAO;CAGT,OAAO;AACT;;;;AAKA,eAAe,qBACb,MACA,YACA,KACA,KACA,YACA,iBACA;CAGA,MAAM,MAAK,MAFQ,QAAQ,EAAA,CAEX,KAAK,OAAO;CAC5B,IAAI,IAAI,MAAM,EAAE;CAEhB,MAAM,qBAAqB,MAAM,IAAI,GAAG;CAKxC,IAAI,YAAY,SAAS,YAAY,YAAY,SAAS,UACxD,GAAG,cAAc,KAAK,SAAS,SAAS,IAAI,CAAC;CAO/C,IACE,YAAY,SAAS,YACrB,KAAK,MAAM,KAAA,KACX,KAAK,SAAS,WACd,KAAK,SAAS,QACd;EACA,GAAG,YAAY,KAAK,QAAQ,CAAC;EAK7B,IAAI,qBAAqB,YAAY,eAAe,GAClD,GAAG,aAAa,CAAC;CAErB;CAEA,WAAW,YAAY,IAAI,WAAW,cAAc,CAAC;CAIrD,QAFY,WAAW,KAAK,IAElB,CAAC,CAAC,UAAZ;EACE,KAAK;EACL,KAAK,kBAAkB;GACrB,MAAM,gBAAgB;GAItB,KAAK,MAAM,SAAS,cAAc,UAChC,MAAM,qBAAqB,OAAO,IAAI,KAAK,KAAK,MAAM,UAAU;GAElE;EACF;EACA,KAAK,QAEH;CACJ;AACF;;;;AAKA,eAAe,qBACb,MACA,IACA,KACA;CACA,MAAM,OAAO,MAAM,QAAQ;CAG3B,GAAG,iBAAiB,KAAK,qBAAqB;CAG9C,IAAI,KAAK,MAAM,KAAA;MACT,OAAO,KAAK,MAAM,UACpB,GAAG,SAAS,KAAK,CAAC;OACb,IAAI,KAAK,MAAM,OACpB,GAAG,YAAY,CAAC;OACX,IAAI,KAAK,EAAE,SAAS,GAAG,GAAG;GAC/B,MAAM,UAAU,WAAW,KAAK,CAAC;GACjC,GAAG,gBAAgB,OAAO;EAC5B;;CAIF,IAAI,KAAK,MAAM,KAAA;MACT,OAAO,KAAK,MAAM,UACpB,GAAG,UAAU,KAAK,CAAC;OACd,IAAI,KAAK,MAAM,OACpB,GAAG,YAAY,CAAC;OACX,IAAI,KAAK,EAAE,SAAS,GAAG,GAAG;GAC/B,MAAM,UAAU,WAAW,KAAK,CAAC;GACjC,GAAG,iBAAiB,OAAO;EAC7B;;CAIF,IAAI,KAAK,SAAS,KAAA,GAChB,GAAG,YAAY,KAAK,IAAI;CAI1B,IAAI,KAAK,SAAS,KAAA,GAChB,GAAG,YAAY,KAAK,IAAI;CAE1B,IAAI,KAAK,SAAS,KAAA,GAChB,GAAG,YAAY,KAAK,IAAI;CAE1B,IAAI,KAAK,SAAS,KAAA,GAChB,GAAG,aAAa,KAAK,IAAI;CAE3B,IAAI,KAAK,SAAS,KAAA,GAChB,GAAG,aAAa,KAAK,IAAI;CAI3B,IAAI,KAAK,YAAY,KAAA,GAAW;EAC9B,MAAM,UAAU,kBAAkB,KAAK,OAAO;EAC9C,GAAG,WAAW,KAAK,UAAU,QAAQ,GAAG;EACxC,GAAG,WAAW,KAAK,YAAY,QAAQ,KAAK;EAC5C,GAAG,WAAW,KAAK,aAAa,QAAQ,MAAM;EAC9C,GAAG,WAAW,KAAK,WAAW,QAAQ,IAAI;CAC5C;CAGA,IAAI,KAAK,WAAW,KAAA,GAAW;EAC7B,MAAM,SAAS,kBAAkB,KAAK,MAAM;EAC5C,GAAG,UAAU,KAAK,UAAU,OAAO,GAAG;EACtC,GAAG,UAAU,KAAK,YAAY,OAAO,KAAK;EAC1C,GAAG,UAAU,KAAK,aAAa,OAAO,MAAM;EAC5C,GAAG,UAAU,KAAK,WAAW,OAAO,IAAI;CAC1C;CAGA,IAAI,KAAK,aAAa,YACpB,GAAG,gBAAgB,KAAK,sBAAsB;CAEhD,IAAI,KAAK,QAAQ,KAAA,GACf,GAAG,YAAY,KAAK,UAAU,KAAK,GAAG;CAExC,IAAI,KAAK,UAAU,KAAA,GACjB,GAAG,YAAY,KAAK,YAAY,KAAK,KAAK;CAE5C,IAAI,KAAK,WAAW,KAAA,GAClB,GAAG,YAAY,KAAK,aAAa,KAAK,MAAM;CAE9C,IAAI,KAAK,SAAS,KAAA,GAChB,GAAG,YAAY,KAAK,WAAW,KAAK,IAAI;CAI1C,IAAI,KAAK,cAAc,KAAA,GACrB,QAAQ,KAAK,WAAb;EACE,KAAK;GACH,GAAG,aAAa,KAAK,UAAU;GAC/B;EACF,KAAK;GACH,GAAG,aAAa,KAAK,gBAAgB;GACrC;EACF,KAAK;GACH,GAAG,aAAa,KAAK,YAAY;GACjC;EACF,KAAK;GACH,GAAG,aAAa,KAAK,cAAc;GACnC;EACF,KAAK;GACH,GAAG,aAAa,KAAK,aAAa;GAClC;CACJ;CAIF,MAAM,MAAM,WAAW,KAAK,IAAI;CAChC,IAAI,IAAI,gBACN,MAAM,IAAI,eAAe,MAAM,IAAI,MAAM,GAAG;AAEhD"}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ import { ParseXmlError, parseXml } from "./parseXml/parseXml.js";
3
3
  import { serializeXml } from "./parseXml/serializeXml.js";
4
4
  import { TextMeasurementMode } from "./calcYogaLayout/measureText.js";
5
5
  import { Diagnostic, DiagnosticCode, DiagnosticsError } from "./diagnostics.js";
6
+ import { PptxOutputType, PptxWriteFileOptions, PptxWriteOptions, WritablePptx } from "./renderPptx/writablePptx.js";
6
7
  import { BuildPptxResult, buildPptx } from "./buildPptx.js";
7
8
  import { extractThemeTokensFromPptx } from "./extractThemeTokensFromPptx.js";
8
9
  import { extractSlideMastersAsPptx } from "./extractSlideMastersAsPptx.js";
9
- export { type BuildPptxResult, type Diagnostic, type DiagnosticCode, DiagnosticsError, FALLBACK_THEME_TOKENS, type MasterImageObject, type MasterLineObject, type MasterObject, type MasterRectObject, type MasterTextObject, type POMNode, ParseXmlError, type SlideMasterBackground, type SlideMasterMargin, type SlideMasterOptions, type SlideNumberOptions, type TextMeasurementMode, type ThemeTokens, buildPptx, extractSlideMastersAsPptx, extractThemeTokensFromPptx, parseXml, serializeXml };
10
+ export { type BuildPptxResult, type Diagnostic, type DiagnosticCode, DiagnosticsError, FALLBACK_THEME_TOKENS, type MasterImageObject, type MasterLineObject, type MasterObject, type MasterRectObject, type MasterTextObject, type POMNode, ParseXmlError, type PptxOutputType, type PptxWriteFileOptions, type PptxWriteOptions, type SlideMasterBackground, type SlideMasterMargin, type SlideMasterOptions, type SlideNumberOptions, type TextMeasurementMode, type ThemeTokens, type WritablePptx, buildPptx, extractSlideMastersAsPptx, extractThemeTokensFromPptx, parseXml, serializeXml };
@@ -1,5 +1,5 @@
1
- import { getContentArea } from "../../renderPptx/utils/contentArea.js";
2
1
  import { getNodeMetadata } from "../nodeMetadata.js";
2
+ import { getContentArea } from "../../renderPptx/utils/contentArea.js";
3
3
  import { rasterizeIcon } from "../../icons/renderIcon.js";
4
4
  import "../../icons/index.js";
5
5
  import { renderIconNode } from "../../renderPptx/nodes/icon.js";
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","names":[],"sources":["../../../src/registry/definitions/list.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport { measureFontLineHeightRatio } from \"../../calcYogaLayout/fontLoader.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderUlNode, renderOlNode } from \"../../renderPptx/nodes/list.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nfunction applyListYogaStyle(\n node: POMNode,\n yn: YogaNode,\n yoga: Yoga,\n ctx: BuildContext,\n) {\n const n = node as Extract<POMNode, { type: \"ul\" | \"ol\" }>;\n const combinedText = n.items.map((item) => item.text).join(\"\\n\");\n const baseFontSizePx = n.fontSize ?? 24;\n // Li 単位の fontSize 上書きと、Li 内 <Span fontSize=\"...\"> による run 単位\n // 上書きを両方考慮した最大値で計測する。単一 fontSize でしか計測できない\n // 制約に対する保守的な見積もりで、横方向は過剰評価になるが縦方向 clipping\n // を防ぐ。\n const fontSizePx = Math.max(\n baseFontSizePx,\n ...n.items.flatMap((item) => [\n item.fontSize ?? baseFontSizePx,\n ...(item.runs?.map(\n (r) => r.fontSize ?? item.fontSize ?? baseFontSizePx,\n ) ?? []),\n ]),\n );\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const spacingMultiple = n.lineHeight ?? 1.3;\n\n const fontMetricsRatio = measureFontLineHeightRatio(fontWeight);\n const lineHeight = fontMetricsRatio * spacingMultiple;\n\n // バレット/番号のインデント幅(pptxgenjs DEF_BULLET_MARGIN = 27pt = 36px @96dpi)\n const bulletIndentPx = 36;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const textMaxWidthPx = Math.max(0, maxWidthPx - bulletIndentPx);\n\n const { widthPx, heightPx } = measureText(\n combinedText,\n textMaxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n },\n ctx.textMeasurementMode,\n );\n\n return {\n width: widthPx + bulletIndentPx,\n height: heightPx,\n };\n });\n}\n\nexport const ulNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"ul\"),\n applyYogaStyle: applyListYogaStyle,\n render(node, ctx) {\n renderUlNode(node as Extract<typeof node, { type: \"ul\" }>, ctx);\n },\n};\n\nexport const olNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"ol\"),\n applyYogaStyle: applyListYogaStyle,\n render(node, ctx) {\n renderOlNode(node as Extract<typeof node, { type: \"ol\" }>, ctx);\n },\n};\n"],"mappings":";;;;;AASA,SAAS,mBACP,MACA,IACA,MACA,KACA;CACA,MAAM,IAAI;CACV,MAAM,eAAe,EAAE,MAAM,KAAK,SAAS,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI;CAC/D,MAAM,iBAAiB,EAAE,YAAY;CAKrC,MAAM,aAAa,KAAK,IACtB,gBACA,GAAG,EAAE,MAAM,SAAS,SAAS,CAC3B,KAAK,YAAY,gBACjB,GAAI,KAAK,MAAM,KACZ,MAAM,EAAE,YAAY,KAAK,YAAY,cACxC,KAAK,CAAC,CACR,CAAC,CACH;CACA,MAAM,aAAa,EAAE,cAAc;CACnC,MAAM,aAAa,EAAE,OAAO,SAAS;CACrC,MAAM,kBAAkB,EAAE,cAAc;CAGxC,MAAM,aADmB,2BAA2B,UAClB,IAAI;CAGtC,MAAM,iBAAiB;CAEvB,GAAG,gBAAgB,OAAO,cAAc;EACtC,MAAM,oBAAoB;GACxB,QAAQ,WAAR;IACE,KAAK,KAAK,wBACR,OAAO,OAAO;IAChB,KAAK,KAAK;IACV,KAAK,KAAK,sBACR,OAAO;IACT,SACE,OAAO,OAAO;GAClB;EACF,EAAA,CAAG;EAEH,MAAM,iBAAiB,KAAK,IAAI,GAAG,aAAa,cAAc;EAE9D,MAAM,EAAE,SAAS,aAAa,YAC5B,cACA,gBACA;GACE;GACA;GACA;GACA;EACF,GACA,IAAI,mBACN;EAEA,OAAO;GACL,OAAO,UAAU;GACjB,QAAQ;EACV;CACF,CAAC;AACH;AAEA,MAAa,YAA4B;CACvC,GAAG,gBAAgB,IAAI;CACvB,gBAAgB;CAChB,OAAO,MAAM,KAAK;EAChB,aAAa,MAA8C,GAAG;CAChE;AACF;AAEA,MAAa,YAA4B;CACvC,GAAG,gBAAgB,IAAI;CACvB,gBAAgB;CAChB,OAAO,MAAM,KAAK;EAChB,aAAa,MAA8C,GAAG;CAChE;AACF"}
1
+ {"version":3,"file":"list.js","names":[],"sources":["../../../src/registry/definitions/list.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport { measureFontLineHeightRatio } from \"../../calcYogaLayout/fontLoader.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderUlNode, renderOlNode } from \"../../renderPptx/nodes/list.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nfunction applyListYogaStyle(\n node: POMNode,\n yn: YogaNode,\n yoga: Yoga,\n ctx: BuildContext,\n) {\n const n = node as Extract<POMNode, { type: \"ul\" | \"ol\" }>;\n const combinedText = n.items.map((item) => item.text).join(\"\\n\");\n const baseFontSizePx = n.fontSize ?? 24;\n // Li 単位の fontSize 上書きと、Li 内 <Span fontSize=\"...\"> による run 単位\n // 上書きを両方考慮した最大値で計測する。単一 fontSize でしか計測できない\n // 制約に対する保守的な見積もりで、横方向は過剰評価になるが縦方向 clipping\n // を防ぐ。\n const fontSizePx = Math.max(\n baseFontSizePx,\n ...n.items.flatMap((item) => [\n item.fontSize ?? baseFontSizePx,\n ...(item.runs?.map(\n (r) => r.fontSize ?? item.fontSize ?? baseFontSizePx,\n ) ?? []),\n ]),\n );\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const spacingMultiple = n.lineHeight ?? 1.3;\n\n const fontMetricsRatio = measureFontLineHeightRatio(fontWeight);\n const lineHeight = fontMetricsRatio * spacingMultiple;\n\n // バレット/番号の互換インデント幅(27pt = 36px @96dpi)\n const bulletIndentPx = 36;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const textMaxWidthPx = Math.max(0, maxWidthPx - bulletIndentPx);\n\n const { widthPx, heightPx } = measureText(\n combinedText,\n textMaxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n },\n ctx.textMeasurementMode,\n );\n\n return {\n width: widthPx + bulletIndentPx,\n height: heightPx,\n };\n });\n}\n\nexport const ulNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"ul\"),\n applyYogaStyle: applyListYogaStyle,\n render(node, ctx) {\n renderUlNode(node as Extract<typeof node, { type: \"ul\" }>, ctx);\n },\n};\n\nexport const olNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"ol\"),\n applyYogaStyle: applyListYogaStyle,\n render(node, ctx) {\n renderOlNode(node as Extract<typeof node, { type: \"ol\" }>, ctx);\n },\n};\n"],"mappings":";;;;;AASA,SAAS,mBACP,MACA,IACA,MACA,KACA;CACA,MAAM,IAAI;CACV,MAAM,eAAe,EAAE,MAAM,KAAK,SAAS,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI;CAC/D,MAAM,iBAAiB,EAAE,YAAY;CAKrC,MAAM,aAAa,KAAK,IACtB,gBACA,GAAG,EAAE,MAAM,SAAS,SAAS,CAC3B,KAAK,YAAY,gBACjB,GAAI,KAAK,MAAM,KACZ,MAAM,EAAE,YAAY,KAAK,YAAY,cACxC,KAAK,CAAC,CACR,CAAC,CACH;CACA,MAAM,aAAa,EAAE,cAAc;CACnC,MAAM,aAAa,EAAE,OAAO,SAAS;CACrC,MAAM,kBAAkB,EAAE,cAAc;CAGxC,MAAM,aADmB,2BAA2B,UAClB,IAAI;CAGtC,MAAM,iBAAiB;CAEvB,GAAG,gBAAgB,OAAO,cAAc;EACtC,MAAM,oBAAoB;GACxB,QAAQ,WAAR;IACE,KAAK,KAAK,wBACR,OAAO,OAAO;IAChB,KAAK,KAAK;IACV,KAAK,KAAK,sBACR,OAAO;IACT,SACE,OAAO,OAAO;GAClB;EACF,EAAA,CAAG;EAEH,MAAM,iBAAiB,KAAK,IAAI,GAAG,aAAa,cAAc;EAE9D,MAAM,EAAE,SAAS,aAAa,YAC5B,cACA,gBACA;GACE;GACA;GACA;GACA;EACF,GACA,IAAI,mBACN;EAEA,OAAO;GACL,OAAO,UAAU;GACjB,QAAQ;EACV;CACF,CAAC;AACH;AAEA,MAAa,YAA4B;CACvC,GAAG,gBAAgB,IAAI;CACvB,gBAAgB;CAChB,OAAO,MAAM,KAAK;EAChB,aAAa,MAA8C,GAAG;CAChE;AACF;AAEA,MAAa,YAA4B;CACvC,GAAG,gBAAgB,IAAI;CACvB,gBAAgB;CAChB,OAAO,MAAM,KAAK;EAChB,aAAa,MAA8C,GAAG;CAChE;AACF"}
@@ -1,60 +1,185 @@
1
- import { getContentAreaIn } from "../utils/contentArea.js";
1
+ import { pxToEmu } from "../units.js";
2
+ import { getContentArea } from "../utils/contentArea.js";
3
+ import { toColorInput } from "../pptxAuthoring.js";
4
+ import { asEmu, asOoxmlPercent, asPt } from "@pptx-glimpse/document";
2
5
  //#region src/renderPptx/nodes/chart.ts
6
+ const DEFAULT_BAR_CHART_COLORS = [
7
+ "C0504D",
8
+ "4F81BD",
9
+ "9BBB59",
10
+ "8064A2",
11
+ "4BACC6",
12
+ "F79646",
13
+ "628FC6",
14
+ "C86360",
15
+ "C0504D",
16
+ "4F81BD",
17
+ "9BBB59",
18
+ "8064A2",
19
+ "4BACC6",
20
+ "F79646",
21
+ "628FC6",
22
+ "C86360"
23
+ ];
24
+ const DEFAULT_PIE_CHART_COLORS = [
25
+ "5DA5DA",
26
+ "FAA43A",
27
+ "60BD68",
28
+ "F17CB0",
29
+ "B2912F",
30
+ "B276B2",
31
+ "DECF3F",
32
+ "F15854",
33
+ "A7A7A7",
34
+ "5DA5DA",
35
+ "FAA43A",
36
+ "60BD68",
37
+ "F17CB0",
38
+ "B2912F",
39
+ "B276B2",
40
+ "DECF3F",
41
+ "F15854",
42
+ "A7A7A7"
43
+ ];
3
44
  function renderChartNode(node, ctx) {
4
- const chartData = node.data.map((d) => ({
5
- name: d.name,
6
- labels: d.labels,
7
- values: d.values
8
- }));
9
45
  const isSparkline = node.sparkline === true && (node.chartType === "bar" || node.chartType === "line" || node.chartType === "area");
10
- const chartOptions = {
11
- ...getContentAreaIn(node),
46
+ const content = getContentArea(node);
47
+ const series = normalizeChartSeries(node.data);
48
+ const chartColors = node.chartColors ?? (node.chartType === "pie" || node.chartType === "doughnut" ? DEFAULT_PIE_CHART_COLORS : DEFAULT_BAR_CHART_COLORS);
49
+ const pointColors = chartColors.length > 0 && (node.chartType === "pie" || node.chartType === "doughnut" || node.chartType === "bar" && node.data.length === 1 && node.chartColors !== void 0 && node.chartColors.length > 1) ? node.data[0]?.values.map((_value, index) => chartColors[index % chartColors.length]) : void 0;
50
+ ctx.buildContext.pptxAuthoring.registerChart({
51
+ chartType: node.chartType,
52
+ series: series.map((item, index) => ({
53
+ name: item.name,
54
+ categories: item.labels,
55
+ values: item.values,
56
+ color: chartColors[index % chartColors.length],
57
+ dataPoints: index === 0 ? pointColors?.map((color, pointIndex) => ({
58
+ index: pointIndex,
59
+ fill: {
60
+ kind: "solid",
61
+ color: toColorInput(color)
62
+ }
63
+ })) : void 0,
64
+ marker: node.chartType === "line" || node.chartType === "radar" ? {
65
+ symbol: "circle",
66
+ size: 6,
67
+ fill: {
68
+ kind: "solid",
69
+ color: toColorInput(chartColors[index % chartColors.length])
70
+ }
71
+ } : void 0
72
+ })),
73
+ offsetX: asEmu(Math.round(pxToEmu(content.x))),
74
+ offsetY: asEmu(Math.round(pxToEmu(content.y))),
75
+ width: asEmu(Math.round(pxToEmu(Math.max(content.w, 1)))),
76
+ height: asEmu(Math.round(pxToEmu(Math.max(content.h, 1)))),
77
+ title: !isSparkline && node.showTitle ? node.title || "Chart Title" : void 0,
78
+ titleStyle: !isSparkline && node.showTitle ? {
79
+ fontFace: "Arial",
80
+ fontSize: asPt(18),
81
+ color: toColorInput("000000"),
82
+ bold: false,
83
+ italic: false
84
+ } : void 0,
85
+ displayBlanksAs: "span",
86
+ roundedCorners: !isSparkline,
87
+ chartArea: isSparkline ? { fill: {
88
+ kind: "solid",
89
+ color: {
90
+ ...toColorInput("FFFFFF"),
91
+ transforms: [{
92
+ kind: "alpha",
93
+ value: asOoxmlPercent(0)
94
+ }]
95
+ }
96
+ } } : {
97
+ fill: { kind: "none" },
98
+ outline: { fill: { kind: "none" } }
99
+ },
100
+ plotArea: isSparkline ? { fill: {
101
+ kind: "solid",
102
+ color: {
103
+ ...toColorInput("FFFFFF"),
104
+ transforms: [{
105
+ kind: "alpha",
106
+ value: asOoxmlPercent(0)
107
+ }]
108
+ }
109
+ } } : {
110
+ fill: { kind: "none" },
111
+ outline: { fill: { kind: "none" } }
112
+ },
12
113
  showLegend: isSparkline ? false : node.showLegend ?? false,
13
- showTitle: isSparkline ? false : node.showTitle ?? false,
14
- title: isSparkline ? void 0 : node.title,
15
- chartColors: node.chartColors
16
- };
17
- if (node.chartType === "radar" && node.radarStyle) chartOptions.radarStyle = node.radarStyle;
18
- if (isSparkline) {
19
- chartOptions.catAxisHidden = true;
20
- chartOptions.valAxisHidden = true;
21
- chartOptions.catAxisLineShow = false;
22
- chartOptions.valAxisLineShow = false;
23
- chartOptions.showCatAxisTitle = false;
24
- chartOptions.showValAxisTitle = false;
25
- chartOptions.catGridLine = { style: "none" };
26
- chartOptions.valGridLine = { style: "none" };
27
- chartOptions.chartArea = {
28
- fill: {
29
- type: "solid",
30
- color: "FFFFFF",
31
- transparency: 100
114
+ radarStyle: node.chartType === "radar" ? node.radarStyle : void 0,
115
+ categoryAxis: isSparkline ? {
116
+ hidden: true,
117
+ lineVisible: false,
118
+ gridLinesVisible: false
119
+ } : {
120
+ majorTickMark: "outside",
121
+ labelPosition: "low",
122
+ numberFormat: {
123
+ formatCode: "General",
124
+ sourceLinked: true
32
125
  },
33
- border: {
34
- color: "FFFFFF",
35
- pt: 0
126
+ showMultiLevelLabels: false,
127
+ textStyle: {
128
+ fontFace: "Arial",
129
+ fontSize: asPt(12),
130
+ color: toColorInput("000000")
131
+ }
132
+ },
133
+ valueAxis: isSparkline ? {
134
+ hidden: true,
135
+ lineVisible: false,
136
+ gridLinesVisible: false
137
+ } : {
138
+ majorTickMark: "outside",
139
+ labelPosition: "nextTo",
140
+ numberFormat: {
141
+ formatCode: "General",
142
+ sourceLinked: false
36
143
  },
37
- roundedCorners: false
38
- };
39
- chartOptions.plotArea = {
40
- fill: {
41
- type: "solid",
42
- color: "FFFFFF",
43
- transparency: 100
144
+ majorGridline: {
145
+ width: asEmu(12700),
146
+ fill: {
147
+ kind: "solid",
148
+ color: toColorInput("888888")
149
+ }
44
150
  },
45
- border: {
46
- color: "FFFFFF",
47
- pt: 0
151
+ textStyle: {
152
+ fontFace: "Arial",
153
+ fontSize: asPt(12),
154
+ color: toColorInput("000000")
48
155
  }
49
- };
50
- chartOptions.layout = {
156
+ },
157
+ plotLayout: isSparkline ? {
158
+ coordinateMode: "edge",
51
159
  x: 0,
52
160
  y: 0,
53
- w: 1,
54
- h: 1
55
- };
56
- }
57
- ctx.slide.addChart(node.chartType, chartData, chartOptions);
161
+ width: 1,
162
+ height: 1
163
+ } : void 0
164
+ });
165
+ }
166
+ /**
167
+ * glimpse の native writer は全系列で同じ category 軸と同じ点数を要求する。
168
+ * 従来受理していた不揃いな入力も生成を継続できるよう、位置ベースで
169
+ * category を共有し、欠けた値を 0 で補う。
170
+ */
171
+ function normalizeChartSeries(data) {
172
+ const sourceData = data.length > 0 ? data : [{
173
+ labels: [],
174
+ values: []
175
+ }];
176
+ const pointCount = Math.max(1, ...sourceData.flatMap((series) => [series.labels.length, series.values.length]));
177
+ const categories = Array.from({ length: pointCount }, (_, index) => sourceData.find((series) => series.labels[index] !== void 0)?.labels[index] ?? "");
178
+ return sourceData.map((series) => ({
179
+ name: series.name,
180
+ labels: categories,
181
+ values: Array.from({ length: pointCount }, (_, index) => series.values[index] ?? 0)
182
+ }));
58
183
  }
59
184
  //#endregion
60
185
  export { renderChartNode };
@@ -1 +1 @@
1
- {"version":3,"file":"chart.js","names":[],"sources":["../../../src/renderPptx/nodes/chart.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { getContentAreaIn } from \"../utils/contentArea.ts\";\n\ntype ChartPositionedNode = Extract<PositionedNode, { type: \"chart\" }>;\n\nexport function renderChartNode(\n node: ChartPositionedNode,\n ctx: RenderContext,\n): void {\n const chartData = node.data.map((d) => ({\n name: d.name,\n labels: d.labels,\n values: d.values,\n }));\n\n // sparkline モードは bar / line / area のみ対応。pie / doughnut / radar は\n // 元々凡例 / 軸の概念が異なるため sparkline=true でも通常描画にフォールバックする。\n const isSparkline =\n node.sparkline === true &&\n (node.chartType === \"bar\" ||\n node.chartType === \"line\" ||\n node.chartType === \"area\");\n\n const chartOptions: Record<string, unknown> = {\n ...getContentAreaIn(node),\n showLegend: isSparkline ? false : (node.showLegend ?? false),\n showTitle: isSparkline ? false : (node.showTitle ?? false),\n title: isSparkline ? undefined : node.title,\n chartColors: node.chartColors,\n };\n\n // radar専用オプション\n if (node.chartType === \"radar\" && node.radarStyle) {\n chartOptions.radarStyle = node.radarStyle;\n }\n\n // sparkline モード: 凡例 / 軸 / グリッド線 / マージンをすべて非表示にし、\n // プロット領域をチャート領域いっぱいに広げて小寸法でも視認できるようにする\n if (isSparkline) {\n chartOptions.catAxisHidden = true;\n chartOptions.valAxisHidden = true;\n chartOptions.catAxisLineShow = false;\n chartOptions.valAxisLineShow = false;\n chartOptions.showCatAxisTitle = false;\n chartOptions.showValAxisTitle = false;\n chartOptions.catGridLine = { style: \"none\" };\n chartOptions.valGridLine = { style: \"none\" };\n chartOptions.chartArea = {\n fill: { type: \"solid\", color: \"FFFFFF\", transparency: 100 },\n border: { color: \"FFFFFF\", pt: 0 },\n roundedCorners: false,\n };\n chartOptions.plotArea = {\n fill: { type: \"solid\", color: \"FFFFFF\", transparency: 100 },\n border: { color: \"FFFFFF\", pt: 0 },\n };\n chartOptions.layout = { x: 0, y: 0, w: 1, h: 1 };\n }\n\n ctx.slide.addChart(node.chartType, chartData, chartOptions);\n}\n"],"mappings":";;AAMA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,YAAY,KAAK,KAAK,KAAK,OAAO;EACtC,MAAM,EAAE;EACR,QAAQ,EAAE;EACV,QAAQ,EAAE;CACZ,EAAE;CAIF,MAAM,cACJ,KAAK,cAAc,SAClB,KAAK,cAAc,SAClB,KAAK,cAAc,UACnB,KAAK,cAAc;CAEvB,MAAM,eAAwC;EAC5C,GAAG,iBAAiB,IAAI;EACxB,YAAY,cAAc,QAAS,KAAK,cAAc;EACtD,WAAW,cAAc,QAAS,KAAK,aAAa;EACpD,OAAO,cAAc,KAAA,IAAY,KAAK;EACtC,aAAa,KAAK;CACpB;CAGA,IAAI,KAAK,cAAc,WAAW,KAAK,YACrC,aAAa,aAAa,KAAK;CAKjC,IAAI,aAAa;EACf,aAAa,gBAAgB;EAC7B,aAAa,gBAAgB;EAC7B,aAAa,kBAAkB;EAC/B,aAAa,kBAAkB;EAC/B,aAAa,mBAAmB;EAChC,aAAa,mBAAmB;EAChC,aAAa,cAAc,EAAE,OAAO,OAAO;EAC3C,aAAa,cAAc,EAAE,OAAO,OAAO;EAC3C,aAAa,YAAY;GACvB,MAAM;IAAE,MAAM;IAAS,OAAO;IAAU,cAAc;GAAI;GAC1D,QAAQ;IAAE,OAAO;IAAU,IAAI;GAAE;GACjC,gBAAgB;EAClB;EACA,aAAa,WAAW;GACtB,MAAM;IAAE,MAAM;IAAS,OAAO;IAAU,cAAc;GAAI;GAC1D,QAAQ;IAAE,OAAO;IAAU,IAAI;GAAE;EACnC;EACA,aAAa,SAAS;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;CACjD;CAEA,IAAI,MAAM,SAAS,KAAK,WAAW,WAAW,YAAY;AAC5D"}
1
+ {"version":3,"file":"chart.js","names":[],"sources":["../../../src/renderPptx/nodes/chart.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { asEmu, asOoxmlPercent, asPt } from \"@pptx-glimpse/document\";\nimport { pxToEmu } from \"../units.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\nimport { toColorInput } from \"../pptxAuthoring.ts\";\n\ntype ChartPositionedNode = Extract<PositionedNode, { type: \"chart\" }>;\n\nconst DEFAULT_BAR_CHART_COLORS = [\n \"C0504D\",\n \"4F81BD\",\n \"9BBB59\",\n \"8064A2\",\n \"4BACC6\",\n \"F79646\",\n \"628FC6\",\n \"C86360\",\n \"C0504D\",\n \"4F81BD\",\n \"9BBB59\",\n \"8064A2\",\n \"4BACC6\",\n \"F79646\",\n \"628FC6\",\n \"C86360\",\n];\nconst DEFAULT_PIE_CHART_COLORS = [\n \"5DA5DA\",\n \"FAA43A\",\n \"60BD68\",\n \"F17CB0\",\n \"B2912F\",\n \"B276B2\",\n \"DECF3F\",\n \"F15854\",\n \"A7A7A7\",\n \"5DA5DA\",\n \"FAA43A\",\n \"60BD68\",\n \"F17CB0\",\n \"B2912F\",\n \"B276B2\",\n \"DECF3F\",\n \"F15854\",\n \"A7A7A7\",\n];\n\nexport function renderChartNode(\n node: ChartPositionedNode,\n ctx: RenderContext,\n): void {\n // sparkline モードは bar / line / area のみ対応。pie / doughnut / radar は\n // 元々凡例 / 軸の概念が異なるため sparkline=true でも通常描画にフォールバックする。\n const isSparkline =\n node.sparkline === true &&\n (node.chartType === \"bar\" ||\n node.chartType === \"line\" ||\n node.chartType === \"area\");\n\n const content = getContentArea(node);\n const series = normalizeChartSeries(node.data);\n const chartColors =\n node.chartColors ??\n (node.chartType === \"pie\" || node.chartType === \"doughnut\"\n ? DEFAULT_PIE_CHART_COLORS\n : DEFAULT_BAR_CHART_COLORS);\n const pointColors =\n chartColors.length > 0 &&\n (node.chartType === \"pie\" ||\n node.chartType === \"doughnut\" ||\n (node.chartType === \"bar\" &&\n node.data.length === 1 &&\n node.chartColors !== undefined &&\n node.chartColors.length > 1))\n ? node.data[0]?.values.map(\n (_value, index) => chartColors[index % chartColors.length],\n )\n : undefined;\n ctx.buildContext.pptxAuthoring.registerChart({\n chartType: node.chartType,\n series: series.map((item, index) => ({\n name: item.name,\n categories: item.labels,\n values: item.values,\n color: chartColors[index % chartColors.length],\n dataPoints:\n index === 0\n ? pointColors?.map((color, pointIndex) => ({\n index: pointIndex,\n fill: { kind: \"solid\", color: toColorInput(color)! },\n }))\n : undefined,\n marker:\n node.chartType === \"line\" || node.chartType === \"radar\"\n ? {\n symbol: \"circle\",\n size: 6,\n fill: {\n kind: \"solid\",\n color: toColorInput(chartColors[index % chartColors.length])!,\n },\n }\n : undefined,\n })),\n offsetX: asEmu(Math.round(pxToEmu(content.x))),\n offsetY: asEmu(Math.round(pxToEmu(content.y))),\n width: asEmu(Math.round(pxToEmu(Math.max(content.w, 1)))),\n height: asEmu(Math.round(pxToEmu(Math.max(content.h, 1)))),\n title:\n !isSparkline && node.showTitle ? node.title || \"Chart Title\" : undefined,\n titleStyle:\n !isSparkline && node.showTitle\n ? {\n fontFace: \"Arial\",\n fontSize: asPt(18),\n color: toColorInput(\"000000\"),\n bold: false,\n italic: false,\n }\n : undefined,\n displayBlanksAs: \"span\",\n roundedCorners: !isSparkline,\n chartArea: isSparkline\n ? {\n fill: {\n kind: \"solid\",\n color: {\n ...toColorInput(\"FFFFFF\")!,\n transforms: [{ kind: \"alpha\", value: asOoxmlPercent(0) }],\n },\n },\n }\n : { fill: { kind: \"none\" }, outline: { fill: { kind: \"none\" } } },\n plotArea: isSparkline\n ? {\n fill: {\n kind: \"solid\",\n color: {\n ...toColorInput(\"FFFFFF\")!,\n transforms: [{ kind: \"alpha\", value: asOoxmlPercent(0) }],\n },\n },\n }\n : { fill: { kind: \"none\" }, outline: { fill: { kind: \"none\" } } },\n showLegend: isSparkline ? false : (node.showLegend ?? false),\n radarStyle: node.chartType === \"radar\" ? node.radarStyle : undefined,\n categoryAxis: isSparkline\n ? { hidden: true, lineVisible: false, gridLinesVisible: false }\n : {\n majorTickMark: \"outside\",\n labelPosition: \"low\",\n numberFormat: { formatCode: \"General\", sourceLinked: true },\n showMultiLevelLabels: false,\n textStyle: {\n fontFace: \"Arial\",\n fontSize: asPt(12),\n color: toColorInput(\"000000\"),\n },\n },\n valueAxis: isSparkline\n ? { hidden: true, lineVisible: false, gridLinesVisible: false }\n : {\n majorTickMark: \"outside\",\n labelPosition: \"nextTo\",\n numberFormat: { formatCode: \"General\", sourceLinked: false },\n majorGridline: {\n width: asEmu(12700),\n fill: { kind: \"solid\", color: toColorInput(\"888888\")! },\n },\n textStyle: {\n fontFace: \"Arial\",\n fontSize: asPt(12),\n color: toColorInput(\"000000\"),\n },\n },\n plotLayout: isSparkline\n ? { coordinateMode: \"edge\", x: 0, y: 0, width: 1, height: 1 }\n : undefined,\n });\n}\n\n/**\n * glimpse の native writer は全系列で同じ category 軸と同じ点数を要求する。\n * 従来受理していた不揃いな入力も生成を継続できるよう、位置ベースで\n * category を共有し、欠けた値を 0 で補う。\n */\nfunction normalizeChartSeries(data: ChartPositionedNode[\"data\"]) {\n const sourceData = data.length > 0 ? data : [{ labels: [], values: [] }];\n const pointCount = Math.max(\n 1,\n ...sourceData.flatMap((series) => [\n series.labels.length,\n series.values.length,\n ]),\n );\n const categories = Array.from(\n { length: pointCount },\n (_, index) =>\n sourceData.find((series) => series.labels[index] !== undefined)?.labels[\n index\n ] ?? \"\",\n );\n return sourceData.map((series) => ({\n name: series.name,\n labels: categories,\n values: Array.from(\n { length: pointCount },\n (_, index) => series.values[index] ?? 0,\n ),\n }));\n}\n"],"mappings":"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"}