@hirokisakabe/pom 1.1.3 → 1.3.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 (35) hide show
  1. package/README.md +130 -15
  2. package/dist/calcYogaLayout/calcYogaLayout.js +8 -1
  3. package/dist/component.d.ts +90 -0
  4. package/dist/component.d.ts.map +1 -0
  5. package/dist/component.js +123 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/inputSchema.d.ts +300 -4
  10. package/dist/inputSchema.d.ts.map +1 -1
  11. package/dist/inputSchema.js +31 -1
  12. package/dist/renderPptx/nodes/image.d.ts.map +1 -1
  13. package/dist/renderPptx/nodes/image.js +19 -0
  14. package/dist/renderPptx/nodes/processArrow.d.ts.map +1 -1
  15. package/dist/renderPptx/nodes/processArrow.js +9 -0
  16. package/dist/renderPptx/nodes/shape.d.ts.map +1 -1
  17. package/dist/renderPptx/nodes/shape.js +6 -0
  18. package/dist/renderPptx/nodes/table.d.ts.map +1 -1
  19. package/dist/renderPptx/nodes/table.js +5 -0
  20. package/dist/renderPptx/renderPptx.d.ts +1 -1
  21. package/dist/renderPptx/renderPptx.d.ts.map +1 -1
  22. package/dist/renderPptx/renderPptx.js +31 -5
  23. package/dist/renderPptx/textOptions.d.ts +26 -1
  24. package/dist/renderPptx/textOptions.d.ts.map +1 -1
  25. package/dist/renderPptx/textOptions.js +27 -0
  26. package/dist/renderPptx/utils/backgroundBorder.d.ts +3 -1
  27. package/dist/renderPptx/utils/backgroundBorder.d.ts.map +1 -1
  28. package/dist/renderPptx/utils/backgroundBorder.js +103 -23
  29. package/dist/renderPptx/utils/textDrawing.d.ts +5 -0
  30. package/dist/renderPptx/utils/textDrawing.d.ts.map +1 -1
  31. package/dist/renderPptx/utils/textDrawing.js +6 -1
  32. package/dist/types.d.ts +401 -7
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/types.js +63 -0
  35. package/package.json +1 -1
package/README.md CHANGED
@@ -60,24 +60,138 @@ await pptx.writeFile({ fileName: "presentation.pptx" });
60
60
 
61
61
  ## Available Nodes
62
62
 
63
- | Node | Description |
64
- | ------------ | ---------------------------------------------- |
65
- | text | Text with font styling and bullet points |
66
- | image | Images from file path, URL, or base64 |
67
- | table | Tables with customizable columns and rows |
68
- | shape | PowerPoint shapes (roundRect, ellipse, etc.) |
69
- | chart | Charts (bar, line, pie, area, doughnut, radar) |
70
- | timeline | Timeline/roadmap visualizations |
71
- | matrix | 2x2 positioning maps |
72
- | tree | Organization charts and decision trees |
73
- | flow | Flowcharts with nodes and edges |
74
- | processArrow | Chevron-style process diagrams |
75
- | box | Container for single child with padding |
76
- | vstack | Vertical stack layout |
77
- | hstack | Horizontal stack layout |
63
+ | Node | Description |
64
+ | ------------ | ----------------------------------------------------- |
65
+ | text | Text with font styling, decoration, and bullet points |
66
+ | image | Images from file path, URL, or base64 |
67
+ | table | Tables with customizable columns and rows |
68
+ | shape | PowerPoint shapes (roundRect, ellipse, etc.) |
69
+ | chart | Charts (bar, line, pie, area, doughnut, radar) |
70
+ | timeline | Timeline/roadmap visualizations |
71
+ | matrix | 2x2 positioning maps |
72
+ | tree | Organization charts and decision trees |
73
+ | flow | Flowcharts with nodes and edges |
74
+ | processArrow | Chevron-style process diagrams |
75
+ | box | Container for single child with padding |
76
+ | vstack | Vertical stack layout |
77
+ | hstack | Horizontal stack layout |
78
78
 
79
79
  For detailed node documentation, see [Nodes Reference](./docs/nodes.md).
80
80
 
81
+ ## Components
82
+
83
+ pom provides a `defineComponent` helper for creating reusable component templates. Components are simple functions that take props and return a `POMNode` — no runtime magic, no new node types.
84
+
85
+ ```typescript
86
+ import { defineComponent, mergeTheme, POMNode, Theme } from "@hirokisakabe/pom";
87
+
88
+ // Define a reusable card component
89
+ const SectionCard = defineComponent<{
90
+ title: string;
91
+ content: POMNode; // Slot: any POMNode
92
+ theme?: Partial<Theme>; // Theme override
93
+ }>((props) => {
94
+ const t = mergeTheme(props.theme);
95
+ return {
96
+ type: "box",
97
+ padding: t.spacing.md,
98
+ backgroundColor: "FFFFFF",
99
+ border: { color: t.colors.border, width: 1 },
100
+ borderRadius: 8,
101
+ children: {
102
+ type: "vstack",
103
+ gap: t.spacing.sm,
104
+ children: [
105
+ {
106
+ type: "text",
107
+ text: props.title,
108
+ fontPx: t.fontPx.heading,
109
+ bold: true,
110
+ },
111
+ props.content,
112
+ ],
113
+ },
114
+ };
115
+ });
116
+
117
+ // Use the component
118
+ const slide: POMNode = {
119
+ type: "vstack",
120
+ w: 1280,
121
+ h: 720,
122
+ padding: 48,
123
+ gap: 24,
124
+ children: [
125
+ SectionCard({
126
+ title: "Revenue",
127
+ content: { type: "text", text: "$1,000,000" },
128
+ }),
129
+ SectionCard({
130
+ title: "Custom Theme",
131
+ content: { type: "text", text: "Styled card" },
132
+ theme: { colors: { border: "CBD5E1" } },
133
+ }),
134
+ ],
135
+ };
136
+ ```
137
+
138
+ Components support:
139
+
140
+ - **Slots**: Pass `POMNode` or `POMNode[]` as props for content injection
141
+ - **Theme**: Use `mergeTheme()` for colors, spacing, and font size overrides
142
+ - **Nesting**: Components can call other components
143
+ - **JSON / LLM**: Use `expandComponents()` to resolve component references in JSON
144
+
145
+ ### Using Components with LLM-generated JSON
146
+
147
+ When an LLM outputs JSON containing `{ type: "component", name: "...", props: {...} }`, use `expandComponentSlides()` to resolve them before building:
148
+
149
+ ```typescript
150
+ import {
151
+ buildPptx,
152
+ defineComponent,
153
+ expandComponentSlides,
154
+ } from "@hirokisakabe/pom";
155
+
156
+ // Register components
157
+ const SectionCard = defineComponent<{ title: string; content: unknown }>(
158
+ (props) => ({
159
+ type: "box",
160
+ padding: 16,
161
+ children: {
162
+ type: "vstack",
163
+ gap: 8,
164
+ children: [
165
+ { type: "text", text: props.title, bold: true },
166
+ props.content,
167
+ ],
168
+ },
169
+ }),
170
+ );
171
+
172
+ const registry = { SectionCard };
173
+
174
+ // LLM output (JSON with component references)
175
+ const llmOutput = [
176
+ {
177
+ type: "vstack",
178
+ w: 1280,
179
+ h: 720,
180
+ children: [
181
+ {
182
+ type: "component",
183
+ name: "SectionCard",
184
+ props: { title: "KPI", content: { type: "text", text: "$1M" } },
185
+ },
186
+ ],
187
+ },
188
+ ];
189
+
190
+ // Expand → Build
191
+ const slides = expandComponentSlides(llmOutput, registry);
192
+ const pptx = await buildPptx(slides, { w: 1280, h: 720 });
193
+ ```
194
+
81
195
  ## Documentation
82
196
 
83
197
  | Document | Description |
@@ -86,6 +200,7 @@ For detailed node documentation, see [Nodes Reference](./docs/nodes.md).
86
200
  | [Master Slide](./docs/master-slide.md) | Headers, footers, and page numbers |
87
201
  | [Serverless Environments](./docs/serverless.md) | Text measurement options for serverless |
88
202
  | [LLM Integration](./docs/llm-integration.md) | Guide for generating slides with AI/LLM |
203
+ | [Components](./docs/nodes.md#components) | Reusable component templates |
89
204
 
90
205
  ## License
91
206
 
@@ -35,6 +35,10 @@ async function prefetchAllImageSizes(node) {
35
35
  function collectImageSources(node) {
36
36
  const sources = [];
37
37
  function traverse(n) {
38
+ // backgroundImage の src を収集(全ノード共通)
39
+ if (n.backgroundImage) {
40
+ sources.push(n.backgroundImage.src);
41
+ }
38
42
  if (n.type === "image") {
39
43
  sources.push(n.src);
40
44
  }
@@ -73,7 +77,10 @@ async function buildPomWithYogaTree(node, parentYoga, parentNode) {
73
77
  node.yogaNode = yn; // 対応する YogaNode をセット
74
78
  await applyStyleToYogaNode(node, yn);
75
79
  // HStack の子要素で幅が指定されていない場合、デフォルトで均等分割
76
- if (parentNode?.type === "hstack" && node.w === undefined) {
80
+ // テーブルは setMeasureFunc でカラム幅合計を返すため除外
81
+ if (parentNode?.type === "hstack" &&
82
+ node.w === undefined &&
83
+ node.type !== "table") {
77
84
  yn.setFlexGrow(1);
78
85
  yn.setFlexBasis(0);
79
86
  }
@@ -0,0 +1,90 @@
1
+ import type { POMNode } from "./types.ts";
2
+ /**
3
+ * 再利用可能なコンポーネントを定義する。
4
+ * Props を受け取り POMNode を返す関数を作成する。
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const SectionCard = defineComponent<{
9
+ * title: string;
10
+ * content: POMNode;
11
+ * color?: string;
12
+ * }>((props) => ({
13
+ * type: "box",
14
+ * padding: 20,
15
+ * backgroundColor: "FFFFFF",
16
+ * border: { color: "E2E8F0", width: 1 },
17
+ * borderRadius: 8,
18
+ * children: {
19
+ * type: "vstack",
20
+ * gap: 12,
21
+ * children: [
22
+ * {
23
+ * type: "text",
24
+ * text: props.title,
25
+ * fontPx: 18,
26
+ * bold: true,
27
+ * color: props.color ?? "334155",
28
+ * },
29
+ * props.content,
30
+ * ],
31
+ * },
32
+ * }));
33
+ *
34
+ * // Usage:
35
+ * const node = SectionCard({
36
+ * title: "KPI Summary",
37
+ * content: { type: "text", text: "Revenue: $1M" },
38
+ * });
39
+ * ```
40
+ */
41
+ export declare function defineComponent<Props>(render: (props: Props) => POMNode): (props: Props) => POMNode;
42
+ /**
43
+ * テーマ設定の型。コンポーネントにスタイルの一括オーバーライドを提供する。
44
+ */
45
+ export type Theme = {
46
+ colors?: {
47
+ primary?: string;
48
+ secondary?: string;
49
+ background?: string;
50
+ text?: string;
51
+ border?: string;
52
+ accent?: string;
53
+ };
54
+ spacing?: {
55
+ xs?: number;
56
+ sm?: number;
57
+ md?: number;
58
+ lg?: number;
59
+ xl?: number;
60
+ };
61
+ fontPx?: {
62
+ title?: number;
63
+ heading?: number;
64
+ body?: number;
65
+ caption?: number;
66
+ };
67
+ };
68
+ type RequiredTheme = {
69
+ colors: Required<NonNullable<Theme["colors"]>>;
70
+ spacing: Required<NonNullable<Theme["spacing"]>>;
71
+ fontPx: Required<NonNullable<Theme["fontPx"]>>;
72
+ };
73
+ export declare const defaultTheme: RequiredTheme;
74
+ /**
75
+ * デフォルトテーマとユーザー指定テーマをマージする。
76
+ */
77
+ export declare function mergeTheme(theme?: Partial<Theme>): RequiredTheme;
78
+ export type ComponentRegistry = Record<string, (props: any) => POMNode>;
79
+ /**
80
+ * JSON 入力内のコンポーネントノードを展開して POMNode を返す。
81
+ * LLM が出力した JSON に `{ type: "component", name: "...", props: {...} }` が
82
+ * 含まれている場合、レジストリからコンポーネント関数を取得して展開する。
83
+ */
84
+ export declare function expandComponents(input: unknown, registry: ComponentRegistry): POMNode;
85
+ /**
86
+ * 複数スライドの JSON 入力内のコンポーネントノードを展開する。
87
+ */
88
+ export declare function expandComponentSlides(inputs: unknown[], registry: ComponentRegistry): POMNode[];
89
+ export {};
90
+ //# sourceMappingURL=component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,eAAe,CAAC,KAAK,EACnC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,GAChC,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAE3B;AAED;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,CAAC;IACF,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC/C,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;CAChD,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,aAsB1B,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,aAAa,CAMhE;AAGD,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC;AA0CxE;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAET;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,EAAE,EACjB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,EAAE,CAEX"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 再利用可能なコンポーネントを定義する。
3
+ * Props を受け取り POMNode を返す関数を作成する。
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const SectionCard = defineComponent<{
8
+ * title: string;
9
+ * content: POMNode;
10
+ * color?: string;
11
+ * }>((props) => ({
12
+ * type: "box",
13
+ * padding: 20,
14
+ * backgroundColor: "FFFFFF",
15
+ * border: { color: "E2E8F0", width: 1 },
16
+ * borderRadius: 8,
17
+ * children: {
18
+ * type: "vstack",
19
+ * gap: 12,
20
+ * children: [
21
+ * {
22
+ * type: "text",
23
+ * text: props.title,
24
+ * fontPx: 18,
25
+ * bold: true,
26
+ * color: props.color ?? "334155",
27
+ * },
28
+ * props.content,
29
+ * ],
30
+ * },
31
+ * }));
32
+ *
33
+ * // Usage:
34
+ * const node = SectionCard({
35
+ * title: "KPI Summary",
36
+ * content: { type: "text", text: "Revenue: $1M" },
37
+ * });
38
+ * ```
39
+ */
40
+ export function defineComponent(render) {
41
+ return render;
42
+ }
43
+ export const defaultTheme = {
44
+ colors: {
45
+ primary: "1D4ED8",
46
+ secondary: "64748B",
47
+ background: "F8FAFC",
48
+ text: "1E293B",
49
+ border: "E2E8F0",
50
+ accent: "0EA5E9",
51
+ },
52
+ spacing: {
53
+ xs: 4,
54
+ sm: 8,
55
+ md: 16,
56
+ lg: 24,
57
+ xl: 48,
58
+ },
59
+ fontPx: {
60
+ title: 32,
61
+ heading: 20,
62
+ body: 14,
63
+ caption: 11,
64
+ },
65
+ };
66
+ /**
67
+ * デフォルトテーマとユーザー指定テーマをマージする。
68
+ */
69
+ export function mergeTheme(theme) {
70
+ return {
71
+ colors: { ...defaultTheme.colors, ...theme?.colors },
72
+ spacing: { ...defaultTheme.spacing, ...theme?.spacing },
73
+ fontPx: { ...defaultTheme.fontPx, ...theme?.fontPx },
74
+ };
75
+ }
76
+ function isRecord(value) {
77
+ return typeof value === "object" && value !== null && !Array.isArray(value);
78
+ }
79
+ function expandNode(input, registry) {
80
+ if (Array.isArray(input)) {
81
+ return input.map((item) => expandNode(item, registry));
82
+ }
83
+ if (!isRecord(input)) {
84
+ return input;
85
+ }
86
+ if (input.type === "component") {
87
+ const name = input.name;
88
+ const fn = registry[name];
89
+ if (!fn) {
90
+ throw new Error(`Unknown component: "${name}"`);
91
+ }
92
+ const rawProps = isRecord(input.props) ? input.props : {};
93
+ const expandedProps = Object.fromEntries(Object.entries(rawProps).map(([key, value]) => [
94
+ key,
95
+ expandNode(value, registry),
96
+ ]));
97
+ return expandNode(fn(expandedProps), registry);
98
+ }
99
+ const result = {};
100
+ for (const [key, value] of Object.entries(input)) {
101
+ if (key === "children") {
102
+ result[key] = expandNode(value, registry);
103
+ }
104
+ else {
105
+ result[key] = value;
106
+ }
107
+ }
108
+ return result;
109
+ }
110
+ /**
111
+ * JSON 入力内のコンポーネントノードを展開して POMNode を返す。
112
+ * LLM が出力した JSON に `{ type: "component", name: "...", props: {...} }` が
113
+ * 含まれている場合、レジストリからコンポーネント関数を取得して展開する。
114
+ */
115
+ export function expandComponents(input, registry) {
116
+ return expandNode(input, registry);
117
+ }
118
+ /**
119
+ * 複数スライドの JSON 入力内のコンポーネントノードを展開する。
120
+ */
121
+ export function expandComponentSlides(inputs, registry) {
122
+ return inputs.map((input) => expandComponents(input, registry));
123
+ }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,6 @@ export * from "./types.ts";
2
2
  export * from "./inputSchema.ts";
3
3
  export { buildPptx } from "./buildPptx.ts";
4
4
  export type { TextMeasurementMode } from "./buildPptx.ts";
5
+ export { defineComponent, defaultTheme, mergeTheme, expandComponents, expandComponentSlides, } from "./component.ts";
6
+ export type { Theme, ComponentRegistry } from "./component.ts";
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./types.js";
2
2
  export * from "./inputSchema.js";
3
3
  export { buildPptx } from "./buildPptx.js";
4
+ export { defineComponent, defaultTheme, mergeTheme, expandComponents, expandComponentSlides, } from "./component.js";