@hirokisakabe/pom 6.0.1 → 6.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/README.md CHANGED
@@ -69,26 +69,26 @@ await pptx.writeFile({ fileName: "presentation.pptx" });
69
69
 
70
70
  ## Available Nodes
71
71
 
72
- | Node | Description |
73
- | ------------ | ---------------------------------------------- |
74
- | Text | Text with font styling and decoration |
75
- | Ul | Unordered (bullet) list with Li items |
76
- | Ol | Ordered (numbered) list with Li items |
77
- | Image | Images from file path, URL, or base64 |
78
- | Table | Tables with customizable columns and rows |
79
- | Shape | PowerPoint shapes (roundRect, ellipse, etc.) |
80
- | Chart | Charts (bar, line, pie, area, doughnut, radar) |
81
- | Timeline | Timeline / roadmap visualizations |
82
- | Matrix | 2x2 positioning maps |
83
- | Tree | Organization charts and decision trees |
84
- | Flow | Flowcharts with nodes and edges |
85
- | ProcessArrow | Chevron-style process diagrams |
86
- | Pyramid | Pyramid diagrams for hierarchies |
87
- | Line | Horizontal / vertical lines |
88
- | Layer | Absolute-positioned overlay container |
89
- | VStack | Vertical stack layout |
90
- | HStack | Horizontal stack layout |
91
- | Icon | Lucide icons / inline SVG |
72
+ | Node | Description |
73
+ | ------------ | ---------------------------------------------------------- |
74
+ | Text | Text with font styling, decoration, and inline bold/italic |
75
+ | Ul | Unordered (bullet) list with Li items |
76
+ | Ol | Ordered (numbered) list with Li items |
77
+ | Image | Images from file path, URL, or base64 |
78
+ | Table | Tables with customizable columns and rows |
79
+ | Shape | PowerPoint shapes (roundRect, ellipse, etc.) |
80
+ | Chart | Charts (bar, line, pie, area, doughnut, radar) |
81
+ | Timeline | Timeline / roadmap visualizations |
82
+ | Matrix | 2x2 positioning maps |
83
+ | Tree | Organization charts and decision trees |
84
+ | Flow | Flowcharts with nodes and edges |
85
+ | ProcessArrow | Chevron-style process diagrams |
86
+ | Pyramid | Pyramid diagrams for hierarchies |
87
+ | Line | Horizontal / vertical lines |
88
+ | Layer | Absolute-positioned overlay container |
89
+ | VStack | Vertical stack layout |
90
+ | HStack | Horizontal stack layout |
91
+ | Icon | Lucide icons / inline SVG |
92
92
 
93
93
  For detailed node documentation, see [Nodes](./docs/nodes.md).
94
94
 
@@ -1 +1 @@
1
- {"version":3,"file":"buildPptx.d.ts","sourceRoot":"","sources":["../src/buildPptx.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAG3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAkB,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhE,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,WAAW,EAAE,OAAO,CAAC;IAClC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACnC,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GACA,OAAO,CAAC,eAAe,CAAC,CA8B1B"}
1
+ {"version":3,"file":"buildPptx.d.ts","sourceRoot":"","sources":["../src/buildPptx.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAG3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAkB,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhE,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,WAAW,EAAE,OAAO,CAAC;IAClC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACnC,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GACA,OAAO,CAAC,eAAe,CAAC,CAmC1B"}
package/dist/buildPptx.js CHANGED
@@ -21,7 +21,7 @@ export async function buildPptx(xml, slideSize, options) {
21
21
  map = await calcYogaLayout(node, slideSize, ctx);
22
22
  }
23
23
  const layoutMap = extractLayoutResults(map);
24
- const positioned = toPositioned(node, ctx, layoutMap);
24
+ const positioned = await toPositioned(node, ctx, layoutMap);
25
25
  positionedPages.push(positioned);
26
26
  }
27
27
  finally {
@@ -29,7 +29,7 @@ export async function buildPptx(xml, slideSize, options) {
29
29
  freeYogaTree(map);
30
30
  }
31
31
  }
32
- const pptx = renderPptx(positionedPages, slideSize, ctx, options?.master);
32
+ const pptx = await renderPptx(positionedPages, slideSize, ctx, options?.master);
33
33
  const diagnostics = ctx.diagnostics.items;
34
34
  if (options?.strict && diagnostics.length > 0) {
35
35
  throw new DiagnosticsError(diagnostics);
@@ -1,7 +1,7 @@
1
- export declare function rasterizeIcon(name: string, size: number, color: string, cache: Map<string, string>): string;
1
+ export declare function rasterizeIcon(name: string, size: number, color: string, cache: Map<string, string>): Promise<string>;
2
2
  /**
3
3
  * インライン SVG 文字列を指定サイズでラスタライズし、base64 PNG を返す。
4
4
  * color が指定された場合、SVG ルートに stroke / fill 属性を設定する。
5
5
  */
6
- export declare function rasterizeSvgContent(svgContent: string, size: number, color: string | undefined, cache: Map<string, string>): string;
6
+ export declare function rasterizeSvgContent(svgContent: string, size: number, color: string | undefined, cache: Map<string, string>): Promise<string>;
7
7
  //# sourceMappingURL=renderIcon.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderIcon.d.ts","sourceRoot":"","sources":["../../src/icons/renderIcon.ts"],"names":[],"mappings":"AAWA,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,MAAM,CAYR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,MAAM,CAuCR"}
1
+ {"version":3,"file":"renderIcon.d.ts","sourceRoot":"","sources":["../../src/icons/renderIcon.ts"],"names":[],"mappings":"AA2DA,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,OAAO,CAAC,MAAM,CAAC,CAyCjB"}
@@ -1,5 +1,47 @@
1
- import { Resvg } from "@resvg/resvg-js";
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
2
6
  import { ICON_DATA } from "./iconData.js";
7
+ const RESVG_PKG = ["@resvg", "resvg-wasm"].join("/");
8
+ let resvgModule;
9
+ let wasmInitPromise;
10
+ /**
11
+ * WASM バイナリのパスを解決する。
12
+ * バンドル環境(esbuild)では同ディレクトリの index_bg.wasm を参照し、
13
+ * 非バンドル環境では createRequire で node_modules から解決する。
14
+ */
15
+ function resolveWasmPath() {
16
+ const dir = dirname(fileURLToPath(import.meta.url));
17
+ const localPath = join(dir, "index_bg.wasm");
18
+ if (existsSync(localPath))
19
+ return localPath;
20
+ const require = createRequire(import.meta.url);
21
+ return require.resolve(`${RESVG_PKG}/index_bg.wasm`);
22
+ }
23
+ /**
24
+ * WASM モジュールを初期化し、Resvg クラスを返す。
25
+ * 並行呼び出しでも安全(Promise をキャッシュ)。
26
+ */
27
+ function ensureWasmInitialized() {
28
+ if (!wasmInitPromise) {
29
+ wasmInitPromise = (async () => {
30
+ const req = createRequire(import.meta.url);
31
+ const mod = req(RESVG_PKG);
32
+ const wasmPath = resolveWasmPath();
33
+ const wasmBuffer = await readFile(wasmPath);
34
+ await mod.initWasm(wasmBuffer);
35
+ resvgModule = mod;
36
+ })();
37
+ }
38
+ return wasmInitPromise;
39
+ }
40
+ function getResvg() {
41
+ if (!resvgModule)
42
+ throw new Error("WASM not initialized");
43
+ return resvgModule.Resvg;
44
+ }
3
45
  function buildIconSvg(name, size, color) {
4
46
  const pathData = ICON_DATA[name];
5
47
  if (!pathData) {
@@ -7,11 +49,13 @@ function buildIconSvg(name, size, color) {
7
49
  }
8
50
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${pathData}</svg>`;
9
51
  }
10
- export function rasterizeIcon(name, size, color, cache) {
52
+ export async function rasterizeIcon(name, size, color, cache) {
11
53
  const key = `${name}|${size}|${color}`;
12
54
  const cached = cache.get(key);
13
55
  if (cached)
14
56
  return cached;
57
+ await ensureWasmInitialized();
58
+ const Resvg = getResvg();
15
59
  const svg = buildIconSvg(name, size, color);
16
60
  const resvg = new Resvg(svg, { fitTo: { mode: "width", value: size } });
17
61
  const pngData = resvg.render();
@@ -24,7 +68,7 @@ export function rasterizeIcon(name, size, color, cache) {
24
68
  * インライン SVG 文字列を指定サイズでラスタライズし、base64 PNG を返す。
25
69
  * color が指定された場合、SVG ルートに stroke / fill 属性を設定する。
26
70
  */
27
- export function rasterizeSvgContent(svgContent, size, color, cache) {
71
+ export async function rasterizeSvgContent(svgContent, size, color, cache) {
28
72
  const key = `svg:${svgContent}|${size}|${color ?? ""}`;
29
73
  const cached = cache.get(key);
30
74
  if (cached)
@@ -52,6 +96,8 @@ export function rasterizeSvgContent(svgContent, size, color, cache) {
52
96
  }
53
97
  return `<svg${newAttrs}>`;
54
98
  });
99
+ await ensureWasmInitialized();
100
+ const Resvg = getResvg();
55
101
  const resvg = new Resvg(svg, { fitTo: { mode: "width", value: size } });
56
102
  const pngData = resvg.render();
57
103
  const pngBuffer = pngData.asPng();
@@ -1 +1 @@
1
- {"version":3,"file":"coercionRules.d.ts","sourceRoot":"","sources":["../../src/parseXml/coercionRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,MAAM,GACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,CAAC;AAI5D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAyD1C;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,EAAE,GACtB,OAAO,CAuCT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAarD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,CAY1C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAQpE;AA6HD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA8I1E,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAC7C,MAAM,EACN,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA6E7B,CAAC"}
1
+ {"version":3,"file":"coercionRules.d.ts","sourceRoot":"","sources":["../../src/parseXml/coercionRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,MAAM,GACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,CAAC;AAI5D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAyD1C;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,EAAE,GACtB,OAAO,CAuCT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAarD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,CAY1C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAQpE;AA8HD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA0I1E,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAC7C,MAAM,EACN,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA+E7B,CAAC"}
@@ -237,6 +237,7 @@ const BASE_RULES = {
237
237
  bottom: "number",
238
238
  left: "number",
239
239
  alignSelf: "string",
240
+ shadow: SHADOW_STYLE_RULE,
240
241
  };
241
242
  // テキスト系の共通属性
242
243
  const TEXT_STYLE_RULES = {
@@ -274,7 +275,6 @@ export const NODE_COERCION_MAP = {
274
275
  ...BASE_RULES,
275
276
  src: "string",
276
277
  sizing: IMAGE_SIZING_RULE,
277
- shadow: SHADOW_STYLE_RULE,
278
278
  },
279
279
  icon: {
280
280
  ...BASE_RULES,
@@ -296,7 +296,6 @@ export const NODE_COERCION_MAP = {
296
296
  text: "string",
297
297
  fill: FILL_STYLE_RULE,
298
298
  line: BORDER_STYLE_RULE,
299
- shadow: SHADOW_STYLE_RULE,
300
299
  ...TEXT_STYLE_RULES,
301
300
  },
302
301
  chart: {
@@ -380,7 +379,6 @@ export const NODE_COERCION_MAP = {
380
379
  gap: "number",
381
380
  alignItems: "string",
382
381
  justifyContent: "string",
383
- shadow: SHADOW_STYLE_RULE,
384
382
  flexWrap: "string",
385
383
  },
386
384
  hstack: {
@@ -388,7 +386,6 @@ export const NODE_COERCION_MAP = {
388
386
  gap: "number",
389
387
  alignItems: "string",
390
388
  justifyContent: "string",
391
- shadow: SHADOW_STYLE_RULE,
392
389
  flexWrap: "string",
393
390
  },
394
391
  layer: {
@@ -472,4 +469,6 @@ export const CHILD_ELEMENT_COERCION_MAP = {
472
469
  fontSize: "number",
473
470
  fontFamily: "string",
474
471
  },
472
+ B: {},
473
+ I: {},
475
474
  };
@@ -1 +1 @@
1
- {"version":3,"file":"parseXml.d.ts","sourceRoot":"","sources":["../../src/parseXml/parseXml.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAgBb,MAAM,aAAa,CAAC;AAYrB,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,MAAM,EAAE,MAAM,EAAE,CAAC;gBACrB,MAAM,EAAE,MAAM,EAAE;CAM7B;AAGD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmB9C,CAAC;AA67BF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAiCrD"}
1
+ {"version":3,"file":"parseXml.d.ts","sourceRoot":"","sources":["../../src/parseXml/parseXml.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAgBb,MAAM,aAAa,CAAC;AAYrB,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,MAAM,EAAE,MAAM,EAAE,CAAC;gBACrB,MAAM,EAAE,MAAM,EAAE;CAM7B;AAGD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmB9C,CAAC;AA2hCF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAiCrD"}
@@ -284,6 +284,48 @@ function getTextContent(node) {
284
284
  }
285
285
  return textParts.length > 0 ? textParts.join("") : undefined;
286
286
  }
287
+ function getRawChildren(node) {
288
+ const tagName = getTagName(node);
289
+ return node[tagName] ?? [];
290
+ }
291
+ const INLINE_FORMAT_TAGS = new Set(["B", "I"]);
292
+ function hasInlineFormatChildren(childElements) {
293
+ return (childElements.length > 0 &&
294
+ childElements.every((el) => INLINE_FORMAT_TAGS.has(getTagName(el))));
295
+ }
296
+ function extractTextRuns(children, inheritBold, inheritItalic) {
297
+ const runs = [];
298
+ for (const child of children) {
299
+ if (isTextNode(child)) {
300
+ const run = { text: child["#text"] };
301
+ if (inheritBold)
302
+ run.bold = true;
303
+ if (inheritItalic)
304
+ run.italic = true;
305
+ runs.push(run);
306
+ }
307
+ else {
308
+ const tag = getTagName(child);
309
+ const innerChildren = getRawChildren(child);
310
+ if (tag === "B") {
311
+ runs.push(...extractTextRuns(innerChildren, true, inheritItalic));
312
+ }
313
+ else if (tag === "I") {
314
+ runs.push(...extractTextRuns(innerChildren, inheritBold, true));
315
+ }
316
+ }
317
+ }
318
+ return runs;
319
+ }
320
+ function buildRunsAndText(node) {
321
+ const rawChildren = getRawChildren(node);
322
+ const childElements = rawChildren.filter((c) => !isTextNode(c));
323
+ if (!hasInlineFormatChildren(childElements))
324
+ return null;
325
+ const runs = extractTextRuns(rawChildren);
326
+ const text = runs.map((r) => r.text).join("");
327
+ return { runs, text };
328
+ }
287
329
  function coerceChildAttrs(parentTagName, tagName, attrs, errors) {
288
330
  const rules = CHILD_ELEMENT_COERCION_MAP[tagName];
289
331
  const result = {};
@@ -487,9 +529,16 @@ function convertTableChildren(childElements, result, errors) {
487
529
  continue;
488
530
  }
489
531
  const cellAttrs = coerceChildAttrs("TableRow", cellTag, getAttributes(cellEl), errors);
490
- const cellText = getTextContent(cellEl);
491
- if (cellText !== undefined && !("text" in cellAttrs)) {
492
- cellAttrs.text = cellText;
532
+ const runsResult = buildRunsAndText(cellEl);
533
+ if (runsResult) {
534
+ cellAttrs.runs = runsResult.runs;
535
+ cellAttrs.text = runsResult.text;
536
+ }
537
+ else {
538
+ const cellText = getTextContent(cellEl);
539
+ if (cellText !== undefined && !("text" in cellAttrs)) {
540
+ cellAttrs.text = cellText;
541
+ }
493
542
  }
494
543
  cells.push(cellAttrs);
495
544
  }
@@ -571,9 +620,16 @@ function convertListChildren(parentTag, childElements, result, errors) {
571
620
  continue;
572
621
  }
573
622
  const attrs = coerceChildAttrs(parentTag, tag, getAttributes(child), errors);
574
- const textContent = getTextContent(child);
575
- if (textContent !== undefined && !("text" in attrs)) {
576
- attrs.text = textContent;
623
+ const runsResult = buildRunsAndText(child);
624
+ if (runsResult) {
625
+ attrs.runs = runsResult.runs;
626
+ attrs.text = runsResult.text;
627
+ }
628
+ else {
629
+ const textContent = getTextContent(child);
630
+ if (textContent !== undefined && !("text" in attrs)) {
631
+ attrs.text = textContent;
632
+ }
577
633
  }
578
634
  items.push(attrs);
579
635
  }
@@ -606,7 +662,25 @@ function convertIconChildren(childElements, result, errors) {
606
662
  }
607
663
  result.svgContent = serializeSvgElement(child);
608
664
  }
665
+ function convertTextInlineChildren(childElements, result, errors, node) {
666
+ // B/I 以外の子要素がある場合はエラー
667
+ for (const el of childElements) {
668
+ const tag = getTagName(el);
669
+ if (!INLINE_FORMAT_TAGS.has(tag)) {
670
+ errors.push(`<Text>: Unexpected child element <${tag}>. Only <B> and <I> are allowed inside <Text>`);
671
+ return;
672
+ }
673
+ }
674
+ if (!node || childElements.length === 0)
675
+ return;
676
+ const runsResult = buildRunsAndText(node);
677
+ if (runsResult) {
678
+ result.runs = runsResult.runs;
679
+ result.text = runsResult.text;
680
+ }
681
+ }
609
682
  const CHILD_ELEMENT_CONVERTERS = {
683
+ text: convertTextInlineChildren,
610
684
  ul: (childElements, result, errors) => convertListChildren("Ul", childElements, result, errors),
611
685
  ol: (childElements, result, errors) => convertListChildren("Ol", childElements, result, errors),
612
686
  processArrow: convertProcessArrowChildren,
@@ -627,14 +701,14 @@ function convertElement(node, errors) {
627
701
  const childElements = getChildElements(node);
628
702
  const textContent = getTextContent(node);
629
703
  if (nodeType) {
630
- return convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors);
704
+ return convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors, node);
631
705
  }
632
706
  else {
633
707
  errors.push(`Unknown tag: <${tagName}>`);
634
708
  return null;
635
709
  }
636
710
  }
637
- function convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors) {
711
+ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors, xmlNode) {
638
712
  const result = { type: nodeType };
639
713
  // Expand dot-notation attributes (e.g., fill.color="hex" → { fill: { color: "hex" } })
640
714
  const { regular: regularAttrs, dotGroups } = expandDotNotation(attrs);
@@ -708,7 +782,7 @@ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, er
708
782
  // Child element notation for complex properties
709
783
  const childConverter = CHILD_ELEMENT_CONVERTERS[nodeType];
710
784
  if (childConverter && childElements.length > 0) {
711
- childConverter(childElements, result, errors);
785
+ childConverter(childElements, result, errors, xmlNode);
712
786
  }
713
787
  // Children for container nodes
714
788
  else if (CONTAINER_TYPES.has(nodeType) && childElements.length > 0) {
@@ -11,7 +11,7 @@ export const iconNodeDef = {
11
11
  const totalSize = n.variant ? Math.ceil(iconSize * 1.75) : iconSize;
12
12
  yn.setMeasureFunc(() => ({ width: totalSize, height: totalSize }));
13
13
  },
14
- toPositioned(pom, absoluteX, absoluteY, layout, ctx) {
14
+ async toPositioned(pom, absoluteX, absoluteY, layout, ctx) {
15
15
  const n = pom;
16
16
  const iconSize = n.size ?? 24;
17
17
  // padding を考慮したコンテンツ領域で bg/icon の座標を計算
@@ -25,8 +25,8 @@ export const iconNodeDef = {
25
25
  // 実描画サイズに合わせてラスタライズ(不要に大きい PNG を防ぐ)
26
26
  const rasterSize = Math.max(Math.ceil(n.variant ? iconSize : Math.min(content.w, content.h)), iconSize);
27
27
  const iconImageData = n.svgContent
28
- ? rasterizeSvgContent(n.svgContent, rasterSize, n.color, ctx.iconRasterCache)
29
- : rasterizeIcon(n.name, rasterSize, n.color ?? "#000000", ctx.iconRasterCache);
28
+ ? await rasterizeSvgContent(n.svgContent, rasterSize, n.color, ctx.iconRasterCache)
29
+ : await rasterizeIcon(n.name, rasterSize, n.color ?? "#000000", ctx.iconRasterCache);
30
30
  const positioned = {
31
31
  ...n,
32
32
  x: absoluteX,
@@ -1 +1 @@
1
- {"version":3,"file":"layer.d.ts","sourceRoot":"","sources":["../../../src/registry/definitions/layer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,eAAO,MAAM,YAAY,EAAE,cA4D1B,CAAC"}
1
+ {"version":3,"file":"layer.d.ts","sourceRoot":"","sources":["../../../src/registry/definitions/layer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,eAAO,MAAM,YAAY,EAAE,cA8D1B,CAAC"}
@@ -3,7 +3,7 @@ export const layerNodeDef = {
3
3
  type: "layer",
4
4
  category: "absolute-child",
5
5
  // applyYogaStyle: layer は子を絶対配置するコンテナ。サイズは明示的に指定されることを期待
6
- toPositioned(pom, absoluteX, absoluteY, layout, ctx, map) {
6
+ async toPositioned(pom, absoluteX, absoluteY, layout, ctx, map) {
7
7
  const n = pom;
8
8
  // layer の子要素は layer 内の相対座標(child.x, child.y)を持つ
9
9
  // layer の絶対座標に加算してスライド上の絶対座標に変換
@@ -13,7 +13,7 @@ export const layerNodeDef = {
13
13
  y: absoluteY,
14
14
  w: layout.width,
15
15
  h: layout.height,
16
- children: n.children.map((child) => {
16
+ children: await Promise.all(n.children.map(async (child) => {
17
17
  const childX = child.x ?? 0;
18
18
  const childY = child.y ?? 0;
19
19
  // line ノードは特別な処理が必要
@@ -44,8 +44,8 @@ export const layerNodeDef = {
44
44
  }
45
45
  const adjustedParentX = absoluteX + childX - childLayout.left;
46
46
  const adjustedParentY = absoluteY + childY - childLayout.top;
47
- return toPositioned(child, ctx, map, adjustedParentX, adjustedParentY);
48
- }),
47
+ return (await toPositioned(child, ctx, map, adjustedParentX, adjustedParentY));
48
+ })),
49
49
  };
50
50
  },
51
51
  // render: category ベースの子要素再帰で対応
@@ -18,7 +18,7 @@ export interface NodeDefinition {
18
18
  toPositioned?: (pom: POMNode, absoluteX: number, absoluteY: number, layout: {
19
19
  width: number;
20
20
  height: number;
21
- }, ctx: BuildContext, map: LayoutResultMap) => PositionedNode;
21
+ }, ctx: BuildContext, map: LayoutResultMap) => PositionedNode | Promise<PositionedNode>;
22
22
  /** PositionedNode をスライドにレンダリングする(リーフノード用) */
23
23
  render?: (node: PositionedNode, ctx: RenderContext) => void;
24
24
  /** 画像ソース収集(prefetch 用) */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/registry/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC;AAExD,4BAA4B;AAC5B,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,aAAa,GACb,gBAAgB,CAAC;AAErB,MAAM,WAAW,cAAc;IAC7B,cAAc;IACd,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAEtB,cAAc;IACd,QAAQ,EAAE,YAAY,CAAC;IAEvB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,CACf,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,YAAY,KACd,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B,kEAAkE;IAClE,YAAY,CAAC,EAAE,CACb,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EACzC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,eAAe,KACjB,cAAc,CAAC;IAEpB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IAE5D,0BAA0B;IAC1B,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC;CACnD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/registry/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC;AAExD,4BAA4B;AAC5B,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,aAAa,GACb,gBAAgB,CAAC;AAErB,MAAM,WAAW,cAAc;IAC7B,cAAc;IACd,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAEtB,cAAc;IACd,QAAQ,EAAE,YAAY,CAAC;IAEvB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,CACf,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,YAAY,KACd,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B,kEAAkE;IAClE,YAAY,CAAC,EAAE,CACb,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EACzC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,eAAe,KACjB,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAE9C,6CAA6C;IAC7C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IAE5D,0BAA0B;IAC1B,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC;CACnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/renderPptx/nodes/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAU,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,KAAK,gBAAgB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAChE,KAAK,gBAAgB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AA6BhE,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CA4D7E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAkE7E"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/renderPptx/nodes/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAU,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,KAAK,gBAAgB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAChE,KAAK,gBAAgB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAgFhE,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CA4C7E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAkD7E"}
@@ -13,6 +13,52 @@ function resolveStyle(li, parent) {
13
13
  fontFamily: li.fontFamily ?? parent.fontFamily ?? "Noto Sans JP",
14
14
  };
15
15
  }
16
+ function buildListTextItems(items, parent, bullet) {
17
+ const textItems = [];
18
+ for (let i = 0; i < items.length; i++) {
19
+ const li = items[i];
20
+ const style = resolveStyle(li, parent);
21
+ const isLast = i === items.length - 1;
22
+ const baseOptions = {
23
+ fontSize: pxToPt(style.fontSize),
24
+ fontFace: style.fontFamily,
25
+ color: style.color,
26
+ underline: convertUnderline(style.underline),
27
+ strike: convertStrike(style.strike),
28
+ highlight: style.highlight,
29
+ };
30
+ if (li.runs && li.runs.length > 0) {
31
+ for (let j = 0; j < li.runs.length; j++) {
32
+ const run = li.runs[j];
33
+ const isLastRun = j === li.runs.length - 1;
34
+ let text = run.text;
35
+ if (isLastRun && !isLast)
36
+ text += "\n";
37
+ textItems.push({
38
+ text,
39
+ options: {
40
+ ...baseOptions,
41
+ bold: run.bold ?? style.bold,
42
+ italic: run.italic ?? style.italic,
43
+ bullet: j === 0 ? bullet : false,
44
+ },
45
+ });
46
+ }
47
+ }
48
+ else {
49
+ textItems.push({
50
+ text: isLast ? li.text : li.text + "\n",
51
+ options: {
52
+ ...baseOptions,
53
+ bold: style.bold,
54
+ italic: style.italic,
55
+ bullet,
56
+ },
57
+ });
58
+ }
59
+ }
60
+ return textItems;
61
+ }
16
62
  function hasItemStyleOverride(items) {
17
63
  return items.some((li) => li.fontSize !== undefined ||
18
64
  li.color !== undefined ||
@@ -21,7 +67,8 @@ function hasItemStyleOverride(items) {
21
67
  li.underline !== undefined ||
22
68
  li.strike !== undefined ||
23
69
  li.highlight !== undefined ||
24
- li.fontFamily !== undefined);
70
+ li.fontFamily !== undefined ||
71
+ li.runs !== undefined);
25
72
  }
26
73
  export function renderUlNode(node, ctx) {
27
74
  const fontSizePx = node.fontSize ?? 24;
@@ -30,23 +77,7 @@ export function renderUlNode(node, ctx) {
30
77
  const content = getContentArea(node);
31
78
  if (hasItemStyleOverride(node.items)) {
32
79
  // Li に個別スタイルがある場合は配列形式を使用
33
- const textItems = node.items.map((li, i) => {
34
- const style = resolveStyle(li, node);
35
- return {
36
- text: i < node.items.length - 1 ? li.text + "\n" : li.text,
37
- options: {
38
- fontSize: pxToPt(style.fontSize),
39
- fontFace: style.fontFamily,
40
- color: style.color,
41
- bold: style.bold,
42
- italic: style.italic,
43
- underline: convertUnderline(style.underline),
44
- strike: convertStrike(style.strike),
45
- highlight: style.highlight,
46
- bullet: true,
47
- },
48
- };
49
- });
80
+ const textItems = buildListTextItems(node.items, node, true);
50
81
  ctx.slide.addText(textItems, {
51
82
  x: pxToIn(content.x),
52
83
  y: pxToIn(content.y),
@@ -95,23 +126,7 @@ export function renderOlNode(node, ctx) {
95
126
  bulletOptions.numberStartAt = node.numberStartAt;
96
127
  }
97
128
  if (hasItemStyleOverride(node.items)) {
98
- const textItems = node.items.map((li, i) => {
99
- const style = resolveStyle(li, node);
100
- return {
101
- text: i < node.items.length - 1 ? li.text + "\n" : li.text,
102
- options: {
103
- fontSize: pxToPt(style.fontSize),
104
- fontFace: style.fontFamily,
105
- color: style.color,
106
- bold: style.bold,
107
- italic: style.italic,
108
- underline: convertUnderline(style.underline),
109
- strike: convertStrike(style.strike),
110
- highlight: style.highlight,
111
- bullet: bulletOptions,
112
- },
113
- };
114
- });
129
+ const textItems = buildListTextItems(node.items, node, bulletOptions);
115
130
  ctx.slide.addText(textItems, {
116
131
  x: pxToIn(content.x),
117
132
  y: pxToIn(content.y),
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/renderPptx/nodes/table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASjD,KAAK,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEtE,wBAAgB,eAAe,CAC7B,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,aAAa,GACjB,IAAI,CAsCN"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/renderPptx/nodes/table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASjD,KAAK,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEtE,wBAAgB,eAAe,CAC7B,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,aAAa,GACjB,IAAI,CAgEN"}
@@ -19,6 +19,31 @@ export function renderTableNode(node, ctx) {
19
19
  colspan: cell.colspan,
20
20
  rowspan: cell.rowspan,
21
21
  };
22
+ if (cell.runs && cell.runs.length > 0) {
23
+ const textItems = cell.runs.map((run) => ({
24
+ text: run.text,
25
+ options: {
26
+ fontSize: pxToPt(cell.fontSize ?? 18),
27
+ color: cell.color,
28
+ bold: run.bold ?? cell.bold,
29
+ italic: run.italic ?? cell.italic,
30
+ underline: convertUnderline(cell.underline),
31
+ strike: convertStrike(cell.strike),
32
+ highlight: cell.highlight,
33
+ },
34
+ }));
35
+ return {
36
+ text: textItems,
37
+ options: {
38
+ align: cell.textAlign ?? "left",
39
+ fill: cell.backgroundColor
40
+ ? { color: cell.backgroundColor }
41
+ : undefined,
42
+ colspan: cell.colspan,
43
+ rowspan: cell.rowspan,
44
+ },
45
+ };
46
+ }
22
47
  return {
23
48
  text: cell.text,
24
49
  options: cellOptions,
@@ -1 +1 @@
1
- {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/renderPptx/nodes/text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,KAAK,kBAAkB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,kBAAkB,EACxB,GAAG,EAAE,aAAa,GACjB,IAAI,CAGN"}
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/renderPptx/nodes/text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQjD,KAAK,kBAAkB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,kBAAkB,EACxB,GAAG,EAAE,aAAa,GACjB,IAAI,CAgCN"}
@@ -1,5 +1,35 @@
1
- import { createTextOptions } from "../textOptions.js";
1
+ import { createTextOptions, convertUnderline, convertStrike, } from "../textOptions.js";
2
+ import { pxToPt } from "../units.js";
2
3
  export function renderTextNode(node, ctx) {
3
4
  const textOptions = createTextOptions(node);
4
- ctx.slide.addText(node.text ?? "", textOptions);
5
+ if (node.runs && node.runs.length > 0) {
6
+ const fontSizePx = node.fontSize ?? 24;
7
+ const fontFamily = node.fontFamily ?? "Noto Sans JP";
8
+ const textItems = node.runs.map((run) => ({
9
+ text: run.text,
10
+ options: {
11
+ fontSize: pxToPt(fontSizePx),
12
+ fontFace: fontFamily,
13
+ color: node.color,
14
+ bold: run.bold ?? node.bold,
15
+ italic: run.italic ?? node.italic,
16
+ underline: convertUnderline(node.underline),
17
+ strike: convertStrike(node.strike),
18
+ highlight: node.highlight,
19
+ },
20
+ }));
21
+ ctx.slide.addText(textItems, {
22
+ x: textOptions.x,
23
+ y: textOptions.y,
24
+ w: textOptions.w,
25
+ h: textOptions.h,
26
+ align: textOptions.align,
27
+ valign: textOptions.valign,
28
+ margin: textOptions.margin,
29
+ lineSpacingMultiple: textOptions.lineSpacingMultiple,
30
+ });
31
+ }
32
+ else {
33
+ ctx.slide.addText(node.text ?? "", textOptions);
34
+ }
5
35
  }