@hirokisakabe/pom 5.1.0 → 5.2.1

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 (104) hide show
  1. package/README.md +10 -11
  2. package/dist/autoFit/autoFit.d.ts +2 -1
  3. package/dist/autoFit/autoFit.d.ts.map +1 -1
  4. package/dist/autoFit/autoFit.js +7 -7
  5. package/dist/autoFit/strategies/reduceFontSize.js +1 -1
  6. package/dist/autoFit/strategies/reduceGapAndPadding.js +1 -1
  7. package/dist/autoFit/strategies/reduceTableRowHeight.js +1 -1
  8. package/dist/autoFit/strategies/uniformScale.js +1 -1
  9. package/dist/buildContext.d.ts +12 -0
  10. package/dist/buildContext.d.ts.map +1 -0
  11. package/dist/buildContext.js +8 -0
  12. package/dist/buildPptx.d.ts +1 -1
  13. package/dist/buildPptx.d.ts.map +1 -1
  14. package/dist/buildPptx.js +15 -15
  15. package/dist/calcYogaLayout/calcYogaLayout.d.ts +3 -1
  16. package/dist/calcYogaLayout/calcYogaLayout.d.ts.map +1 -1
  17. package/dist/calcYogaLayout/calcYogaLayout.js +85 -341
  18. package/dist/calcYogaLayout/measureText.d.ts +1 -5
  19. package/dist/calcYogaLayout/measureText.d.ts.map +1 -1
  20. package/dist/calcYogaLayout/measureText.js +2 -10
  21. package/dist/icons/renderIcon.d.ts +1 -1
  22. package/dist/icons/renderIcon.d.ts.map +1 -1
  23. package/dist/icons/renderIcon.js +3 -4
  24. package/dist/parseXml/coercionRules.d.ts +35 -0
  25. package/dist/parseXml/coercionRules.d.ts.map +1 -0
  26. package/dist/parseXml/coercionRules.js +477 -0
  27. package/dist/parseXml/inputSchema.d.ts +326 -64
  28. package/dist/parseXml/inputSchema.d.ts.map +1 -1
  29. package/dist/parseXml/inputSchema.js +13 -3
  30. package/dist/parseXml/parseXml.d.ts.map +1 -1
  31. package/dist/parseXml/parseXml.js +40 -270
  32. package/dist/registry/definitions/box.d.ts +3 -0
  33. package/dist/registry/definitions/box.d.ts.map +1 -0
  34. package/dist/registry/definitions/box.js +6 -0
  35. package/dist/registry/definitions/chart.d.ts +3 -0
  36. package/dist/registry/definitions/chart.d.ts.map +1 -0
  37. package/dist/registry/definitions/chart.js +8 -0
  38. package/dist/registry/definitions/compositeNodes.d.ts +8 -0
  39. package/dist/registry/definitions/compositeNodes.d.ts.map +1 -0
  40. package/dist/registry/definitions/compositeNodes.js +81 -0
  41. package/dist/registry/definitions/icon.d.ts +3 -0
  42. package/dist/registry/definitions/icon.d.ts.map +1 -0
  43. package/dist/registry/definitions/icon.js +28 -0
  44. package/dist/registry/definitions/image.d.ts +3 -0
  45. package/dist/registry/definitions/image.d.ts.map +1 -0
  46. package/dist/registry/definitions/image.js +34 -0
  47. package/dist/registry/definitions/layer.d.ts +3 -0
  48. package/dist/registry/definitions/layer.d.ts.map +1 -0
  49. package/dist/registry/definitions/layer.js +49 -0
  50. package/dist/registry/definitions/line.d.ts +3 -0
  51. package/dist/registry/definitions/line.d.ts.map +1 -0
  52. package/dist/registry/definitions/line.js +26 -0
  53. package/dist/registry/definitions/list.d.ts +4 -0
  54. package/dist/registry/definitions/list.d.ts.map +1 -0
  55. package/dist/registry/definitions/list.js +53 -0
  56. package/dist/registry/definitions/shape.d.ts +3 -0
  57. package/dist/registry/definitions/shape.d.ts.map +1 -0
  58. package/dist/registry/definitions/shape.js +37 -0
  59. package/dist/registry/definitions/stack.d.ts +4 -0
  60. package/dist/registry/definitions/stack.d.ts.map +1 -0
  61. package/dist/registry/definitions/stack.js +78 -0
  62. package/dist/registry/definitions/table.d.ts +3 -0
  63. package/dist/registry/definitions/table.d.ts.map +1 -0
  64. package/dist/registry/definitions/table.js +16 -0
  65. package/dist/registry/definitions/text.d.ts +3 -0
  66. package/dist/registry/definitions/text.d.ts.map +1 -0
  67. package/dist/registry/definitions/text.js +35 -0
  68. package/dist/registry/index.d.ts +2 -0
  69. package/dist/registry/index.d.ts.map +1 -0
  70. package/dist/registry/index.js +34 -0
  71. package/dist/registry/nodeRegistry.d.ts +7 -0
  72. package/dist/registry/nodeRegistry.d.ts.map +1 -0
  73. package/dist/registry/nodeRegistry.js +13 -0
  74. package/dist/registry/types.d.ts +26 -0
  75. package/dist/registry/types.d.ts.map +1 -0
  76. package/dist/registry/types.js +1 -0
  77. package/dist/renderPptx/renderPptx.d.ts +2 -1
  78. package/dist/renderPptx/renderPptx.d.ts.map +1 -1
  79. package/dist/renderPptx/renderPptx.js +30 -61
  80. package/dist/renderPptx/types.d.ts +2 -0
  81. package/dist/renderPptx/types.d.ts.map +1 -1
  82. package/dist/renderPptx/utils/backgroundBorder.d.ts.map +1 -1
  83. package/dist/renderPptx/utils/backgroundBorder.js +1 -1
  84. package/dist/shared/freeYogaTree.d.ts.map +1 -0
  85. package/dist/shared/measureImage.d.ts +13 -3
  86. package/dist/shared/measureImage.d.ts.map +1 -1
  87. package/dist/shared/measureImage.js +14 -19
  88. package/dist/shared/walkTree.d.ts.map +1 -0
  89. package/dist/toPositioned/toPositioned.d.ts +8 -1
  90. package/dist/toPositioned/toPositioned.d.ts.map +1 -1
  91. package/dist/toPositioned/toPositioned.js +32 -189
  92. package/dist/types.d.ts +371 -37
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js +20 -1
  95. package/package.json +3 -3
  96. package/dist/autoFit/freeYogaTree.d.ts.map +0 -1
  97. package/dist/autoFit/walkTree.d.ts.map +0 -1
  98. package/dist/renderPptx/nodes/index.d.ts +0 -15
  99. package/dist/renderPptx/nodes/index.d.ts.map +0 -1
  100. package/dist/renderPptx/nodes/index.js +0 -14
  101. /package/dist/{autoFit → shared}/freeYogaTree.d.ts +0 -0
  102. /package/dist/{autoFit → shared}/freeYogaTree.js +0 -0
  103. /package/dist/{autoFit → shared}/walkTree.d.ts +0 -0
  104. /package/dist/{autoFit → shared}/walkTree.js +0 -0
@@ -1,33 +1,21 @@
1
1
  import { loadYoga } from "yoga-layout/load";
2
- import { measureText } from "./measureText.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
- }
2
+ import { prefetchImageSize } from "../shared/measureImage.js";
3
+ import { getNodeDef } from "../registry/index.js";
17
4
  /**
18
5
  * POMNode ツリーを Yoga でレイアウト計算する
19
6
  * POMNode ツリーの各ノードに yogaNode プロパティがセットされる
20
7
  *
21
8
  * @param root 入力 POMNode ツリーのルート
22
9
  * @param slideSize スライド全体のサイズ(px)
10
+ * @param ctx BuildContext
23
11
  */
24
- export async function calcYogaLayout(root, slideSize) {
12
+ export async function calcYogaLayout(root, slideSize, ctx) {
25
13
  const Yoga = await getYoga();
26
14
  // 事前に全画像のサイズを取得(HTTPS対応のため)
27
- await prefetchAllImageSizes(root);
15
+ await prefetchAllImageSizes(root, ctx);
28
16
  const rootYoga = Yoga.Node.create();
29
17
  root.yogaNode = rootYoga;
30
- await buildPomWithYogaTree(root, rootYoga);
18
+ await buildPomWithYogaTree(root, rootYoga, ctx);
31
19
  // スライド全体サイズを指定
32
20
  rootYoga.setWidth(slideSize.w);
33
21
  rootYoga.setHeight(slideSize.h);
@@ -36,9 +24,9 @@ export async function calcYogaLayout(root, slideSize) {
36
24
  /**
37
25
  * POMNode ツリー内のすべての画像のサイズを事前取得する
38
26
  */
39
- async function prefetchAllImageSizes(node) {
27
+ async function prefetchAllImageSizes(node, ctx) {
40
28
  const imageSources = collectImageSources(node);
41
- await Promise.all(imageSources.map((src) => prefetchImageSize(src)));
29
+ await Promise.all(imageSources.map((src) => prefetchImageSize(src, ctx.imageSizeCache, ctx.imageDataCache)));
42
30
  }
43
31
  /**
44
32
  * POMNode ツリー内のすべての画像のsrcを収集する
@@ -50,20 +38,25 @@ function collectImageSources(node) {
50
38
  if (n.backgroundImage) {
51
39
  sources.push(n.backgroundImage.src);
52
40
  }
53
- if (n.type === "image") {
54
- sources.push(n.src);
55
- }
56
- else if (n.type === "box") {
57
- traverse(n.children);
41
+ const def = getNodeDef(n.type);
42
+ // ノード固有の画像ソース収集
43
+ if (def.collectImageSources) {
44
+ sources.push(...def.collectImageSources(n));
58
45
  }
59
- else if (n.type === "vstack" || n.type === "hstack") {
60
- for (const child of n.children) {
61
- traverse(child);
46
+ // 子要素の再帰
47
+ switch (def.category) {
48
+ case "single-child": {
49
+ const boxNode = n;
50
+ traverse(boxNode.children);
51
+ break;
62
52
  }
63
- }
64
- else if (n.type === "layer") {
65
- for (const child of n.children) {
66
- traverse(child);
53
+ case "multi-child":
54
+ case "absolute-child": {
55
+ const containerNode = n;
56
+ for (const child of containerNode.children) {
57
+ traverse(child);
58
+ }
59
+ break;
67
60
  }
68
61
  }
69
62
  }
@@ -82,11 +75,11 @@ async function getYoga() {
82
75
  /**
83
76
  * POMNode ツリーを再帰的に走査し、YogaNode ツリーを構築する
84
77
  */
85
- async function buildPomWithYogaTree(node, parentYoga, parentNode) {
78
+ async function buildPomWithYogaTree(node, parentYoga, ctx, parentNode) {
86
79
  const yoga = await getYoga();
87
80
  const yn = yoga.Node.create();
88
81
  node.yogaNode = yn; // 対応する YogaNode をセット
89
- await applyStyleToYogaNode(node, yn);
82
+ await applyStyleToYogaNode(node, yn, ctx);
90
83
  // HStack/VStack の子要素に flexShrink=1 をデフォルト設定(CSS Flexbox と同じ挙動)
91
84
  // 主軸方向で %サイズ + gap がある場合の overflow を防ぐ
92
85
  if (parentNode?.type === "hstack" || parentNode?.type === "vstack") {
@@ -101,38 +94,22 @@ async function buildPomWithYogaTree(node, parentYoga, parentNode) {
101
94
  yn.setFlexBasis(0);
102
95
  }
103
96
  parentYoga.insertChild(yn, parentYoga.getChildCount());
104
- switch (node.type) {
105
- case "box": {
106
- await buildPomWithYogaTree(node.children, yn, node);
97
+ const def = getNodeDef(node.type);
98
+ switch (def.category) {
99
+ case "single-child": {
100
+ const boxNode = node;
101
+ await buildPomWithYogaTree(boxNode.children, yn, ctx, node);
107
102
  break;
108
103
  }
109
- case "vstack":
110
- case "hstack": {
111
- for (const child of node.children) {
112
- await buildPomWithYogaTree(child, yn, node);
104
+ case "multi-child":
105
+ case "absolute-child": {
106
+ const containerNode = node;
107
+ for (const child of containerNode.children) {
108
+ await buildPomWithYogaTree(child, yn, ctx, node);
113
109
  }
114
110
  break;
115
111
  }
116
- case "layer": {
117
- // layer の子要素は絶対配置なので、各子要素のサイズ計算のみ行う
118
- // x, y は toPositioned で適用される
119
- for (const child of node.children) {
120
- await buildPomWithYogaTree(child, yn, node);
121
- }
122
- break;
123
- }
124
- case "text":
125
- case "image":
126
- case "table":
127
- case "shape":
128
- case "timeline":
129
- case "matrix":
130
- case "tree":
131
- case "flow":
132
- case "processArrow":
133
- case "pyramid":
134
- case "line":
135
- case "icon":
112
+ case "leaf":
136
113
  // 子要素なし
137
114
  break;
138
115
  }
@@ -140,7 +117,7 @@ async function buildPomWithYogaTree(node, parentYoga, parentNode) {
140
117
  /**
141
118
  * node のスタイルを YogaNode に適用する
142
119
  */
143
- async function applyStyleToYogaNode(node, yn) {
120
+ async function applyStyleToYogaNode(node, yn, ctx) {
144
121
  const yoga = await getYoga();
145
122
  // デフォルト: 縦並び
146
123
  yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);
@@ -206,301 +183,68 @@ async function applyStyleToYogaNode(node, yn) {
206
183
  }
207
184
  }
208
185
  }
209
- switch (node.type) {
210
- case "box":
211
- // 特になし
212
- break;
213
- case "vstack": {
214
- yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);
215
- applyFlexProperties(node, yn, yoga);
216
- break;
217
- }
218
- case "hstack": {
219
- yn.setFlexDirection(yoga.FLEX_DIRECTION_ROW);
220
- applyFlexProperties(node, yn, yoga);
221
- break;
186
+ // margin
187
+ if (node.margin !== undefined) {
188
+ if (typeof node.margin === "number") {
189
+ yn.setMargin(yoga.EDGE_TOP, node.margin);
190
+ yn.setMargin(yoga.EDGE_RIGHT, node.margin);
191
+ yn.setMargin(yoga.EDGE_BOTTOM, node.margin);
192
+ yn.setMargin(yoga.EDGE_LEFT, node.margin);
222
193
  }
223
- case "text":
224
- {
225
- const text = node.text;
226
- const fontSizePx = node.fontSize ?? 24;
227
- const fontFamily = "Noto Sans JP";
228
- const fontWeight = node.bold ? "bold" : "normal";
229
- const lineHeight = 1.3;
230
- yn.setMeasureFunc((width, widthMode) => {
231
- const maxWidthPx = (() => {
232
- switch (widthMode) {
233
- case yoga.MEASURE_MODE_UNDEFINED:
234
- return Number.POSITIVE_INFINITY;
235
- case yoga.MEASURE_MODE_EXACTLY:
236
- case yoga.MEASURE_MODE_AT_MOST:
237
- return width;
238
- }
239
- })();
240
- const { widthPx, heightPx } = measureText(text, maxWidthPx, {
241
- fontFamily,
242
- fontSizePx,
243
- lineHeight,
244
- fontWeight,
245
- });
246
- return {
247
- width: widthPx,
248
- height: heightPx,
249
- };
250
- });
251
- }
252
- break;
253
- case "ul":
254
- case "ol":
255
- {
256
- const combinedText = node.items.map((item) => item.text).join("\n");
257
- const fontSizePx = node.fontSize ?? 24;
258
- const fontFamily = "Noto Sans JP";
259
- const fontWeight = node.bold ? "bold" : "normal";
260
- const spacingMultiple = node.lineHeight ?? 1.3;
261
- // PowerPoint の lineHeight はフォントメトリクス(ascent + descent)に
262
- // 対する倍率。fontSizePx × fontMetricsRatio × spacingMultiple で計算する。
263
- const fontMetricsRatio = measureFontLineHeightRatio(fontWeight);
264
- const lineHeight = fontMetricsRatio * spacingMultiple;
265
- // バレット/番号のインデント幅(pptxgenjs DEF_BULLET_MARGIN = 27pt = 36px @96dpi)
266
- const bulletIndentPx = 36;
267
- yn.setMeasureFunc((width, widthMode) => {
268
- const maxWidthPx = (() => {
269
- switch (widthMode) {
270
- case yoga.MEASURE_MODE_UNDEFINED:
271
- return Number.POSITIVE_INFINITY;
272
- case yoga.MEASURE_MODE_EXACTLY:
273
- case yoga.MEASURE_MODE_AT_MOST:
274
- return width;
275
- }
276
- })();
277
- // バレットインデント分を除いたテキスト利用可能幅で計測
278
- const textMaxWidthPx = Math.max(0, maxWidthPx - bulletIndentPx);
279
- const { widthPx, heightPx } = measureText(combinedText, textMaxWidthPx, {
280
- fontFamily,
281
- fontSizePx,
282
- lineHeight,
283
- fontWeight,
284
- });
285
- return {
286
- width: widthPx + bulletIndentPx,
287
- height: heightPx,
288
- };
289
- });
290
- }
291
- break;
292
- case "image":
293
- {
294
- const src = node.src;
295
- yn.setMeasureFunc(() => {
296
- // 画像の実際のサイズを取得
297
- const { widthPx, heightPx } = measureImage(src);
298
- return {
299
- width: widthPx,
300
- height: heightPx,
301
- };
302
- });
303
- }
304
- break;
305
- case "icon":
306
- {
307
- const size = node.size ?? 24;
308
- yn.setMeasureFunc(() => ({
309
- width: size,
310
- height: size,
311
- }));
312
- }
313
- break;
314
- case "table":
315
- {
316
- yn.setMeasureFunc(() => {
317
- const { width, height } = calcTableIntrinsicSize(node);
318
- return {
319
- width,
320
- height,
321
- };
322
- });
323
- }
324
- break;
325
- case "shape":
326
- {
327
- if (node.text) {
328
- // テキストがある場合、テキストサイズを測定
329
- const text = node.text;
330
- const fontSizePx = node.fontSize ?? 24;
331
- const fontFamily = node.fontFamily ?? "Noto Sans JP";
332
- const fontWeight = node.bold ? "bold" : "normal";
333
- const lineHeight = node.lineHeight ?? 1.3;
334
- yn.setMeasureFunc((width, widthMode) => {
335
- const maxWidthPx = (() => {
336
- switch (widthMode) {
337
- case yoga.MEASURE_MODE_UNDEFINED:
338
- return Number.POSITIVE_INFINITY;
339
- case yoga.MEASURE_MODE_EXACTLY:
340
- case yoga.MEASURE_MODE_AT_MOST:
341
- return width;
342
- }
343
- })();
344
- const { widthPx, heightPx } = measureText(text, maxWidthPx, {
345
- fontFamily,
346
- fontSizePx,
347
- lineHeight,
348
- fontWeight,
349
- });
350
- return {
351
- width: widthPx,
352
- height: heightPx,
353
- };
354
- });
355
- }
356
- // テキストがない場合は、明示的にサイズが指定されていることを期待
357
- }
358
- break;
359
- case "processArrow":
360
- {
361
- yn.setMeasureFunc((width, widthMode, height, heightMode) => {
362
- const intrinsic = measureProcessArrow(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
- };
371
- });
372
- }
373
- break;
374
- case "pyramid":
375
- {
376
- yn.setMeasureFunc((width, widthMode, height, heightMode) => {
377
- const intrinsic = measurePyramid(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
- };
386
- });
387
- }
388
- break;
389
- case "timeline":
390
- {
391
- yn.setMeasureFunc((width, widthMode, height, heightMode) => {
392
- const intrinsic = measureTimeline(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
- };
401
- });
194
+ else {
195
+ if (node.margin.top !== undefined) {
196
+ yn.setMargin(yoga.EDGE_TOP, node.margin.top);
402
197
  }
403
- break;
404
- case "matrix":
405
- {
406
- yn.setMeasureFunc((width, widthMode, height, heightMode) => {
407
- const intrinsic = measureMatrix(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
- };
416
- });
198
+ if (node.margin.right !== undefined) {
199
+ yn.setMargin(yoga.EDGE_RIGHT, node.margin.right);
417
200
  }
418
- break;
419
- case "tree":
420
- {
421
- yn.setMeasureFunc((width, widthMode, height, heightMode) => {
422
- const intrinsic = measureTree(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
- };
431
- });
201
+ if (node.margin.bottom !== undefined) {
202
+ yn.setMargin(yoga.EDGE_BOTTOM, node.margin.bottom);
432
203
  }
433
- break;
434
- case "flow":
435
- {
436
- yn.setMeasureFunc((width, widthMode, height, heightMode) => {
437
- const intrinsic = measureFlow(node);
438
- return {
439
- width: widthMode !== yoga.MEASURE_MODE_UNDEFINED
440
- ? constrainWithMinScale(intrinsic.width, width)
441
- : intrinsic.width,
442
- height: heightMode !== yoga.MEASURE_MODE_UNDEFINED
443
- ? constrainWithMinScale(intrinsic.height, height)
444
- : intrinsic.height,
445
- };
446
- });
204
+ if (node.margin.left !== undefined) {
205
+ yn.setMargin(yoga.EDGE_LEFT, node.margin.left);
447
206
  }
448
- break;
449
- case "line":
450
- // line ノードは絶対座標を使用するため、Yoga レイアウトではサイズ 0 として扱う
451
- yn.setWidth(0);
452
- yn.setHeight(0);
453
- break;
454
- case "layer":
455
- // layer は子を絶対配置するコンテナ
456
- // サイズは明示的に指定されることを期待
457
- break;
207
+ }
458
208
  }
459
- }
460
- /**
461
- * vstack/hstack 共通の Flex プロパティを適用する
462
- */
463
- function applyFlexProperties(node, yn, yoga) {
464
- if (node.gap !== undefined) {
465
- yn.setGap(yoga.GUTTER_ROW, node.gap);
466
- yn.setGap(yoga.GUTTER_COLUMN, node.gap);
209
+ // position
210
+ if (node.position === "absolute") {
211
+ yn.setPositionType(yoga.POSITION_TYPE_ABSOLUTE);
212
+ }
213
+ if (node.top !== undefined) {
214
+ yn.setPosition(yoga.EDGE_TOP, node.top);
467
215
  }
468
- if (node.alignItems !== undefined) {
469
- switch (node.alignItems) {
216
+ if (node.right !== undefined) {
217
+ yn.setPosition(yoga.EDGE_RIGHT, node.right);
218
+ }
219
+ if (node.bottom !== undefined) {
220
+ yn.setPosition(yoga.EDGE_BOTTOM, node.bottom);
221
+ }
222
+ if (node.left !== undefined) {
223
+ yn.setPosition(yoga.EDGE_LEFT, node.left);
224
+ }
225
+ // alignSelf
226
+ if (node.alignSelf !== undefined) {
227
+ switch (node.alignSelf) {
228
+ case "auto":
229
+ yn.setAlignSelf(yoga.ALIGN_AUTO);
230
+ break;
470
231
  case "start":
471
- yn.setAlignItems(yoga.ALIGN_FLEX_START);
232
+ yn.setAlignSelf(yoga.ALIGN_FLEX_START);
472
233
  break;
473
234
  case "center":
474
- yn.setAlignItems(yoga.ALIGN_CENTER);
235
+ yn.setAlignSelf(yoga.ALIGN_CENTER);
475
236
  break;
476
237
  case "end":
477
- yn.setAlignItems(yoga.ALIGN_FLEX_END);
238
+ yn.setAlignSelf(yoga.ALIGN_FLEX_END);
478
239
  break;
479
240
  case "stretch":
480
- yn.setAlignItems(yoga.ALIGN_STRETCH);
241
+ yn.setAlignSelf(yoga.ALIGN_STRETCH);
481
242
  break;
482
243
  }
483
244
  }
484
- if (node.justifyContent !== undefined) {
485
- switch (node.justifyContent) {
486
- case "start":
487
- yn.setJustifyContent(yoga.JUSTIFY_FLEX_START);
488
- break;
489
- case "center":
490
- yn.setJustifyContent(yoga.JUSTIFY_CENTER);
491
- break;
492
- case "end":
493
- yn.setJustifyContent(yoga.JUSTIFY_FLEX_END);
494
- break;
495
- case "spaceBetween":
496
- yn.setJustifyContent(yoga.JUSTIFY_SPACE_BETWEEN);
497
- break;
498
- case "spaceAround":
499
- yn.setJustifyContent(yoga.JUSTIFY_SPACE_AROUND);
500
- break;
501
- case "spaceEvenly":
502
- yn.setJustifyContent(yoga.JUSTIFY_SPACE_EVENLY);
503
- break;
504
- }
245
+ // ノード固有のスタイル適用(measureFunc 等)
246
+ const def = getNodeDef(node.type);
247
+ if (def.applyYogaStyle) {
248
+ await def.applyYogaStyle(node, yn, yoga, ctx);
505
249
  }
506
250
  }
@@ -5,14 +5,10 @@ type MeasureOptions = {
5
5
  lineHeight?: number;
6
6
  };
7
7
  export type TextMeasurementMode = "opentype" | "fallback" | "auto";
8
- /**
9
- * テキスト計測モードを設定する
10
- */
11
- export declare function setTextMeasurementMode(mode: TextMeasurementMode): void;
12
8
  /**
13
9
  * テキストを折り返し付きでレイアウトし、そのサイズを測定する
14
10
  */
15
- export declare function measureText(text: string, maxWidthPx: number, opts: MeasureOptions): {
11
+ export declare function measureText(text: string, maxWidthPx: number, opts: MeasureOptions, mode?: TextMeasurementMode): {
16
12
  widthPx: number;
17
13
  heightPx: number;
18
14
  };
@@ -1 +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;AAEF,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;AAoHnE;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAEtE;AAcD;;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,CAmBA"}
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;AAEF,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;AA6HnE;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,cAAc,EACpB,IAAI,GAAE,mBAA4B,GACjC;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAmBA"}
@@ -94,14 +94,6 @@ function calculateResult(lines, opts) {
94
94
  // 端数切り上げ+余裕分 10px を足す
95
95
  return { widthPx: widthPx + 10, heightPx };
96
96
  }
97
- // 現在のテキスト計測モード
98
- let currentMode = "auto";
99
- /**
100
- * テキスト計測モードを設定する
101
- */
102
- export function setTextMeasurementMode(mode) {
103
- currentMode = mode;
104
- }
105
97
  /**
106
98
  * fontWeight を "normal" | "bold" に正規化する
107
99
  */
@@ -114,10 +106,10 @@ function normalizeFontWeight(weight) {
114
106
  /**
115
107
  * テキストを折り返し付きでレイアウトし、そのサイズを測定する
116
108
  */
117
- export function measureText(text, maxWidthPx, opts) {
109
+ export function measureText(text, maxWidthPx, opts, mode = "auto") {
118
110
  // 計測方法を決定
119
111
  const shouldUseFallback = (() => {
120
- switch (currentMode) {
112
+ switch (mode) {
121
113
  case "opentype":
122
114
  return false;
123
115
  case "fallback":
@@ -1,2 +1,2 @@
1
- export declare function rasterizeIcon(name: string, size: number, color: string): string;
1
+ export declare function rasterizeIcon(name: string, size: number, color: string, cache: Map<string, string>): string;
2
2
  //# sourceMappingURL=renderIcon.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderIcon.d.ts","sourceRoot":"","sources":["../../src/icons/renderIcon.ts"],"names":[],"mappings":"AAaA,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,MAAM,CAYR"}
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"}
@@ -1,6 +1,5 @@
1
1
  import { Resvg } from "@resvg/resvg-js";
2
2
  import { ICON_DATA } from "./iconData.js";
3
- const rasterCache = new Map();
4
3
  function buildIconSvg(name, size, color) {
5
4
  const pathData = ICON_DATA[name];
6
5
  if (!pathData) {
@@ -8,9 +7,9 @@ function buildIconSvg(name, size, color) {
8
7
  }
9
8
  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>`;
10
9
  }
11
- export function rasterizeIcon(name, size, color) {
10
+ export function rasterizeIcon(name, size, color, cache) {
12
11
  const key = `${name}|${size}|${color}`;
13
- const cached = rasterCache.get(key);
12
+ const cached = cache.get(key);
14
13
  if (cached)
15
14
  return cached;
16
15
  const svg = buildIconSvg(name, size, color);
@@ -18,6 +17,6 @@ export function rasterizeIcon(name, size, color) {
18
17
  const pngData = resvg.render();
19
18
  const pngBuffer = pngData.asPng();
20
19
  const result = `image/png;base64,${Buffer.from(pngBuffer).toString("base64")}`;
21
- rasterCache.set(key, result);
20
+ cache.set(key, result);
22
21
  return result;
23
22
  }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 明示的な型変換ルール定義
3
+ *
4
+ * Zod の内部構造(_def)に依存せず、XML 属性値の文字列→適切な型への変換ルールを
5
+ * 静的に定義する。各ノードタイプ・子要素タイプごとに変換テーブルを持つ。
6
+ */
7
+ export type CoercionRule = "number" | "boolean" | "string" | "json" | {
8
+ type: "literal";
9
+ value: string | number | boolean;
10
+ } | {
11
+ type: "union";
12
+ options: CoercionRule[];
13
+ } | {
14
+ type: "object";
15
+ shape: Record<string, CoercionRule>;
16
+ };
17
+ export declare function coerceWithRule(value: string, rule: CoercionRule): {
18
+ value: unknown;
19
+ error: string | null;
20
+ };
21
+ export declare function coerceUnionWithRules(value: string, options: CoercionRule[]): unknown;
22
+ export declare function coerceFallback(value: string): unknown;
23
+ /**
24
+ * CoercionRule からオブジェクト型の shape を取得する。
25
+ * dot notation の展開で使用。
26
+ */
27
+ export declare function getObjectShapeFromRule(rule: CoercionRule): Record<string, CoercionRule> | undefined;
28
+ /**
29
+ * boolean と object の union かどうかを判定する。
30
+ * endArrow="true" と endArrow.type="triangle" の共存を許可するために使用。
31
+ */
32
+ export declare function isBooleanObjectUnionRule(rule: CoercionRule): boolean;
33
+ export declare const NODE_COERCION_MAP: Record<string, Record<string, CoercionRule>>;
34
+ export declare const CHILD_ELEMENT_COERCION_MAP: Record<string, Record<string, CoercionRule>>;
35
+ //# sourceMappingURL=coercionRules.d.ts.map
@@ -0,0 +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,CAgJ1E,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAC7C,MAAM,EACN,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA6E7B,CAAC"}