@hirokisakabe/pom 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 hirokisakabe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # pom
2
+
3
+ **pom (PowerPoint Object Model)** は、PowerPoint プレゼンテーション(pptx)を TypeScript で宣言的に記述するためのライブラリです。
4
+
5
+ ## 特徴
6
+
7
+ - **型安全**: TypeScript による厳密な型定義
8
+ - **宣言的**: JSON ライクなオブジェクトでスライドを記述
9
+ - **柔軟なレイアウト**: VStack/HStack/Box による自動レイアウト
10
+ - **ピクセル単位**: 直感的なピクセル単位での指定(内部でインチに変換)
11
+ - **AI フレンドリー**: LLM がコード生成しやすいシンプルな構造
12
+
13
+ ## ノード
14
+
15
+ ### 共通プロパティ
16
+
17
+ すべてのノードが共通して持てるレイアウト属性。
18
+
19
+ ```typescript
20
+ {
21
+ w?: number | "max" | `${number}%`;
22
+ h?: number | "max" | `${number}%`;
23
+ minW?: number;
24
+ maxW?: number;
25
+ minH?: number;
26
+ maxH?: number;
27
+ padding?: number;
28
+ backgroundColor?: string;
29
+ border?: {
30
+ color?: string;
31
+ width?: number;
32
+ dashType?: "solid" | "dash" | "dashDot" | "lgDash" | "lgDashDot" | "lgDashDotDot" | "sysDash" | "sysDot";
33
+ };
34
+ }
35
+ ```
36
+
37
+ - `backgroundColor` はノード全体に塗りつぶしを適用します(例: `"F8F9FA"`)。
38
+ - `border.width` は px 単位で指定し、色や `dashType` と組み合わせて枠線を制御できます。
39
+
40
+ ### ノード一覧
41
+
42
+ #### 1. Text
43
+
44
+ テキストを表示するノード。
45
+
46
+ ```typescript
47
+ {
48
+ type: "text";
49
+ text: string;
50
+ fontPx?: number;
51
+ alignText?: "left" | "center" | "right";
52
+
53
+ // 共通プロパティ
54
+ w?: number | "max" | `${number}%`;
55
+ h?: number | "max" | `${number}%`;
56
+ ...
57
+ }
58
+ ```
59
+
60
+ #### 2. Image
61
+
62
+ 画像を表示するノード。
63
+
64
+ - `w` と `h` を指定しない場合、画像の実際のサイズが自動的に取得されます
65
+ - サイズを指定した場合、そのサイズで表示されます(アスペクト比は保持されません)
66
+
67
+ ```typescript
68
+ {
69
+ type: "image";
70
+ src: string; // 画像のパス(ローカルパス、URL、base64データ)
71
+
72
+ // 共通プロパティ
73
+ w?: number | "max" | `${number}%`;
74
+ h?: number | "max" | `${number}%`;
75
+ ...
76
+ }
77
+ ```
78
+
79
+ #### 3. Table
80
+
81
+ 表を描画するノード。列幅・行高を px 単位で宣言し、セル単位で装飾を細かく制御できます。
82
+
83
+ ```typescript
84
+ {
85
+ type: "table";
86
+ columns: { width: number }[];
87
+ rows: {
88
+ height?: number;
89
+ cells: {
90
+ text: string;
91
+ fontPx?: number;
92
+ color?: string;
93
+ bold?: boolean;
94
+ alignText?: "left" | "center" | "right";
95
+ backgroundColor?: string;
96
+ }[];
97
+ }[];
98
+ defaultRowHeight?: number;
99
+
100
+ // 共通プロパティ
101
+ w?: number | "max" | `${number}%`;
102
+ h?: number | "max" | `${number}%`;
103
+ ...
104
+ }
105
+ ```
106
+
107
+ - `columns` の合計がテーブルの自然幅になります(必要であれば `w` で上書きできます)。
108
+ - `rows` の `height` を省略すると `defaultRowHeight`(未指定なら32px)が適用されます。
109
+ - セル背景やフォント装飾を `cells` の各要素で個別に指定できます。
110
+
111
+ #### 4. Box
112
+
113
+ 単一の子要素をラップする汎用コンテナ。
114
+
115
+ - 子要素は **1つ**
116
+ - padding や固定サイズを与えてグルーピングに使う
117
+
118
+ ```typescript
119
+ {
120
+ type: "box";
121
+ children: POMNode;
122
+
123
+ // 共通プロパティ
124
+ w?: number | "max" | `${number}%`;
125
+ h?: number | "max" | `${number}%`;
126
+ ...
127
+ }
128
+ ```
129
+
130
+ #### 5. VStack
131
+
132
+ 子要素を **縦方向** に並べる。
133
+
134
+ ```typescript
135
+ {
136
+ type: "vstack";
137
+ children: POMNode[];
138
+ alignItems: "start" | "center" | "end" | "stretch";
139
+ justifyContent: "start" | "center" | "end" | "spaceBetween";
140
+ gap?: number;
141
+
142
+ // 共通プロパティ
143
+ w?: number | "max" | `${number}%`;
144
+ h?: number | "max" | `${number}%`;
145
+ ...
146
+ }
147
+ ```
148
+
149
+ #### 6. HStack
150
+
151
+ 子要素を **横方向** に並べる。
152
+
153
+ ```typescript
154
+ {
155
+ type: "hstack";
156
+ children: POMNode[];
157
+ alignItems: "start" | "center" | "end" | "stretch";
158
+ justifyContent: "start" | "center" | "end" | "spaceBetween";
159
+ gap?: number;
160
+
161
+ // 共通プロパティ
162
+ w?: number | "max" | `${number}%`;
163
+ h?: number | "max" | `${number}%`;
164
+ ...
165
+ }
166
+ ```
167
+
168
+ #### 7. Shape
169
+
170
+ 図形を描画するノード。テキスト付き/なしで異なる表現が可能で、複雑なビジュアル効果をサポートしています。
171
+
172
+ ```typescript
173
+ {
174
+ type: "shape";
175
+ shapeType: PptxGenJS.SHAPE_NAME; // 例: "roundRect", "ellipse", "cloud", "star5" など
176
+ text?: string; // 図形内に表示するテキスト(オプション)
177
+ fill?: {
178
+ color?: string;
179
+ transparency?: number;
180
+ };
181
+ line?: {
182
+ color?: string;
183
+ width?: number;
184
+ dashType?: "solid" | "dash" | "dashDot" | "lgDash" | "lgDashDot" | "lgDashDotDot" | "sysDash" | "sysDot";
185
+ };
186
+ shadow?: {
187
+ type: "outer" | "inner";
188
+ opacity?: number;
189
+ blur?: number;
190
+ angle?: number;
191
+ offset?: number;
192
+ color?: string;
193
+ };
194
+ fontPx?: number;
195
+ fontColor?: string;
196
+ alignText?: "left" | "center" | "right";
197
+
198
+ // 共通プロパティ
199
+ w?: number | "max" | `${number}%`;
200
+ h?: number | "max" | `${number}%`;
201
+ ...
202
+ }
203
+ ```
204
+
205
+ **主な図形タイプの例:**
206
+
207
+ - `roundRect`: 角丸長方形(タイトルボックス、カテゴリ表示)
208
+ - `ellipse`: 楕円/円(ステップ番号、バッジ)
209
+ - `cloud`: 雲型(コメント、重要ポイント)
210
+ - `wedgeRectCallout`: 矢印付き吹き出し(注記)
211
+ - `cloudCallout`: 雲吹き出し(コメント)
212
+ - `star5`: 5つ星(強調、デコレーション)
213
+ - `downArrow`: 下矢印(フロー図)
@@ -0,0 +1,6 @@
1
+ import { POMNode } from "./types";
2
+ export declare function buildPptx(nodes: POMNode[], slideSize: {
3
+ w: number;
4
+ h: number;
5
+ }, fileName: string): Promise<void>;
6
+ //# sourceMappingURL=buildPptx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildPptx.d.ts","sourceRoot":"","sources":["../src/buildPptx.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAkB,MAAM,SAAS,CAAC;AAElD,wBAAsB,SAAS,CAC7B,KAAK,EAAE,OAAO,EAAE,EAChB,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACnC,QAAQ,EAAE,MAAM,iBAajB"}
@@ -0,0 +1,13 @@
1
+ import { calcYogaLayout } from "./calcYogaLayout/calcYogaLayout";
2
+ import { renderPptx } from "./renderPptx/renderPptx";
3
+ import { toPositioned } from "./toPositioned/toPositioned";
4
+ export async function buildPptx(nodes, slideSize, fileName) {
5
+ const positionedPages = [];
6
+ for (const node of nodes) {
7
+ await calcYogaLayout(node, slideSize);
8
+ const positioned = toPositioned(node);
9
+ positionedPages.push(positioned);
10
+ }
11
+ const pptx = renderPptx(positionedPages, slideSize);
12
+ await pptx.writeFile({ fileName });
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { POMNode } from "../types";
2
+ /**
3
+ * POMNode ツリーを Yoga でレイアウト計算する
4
+ * POMNode ツリーの各ノードに yogaNode プロパティがセットされる
5
+ *
6
+ * @param root 入力 POMNode ツリーのルート
7
+ * @param slideSize スライド全体のサイズ(px)
8
+ */
9
+ export declare function calcYogaLayout(root: POMNode, slideSize: {
10
+ w: number;
11
+ h: number;
12
+ }): Promise<void>;
13
+ //# sourceMappingURL=calcYogaLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calcYogaLayout.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/calcYogaLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAOxC;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,OAAO,EACb,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,iBAcpC"}
@@ -0,0 +1,314 @@
1
+ import { loadYoga } from "yoga-layout/load";
2
+ import { measureText } from "./measureText";
3
+ import { measureImage } from "./measureImage";
4
+ import { calcTableIntrinsicSize } from "../table/utils";
5
+ /**
6
+ * POMNode ツリーを Yoga でレイアウト計算する
7
+ * POMNode ツリーの各ノードに yogaNode プロパティがセットされる
8
+ *
9
+ * @param root 入力 POMNode ツリーのルート
10
+ * @param slideSize スライド全体のサイズ(px)
11
+ */
12
+ export async function calcYogaLayout(root, slideSize) {
13
+ const Yoga = await getYoga();
14
+ const rootYoga = Yoga.Node.create();
15
+ root.yogaNode = rootYoga;
16
+ await buildPomWithYogaTree(root, rootYoga);
17
+ // スライド全体サイズを指定
18
+ rootYoga.setWidth(slideSize.w);
19
+ rootYoga.setHeight(slideSize.h);
20
+ rootYoga.calculateLayout(slideSize.w, slideSize.h, Yoga.DIRECTION_LTR);
21
+ }
22
+ /**
23
+ * Yogaシングルトン
24
+ */
25
+ let yogaP = null;
26
+ async function getYoga() {
27
+ if (!yogaP)
28
+ yogaP = loadYoga();
29
+ return yogaP;
30
+ }
31
+ /**
32
+ * POMNode ツリーを再帰的に走査し、YogaNode ツリーを構築する
33
+ */
34
+ async function buildPomWithYogaTree(node, parentYoga) {
35
+ const yoga = await getYoga();
36
+ const yn = yoga.Node.create();
37
+ node.yogaNode = yn; // 対応する YogaNode をセット
38
+ await applyStyleToYogaNode(node, yn);
39
+ parentYoga.insertChild(yn, parentYoga.getChildCount());
40
+ switch (node.type) {
41
+ case "box": {
42
+ await buildPomWithYogaTree(node.children, yn);
43
+ break;
44
+ }
45
+ case "vstack":
46
+ case "hstack": {
47
+ for (const child of node.children) {
48
+ await buildPomWithYogaTree(child, yn);
49
+ }
50
+ break;
51
+ }
52
+ case "text":
53
+ case "image":
54
+ case "table":
55
+ case "shape":
56
+ // 子要素なし
57
+ break;
58
+ }
59
+ }
60
+ /**
61
+ * node のスタイルを YogaNode に適用する
62
+ */
63
+ async function applyStyleToYogaNode(node, yn) {
64
+ const yoga = await getYoga();
65
+ // デフォルト: 縦並び
66
+ yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);
67
+ // width
68
+ if (node.w !== undefined) {
69
+ if (typeof node.w === "number") {
70
+ yn.setWidth(node.w);
71
+ }
72
+ else if (node.w === "max") {
73
+ yn.setFlexGrow(1);
74
+ }
75
+ else if (node.w.endsWith("%")) {
76
+ const percent = parseFloat(node.w);
77
+ yn.setWidthPercent(percent);
78
+ }
79
+ }
80
+ // height
81
+ if (node.h !== undefined) {
82
+ if (typeof node.h === "number") {
83
+ yn.setHeight(node.h);
84
+ }
85
+ else if (node.h === "max") {
86
+ yn.setFlexGrow(1);
87
+ }
88
+ else if (node.h.endsWith("%")) {
89
+ const percent = parseFloat(node.h);
90
+ yn.setHeightPercent(percent);
91
+ }
92
+ }
93
+ // min/max constraints
94
+ if (node.minW !== undefined) {
95
+ yn.setMinWidth(node.minW);
96
+ }
97
+ if (node.maxW !== undefined) {
98
+ yn.setMaxWidth(node.maxW);
99
+ }
100
+ if (node.minH !== undefined) {
101
+ yn.setMinHeight(node.minH);
102
+ }
103
+ if (node.maxH !== undefined) {
104
+ yn.setMaxHeight(node.maxH);
105
+ }
106
+ // padding
107
+ if (node.padding !== undefined) {
108
+ if (typeof node.padding === "number") {
109
+ yn.setPadding(yoga.EDGE_TOP, node.padding);
110
+ yn.setPadding(yoga.EDGE_RIGHT, node.padding);
111
+ yn.setPadding(yoga.EDGE_BOTTOM, node.padding);
112
+ yn.setPadding(yoga.EDGE_LEFT, node.padding);
113
+ }
114
+ else {
115
+ if (node.padding.top !== undefined) {
116
+ yn.setPadding(yoga.EDGE_TOP, node.padding.top);
117
+ }
118
+ if (node.padding.right !== undefined) {
119
+ yn.setPadding(yoga.EDGE_RIGHT, node.padding.right);
120
+ }
121
+ if (node.padding.bottom !== undefined) {
122
+ yn.setPadding(yoga.EDGE_BOTTOM, node.padding.bottom);
123
+ }
124
+ if (node.padding.left !== undefined) {
125
+ yn.setPadding(yoga.EDGE_LEFT, node.padding.left);
126
+ }
127
+ }
128
+ }
129
+ switch (node.type) {
130
+ case "box":
131
+ // 特になし
132
+ break;
133
+ case "vstack": {
134
+ yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);
135
+ if (node.gap !== undefined) {
136
+ yn.setGap(yoga.GUTTER_ROW, node.gap);
137
+ yn.setGap(yoga.GUTTER_COLUMN, node.gap);
138
+ }
139
+ if (node.alignItems !== undefined) {
140
+ switch (node.alignItems) {
141
+ case "start":
142
+ yn.setAlignItems(yoga.ALIGN_FLEX_START);
143
+ break;
144
+ case "center":
145
+ yn.setAlignItems(yoga.ALIGN_CENTER);
146
+ break;
147
+ case "end":
148
+ yn.setAlignItems(yoga.ALIGN_FLEX_END);
149
+ break;
150
+ case "stretch":
151
+ yn.setAlignItems(yoga.ALIGN_STRETCH);
152
+ break;
153
+ }
154
+ }
155
+ if (node.justifyContent !== undefined) {
156
+ switch (node.justifyContent) {
157
+ case "start":
158
+ yn.setJustifyContent(yoga.JUSTIFY_FLEX_START);
159
+ break;
160
+ case "center":
161
+ yn.setJustifyContent(yoga.JUSTIFY_CENTER);
162
+ break;
163
+ case "end":
164
+ yn.setJustifyContent(yoga.JUSTIFY_FLEX_END);
165
+ break;
166
+ case "spaceBetween":
167
+ yn.setJustifyContent(yoga.JUSTIFY_SPACE_BETWEEN);
168
+ break;
169
+ case "spaceAround":
170
+ yn.setJustifyContent(yoga.JUSTIFY_SPACE_AROUND);
171
+ break;
172
+ case "spaceEvenly":
173
+ yn.setJustifyContent(yoga.JUSTIFY_SPACE_EVENLY);
174
+ break;
175
+ }
176
+ }
177
+ break;
178
+ }
179
+ case "hstack": {
180
+ yn.setFlexDirection(yoga.FLEX_DIRECTION_ROW);
181
+ if (node.gap !== undefined) {
182
+ yn.setGap(yoga.GUTTER_ROW, node.gap);
183
+ yn.setGap(yoga.GUTTER_COLUMN, node.gap);
184
+ }
185
+ if (node.alignItems !== undefined) {
186
+ switch (node.alignItems) {
187
+ case "start":
188
+ yn.setAlignItems(yoga.ALIGN_FLEX_START);
189
+ break;
190
+ case "center":
191
+ yn.setAlignItems(yoga.ALIGN_CENTER);
192
+ break;
193
+ case "end":
194
+ yn.setAlignItems(yoga.ALIGN_FLEX_END);
195
+ break;
196
+ case "stretch":
197
+ yn.setAlignItems(yoga.ALIGN_STRETCH);
198
+ break;
199
+ }
200
+ }
201
+ if (node.justifyContent !== undefined) {
202
+ switch (node.justifyContent) {
203
+ case "start":
204
+ yn.setJustifyContent(yoga.JUSTIFY_FLEX_START);
205
+ break;
206
+ case "center":
207
+ yn.setJustifyContent(yoga.JUSTIFY_CENTER);
208
+ break;
209
+ case "end":
210
+ yn.setJustifyContent(yoga.JUSTIFY_FLEX_END);
211
+ break;
212
+ case "spaceBetween":
213
+ yn.setJustifyContent(yoga.JUSTIFY_SPACE_BETWEEN);
214
+ break;
215
+ case "spaceAround":
216
+ yn.setJustifyContent(yoga.JUSTIFY_SPACE_AROUND);
217
+ break;
218
+ case "spaceEvenly":
219
+ yn.setJustifyContent(yoga.JUSTIFY_SPACE_EVENLY);
220
+ break;
221
+ }
222
+ }
223
+ break;
224
+ }
225
+ case "text":
226
+ {
227
+ const text = node.text;
228
+ const fontSizePx = node.fontPx ?? 24;
229
+ const fontFamily = "Noto Sans JP";
230
+ const fontWeight = "normal";
231
+ const lineHeight = 1.3;
232
+ yn.setMeasureFunc((width, widthMode) => {
233
+ const maxWidthPx = (() => {
234
+ switch (widthMode) {
235
+ case yoga.MEASURE_MODE_UNDEFINED:
236
+ return Number.POSITIVE_INFINITY;
237
+ case yoga.MEASURE_MODE_EXACTLY:
238
+ case yoga.MEASURE_MODE_AT_MOST:
239
+ return width;
240
+ }
241
+ })();
242
+ const { widthPx, heightPx } = measureText(text, maxWidthPx, {
243
+ fontFamily,
244
+ fontSizePx,
245
+ lineHeight,
246
+ fontWeight,
247
+ });
248
+ return {
249
+ width: widthPx,
250
+ height: heightPx,
251
+ };
252
+ });
253
+ }
254
+ break;
255
+ case "image":
256
+ {
257
+ const src = node.src;
258
+ yn.setMeasureFunc(() => {
259
+ // 画像の実際のサイズを取得
260
+ const { widthPx, heightPx } = measureImage(src);
261
+ return {
262
+ width: widthPx,
263
+ height: heightPx,
264
+ };
265
+ });
266
+ }
267
+ break;
268
+ case "table":
269
+ {
270
+ yn.setMeasureFunc(() => {
271
+ const { width, height } = calcTableIntrinsicSize(node);
272
+ return {
273
+ width,
274
+ height,
275
+ };
276
+ });
277
+ }
278
+ break;
279
+ case "shape":
280
+ {
281
+ if (node.text) {
282
+ // テキストがある場合、テキストサイズを測定
283
+ const text = node.text;
284
+ const fontSizePx = node.fontPx ?? 24;
285
+ const fontFamily = "Noto Sans JP";
286
+ const fontWeight = "normal";
287
+ const lineHeight = 1.3;
288
+ yn.setMeasureFunc((width, widthMode) => {
289
+ const maxWidthPx = (() => {
290
+ switch (widthMode) {
291
+ case yoga.MEASURE_MODE_UNDEFINED:
292
+ return Number.POSITIVE_INFINITY;
293
+ case yoga.MEASURE_MODE_EXACTLY:
294
+ case yoga.MEASURE_MODE_AT_MOST:
295
+ return width;
296
+ }
297
+ })();
298
+ const { widthPx, heightPx } = measureText(text, maxWidthPx, {
299
+ fontFamily,
300
+ fontSizePx,
301
+ lineHeight,
302
+ fontWeight,
303
+ });
304
+ return {
305
+ width: widthPx,
306
+ height: heightPx,
307
+ };
308
+ });
309
+ }
310
+ // テキストがない場合は、明示的にサイズが指定されていることを期待
311
+ }
312
+ break;
313
+ }
314
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 画像ファイルのサイズを取得する
3
+ * @param src 画像のパス(ローカルパス、またはbase64データ)
4
+ * @returns 画像の幅と高さ(px)
5
+ */
6
+ export declare function measureImage(src: string): {
7
+ widthPx: number;
8
+ heightPx: number;
9
+ };
10
+ //# sourceMappingURL=measureImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measureImage.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/measureImage.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,CA+BA"}
@@ -0,0 +1,36 @@
1
+ import imageSize from "image-size";
2
+ import * as fs from "fs";
3
+ /**
4
+ * 画像ファイルのサイズを取得する
5
+ * @param src 画像のパス(ローカルパス、またはbase64データ)
6
+ * @returns 画像の幅と高さ(px)
7
+ */
8
+ export function measureImage(src) {
9
+ try {
10
+ let buffer;
11
+ // base64データの場合
12
+ if (src.startsWith("data:")) {
13
+ const base64Data = src.split(",")[1];
14
+ buffer = new Uint8Array(Buffer.from(base64Data, "base64"));
15
+ }
16
+ // ローカルファイルパスの場合
17
+ else {
18
+ buffer = new Uint8Array(fs.readFileSync(src));
19
+ }
20
+ const dimensions = imageSize(buffer);
21
+ const width = dimensions.width ?? 100; // デフォルト100px
22
+ const height = dimensions.height ?? 100; // デフォルト100px
23
+ return {
24
+ widthPx: width,
25
+ heightPx: height,
26
+ };
27
+ }
28
+ catch (error) {
29
+ // エラーが発生した場合はデフォルトサイズを返す
30
+ console.warn(`Failed to measure image size for ${src}:`, error);
31
+ return {
32
+ widthPx: 100,
33
+ heightPx: 100,
34
+ };
35
+ }
36
+ }
@@ -0,0 +1,15 @@
1
+ type MeasureOptions = {
2
+ fontFamily: string;
3
+ fontSizePx: number;
4
+ fontWeight?: "normal" | "bold" | number;
5
+ lineHeight?: number;
6
+ };
7
+ /**
8
+ * テキストを折り返し付きでレイアウトし、そのサイズを測定する
9
+ */
10
+ export declare function measureText(text: string, maxWidthPx: number, opts: MeasureOptions): {
11
+ widthPx: number;
12
+ heightPx: number;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=measureText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measureText.d.ts","sourceRoot":"","sources":["../../src/calcYogaLayout/measureText.ts"],"names":[],"mappings":"AAEA,KAAK,cAAc,GAAG;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAKF;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,cAAc,GACnB;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAoCA"}
@@ -0,0 +1,59 @@
1
+ import { createCanvas } from "canvas";
2
+ const canvas = createCanvas(1, 1);
3
+ const ctx = canvas.getContext("2d");
4
+ /**
5
+ * テキストを折り返し付きでレイアウトし、そのサイズを測定する
6
+ */
7
+ export function measureText(text, maxWidthPx, opts) {
8
+ applyFontStyle(opts);
9
+ const words = splitForWrap(text);
10
+ const lines = [];
11
+ let current = "";
12
+ let currentWidth = 0;
13
+ for (const word of words) {
14
+ const candidate = current ? current + word : word;
15
+ const w = ctx.measureText(candidate).width;
16
+ if (w <= maxWidthPx || !current) {
17
+ // まだ詰められる
18
+ current = candidate;
19
+ currentWidth = w;
20
+ }
21
+ else {
22
+ // 折り返す
23
+ lines.push({ widthPx: currentWidth });
24
+ current = word;
25
+ currentWidth = ctx.measureText(word).width;
26
+ }
27
+ }
28
+ if (current) {
29
+ lines.push({ widthPx: currentWidth });
30
+ }
31
+ const lineHeightRatio = opts.lineHeight ?? 1.3;
32
+ const lineHeightPx = opts.fontSizePx * lineHeightRatio;
33
+ const widthPx = lines.length ? Math.max(...lines.map((l) => l.widthPx)) : 0;
34
+ const heightPx = lines.length * lineHeightPx;
35
+ // 端数切り上げ+余裕分 10px を足す
36
+ return { widthPx: widthPx + 10, heightPx };
37
+ }
38
+ function applyFontStyle(opts) {
39
+ const { fontFamily, fontSizePx, fontWeight = "normal" } = opts;
40
+ ctx.font = `${fontWeight} ${fontSizePx}px "${fontFamily}"`;
41
+ }
42
+ // ラップ用の分割ロジック
43
+ // - 英文: 空白で分割しつつ、空白も行末に残す
44
+ // - 日本語: とりあえず 1 文字ずつ(必要なら賢くする)
45
+ function splitForWrap(text) {
46
+ // 超雑実装:全角ひらがな・カタカナ・漢字が多そうなら 1 文字ずつ、それ以外は空白区切り
47
+ const hasCJK = /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u.test(text);
48
+ if (hasCJK) {
49
+ return Array.from(text); // 1 glyph ≒ 1 文字として扱う
50
+ }
51
+ // 英文用:単語 + 後続スペースをトークンにする
52
+ const tokens = [];
53
+ const re = /(\S+\s*|\s+)/g;
54
+ let m;
55
+ while ((m = re.exec(text))) {
56
+ tokens.push(m[0]);
57
+ }
58
+ return tokens;
59
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./types";
2
+ export { buildPptx } from "./buildPptx";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./types";
2
+ export { buildPptx } from "./buildPptx";
@@ -0,0 +1,17 @@
1
+ import type { PositionedNode } from "../types";
2
+ export declare const PX_PER_IN = 96;
3
+ export declare const pxToIn: (px: number) => number;
4
+ export declare const pxToPt: (px: number) => number;
5
+ type SlidePx = {
6
+ w: number;
7
+ h: number;
8
+ };
9
+ /**
10
+ * PositionedNode ツリーを PptxGenJS スライドに変換する
11
+ * @param pages PositionedNode ツリーの配列(各要素が1ページ)
12
+ * @param slidePx スライド全体のサイズ(px)
13
+ * @returns PptxGenJS インスタンス
14
+ */
15
+ export declare function renderPptx(pages: PositionedNode[], slidePx: SlidePx): any;
16
+ export {};
17
+ //# sourceMappingURL=renderPptx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderPptx.d.ts","sourceRoot":"","sources":["../../src/renderPptx/renderPptx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG/C,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,MAAM,GAAI,IAAI,MAAM,WAAmB,CAAC;AACrD,eAAO,MAAM,MAAM,GAAI,IAAI,MAAM,WAA0B,CAAC;AAE5D,KAAK,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,OAAO,OAoMnE"}
@@ -0,0 +1,179 @@
1
+ import PptxGenJS from "pptxgenjs";
2
+ import { resolveRowHeights } from "../table/utils";
3
+ export const PX_PER_IN = 96;
4
+ export const pxToIn = (px) => px / PX_PER_IN;
5
+ export const pxToPt = (px) => (px * 72) / PX_PER_IN;
6
+ /**
7
+ * PositionedNode ツリーを PptxGenJS スライドに変換する
8
+ * @param pages PositionedNode ツリーの配列(各要素が1ページ)
9
+ * @param slidePx スライド全体のサイズ(px)
10
+ * @returns PptxGenJS インスタンス
11
+ */
12
+ export function renderPptx(pages, slidePx) {
13
+ const slideIn = { w: pxToIn(slidePx.w), h: pxToIn(slidePx.h) }; // layout(=px) → PptxGenJS(=inch) への最終変換
14
+ // @ts-expect-error: PptxGenJS の型定義が不完全なため、一時的にエラーを無視
15
+ const pptx = new PptxGenJS.default();
16
+ pptx.defineLayout({ name: "custom", width: slideIn.w, height: slideIn.h });
17
+ pptx.layout = "custom";
18
+ for (const data of pages) {
19
+ const slide = pptx.addSlide();
20
+ function renderBackgroundAndBorder(node) {
21
+ const { backgroundColor, border } = node;
22
+ const hasBackground = Boolean(backgroundColor);
23
+ const hasBorder = Boolean(border &&
24
+ (border.color !== undefined ||
25
+ border.width !== undefined ||
26
+ border.dashType !== undefined));
27
+ if (!hasBackground && !hasBorder) {
28
+ return;
29
+ }
30
+ const fill = hasBackground
31
+ ? { color: backgroundColor }
32
+ : { type: "none" };
33
+ const line = hasBorder
34
+ ? {
35
+ color: border?.color ?? "000000",
36
+ width: border?.width !== undefined ? pxToPt(border.width) : undefined,
37
+ dashType: border?.dashType,
38
+ }
39
+ : { type: "none" };
40
+ const shapeOptions = {
41
+ x: pxToIn(node.x),
42
+ y: pxToIn(node.y),
43
+ w: pxToIn(node.w),
44
+ h: pxToIn(node.h),
45
+ fill,
46
+ line,
47
+ };
48
+ slide.addShape(pptx.ShapeType.rect, shapeOptions);
49
+ }
50
+ /**
51
+ * node をスライドにレンダリングする
52
+ */
53
+ function renderNode(node) {
54
+ renderBackgroundAndBorder(node);
55
+ switch (node.type) {
56
+ case "text": {
57
+ const fontSizePx = node.fontPx ?? 24;
58
+ const fontFamily = "Noto Sans JP";
59
+ const opts = {
60
+ x: pxToIn(node.x),
61
+ y: pxToIn(node.y),
62
+ w: pxToIn(node.w),
63
+ h: pxToIn(node.h),
64
+ fontSize: pxToPt(fontSizePx),
65
+ fontFace: fontFamily,
66
+ align: "left",
67
+ valign: "top",
68
+ margin: 0,
69
+ };
70
+ slide.addText(node.text ?? "", opts);
71
+ break;
72
+ }
73
+ case "image": {
74
+ slide.addImage({
75
+ path: node.src,
76
+ x: pxToIn(node.x),
77
+ y: pxToIn(node.y),
78
+ w: pxToIn(node.w),
79
+ h: pxToIn(node.h),
80
+ });
81
+ break;
82
+ }
83
+ case "box": {
84
+ // 子要素を再帰的に処理
85
+ renderNode(node.children);
86
+ break;
87
+ }
88
+ case "vstack":
89
+ case "hstack": {
90
+ // 子要素を再帰的に処理
91
+ for (const child of node.children) {
92
+ renderNode(child);
93
+ }
94
+ break;
95
+ }
96
+ case "table": {
97
+ const tableRows = node.rows.map((row) => row.cells.map((cell) => {
98
+ const cellOptions = {
99
+ fontSize: pxToPt(cell.fontPx ?? 18),
100
+ color: cell.color,
101
+ bold: cell.bold,
102
+ align: cell.alignText ?? "left",
103
+ fill: cell.backgroundColor
104
+ ? { color: cell.backgroundColor }
105
+ : undefined,
106
+ };
107
+ return {
108
+ text: cell.text,
109
+ options: cellOptions,
110
+ };
111
+ }));
112
+ const tableOptions = {
113
+ x: pxToIn(node.x),
114
+ y: pxToIn(node.y),
115
+ w: pxToIn(node.w),
116
+ h: pxToIn(node.h),
117
+ colW: node.columns.map((column) => pxToIn(column.width)),
118
+ rowH: resolveRowHeights(node).map((height) => pxToIn(height)),
119
+ margin: 0,
120
+ };
121
+ slide.addTable(tableRows, tableOptions);
122
+ break;
123
+ }
124
+ case "shape": {
125
+ const shapeOptions = {
126
+ x: pxToIn(node.x),
127
+ y: pxToIn(node.y),
128
+ w: pxToIn(node.w),
129
+ h: pxToIn(node.h),
130
+ fill: node.fill
131
+ ? {
132
+ color: node.fill.color,
133
+ transparency: node.fill.transparency,
134
+ }
135
+ : undefined,
136
+ line: node.line
137
+ ? {
138
+ color: node.line.color,
139
+ width: node.line.width !== undefined
140
+ ? pxToPt(node.line.width)
141
+ : undefined,
142
+ dashType: node.line.dashType,
143
+ }
144
+ : undefined,
145
+ shadow: node.shadow
146
+ ? {
147
+ type: node.shadow.type,
148
+ opacity: node.shadow.opacity,
149
+ blur: node.shadow.blur,
150
+ angle: node.shadow.angle,
151
+ offset: node.shadow.offset,
152
+ color: node.shadow.color,
153
+ }
154
+ : undefined,
155
+ };
156
+ if (node.text) {
157
+ // テキストがある場合:addTextでshapeを指定
158
+ slide.addText(node.text, {
159
+ ...shapeOptions,
160
+ shape: node.shapeType,
161
+ fontSize: pxToPt(node.fontPx ?? 24),
162
+ fontFace: "Noto Sans JP",
163
+ color: node.fontColor,
164
+ align: node.alignText ?? "center",
165
+ valign: "middle",
166
+ });
167
+ }
168
+ else {
169
+ // テキストがない場合:addShapeを使用
170
+ slide.addShape(node.shapeType, shapeOptions);
171
+ }
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ renderNode(data);
177
+ }
178
+ return pptx;
179
+ }
@@ -0,0 +1,8 @@
1
+ import type { TableNode } from "../types";
2
+ export declare const DEFAULT_TABLE_ROW_HEIGHT = 32;
3
+ export declare function calcTableIntrinsicSize(node: TableNode): {
4
+ width: number;
5
+ height: number;
6
+ };
7
+ export declare function resolveRowHeights(node: TableNode): number[];
8
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/table/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS;;;EAKrD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,YAGhD"}
@@ -0,0 +1,10 @@
1
+ export const DEFAULT_TABLE_ROW_HEIGHT = 32;
2
+ export function calcTableIntrinsicSize(node) {
3
+ const width = node.columns.reduce((sum, column) => sum + column.width, 0);
4
+ const height = resolveRowHeights(node).reduce((sum, h) => sum + h, 0);
5
+ return { width, height };
6
+ }
7
+ export function resolveRowHeights(node) {
8
+ const fallbackRowHeight = node.defaultRowHeight ?? DEFAULT_TABLE_ROW_HEIGHT;
9
+ return node.rows.map((row) => row.height ?? fallbackRowHeight);
10
+ }
@@ -0,0 +1,10 @@
1
+ import type { POMNode, PositionedNode } from "../types";
2
+ /**
3
+ * POMNode ツリーを絶対座標付きの PositionedNode ツリーに変換する
4
+ * @param pom 入力 POMNode
5
+ * @param parentX 親ノードの絶対X座標
6
+ * @param parentY 親ノードの絶対Y座標
7
+ * @returns PositionedNode ツリー
8
+ */
9
+ export declare function toPositioned(pom: POMNode, parentX?: number, parentY?: number): PositionedNode;
10
+ //# sourceMappingURL=toPositioned.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toPositioned.d.ts","sourceRoot":"","sources":["../../src/toPositioned/toPositioned.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,OAAO,SAAI,EACX,OAAO,SAAI,GACV,cAAc,CAiFhB"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * POMNode ツリーを絶対座標付きの PositionedNode ツリーに変換する
3
+ * @param pom 入力 POMNode
4
+ * @param parentX 親ノードの絶対X座標
5
+ * @param parentY 親ノードの絶対Y座標
6
+ * @returns PositionedNode ツリー
7
+ */
8
+ export function toPositioned(pom, parentX = 0, parentY = 0) {
9
+ if (!pom.yogaNode) {
10
+ throw new Error("yogaNode not set on POMNode");
11
+ }
12
+ const layout = pom.yogaNode.getComputedLayout();
13
+ const absoluteX = parentX + layout.left;
14
+ const absoluteY = parentY + layout.top;
15
+ switch (pom.type) {
16
+ case "text": {
17
+ return {
18
+ ...pom,
19
+ x: absoluteX,
20
+ y: absoluteY,
21
+ w: layout.width,
22
+ h: layout.height,
23
+ };
24
+ }
25
+ case "image": {
26
+ return {
27
+ ...pom,
28
+ x: absoluteX,
29
+ y: absoluteY,
30
+ w: layout.width,
31
+ h: layout.height,
32
+ };
33
+ }
34
+ case "table": {
35
+ return {
36
+ ...pom,
37
+ x: absoluteX,
38
+ y: absoluteY,
39
+ w: layout.width,
40
+ h: layout.height,
41
+ };
42
+ }
43
+ case "shape": {
44
+ return {
45
+ ...pom,
46
+ x: absoluteX,
47
+ y: absoluteY,
48
+ w: layout.width,
49
+ h: layout.height,
50
+ };
51
+ }
52
+ case "box": {
53
+ return {
54
+ ...pom,
55
+ x: absoluteX,
56
+ y: absoluteY,
57
+ w: layout.width,
58
+ h: layout.height,
59
+ children: toPositioned(pom.children, absoluteX, absoluteY),
60
+ };
61
+ }
62
+ case "vstack": {
63
+ return {
64
+ ...pom,
65
+ x: absoluteX,
66
+ y: absoluteY,
67
+ w: layout.width,
68
+ h: layout.height,
69
+ children: pom.children.map((child) => toPositioned(child, absoluteX, absoluteY)),
70
+ };
71
+ }
72
+ case "hstack": {
73
+ return {
74
+ ...pom,
75
+ x: absoluteX,
76
+ y: absoluteY,
77
+ w: layout.width,
78
+ h: layout.height,
79
+ children: pom.children.map((child) => toPositioned(child, absoluteX, absoluteY)),
80
+ };
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,118 @@
1
+ import type { Node as YogaNode } from "yoga-layout";
2
+ import type PptxGenJS from "pptxgenjs";
3
+ export type Length = number | "max" | `${number}%`;
4
+ export type Padding = number | {
5
+ top?: number;
6
+ right?: number;
7
+ bottom?: number;
8
+ left?: number;
9
+ };
10
+ export type BorderDash = "solid" | "dash" | "dashDot" | "lgDash" | "lgDashDot" | "lgDashDotDot" | "sysDash" | "sysDot";
11
+ export type BorderStyle = {
12
+ color?: string;
13
+ width?: number;
14
+ dashType?: BorderDash;
15
+ };
16
+ export type FillStyle = {
17
+ color?: string;
18
+ transparency?: number;
19
+ };
20
+ export type ShadowStyle = {
21
+ type?: "outer" | "inner";
22
+ opacity?: number;
23
+ blur?: number;
24
+ angle?: number;
25
+ offset?: number;
26
+ color?: string;
27
+ };
28
+ export type AlignItems = "start" | "center" | "end" | "stretch";
29
+ export type JustifyContent = "start" | "center" | "end" | "spaceBetween" | "spaceAround" | "spaceEvenly";
30
+ export type FlexDirection = "row" | "column";
31
+ type BasePOMNode = {
32
+ yogaNode?: YogaNode;
33
+ w?: Length;
34
+ h?: Length;
35
+ minW?: number;
36
+ maxW?: number;
37
+ minH?: number;
38
+ maxH?: number;
39
+ padding?: Padding;
40
+ backgroundColor?: string;
41
+ border?: BorderStyle;
42
+ };
43
+ export type TextNode = BasePOMNode & {
44
+ type: "text";
45
+ text: string;
46
+ fontPx?: number;
47
+ alignText?: "left" | "center" | "right";
48
+ };
49
+ export type ImageNode = BasePOMNode & {
50
+ type: "image";
51
+ src: string;
52
+ };
53
+ export type TableCell = {
54
+ text: string;
55
+ fontPx?: number;
56
+ color?: string;
57
+ bold?: boolean;
58
+ alignText?: "left" | "center" | "right";
59
+ backgroundColor?: string;
60
+ };
61
+ export type TableRow = {
62
+ cells: TableCell[];
63
+ height?: number;
64
+ };
65
+ export type TableColumn = {
66
+ width: number;
67
+ };
68
+ export type TableNode = BasePOMNode & {
69
+ type: "table";
70
+ columns: TableColumn[];
71
+ rows: TableRow[];
72
+ defaultRowHeight?: number;
73
+ };
74
+ export type BoxNode = BasePOMNode & {
75
+ type: "box";
76
+ children: POMNode;
77
+ };
78
+ export type VStackNode = BasePOMNode & {
79
+ type: "vstack";
80
+ children: POMNode[];
81
+ gap?: number;
82
+ alignItems?: AlignItems;
83
+ justifyContent?: JustifyContent;
84
+ };
85
+ export type HStackNode = BasePOMNode & {
86
+ type: "hstack";
87
+ children: POMNode[];
88
+ gap?: number;
89
+ alignItems?: AlignItems;
90
+ justifyContent?: JustifyContent;
91
+ };
92
+ export type ShapeNode = BasePOMNode & {
93
+ type: "shape";
94
+ shapeType: PptxGenJS.SHAPE_NAME;
95
+ text?: string;
96
+ fill?: FillStyle;
97
+ line?: BorderStyle;
98
+ shadow?: ShadowStyle;
99
+ fontPx?: number;
100
+ fontColor?: string;
101
+ alignText?: "left" | "center" | "right";
102
+ };
103
+ export type POMNode = TextNode | ImageNode | TableNode | BoxNode | VStackNode | HStackNode | ShapeNode;
104
+ type PositionedBase = {
105
+ x: number;
106
+ y: number;
107
+ w: number;
108
+ h: number;
109
+ };
110
+ export type PositionedNode = (TextNode & PositionedBase) | (ImageNode & PositionedBase) | (TableNode & PositionedBase) | (BoxNode & PositionedBase & {
111
+ children: PositionedNode;
112
+ }) | (VStackNode & PositionedBase & {
113
+ children: PositionedNode[];
114
+ }) | (HStackNode & PositionedBase & {
115
+ children: PositionedNode[];
116
+ }) | (ShapeNode & PositionedBase);
117
+ export {};
118
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@hirokisakabe/pom",
3
+ "version": "0.1.0",
4
+ "description": "PowerPoint Object Model - A declarative TypeScript library for creating PowerPoint presentations",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "homepage": "https://github.com/hirokisakabe/pom#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/hirokisakabe/pom/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/hirokisakabe/pom.git"
26
+ },
27
+ "license": "MIT",
28
+ "author": "Hiroki Sakabe",
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "prepublishOnly": "npm run build && npm run lint && npm run fmt:check && npm run typecheck && npm run test:run",
32
+ "fmt": "prettier --write .",
33
+ "fmt:check": "prettier --check .",
34
+ "lint": "eslint",
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "vitest",
37
+ "test:ui": "vitest --ui",
38
+ "test:run": "vitest run"
39
+ },
40
+ "devDependencies": {
41
+ "@eslint/js": "^9.39.1",
42
+ "@types/image-size": "0.7.0",
43
+ "@vitest/ui": "^4.0.8",
44
+ "eslint": "^9.39.1",
45
+ "globals": "^16.5.0",
46
+ "jiti": "2.6.1",
47
+ "prettier": "3.6.2",
48
+ "typescript": "5.9.3",
49
+ "typescript-eslint": "^8.47.0",
50
+ "vitest": "^4.0.8"
51
+ },
52
+ "dependencies": {
53
+ "canvas": "3.2.0",
54
+ "image-size": "2.0.2",
55
+ "pptxgenjs": "4.0.1",
56
+ "yoga-layout": "3.2.1"
57
+ }
58
+ }