@hirokisakabe/pom 2.0.0 → 4.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 (66) hide show
  1. package/README.md +171 -38
  2. package/dist/buildPptx.js +1 -1
  3. package/dist/calcYogaLayout/calcYogaLayout.d.ts.map +1 -1
  4. package/dist/calcYogaLayout/calcYogaLayout.js +121 -20
  5. package/dist/calcYogaLayout/fontLoader.d.ts +16 -0
  6. package/dist/calcYogaLayout/fontLoader.d.ts.map +1 -1
  7. package/dist/calcYogaLayout/fontLoader.js +28 -0
  8. package/dist/calcYogaLayout/measureCompositeNodes.d.ts +13 -1
  9. package/dist/calcYogaLayout/measureCompositeNodes.d.ts.map +1 -1
  10. package/dist/calcYogaLayout/measureCompositeNodes.js +21 -0
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/{inputSchema.d.ts → parseXml/inputSchema.d.ts} +305 -24
  15. package/dist/parseXml/inputSchema.d.ts.map +1 -0
  16. package/dist/{inputSchema.js → parseXml/inputSchema.js} +44 -2
  17. package/dist/{parseXml.d.ts → parseXml/parseXml.d.ts} +1 -1
  18. package/dist/parseXml/parseXml.d.ts.map +1 -0
  19. package/dist/{parseXml.js → parseXml/parseXml.js} +76 -30
  20. package/dist/renderPptx/nodes/flow.d.ts.map +1 -1
  21. package/dist/renderPptx/nodes/flow.js +26 -16
  22. package/dist/renderPptx/nodes/index.d.ts +2 -0
  23. package/dist/renderPptx/nodes/index.d.ts.map +1 -1
  24. package/dist/renderPptx/nodes/index.js +2 -0
  25. package/dist/renderPptx/nodes/list.d.ts +12 -0
  26. package/dist/renderPptx/nodes/list.d.ts.map +1 -0
  27. package/dist/renderPptx/nodes/list.js +145 -0
  28. package/dist/renderPptx/nodes/matrix.d.ts.map +1 -1
  29. package/dist/renderPptx/nodes/matrix.js +43 -31
  30. package/dist/renderPptx/nodes/processArrow.d.ts.map +1 -1
  31. package/dist/renderPptx/nodes/processArrow.js +14 -6
  32. package/dist/renderPptx/nodes/pyramid.d.ts +8 -0
  33. package/dist/renderPptx/nodes/pyramid.d.ts.map +1 -0
  34. package/dist/renderPptx/nodes/pyramid.js +81 -0
  35. package/dist/renderPptx/nodes/shape.js +2 -2
  36. package/dist/renderPptx/nodes/table.d.ts.map +1 -1
  37. package/dist/renderPptx/nodes/table.js +3 -1
  38. package/dist/renderPptx/nodes/timeline.d.ts.map +1 -1
  39. package/dist/renderPptx/nodes/timeline.js +58 -37
  40. package/dist/renderPptx/nodes/tree.d.ts.map +1 -1
  41. package/dist/renderPptx/nodes/tree.js +49 -40
  42. package/dist/renderPptx/renderPptx.d.ts.map +1 -1
  43. package/dist/renderPptx/renderPptx.js +11 -2
  44. package/dist/renderPptx/textOptions.d.ts +1 -28
  45. package/dist/renderPptx/textOptions.d.ts.map +1 -1
  46. package/dist/renderPptx/textOptions.js +1 -27
  47. package/dist/renderPptx/utils/backgroundBorder.js +1 -1
  48. package/dist/renderPptx/utils/scaleToFit.d.ts +8 -0
  49. package/dist/renderPptx/utils/scaleToFit.d.ts.map +1 -0
  50. package/dist/renderPptx/utils/scaleToFit.js +19 -0
  51. package/dist/shared/measureImage.d.ts.map +1 -0
  52. package/dist/{table/utils.d.ts → shared/tableUtils.d.ts} +1 -1
  53. package/dist/shared/tableUtils.d.ts.map +1 -0
  54. package/dist/toPositioned/toPositioned.d.ts.map +1 -1
  55. package/dist/toPositioned/toPositioned.js +13 -2
  56. package/dist/types.d.ts +345 -53
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js +66 -8
  59. package/package.json +1 -1
  60. package/dist/calcYogaLayout/measureImage.d.ts.map +0 -1
  61. package/dist/inputSchema.d.ts.map +0 -1
  62. package/dist/parseXml.d.ts.map +0 -1
  63. package/dist/table/utils.d.ts.map +0 -1
  64. /package/dist/{calcYogaLayout → shared}/measureImage.d.ts +0 -0
  65. /package/dist/{calcYogaLayout → shared}/measureImage.js +0 -0
  66. /package/dist/{table/utils.js → shared/tableUtils.js} +0 -0
package/README.md CHANGED
@@ -1,25 +1,60 @@
1
- # pom
1
+ <h1 align="center">pom</h1>
2
+ <p align="center">
3
+ Declarative PowerPoint generation from XML — built for AI
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="https://www.npmjs.com/package/@hirokisakabe/pom"><img src="https://img.shields.io/npm/v/@hirokisakabe/pom.svg" alt="npm version"></a>
8
+ <a href="https://github.com/hirokisakabe/pom/actions/workflows/ci.yml"><img src="https://github.com/hirokisakabe/pom/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
9
+ <a href="https://github.com/hirokisakabe/pom/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@hirokisakabe/pom.svg" alt="License"></a>
10
+ </p>
11
+
12
+ <p align="center">
13
+ <b>pom (PowerPoint Object Model)</b> is a TypeScript library that converts XML into PowerPoint files (.pptx).<br>
14
+ Flexbox-style layout powered by <a href="https://www.yogalayout.dev/">yoga-layout</a>, rendered with <a href="https://github.com/nicktomlin/pptxgenjs">pptxgenjs</a>.
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://pom-playground.vercel.app/"><b>Try it online — Playground</b></a>
19
+ </p>
20
+
21
+ <p align="center">
22
+ <a href="https://pom-playground.vercel.app/">
23
+ <img src="./docs/images/playground.png" alt="Playground" width="800">
24
+ </a>
25
+ </p>
26
+
27
+ ---
28
+
29
+ ## Table of Contents
30
+
31
+ - [Features](#features)
32
+ - [Quick Start](#quick-start)
33
+ - [Available Nodes](#available-nodes)
34
+ - [Node Examples](#node-examples)
35
+ - [Documentation](#documentation)
36
+ - [License](#license)
2
37
 
3
- **pom (PowerPoint Object Model)** is a library for declaratively describing PowerPoint presentations (pptx) in TypeScript. It is designed for use cases where XML in POM format generated by AI is converted into PowerPoint files.
4
-
5
- > **[Try it online](https://pom-playground.vercel.app/)** — XML Playground
6
-
7
- ## Requirements
38
+ ## Features
8
39
 
9
- - Node.js 18 or higher (for Node.js environments)
10
- - Modern browser with ES2020+ support (for browser environments)
40
+ - **AI Friendly** — Simple XML structure designed for LLM code generation. Pair with [LLM Integration guide](./docs/llm-integration.md) for prompt-ready references.
41
+ - **Declarative** Describe slides as XML. No imperative API calls needed.
42
+ - **Flexible Layout** — Flexbox-style layout with VStack / HStack / Box, powered by yoga-layout.
43
+ - **Rich Nodes** — 15 built-in node types: charts, flowcharts, tables, timelines, org trees, and more.
44
+ - **Schema-validated** — XML input is validated with Zod schemas at runtime with clear error messages.
45
+ - **PowerPoint Native** — Full access to native PowerPoint shape features (roundRect, ellipse, arrows, etc.).
46
+ - **Pixel Units** — Intuitive pixel-based sizing (internally converted to inches at 96 DPI).
47
+ - **Master Slide** — Define headers, footers, and page numbers once — applied to all slides automatically.
48
+ - **Accurate Text Measurement** — Text width measured with opentype.js and bundled Noto Sans JP fonts for consistent layout.
11
49
 
12
- > [!NOTE]
13
- > pom works in both Node.js and browser environments. Text measurement uses opentype.js with bundled Noto Sans JP fonts, ensuring consistent layout across all environments.
50
+ ## Quick Start
14
51
 
15
- ## Installation
52
+ > Requires Node.js 18+
16
53
 
17
54
  ```bash
18
55
  npm install @hirokisakabe/pom
19
56
  ```
20
57
 
21
- ## Quick Start
22
-
23
58
  ```typescript
24
59
  import { buildPptx } from "@hirokisakabe/pom";
25
60
 
@@ -34,36 +69,134 @@ const pptx = await buildPptx(xml, { w: 1280, h: 720 });
34
69
  await pptx.writeFile({ fileName: "presentation.pptx" });
35
70
  ```
36
71
 
37
- ## Features
38
-
39
- - **Schema-validated**: XML input validated with Zod schemas at runtime
40
- - **Declarative**: Describe slides with XML
41
- - **PowerPoint First**: Native support for Shape features
42
- - **Flexible Layout**: Automatic layout with VStack/HStack/Box
43
- - **Pixel Units**: Intuitive pixel-based sizing (internally converted to inches)
44
- - **Master Slide**: Automatically insert common headers, footers, and page numbers across all pages
45
- - **AI Friendly**: Simple structure that makes it easy for LLMs to generate code
46
-
47
72
  ## Available Nodes
48
73
 
49
- | Node | Description |
50
- | ------------ | ----------------------------------------------------- |
51
- | text | Text with font styling, decoration, and bullet points |
52
- | image | Images from file path, URL, or base64 |
53
- | table | Tables with customizable columns and rows |
54
- | shape | PowerPoint shapes (roundRect, ellipse, etc.) |
55
- | chart | Charts (bar, line, pie, area, doughnut, radar) |
56
- | timeline | Timeline/roadmap visualizations |
57
- | matrix | 2x2 positioning maps |
58
- | tree | Organization charts and decision trees |
59
- | flow | Flowcharts with nodes and edges |
60
- | processArrow | Chevron-style process diagrams |
61
- | box | Container for single child with padding |
62
- | vstack | Vertical stack layout |
63
- | hstack | Horizontal stack layout |
74
+ | Node | Description |
75
+ | ------------ | ---------------------------------------------- |
76
+ | Text | Text with font styling and decoration |
77
+ | Ul | Unordered (bullet) list with Li items |
78
+ | Ol | Ordered (numbered) list with Li items |
79
+ | Image | Images from file path, URL, or base64 |
80
+ | Table | Tables with customizable columns and rows |
81
+ | Shape | PowerPoint shapes (roundRect, ellipse, etc.) |
82
+ | Chart | Charts (bar, line, pie, area, doughnut, radar) |
83
+ | Timeline | Timeline / roadmap visualizations |
84
+ | Matrix | 2x2 positioning maps |
85
+ | Tree | Organization charts and decision trees |
86
+ | Flow | Flowcharts with nodes and edges |
87
+ | ProcessArrow | Chevron-style process diagrams |
88
+ | Pyramid | Pyramid diagrams for hierarchies |
89
+ | Line | Horizontal / vertical lines |
90
+ | Layer | Absolute-positioned overlay container |
91
+ | Box | Container for single child with padding |
92
+ | VStack | Vertical stack layout |
93
+ | HStack | Horizontal stack layout |
64
94
 
65
95
  For detailed node documentation, see [Nodes Reference](./docs/nodes.md).
66
96
 
97
+ ## Node Examples
98
+
99
+ ### Chart
100
+
101
+ ```xml
102
+ <Chart chartType="bar" w="350" h="250" showTitle="true" title="Bar Chart" showLegend="true">
103
+ <ChartSeries name="Q1">
104
+ <ChartDataPoint label="Jan" value="30" />
105
+ <ChartDataPoint label="Feb" value="45" />
106
+ </ChartSeries>
107
+ </Chart>
108
+ ```
109
+
110
+ <img src="./docs/images/chart.png" alt="Chart example" width="600">
111
+
112
+ ### Flow
113
+
114
+ ```xml
115
+ <Flow direction="horizontal" w="100%" h="300">
116
+ <FlowNode id="start" shape="flowChartTerminator" text="Start" color="16A34A" />
117
+ <FlowNode id="process" shape="flowChartProcess" text="Process" color="1D4ED8" />
118
+ <FlowNode id="end" shape="flowChartTerminator" text="End" color="DC2626" />
119
+ <FlowConnection from="start" to="process" />
120
+ <FlowConnection from="process" to="end" />
121
+ </Flow>
122
+ ```
123
+
124
+ <img src="./docs/images/flow.png" alt="Flow example" width="600">
125
+
126
+ ### Tree
127
+
128
+ ```xml
129
+ <Tree layout="vertical" nodeShape="roundRect" w="100%" h="400">
130
+ <TreeItem label="CEO" color="0F172A">
131
+ <TreeItem label="CTO" color="1D4ED8">
132
+ <TreeItem label="Dev Team" color="0EA5E9" />
133
+ </TreeItem>
134
+ <TreeItem label="CFO" color="16A34A">
135
+ <TreeItem label="Finance" color="0EA5E9" />
136
+ </TreeItem>
137
+ </TreeItem>
138
+ </Tree>
139
+ ```
140
+
141
+ <img src="./docs/images/tree.png" alt="Tree example" width="600">
142
+
143
+ ### Table
144
+
145
+ ```xml
146
+ <Table defaultRowHeight="36">
147
+ <TableColumn width="80" />
148
+ <TableColumn width="200" />
149
+ <TableRow>
150
+ <TableCell bold="true" backgroundColor="0F172A" color="FFFFFF">ID</TableCell>
151
+ <TableCell bold="true" backgroundColor="0F172A" color="FFFFFF">Name</TableCell>
152
+ </TableRow>
153
+ <TableRow>
154
+ <TableCell>001</TableCell>
155
+ <TableCell>Project Alpha</TableCell>
156
+ </TableRow>
157
+ </Table>
158
+ ```
159
+
160
+ <img src="./docs/images/table.png" alt="Table example" width="600">
161
+
162
+ ### Timeline
163
+
164
+ ```xml
165
+ <Timeline direction="horizontal" w="100%" h="200">
166
+ <TimelineItem date="2024 Q1" title="Phase 1" description="Planning" color="1D4ED8" />
167
+ <TimelineItem date="2024 Q2" title="Phase 2" description="Development" color="16A34A" />
168
+ <TimelineItem date="2024 Q3" title="Phase 3" description="Testing" color="0EA5E9" />
169
+ </Timeline>
170
+ ```
171
+
172
+ <img src="./docs/images/timeline.png" alt="Timeline example" width="600">
173
+
174
+ ### ProcessArrow
175
+
176
+ ```xml
177
+ <ProcessArrow direction="horizontal" w="100%" h="100">
178
+ <ProcessArrowStep label="Planning" color="4472C4" />
179
+ <ProcessArrowStep label="Design" color="5B9BD5" />
180
+ <ProcessArrowStep label="Development" color="70AD47" />
181
+ <ProcessArrowStep label="Testing" color="FFC000" />
182
+ <ProcessArrowStep label="Release" color="ED7D31" />
183
+ </ProcessArrow>
184
+ ```
185
+
186
+ <img src="./docs/images/processArrow.png" alt="ProcessArrow example" width="600">
187
+
188
+ ### Pyramid
189
+
190
+ ```xml
191
+ <Pyramid direction="up" w="600" h="300">
192
+ <PyramidLevel label="Strategy" color="E91E63" />
193
+ <PyramidLevel label="Tactics" color="9C27B0" />
194
+ <PyramidLevel label="Execution" color="673AB7" />
195
+ </Pyramid>
196
+ ```
197
+
198
+ <img src="./docs/images/pyramid.png" alt="Pyramid example" width="600">
199
+
67
200
  ## Documentation
68
201
 
69
202
  | Document | Description |
package/dist/buildPptx.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { calcYogaLayout } from "./calcYogaLayout/calcYogaLayout.js";
2
2
  import { setTextMeasurementMode, } from "./calcYogaLayout/measureText.js";
3
- import { parseXml } from "./parseXml.js";
3
+ import { parseXml } from "./parseXml/parseXml.js";
4
4
  import { renderPptx } from "./renderPptx/renderPptx.js";
5
5
  import { toPositioned } from "./toPositioned/toPositioned.js";
6
6
  export async function buildPptx(xml, slideSize, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"calcYogaLayout.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/calcYogaLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,aAAa,CAAC;AAcnE;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,OAAO,EACb,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,iBAiBpC"}
1
+ {"version":3,"file":"calcYogaLayout.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/calcYogaLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,aAAa,CAAC;AA+BnE;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,OAAO,EACb,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,iBAiBpC"}
@@ -1,8 +1,19 @@
1
1
  import { loadYoga } from "yoga-layout/load";
2
2
  import { measureText } from "./measureText.js";
3
- import { measureImage, prefetchImageSize } from "./measureImage.js";
4
- import { calcTableIntrinsicSize } from "../table/utils.js";
5
- import { measureProcessArrow, measureTimeline, measureMatrix, measureTree, measureFlow, } from "./measureCompositeNodes.js";
3
+ import { measureFontLineHeightRatio } from "./fontLoader.js";
4
+ import { measureImage, prefetchImageSize } from "../shared/measureImage.js";
5
+ import { calcTableIntrinsicSize } from "../shared/tableUtils.js";
6
+ import { measureProcessArrow, measureTimeline, measureMatrix, measureTree, measureFlow, measurePyramid, } from "./measureCompositeNodes.js";
7
+ /**
8
+ * コンポジットノードの最小スケール閾値。
9
+ * renderPptx/utils/scaleToFit.ts の MIN_SCALE_THRESHOLD と同じ値を維持すること。
10
+ */
11
+ const MIN_SCALE_THRESHOLD = 0.5;
12
+ /** 制約付きサイズを閾値でクランプする */
13
+ function constrainWithMinScale(intrinsicSize, availableSize) {
14
+ const minSize = intrinsicSize * MIN_SCALE_THRESHOLD;
15
+ return Math.max(minSize, Math.min(intrinsicSize, availableSize));
16
+ }
6
17
  /**
7
18
  * POMNode ツリーを Yoga でレイアウト計算する
8
19
  * POMNode ツリーの各ノードに yogaNode プロパティがセットされる
@@ -114,6 +125,7 @@ async function buildPomWithYogaTree(node, parentYoga, parentNode) {
114
125
  case "tree":
115
126
  case "flow":
116
127
  case "processArrow":
128
+ case "pyramid":
117
129
  case "line":
118
130
  // 子要素なし
119
131
  break;
@@ -232,6 +244,45 @@ async function applyStyleToYogaNode(node, yn) {
232
244
  });
233
245
  }
234
246
  break;
247
+ case "ul":
248
+ case "ol":
249
+ {
250
+ const combinedText = node.items.map((item) => item.text).join("\n");
251
+ const fontSizePx = node.fontPx ?? 24;
252
+ const fontFamily = "Noto Sans JP";
253
+ const fontWeight = node.bold ? "bold" : "normal";
254
+ const lineSpacingMultiple = node.lineSpacingMultiple ?? 1.3;
255
+ // PowerPoint の lineSpacingMultiple はフォントメトリクス(ascent + descent)に
256
+ // 対する倍率。fontSizePx × fontMetricsRatio × lineSpacingMultiple で計算する。
257
+ const fontMetricsRatio = measureFontLineHeightRatio(fontWeight);
258
+ const lineHeight = fontMetricsRatio * lineSpacingMultiple;
259
+ // バレット/番号のインデント幅(pptxgenjs DEF_BULLET_MARGIN = 27pt = 36px @96dpi)
260
+ const bulletIndentPx = 36;
261
+ yn.setMeasureFunc((width, widthMode) => {
262
+ const maxWidthPx = (() => {
263
+ switch (widthMode) {
264
+ case yoga.MEASURE_MODE_UNDEFINED:
265
+ return Number.POSITIVE_INFINITY;
266
+ case yoga.MEASURE_MODE_EXACTLY:
267
+ case yoga.MEASURE_MODE_AT_MOST:
268
+ return width;
269
+ }
270
+ })();
271
+ // バレットインデント分を除いたテキスト利用可能幅で計測
272
+ const textMaxWidthPx = Math.max(0, maxWidthPx - bulletIndentPx);
273
+ const { widthPx, heightPx } = measureText(combinedText, textMaxWidthPx, {
274
+ fontFamily,
275
+ fontSizePx,
276
+ lineHeight,
277
+ fontWeight,
278
+ });
279
+ return {
280
+ width: widthPx + bulletIndentPx,
281
+ height: heightPx,
282
+ };
283
+ });
284
+ }
285
+ break;
235
286
  case "image":
236
287
  {
237
288
  const src = node.src;
@@ -262,9 +313,9 @@ async function applyStyleToYogaNode(node, yn) {
262
313
  // テキストがある場合、テキストサイズを測定
263
314
  const text = node.text;
264
315
  const fontSizePx = node.fontPx ?? 24;
265
- const fontFamily = "Noto Sans JP";
316
+ const fontFamily = node.fontFamily ?? "Noto Sans JP";
266
317
  const fontWeight = node.bold ? "bold" : "normal";
267
- const lineHeight = 1.3;
318
+ const lineHeight = node.lineSpacingMultiple ?? 1.3;
268
319
  yn.setMeasureFunc((width, widthMode) => {
269
320
  const maxWidthPx = (() => {
270
321
  switch (widthMode) {
@@ -292,41 +343,91 @@ async function applyStyleToYogaNode(node, yn) {
292
343
  break;
293
344
  case "processArrow":
294
345
  {
295
- yn.setMeasureFunc(() => {
296
- const { width, height } = measureProcessArrow(node);
297
- return { width, height };
346
+ yn.setMeasureFunc((width, widthMode, height, heightMode) => {
347
+ const intrinsic = measureProcessArrow(node);
348
+ return {
349
+ width: widthMode !== yoga.MEASURE_MODE_UNDEFINED
350
+ ? constrainWithMinScale(intrinsic.width, width)
351
+ : intrinsic.width,
352
+ height: heightMode !== yoga.MEASURE_MODE_UNDEFINED
353
+ ? constrainWithMinScale(intrinsic.height, height)
354
+ : intrinsic.height,
355
+ };
356
+ });
357
+ }
358
+ break;
359
+ case "pyramid":
360
+ {
361
+ yn.setMeasureFunc((width, widthMode, height, heightMode) => {
362
+ const intrinsic = measurePyramid(node);
363
+ return {
364
+ width: widthMode !== yoga.MEASURE_MODE_UNDEFINED
365
+ ? constrainWithMinScale(intrinsic.width, width)
366
+ : intrinsic.width,
367
+ height: heightMode !== yoga.MEASURE_MODE_UNDEFINED
368
+ ? constrainWithMinScale(intrinsic.height, height)
369
+ : intrinsic.height,
370
+ };
298
371
  });
299
372
  }
300
373
  break;
301
374
  case "timeline":
302
375
  {
303
- yn.setMeasureFunc(() => {
304
- const { width, height } = measureTimeline(node);
305
- return { width, height };
376
+ yn.setMeasureFunc((width, widthMode, height, heightMode) => {
377
+ const intrinsic = measureTimeline(node);
378
+ return {
379
+ width: widthMode !== yoga.MEASURE_MODE_UNDEFINED
380
+ ? constrainWithMinScale(intrinsic.width, width)
381
+ : intrinsic.width,
382
+ height: heightMode !== yoga.MEASURE_MODE_UNDEFINED
383
+ ? constrainWithMinScale(intrinsic.height, height)
384
+ : intrinsic.height,
385
+ };
306
386
  });
307
387
  }
308
388
  break;
309
389
  case "matrix":
310
390
  {
311
- yn.setMeasureFunc(() => {
312
- const { width, height } = measureMatrix(node);
313
- return { width, height };
391
+ yn.setMeasureFunc((width, widthMode, height, heightMode) => {
392
+ const intrinsic = measureMatrix(node);
393
+ return {
394
+ width: widthMode !== yoga.MEASURE_MODE_UNDEFINED
395
+ ? constrainWithMinScale(intrinsic.width, width)
396
+ : intrinsic.width,
397
+ height: heightMode !== yoga.MEASURE_MODE_UNDEFINED
398
+ ? constrainWithMinScale(intrinsic.height, height)
399
+ : intrinsic.height,
400
+ };
314
401
  });
315
402
  }
316
403
  break;
317
404
  case "tree":
318
405
  {
319
- yn.setMeasureFunc(() => {
320
- const { width, height } = measureTree(node);
321
- return { width, height };
406
+ yn.setMeasureFunc((width, widthMode, height, heightMode) => {
407
+ const intrinsic = measureTree(node);
408
+ return {
409
+ width: widthMode !== yoga.MEASURE_MODE_UNDEFINED
410
+ ? constrainWithMinScale(intrinsic.width, width)
411
+ : intrinsic.width,
412
+ height: heightMode !== yoga.MEASURE_MODE_UNDEFINED
413
+ ? constrainWithMinScale(intrinsic.height, height)
414
+ : intrinsic.height,
415
+ };
322
416
  });
323
417
  }
324
418
  break;
325
419
  case "flow":
326
420
  {
327
- yn.setMeasureFunc(() => {
328
- const { width, height } = measureFlow(node);
329
- return { width, height };
421
+ yn.setMeasureFunc((width, widthMode, height, heightMode) => {
422
+ const intrinsic = measureFlow(node);
423
+ return {
424
+ width: widthMode !== yoga.MEASURE_MODE_UNDEFINED
425
+ ? constrainWithMinScale(intrinsic.width, width)
426
+ : intrinsic.width,
427
+ height: heightMode !== yoga.MEASURE_MODE_UNDEFINED
428
+ ? constrainWithMinScale(intrinsic.height, height)
429
+ : intrinsic.height,
430
+ };
330
431
  });
331
432
  }
332
433
  break;
@@ -10,4 +10,20 @@
10
10
  * @returns テキスト幅(ピクセル)
11
11
  */
12
12
  export declare function measureTextWidth(text: string, fontSizePx: number, weight: "normal" | "bold"): number;
13
+ /**
14
+ * フォントの自然な行高さ比率を取得する
15
+ *
16
+ * PowerPoint の lineSpacingMultiple はフォントサイズではなく、
17
+ * フォントメトリクス(ascent + descent)に対する倍率として適用される。
18
+ * この関数は fontSizePx に対する自然な行高さの比率を返す。
19
+ *
20
+ * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:
21
+ * sTypoAscender, sTypoDescender, sTypoLineGap を使用
22
+ * - 設定されていない場合:
23
+ * usWinAscent, usWinDescent を使用
24
+ *
25
+ * @param weight フォントウェイト
26
+ * @returns fontSizePx に対する行高さの比率(例: 1.448)
27
+ */
28
+ export declare function measureFontLineHeightRatio(weight: "normal" | "bold"): number;
13
29
  //# sourceMappingURL=fontLoader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fontLoader.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/fontLoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2DH;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,QAAQ,GAAG,MAAM,GACxB,MAAM,CAGR"}
1
+ {"version":3,"file":"fontLoader.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/fontLoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2DH;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,QAAQ,GAAG,MAAM,GACxB,MAAM,CAGR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAgB5E"}
@@ -57,3 +57,31 @@ export function measureTextWidth(text, fontSizePx, weight) {
57
57
  const font = getFont(weight);
58
58
  return font.getAdvanceWidth(text, fontSizePx, { kerning: true });
59
59
  }
60
+ /**
61
+ * フォントの自然な行高さ比率を取得する
62
+ *
63
+ * PowerPoint の lineSpacingMultiple はフォントサイズではなく、
64
+ * フォントメトリクス(ascent + descent)に対する倍率として適用される。
65
+ * この関数は fontSizePx に対する自然な行高さの比率を返す。
66
+ *
67
+ * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:
68
+ * sTypoAscender, sTypoDescender, sTypoLineGap を使用
69
+ * - 設定されていない場合:
70
+ * usWinAscent, usWinDescent を使用
71
+ *
72
+ * @param weight フォントウェイト
73
+ * @returns fontSizePx に対する行高さの比率(例: 1.448)
74
+ */
75
+ export function measureFontLineHeightRatio(weight) {
76
+ const font = getFont(weight);
77
+ const upm = font.unitsPerEm;
78
+ const os2 = font.tables?.os2;
79
+ if (!os2) {
80
+ return 1.0;
81
+ }
82
+ const useTypoMetrics = Boolean(os2.fsSelection & (1 << 7));
83
+ if (useTypoMetrics) {
84
+ return (os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap) / upm;
85
+ }
86
+ return (os2.usWinAscent + os2.usWinDescent) / upm;
87
+ }
@@ -1,4 +1,4 @@
1
- import type { ProcessArrowNode, TimelineNode, MatrixNode, TreeNode, FlowNode } from "../types.ts";
1
+ import type { ProcessArrowNode, TimelineNode, MatrixNode, TreeNode, FlowNode, PyramidNode } from "../types.ts";
2
2
  /**
3
3
  * ProcessArrow ノードの intrinsic size を計算する
4
4
  */
@@ -46,4 +46,16 @@ export declare function measureFlow(node: FlowNode): {
46
46
  width: number;
47
47
  height: number;
48
48
  };
49
+ /**
50
+ * Pyramid ノードの intrinsic size を計算する
51
+ *
52
+ * ピラミッドの描画には以下のデフォルト値が使用される:
53
+ * - baseWidth: 400px (最も広い層の幅)
54
+ * - layerHeight: 50px (各層の高さ)
55
+ * - gap: 2px (層間の隙間)
56
+ */
57
+ export declare function measurePyramid(node: PyramidNode): {
58
+ width: number;
59
+ height: number;
60
+ };
49
61
  //# sourceMappingURL=measureCompositeNodes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"measureCompositeNodes.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/measureCompositeNodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,QAAQ,EAET,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAsBA;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAoCA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAQA;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAuCA;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAsBA"}
1
+ {"version":3,"file":"measureCompositeNodes.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/measureCompositeNodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,WAAW,EAEZ,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAsBA;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAoCA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAQA;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAuCA;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAsBA;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAcA"}
@@ -141,3 +141,24 @@ export function measureFlow(node) {
141
141
  };
142
142
  }
143
143
  }
144
+ /**
145
+ * Pyramid ノードの intrinsic size を計算する
146
+ *
147
+ * ピラミッドの描画には以下のデフォルト値が使用される:
148
+ * - baseWidth: 400px (最も広い層の幅)
149
+ * - layerHeight: 50px (各層の高さ)
150
+ * - gap: 2px (層間の隙間)
151
+ */
152
+ export function measurePyramid(node) {
153
+ const levelCount = node.levels.length;
154
+ if (levelCount === 0) {
155
+ return { width: 0, height: 0 };
156
+ }
157
+ const baseWidth = 400;
158
+ const layerHeight = 50;
159
+ const gap = 2;
160
+ return {
161
+ width: baseWidth,
162
+ height: levelCount * layerHeight + (levelCount - 1) * gap,
163
+ };
164
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { buildPptx } from "./buildPptx.ts";
2
2
  export type { TextMeasurementMode } from "./buildPptx.ts";
3
- export { ParseXmlError } from "./parseXml.ts";
3
+ export { ParseXmlError } from "./parseXml/parseXml.ts";
4
4
  export type { SlideMasterOptions, SlideMasterBackground, SlideMasterMargin, MasterObject, MasterTextObject, MasterImageObject, MasterRectObject, MasterLineObject, SlideNumberOptions, } from "./types.ts";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { buildPptx } from "./buildPptx.js";
2
- export { ParseXmlError } from "./parseXml.js";
2
+ export { ParseXmlError } from "./parseXml/parseXml.js";