@hirokisakabe/pom 8.2.1 → 8.4.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 (136) hide show
  1. package/README.md +37 -25
  2. package/dist/autoFit/autoFit.js +1 -1
  3. package/dist/autoFit/autoFit.js.map +1 -1
  4. package/dist/autoFit/strategies/reduceFontSize.js +16 -14
  5. package/dist/autoFit/strategies/reduceFontSize.js.map +1 -1
  6. package/dist/autoFit/strategies/reduceGapAndPadding.js +13 -20
  7. package/dist/autoFit/strategies/reduceGapAndPadding.js.map +1 -1
  8. package/dist/autoFit/strategies/reduceTableRowHeight.js +8 -2
  9. package/dist/autoFit/strategies/reduceTableRowHeight.js.map +1 -1
  10. package/dist/autoFit/strategies/uniformScale.js +19 -20
  11. package/dist/autoFit/strategies/uniformScale.js.map +1 -1
  12. package/dist/autoFit/strategyResult.js +15 -0
  13. package/dist/autoFit/strategyResult.js.map +1 -0
  14. package/dist/buildContext.js +3 -1
  15. package/dist/buildContext.js.map +1 -1
  16. package/dist/buildPptx.d.ts.map +1 -1
  17. package/dist/buildPptx.js +5 -1
  18. package/dist/buildPptx.js.map +1 -1
  19. package/dist/calcYogaLayout/calcYogaLayout.js +18 -28
  20. package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
  21. package/dist/calcYogaLayout/fontLoader.js.map +1 -1
  22. package/dist/calcYogaLayout/measureText.d.ts.map +1 -1
  23. package/dist/calcYogaLayout/measureText.js +9 -2
  24. package/dist/calcYogaLayout/measureText.js.map +1 -1
  25. package/dist/diagnostics.d.ts +1 -1
  26. package/dist/diagnostics.d.ts.map +1 -1
  27. package/dist/diagnostics.js.map +1 -1
  28. package/dist/icons/renderIcon.js.map +1 -1
  29. package/dist/parseMasterPptx.js.map +1 -1
  30. package/dist/parseXml/coercionRules.js +48 -9
  31. package/dist/parseXml/coercionRules.js.map +1 -1
  32. package/dist/parseXml/parseXml.d.ts +8 -3
  33. package/dist/parseXml/parseXml.d.ts.map +1 -1
  34. package/dist/parseXml/parseXml.js +192 -209
  35. package/dist/parseXml/parseXml.js.map +1 -1
  36. package/dist/parseXml/serializeXml.d.ts.map +1 -1
  37. package/dist/parseXml/serializeXml.js +13 -17
  38. package/dist/parseXml/serializeXml.js.map +1 -1
  39. package/dist/registry/definitions/arrow.js +2 -2
  40. package/dist/registry/definitions/arrow.js.map +1 -1
  41. package/dist/registry/definitions/chart.js +2 -2
  42. package/dist/registry/definitions/chart.js.map +1 -1
  43. package/dist/registry/definitions/compositeNodes.js +7 -12
  44. package/dist/registry/definitions/compositeNodes.js.map +1 -1
  45. package/dist/registry/definitions/icon.js +2 -2
  46. package/dist/registry/definitions/icon.js.map +1 -1
  47. package/dist/registry/definitions/image.js +2 -2
  48. package/dist/registry/definitions/image.js.map +1 -1
  49. package/dist/registry/definitions/layer.js +4 -5
  50. package/dist/registry/definitions/layer.js.map +1 -1
  51. package/dist/registry/definitions/line.js +2 -2
  52. package/dist/registry/definitions/line.js.map +1 -1
  53. package/dist/registry/definitions/list.js +3 -4
  54. package/dist/registry/definitions/list.js.map +1 -1
  55. package/dist/registry/definitions/shape.js +2 -2
  56. package/dist/registry/definitions/shape.js.map +1 -1
  57. package/dist/registry/definitions/stack.js +3 -4
  58. package/dist/registry/definitions/stack.js.map +1 -1
  59. package/dist/registry/definitions/svg.js +2 -2
  60. package/dist/registry/definitions/svg.js.map +1 -1
  61. package/dist/registry/definitions/table.js +2 -2
  62. package/dist/registry/definitions/table.js.map +1 -1
  63. package/dist/registry/definitions/text.js +5 -3
  64. package/dist/registry/definitions/text.js.map +1 -1
  65. package/dist/registry/index.js.map +1 -1
  66. package/dist/registry/nodeMetadata.js +208 -0
  67. package/dist/registry/nodeMetadata.js.map +1 -0
  68. package/dist/registry/nodeRegistry.js +3 -0
  69. package/dist/registry/nodeRegistry.js.map +1 -1
  70. package/dist/registry/xmlChildRules.js +55 -0
  71. package/dist/registry/xmlChildRules.js.map +1 -0
  72. package/dist/renderPptx/gradientFills.js +139 -0
  73. package/dist/renderPptx/gradientFills.js.map +1 -0
  74. package/dist/renderPptx/nodes/arrow.js +7 -28
  75. package/dist/renderPptx/nodes/arrow.js.map +1 -1
  76. package/dist/renderPptx/nodes/chart.js +2 -7
  77. package/dist/renderPptx/nodes/chart.js.map +1 -1
  78. package/dist/renderPptx/nodes/flow.js +6 -13
  79. package/dist/renderPptx/nodes/flow.js.map +1 -1
  80. package/dist/renderPptx/nodes/icon.js +4 -2
  81. package/dist/renderPptx/nodes/icon.js.map +1 -1
  82. package/dist/renderPptx/nodes/image.js +5 -13
  83. package/dist/renderPptx/nodes/image.js.map +1 -1
  84. package/dist/renderPptx/nodes/line.js +9 -33
  85. package/dist/renderPptx/nodes/line.js.map +1 -1
  86. package/dist/renderPptx/nodes/list.js +8 -20
  87. package/dist/renderPptx/nodes/list.js.map +1 -1
  88. package/dist/renderPptx/nodes/matrix.js +10 -11
  89. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  90. package/dist/renderPptx/nodes/processArrow.js +9 -16
  91. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  92. package/dist/renderPptx/nodes/pyramid.js +5 -7
  93. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  94. package/dist/renderPptx/nodes/shape.js +7 -20
  95. package/dist/renderPptx/nodes/shape.js.map +1 -1
  96. package/dist/renderPptx/nodes/svg.js +2 -5
  97. package/dist/renderPptx/nodes/svg.js.map +1 -1
  98. package/dist/renderPptx/nodes/table.js +2 -5
  99. package/dist/renderPptx/nodes/table.js.map +1 -1
  100. package/dist/renderPptx/nodes/text.js +22 -15
  101. package/dist/renderPptx/nodes/text.js.map +1 -1
  102. package/dist/renderPptx/nodes/timeline.js +20 -22
  103. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  104. package/dist/renderPptx/nodes/tree.js +5 -5
  105. package/dist/renderPptx/nodes/tree.js.map +1 -1
  106. package/dist/renderPptx/renderPptx.js +18 -30
  107. package/dist/renderPptx/renderPptx.js.map +1 -1
  108. package/dist/renderPptx/textOptions.js +34 -9
  109. package/dist/renderPptx/textOptions.js.map +1 -1
  110. package/dist/renderPptx/units.js +11 -1
  111. package/dist/renderPptx/units.js.map +1 -1
  112. package/dist/renderPptx/utils/backgroundBorder.js +107 -59
  113. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  114. package/dist/renderPptx/utils/contentArea.js +26 -9
  115. package/dist/renderPptx/utils/contentArea.js.map +1 -1
  116. package/dist/renderPptx/utils/scaleToFit.js +17 -1
  117. package/dist/renderPptx/utils/scaleToFit.js.map +1 -1
  118. package/dist/renderPptx/utils/straightLine.js +41 -0
  119. package/dist/renderPptx/utils/straightLine.js.map +1 -0
  120. package/dist/renderPptx/utils/visualStyle.js +113 -0
  121. package/dist/renderPptx/utils/visualStyle.js.map +1 -0
  122. package/dist/shared/boxSpacing.js +63 -0
  123. package/dist/shared/boxSpacing.js.map +1 -0
  124. package/dist/shared/gradient.js +103 -0
  125. package/dist/shared/gradient.js.map +1 -0
  126. package/dist/shared/measureImage.js.map +1 -1
  127. package/dist/shared/tableUtils.js.map +1 -1
  128. package/dist/shared/walkTree.js +1 -7
  129. package/dist/shared/walkTree.js.map +1 -1
  130. package/dist/toPositioned/toPositioned.js +1 -1
  131. package/dist/toPositioned/toPositioned.js.map +1 -1
  132. package/dist/types.d.ts +1166 -93
  133. package/dist/types.d.ts.map +1 -1
  134. package/dist/types.js +54 -18
  135. package/dist/types.js.map +1 -1
  136. package/package.json +10 -9
@@ -1,5 +1,6 @@
1
1
  import { prefetchImageSize } from "../shared/measureImage.js";
2
2
  import { freeYogaTree } from "../shared/freeYogaTree.js";
3
+ import { resolveBoxSpacing } from "../shared/boxSpacing.js";
3
4
  import { getNodeDef } from "../registry/nodeRegistry.js";
4
5
  import "../registry/index.js";
5
6
  import { loadYoga } from "yoga-layout/load";
@@ -46,13 +47,9 @@ function collectImageSources(node) {
46
47
  if (n.backgroundImage) sources.push(n.backgroundImage.src);
47
48
  const def = getNodeDef(n.type);
48
49
  if (def.collectImageSources) sources.push(...def.collectImageSources(n));
49
- switch (def.category) {
50
- case "multi-child":
51
- case "absolute-child": {
52
- const containerNode = n;
53
- for (const child of containerNode.children) traverse(child);
54
- break;
55
- }
50
+ if (def.category === "multi-child" || def.category === "absolute-child") {
51
+ const containerNode = n;
52
+ for (const child of containerNode.children) traverse(child);
56
53
  }
57
54
  }
58
55
  traverse(node);
@@ -92,7 +89,7 @@ async function buildPomWithYogaTree(node, parentYoga, ctx, map, parentNode, gran
92
89
  await applyStyleToYogaNode(node, yn, ctx);
93
90
  if (parentNode?.type === "hstack" || parentNode?.type === "vstack") yn.setFlexShrink(node.type === "icon" ? 0 : 1);
94
91
  if (parentNode?.type === "hstack" && node.w === void 0 && node.type !== "table" && node.type !== "icon") {
95
- yn.setFlexGrow(1);
92
+ yn.setFlexGrow(node.grow ?? 1);
96
93
  if (nodeHasDefiniteWidth(parentNode, grandparentNode)) yn.setFlexBasis(0);
97
94
  }
98
95
  parentYoga.insertChild(yn, parentYoga.getChildCount());
@@ -128,31 +125,24 @@ async function applyStyleToYogaNode(node, yn, ctx) {
128
125
  yn.setHeightPercent(percent);
129
126
  }
130
127
  }
128
+ if (node.grow !== void 0) yn.setFlexGrow(node.grow);
131
129
  if (node.minW !== void 0) yn.setMinWidth(node.minW);
132
130
  if (node.maxW !== void 0) yn.setMaxWidth(node.maxW);
133
131
  if (node.minH !== void 0) yn.setMinHeight(node.minH);
134
132
  if (node.maxH !== void 0) yn.setMaxHeight(node.maxH);
135
- if (node.padding !== void 0) if (typeof node.padding === "number") {
136
- yn.setPadding(yoga.EDGE_TOP, node.padding);
137
- yn.setPadding(yoga.EDGE_RIGHT, node.padding);
138
- yn.setPadding(yoga.EDGE_BOTTOM, node.padding);
139
- yn.setPadding(yoga.EDGE_LEFT, node.padding);
140
- } else {
141
- if (node.padding.top !== void 0) yn.setPadding(yoga.EDGE_TOP, node.padding.top);
142
- if (node.padding.right !== void 0) yn.setPadding(yoga.EDGE_RIGHT, node.padding.right);
143
- if (node.padding.bottom !== void 0) yn.setPadding(yoga.EDGE_BOTTOM, node.padding.bottom);
144
- if (node.padding.left !== void 0) yn.setPadding(yoga.EDGE_LEFT, node.padding.left);
133
+ if (node.padding !== void 0) {
134
+ const padding = resolveBoxSpacing(node.padding);
135
+ yn.setPadding(yoga.EDGE_TOP, padding.top);
136
+ yn.setPadding(yoga.EDGE_RIGHT, padding.right);
137
+ yn.setPadding(yoga.EDGE_BOTTOM, padding.bottom);
138
+ yn.setPadding(yoga.EDGE_LEFT, padding.left);
145
139
  }
146
- if (node.margin !== void 0) if (typeof node.margin === "number") {
147
- yn.setMargin(yoga.EDGE_TOP, node.margin);
148
- yn.setMargin(yoga.EDGE_RIGHT, node.margin);
149
- yn.setMargin(yoga.EDGE_BOTTOM, node.margin);
150
- yn.setMargin(yoga.EDGE_LEFT, node.margin);
151
- } else {
152
- if (node.margin.top !== void 0) yn.setMargin(yoga.EDGE_TOP, node.margin.top);
153
- if (node.margin.right !== void 0) yn.setMargin(yoga.EDGE_RIGHT, node.margin.right);
154
- if (node.margin.bottom !== void 0) yn.setMargin(yoga.EDGE_BOTTOM, node.margin.bottom);
155
- if (node.margin.left !== void 0) yn.setMargin(yoga.EDGE_LEFT, node.margin.left);
140
+ if (node.margin !== void 0) {
141
+ const margin = resolveBoxSpacing(node.margin);
142
+ yn.setMargin(yoga.EDGE_TOP, margin.top);
143
+ yn.setMargin(yoga.EDGE_RIGHT, margin.right);
144
+ yn.setMargin(yoga.EDGE_BOTTOM, margin.bottom);
145
+ yn.setMargin(yoga.EDGE_LEFT, margin.left);
156
146
  }
157
147
  if (node.position === "absolute") yn.setPositionType(yoga.POSITION_TYPE_ABSOLUTE);
158
148
  if (node.top !== void 0) yn.setPosition(yoga.EDGE_TOP, node.top);
@@ -1 +1 @@
1
- {"version":3,"file":"calcYogaLayout.js","names":[],"sources":["../../src/calcYogaLayout/calcYogaLayout.ts"],"sourcesContent":["import type { POMNode, AlignItems } from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { YogaNodeMap } from \"./types.ts\";\nimport { Node as YogaNode } from \"yoga-layout\";\nimport { loadYoga } from \"yoga-layout/load\";\nimport { prefetchImageSize } from \"../shared/measureImage.ts\";\nimport { freeYogaTree } from \"../shared/freeYogaTree.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\n\n/**\n * POMNode ツリーを Yoga でレイアウト計算する\n * POMNode と YogaNode の対応を YogaNodeMap として返す\n *\n * @param root 入力 POMNode ツリーのルート\n * @param slideSize スライド全体のサイズ(px)\n * @param ctx BuildContext\n * @returns YogaNodeMap(POMNode → YogaNode のマッピング)\n */\nexport async function calcYogaLayout(\n root: POMNode,\n slideSize: { w: number; h: number },\n ctx: BuildContext,\n): Promise<YogaNodeMap> {\n const Yoga = await getYoga();\n\n // 事前に全画像のサイズを取得(HTTPS対応のため)\n await prefetchAllImageSizes(root, ctx);\n\n const map: YogaNodeMap = new Map();\n\n try {\n const rootYoga = Yoga.Node.create();\n map.set(root, rootYoga);\n\n await buildPomWithYogaTree(root, rootYoga, ctx, map);\n\n // スライド全体サイズを指定\n rootYoga.setWidth(slideSize.w);\n rootYoga.setHeight(slideSize.h);\n\n rootYoga.calculateLayout(slideSize.w, slideSize.h, Yoga.DIRECTION_LTR);\n } catch (e) {\n // 途中で失敗した場合、作成済みの YogaNode を解放してから再 throw\n freeYogaTree(map);\n throw e;\n }\n\n return map;\n}\n\n/**\n * POMNode ツリー内のすべての画像のサイズを事前取得する\n */\nasync function prefetchAllImageSizes(\n node: POMNode,\n ctx: BuildContext,\n): Promise<void> {\n const imageSources = collectImageSources(node);\n await Promise.all(\n imageSources.map((src) =>\n prefetchImageSize(\n src,\n ctx.imageSizeCache,\n ctx.imageDataCache,\n ctx.diagnostics,\n ),\n ),\n );\n}\n\n/**\n * POMNode ツリー内のすべての画像のsrcを収集する\n */\nfunction collectImageSources(node: POMNode): string[] {\n const sources: string[] = [];\n\n function traverse(n: POMNode) {\n // backgroundImage の src を収集(全ノード共通)\n if (n.backgroundImage) {\n sources.push(n.backgroundImage.src);\n }\n\n const def = getNodeDef(n.type);\n\n // ノード固有の画像ソース収集\n if (def.collectImageSources) {\n sources.push(...def.collectImageSources(n));\n }\n\n // 子要素の再帰\n switch (def.category) {\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = n as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n traverse(child);\n }\n break;\n }\n }\n }\n\n traverse(node);\n return sources;\n}\n\n/**\n * Yogaシングルトン\n */\nlet yogaP: Promise<Yoga> | null = null;\ntype Yoga = Awaited<ReturnType<typeof loadYoga>>;\nasync function getYoga(): Promise<Yoga> {\n if (yogaP === null) yogaP = loadYoga();\n return yogaP;\n}\n\n/**\n * ノードが交差軸方向(幅)で確定サイズを持つかを判定する\n * - 明示的な w がある場合は確定\n * - alignSelf で stretch 以外が指定されている場合は不確定\n * - 親の alignItems で stretch(デフォルト)以外が指定されている場合は不確定\n */\nfunction nodeHasDefiniteWidth(node: POMNode, parentNode?: POMNode): boolean {\n // 明示的な幅がある\n if (node.w !== undefined) return true;\n\n // alignSelf で stretch 以外が明示されている場合は不確定\n if (\n node.alignSelf !== undefined &&\n node.alignSelf !== \"stretch\" &&\n node.alignSelf !== \"auto\"\n ) {\n return false;\n }\n\n // 親がいない場合(ルートノード)は確定\n if (!parentNode) return true;\n\n // 親の alignItems を取得(VStack/HStack のみ持つ)\n let parentAlignItems: AlignItems | undefined;\n if (parentNode.type === \"vstack\") {\n parentAlignItems = parentNode.alignItems;\n } else if (parentNode.type === \"hstack\") {\n parentAlignItems = parentNode.alignItems;\n }\n\n // VStack(column 方向)の子の場合、交差軸は水平方向\n // alignItems が stretch(デフォルト)なら子は親幅に伸長される\n if (parentNode.type === \"vstack\" || parentNode.type === \"layer\") {\n return parentAlignItems === undefined || parentAlignItems === \"stretch\";\n }\n\n // HStack の子の場合、幅は主軸方向で flex により決まるため確定とみなす\n if (parentNode.type === \"hstack\") {\n return true;\n }\n\n return true;\n}\n\n/**\n * POMNode ツリーを再帰的に走査し、YogaNode ツリーを構築する\n */\nasync function buildPomWithYogaTree(\n node: POMNode,\n parentYoga: YogaNode,\n ctx: BuildContext,\n map: YogaNodeMap,\n parentNode?: POMNode,\n grandparentNode?: POMNode,\n) {\n const yoga = await getYoga();\n\n const yn = yoga.Node.create();\n map.set(node, yn); // 対応する YogaNode をマップに登録\n\n await applyStyleToYogaNode(node, yn, ctx);\n\n // HStack/VStack の子要素に flexShrink=1 をデフォルト設定(CSS Flexbox と同じ挙動)\n // 主軸方向で %サイズ + gap がある場合の overflow を防ぐ\n // アイコンは固定サイズのコンテンツなので shrink させない\n if (parentNode?.type === \"hstack\" || parentNode?.type === \"vstack\") {\n yn.setFlexShrink(node.type === \"icon\" ? 0 : 1);\n }\n\n // HStack の子要素で幅が指定されていない場合、デフォルトで均等分割\n // テーブルは setMeasureFunc でカラム幅合計を返すため除外\n // アイコンは固定サイズのコンテンツなので除外\n if (\n parentNode?.type === \"hstack\" &&\n node.w === undefined &&\n node.type !== \"table\" &&\n node.type !== \"icon\"\n ) {\n yn.setFlexGrow(1);\n // HStack が確定幅を持つ場合のみ flexBasis=0 で均等分割\n // HStack が auto-sized(親の alignItems が center/start/end 等)の場合、\n // flexBasis=0 だと子要素の自然な幅が失われてレイアウトが崩れるため、\n // flexBasis=auto(デフォルト)のまま維持する\n if (nodeHasDefiniteWidth(parentNode, grandparentNode)) {\n yn.setFlexBasis(0);\n }\n }\n\n parentYoga.insertChild(yn, parentYoga.getChildCount());\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n await buildPomWithYogaTree(child, yn, ctx, map, node, parentNode);\n }\n break;\n }\n case \"leaf\":\n // 子要素なし\n break;\n }\n}\n\n/**\n * node のスタイルを YogaNode に適用する\n */\nasync function applyStyleToYogaNode(\n node: POMNode,\n yn: YogaNode,\n ctx: BuildContext,\n) {\n const yoga = await getYoga();\n\n // デフォルト: 縦並び\n yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);\n\n // width\n if (node.w !== undefined) {\n if (typeof node.w === \"number\") {\n yn.setWidth(node.w);\n } else if (node.w === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.w.endsWith(\"%\")) {\n const percent = parseFloat(node.w);\n yn.setWidthPercent(percent);\n }\n }\n\n // height\n if (node.h !== undefined) {\n if (typeof node.h === \"number\") {\n yn.setHeight(node.h);\n } else if (node.h === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.h.endsWith(\"%\")) {\n const percent = parseFloat(node.h);\n yn.setHeightPercent(percent);\n }\n }\n\n // min/max constraints\n if (node.minW !== undefined) {\n yn.setMinWidth(node.minW);\n }\n if (node.maxW !== undefined) {\n yn.setMaxWidth(node.maxW);\n }\n if (node.minH !== undefined) {\n yn.setMinHeight(node.minH);\n }\n if (node.maxH !== undefined) {\n yn.setMaxHeight(node.maxH);\n }\n\n // padding\n if (node.padding !== undefined) {\n if (typeof node.padding === \"number\") {\n yn.setPadding(yoga.EDGE_TOP, node.padding);\n yn.setPadding(yoga.EDGE_RIGHT, node.padding);\n yn.setPadding(yoga.EDGE_BOTTOM, node.padding);\n yn.setPadding(yoga.EDGE_LEFT, node.padding);\n } else {\n if (node.padding.top !== undefined) {\n yn.setPadding(yoga.EDGE_TOP, node.padding.top);\n }\n if (node.padding.right !== undefined) {\n yn.setPadding(yoga.EDGE_RIGHT, node.padding.right);\n }\n if (node.padding.bottom !== undefined) {\n yn.setPadding(yoga.EDGE_BOTTOM, node.padding.bottom);\n }\n if (node.padding.left !== undefined) {\n yn.setPadding(yoga.EDGE_LEFT, node.padding.left);\n }\n }\n }\n\n // margin\n if (node.margin !== undefined) {\n if (typeof node.margin === \"number\") {\n yn.setMargin(yoga.EDGE_TOP, node.margin);\n yn.setMargin(yoga.EDGE_RIGHT, node.margin);\n yn.setMargin(yoga.EDGE_BOTTOM, node.margin);\n yn.setMargin(yoga.EDGE_LEFT, node.margin);\n } else {\n if (node.margin.top !== undefined) {\n yn.setMargin(yoga.EDGE_TOP, node.margin.top);\n }\n if (node.margin.right !== undefined) {\n yn.setMargin(yoga.EDGE_RIGHT, node.margin.right);\n }\n if (node.margin.bottom !== undefined) {\n yn.setMargin(yoga.EDGE_BOTTOM, node.margin.bottom);\n }\n if (node.margin.left !== undefined) {\n yn.setMargin(yoga.EDGE_LEFT, node.margin.left);\n }\n }\n }\n\n // position\n if (node.position === \"absolute\") {\n yn.setPositionType(yoga.POSITION_TYPE_ABSOLUTE);\n }\n if (node.top !== undefined) {\n yn.setPosition(yoga.EDGE_TOP, node.top);\n }\n if (node.right !== undefined) {\n yn.setPosition(yoga.EDGE_RIGHT, node.right);\n }\n if (node.bottom !== undefined) {\n yn.setPosition(yoga.EDGE_BOTTOM, node.bottom);\n }\n if (node.left !== undefined) {\n yn.setPosition(yoga.EDGE_LEFT, node.left);\n }\n\n // alignSelf\n if (node.alignSelf !== undefined) {\n switch (node.alignSelf) {\n case \"auto\":\n yn.setAlignSelf(yoga.ALIGN_AUTO);\n break;\n case \"start\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_START);\n break;\n case \"center\":\n yn.setAlignSelf(yoga.ALIGN_CENTER);\n break;\n case \"end\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_END);\n break;\n case \"stretch\":\n yn.setAlignSelf(yoga.ALIGN_STRETCH);\n break;\n }\n }\n\n // ノード固有のスタイル適用(measureFunc 等)\n const def = getNodeDef(node.type);\n if (def.applyYogaStyle) {\n await def.applyYogaStyle(node, yn, yoga, ctx);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"calcYogaLayout.js","names":[],"sources":["../../src/calcYogaLayout/calcYogaLayout.ts"],"sourcesContent":["import type { POMNode, AlignItems } from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { YogaNodeMap } from \"./types.ts\";\nimport { Node as YogaNode } from \"yoga-layout\";\nimport { loadYoga } from \"yoga-layout/load\";\nimport { prefetchImageSize } from \"../shared/measureImage.ts\";\nimport { freeYogaTree } from \"../shared/freeYogaTree.ts\";\nimport { resolveBoxSpacing } from \"../shared/boxSpacing.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\n\n/**\n * POMNode ツリーを Yoga でレイアウト計算する\n * POMNode と YogaNode の対応を YogaNodeMap として返す\n *\n * @param root 入力 POMNode ツリーのルート\n * @param slideSize スライド全体のサイズ(px)\n * @param ctx BuildContext\n * @returns YogaNodeMap(POMNode → YogaNode のマッピング)\n */\nexport async function calcYogaLayout(\n root: POMNode,\n slideSize: { w: number; h: number },\n ctx: BuildContext,\n): Promise<YogaNodeMap> {\n const Yoga = await getYoga();\n\n // 事前に全画像のサイズを取得(HTTPS対応のため)\n await prefetchAllImageSizes(root, ctx);\n\n const map: YogaNodeMap = new Map();\n\n try {\n const rootYoga = Yoga.Node.create();\n map.set(root, rootYoga);\n\n await buildPomWithYogaTree(root, rootYoga, ctx, map);\n\n // スライド全体サイズを指定\n rootYoga.setWidth(slideSize.w);\n rootYoga.setHeight(slideSize.h);\n\n rootYoga.calculateLayout(slideSize.w, slideSize.h, Yoga.DIRECTION_LTR);\n } catch (e) {\n // 途中で失敗した場合、作成済みの YogaNode を解放してから再 throw\n freeYogaTree(map);\n throw e;\n }\n\n return map;\n}\n\n/**\n * POMNode ツリー内のすべての画像のサイズを事前取得する\n */\nasync function prefetchAllImageSizes(\n node: POMNode,\n ctx: BuildContext,\n): Promise<void> {\n const imageSources = collectImageSources(node);\n await Promise.all(\n imageSources.map((src) =>\n prefetchImageSize(\n src,\n ctx.imageSizeCache,\n ctx.imageDataCache,\n ctx.diagnostics,\n ),\n ),\n );\n}\n\n/**\n * POMNode ツリー内のすべての画像のsrcを収集する\n */\nfunction collectImageSources(node: POMNode): string[] {\n const sources: string[] = [];\n\n function traverse(n: POMNode) {\n // backgroundImage の src を収集(全ノード共通)\n if (n.backgroundImage) {\n sources.push(n.backgroundImage.src);\n }\n\n const def = getNodeDef(n.type);\n\n // ノード固有の画像ソース収集\n if (def.collectImageSources) {\n sources.push(...def.collectImageSources(n));\n }\n\n // 子要素の再帰\n if (def.category === \"multi-child\" || def.category === \"absolute-child\") {\n const containerNode = n as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n traverse(child);\n }\n }\n }\n\n traverse(node);\n return sources;\n}\n\n/**\n * Yogaシングルトン\n */\nlet yogaP: Promise<Yoga> | null = null;\ntype Yoga = Awaited<ReturnType<typeof loadYoga>>;\nasync function getYoga(): Promise<Yoga> {\n if (yogaP === null) yogaP = loadYoga();\n return yogaP;\n}\n\n/**\n * ノードが交差軸方向(幅)で確定サイズを持つかを判定する\n * - 明示的な w がある場合は確定\n * - alignSelf で stretch 以外が指定されている場合は不確定\n * - 親の alignItems で stretch(デフォルト)以外が指定されている場合は不確定\n */\nfunction nodeHasDefiniteWidth(node: POMNode, parentNode?: POMNode): boolean {\n // 明示的な幅がある\n if (node.w !== undefined) return true;\n\n // alignSelf で stretch 以外が明示されている場合は不確定\n if (\n node.alignSelf !== undefined &&\n node.alignSelf !== \"stretch\" &&\n node.alignSelf !== \"auto\"\n ) {\n return false;\n }\n\n // 親がいない場合(ルートノード)は確定\n if (!parentNode) return true;\n\n // 親の alignItems を取得(VStack/HStack のみ持つ)\n let parentAlignItems: AlignItems | undefined;\n if (parentNode.type === \"vstack\") {\n parentAlignItems = parentNode.alignItems;\n } else if (parentNode.type === \"hstack\") {\n parentAlignItems = parentNode.alignItems;\n }\n\n // VStack(column 方向)の子の場合、交差軸は水平方向\n // alignItems が stretch(デフォルト)なら子は親幅に伸長される\n if (parentNode.type === \"vstack\" || parentNode.type === \"layer\") {\n return parentAlignItems === undefined || parentAlignItems === \"stretch\";\n }\n\n // HStack の子の場合、幅は主軸方向で flex により決まるため確定とみなす\n if (parentNode.type === \"hstack\") {\n return true;\n }\n\n return true;\n}\n\n/**\n * POMNode ツリーを再帰的に走査し、YogaNode ツリーを構築する\n */\nasync function buildPomWithYogaTree(\n node: POMNode,\n parentYoga: YogaNode,\n ctx: BuildContext,\n map: YogaNodeMap,\n parentNode?: POMNode,\n grandparentNode?: POMNode,\n) {\n const yoga = await getYoga();\n\n const yn = yoga.Node.create();\n map.set(node, yn); // 対応する YogaNode をマップに登録\n\n await applyStyleToYogaNode(node, yn, ctx);\n\n // HStack/VStack の子要素に flexShrink=1 をデフォルト設定(CSS Flexbox と同じ挙動)\n // 主軸方向で %サイズ + gap がある場合の overflow を防ぐ\n // アイコンは固定サイズのコンテンツなので shrink させない\n if (parentNode?.type === \"hstack\" || parentNode?.type === \"vstack\") {\n yn.setFlexShrink(node.type === \"icon\" ? 0 : 1);\n }\n\n // HStack の子要素で幅が指定されていない場合、grow の比率で余白を配分\n // (grow 未指定は 1 として扱い均等分割)\n // テーブルは setMeasureFunc でカラム幅合計を返すため除外\n // アイコンは固定サイズのコンテンツなので除外\n if (\n parentNode?.type === \"hstack\" &&\n node.w === undefined &&\n node.type !== \"table\" &&\n node.type !== \"icon\"\n ) {\n yn.setFlexGrow(node.grow ?? 1);\n // HStack が確定幅を持つ場合のみ flexBasis=0 で均等分割\n // HStack が auto-sized(親の alignItems が center/start/end 等)の場合、\n // flexBasis=0 だと子要素の自然な幅が失われてレイアウトが崩れるため、\n // flexBasis=auto(デフォルト)のまま維持する\n if (nodeHasDefiniteWidth(parentNode, grandparentNode)) {\n yn.setFlexBasis(0);\n }\n }\n\n parentYoga.insertChild(yn, parentYoga.getChildCount());\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n POMNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n for (const child of containerNode.children) {\n await buildPomWithYogaTree(child, yn, ctx, map, node, parentNode);\n }\n break;\n }\n case \"leaf\":\n // 子要素なし\n break;\n }\n}\n\n/**\n * node のスタイルを YogaNode に適用する\n */\nasync function applyStyleToYogaNode(\n node: POMNode,\n yn: YogaNode,\n ctx: BuildContext,\n) {\n const yoga = await getYoga();\n\n // デフォルト: 縦並び\n yn.setFlexDirection(yoga.FLEX_DIRECTION_COLUMN);\n\n // width\n if (node.w !== undefined) {\n if (typeof node.w === \"number\") {\n yn.setWidth(node.w);\n } else if (node.w === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.w.endsWith(\"%\")) {\n const percent = parseFloat(node.w);\n yn.setWidthPercent(percent);\n }\n }\n\n // height\n if (node.h !== undefined) {\n if (typeof node.h === \"number\") {\n yn.setHeight(node.h);\n } else if (node.h === \"max\") {\n yn.setFlexGrow(1);\n } else if (node.h.endsWith(\"%\")) {\n const percent = parseFloat(node.h);\n yn.setHeightPercent(percent);\n }\n }\n\n // flex-grow(w=\"max\" / h=\"max\" の setFlexGrow(1) より優先)\n if (node.grow !== undefined) {\n yn.setFlexGrow(node.grow);\n }\n\n // min/max constraints\n if (node.minW !== undefined) {\n yn.setMinWidth(node.minW);\n }\n if (node.maxW !== undefined) {\n yn.setMaxWidth(node.maxW);\n }\n if (node.minH !== undefined) {\n yn.setMinHeight(node.minH);\n }\n if (node.maxH !== undefined) {\n yn.setMaxHeight(node.maxH);\n }\n\n // padding(yoga の未設定 edge は 0 扱いのため、解決済みの 0 を明示設定しても等価)\n if (node.padding !== undefined) {\n const padding = resolveBoxSpacing(node.padding);\n yn.setPadding(yoga.EDGE_TOP, padding.top);\n yn.setPadding(yoga.EDGE_RIGHT, padding.right);\n yn.setPadding(yoga.EDGE_BOTTOM, padding.bottom);\n yn.setPadding(yoga.EDGE_LEFT, padding.left);\n }\n\n // margin\n if (node.margin !== undefined) {\n const margin = resolveBoxSpacing(node.margin);\n yn.setMargin(yoga.EDGE_TOP, margin.top);\n yn.setMargin(yoga.EDGE_RIGHT, margin.right);\n yn.setMargin(yoga.EDGE_BOTTOM, margin.bottom);\n yn.setMargin(yoga.EDGE_LEFT, margin.left);\n }\n\n // position\n if (node.position === \"absolute\") {\n yn.setPositionType(yoga.POSITION_TYPE_ABSOLUTE);\n }\n if (node.top !== undefined) {\n yn.setPosition(yoga.EDGE_TOP, node.top);\n }\n if (node.right !== undefined) {\n yn.setPosition(yoga.EDGE_RIGHT, node.right);\n }\n if (node.bottom !== undefined) {\n yn.setPosition(yoga.EDGE_BOTTOM, node.bottom);\n }\n if (node.left !== undefined) {\n yn.setPosition(yoga.EDGE_LEFT, node.left);\n }\n\n // alignSelf\n if (node.alignSelf !== undefined) {\n switch (node.alignSelf) {\n case \"auto\":\n yn.setAlignSelf(yoga.ALIGN_AUTO);\n break;\n case \"start\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_START);\n break;\n case \"center\":\n yn.setAlignSelf(yoga.ALIGN_CENTER);\n break;\n case \"end\":\n yn.setAlignSelf(yoga.ALIGN_FLEX_END);\n break;\n case \"stretch\":\n yn.setAlignSelf(yoga.ALIGN_STRETCH);\n break;\n }\n }\n\n // ノード固有のスタイル適用(measureFunc 等)\n const def = getNodeDef(node.type);\n if (def.applyYogaStyle) {\n await def.applyYogaStyle(node, yn, yoga, ctx);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fontLoader.js","names":[],"sources":["../../src/calcYogaLayout/fontLoader.ts"],"sourcesContent":["/**\n * opentype.js を使用したフォント読み込みモジュール\n * Node.js とブラウザ両方で動作する\n */\n\nimport type { Font } from \"opentype.js\";\nimport * as opentypeModule from \"opentype.js\";\nimport { NOTO_SANS_JP_REGULAR_BASE64 } from \"./fonts/notoSansJPRegular.ts\";\nimport { NOTO_SANS_JP_BOLD_BASE64 } from \"./fonts/notoSansJPBold.ts\";\n\n// opentype.js 2.0 は ESM ビルドで named export のみを提供する一方、\n// CJS UMD ビルドでは module.exports = factory() の動的構造のため\n// Node ESM から取り込むと named exports が静的解析できない。\n// どちらの形でも動くよう default プロパティを優先して unwrap する。\nconst opentype =\n (opentypeModule as unknown as { default?: typeof opentypeModule }).default ??\n opentypeModule;\n\n// フォントキャッシュ\nconst fontCache = new Map<string, Font>();\n\n/**\n * Base64 文字列を ArrayBuffer に変換する\n * Node.js とブラウザ両方で動作する\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Node.js 環境\n if (typeof Buffer !== \"undefined\") {\n const buffer = Buffer.from(base64, \"base64\");\n return buffer.buffer.slice(\n buffer.byteOffset,\n buffer.byteOffset + buffer.byteLength,\n );\n }\n // ブラウザ環境\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * フォントを取得する(キャッシュ付き)\n * @param weight フォントウェイト (\"normal\" or \"bold\")\n * @returns opentype.js の Font オブジェクト\n */\nfunction getFont(weight: \"normal\" | \"bold\"): Font {\n const cacheKey = weight;\n\n // キャッシュがあればそれを返す\n const cached = fontCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Base64 データを選択\n const base64 =\n weight === \"bold\" ? NOTO_SANS_JP_BOLD_BASE64 : NOTO_SANS_JP_REGULAR_BASE64;\n\n // ArrayBuffer に変換してパース\n const buffer = base64ToArrayBuffer(base64);\n const font = opentype.parse(buffer);\n\n // キャッシュに保存\n fontCache.set(cacheKey, font);\n\n return font;\n}\n\n/** バンドル済みフォント名の一覧 */\nconst BUNDLED_FONT_NAMES = new Set([\"Noto Sans JP\"]);\n\n/**\n * 指定されたフォントがバンドル済みかどうかを判定する\n */\nexport function isBundledFont(fontFamily: string): boolean {\n return BUNDLED_FONT_NAMES.has(fontFamily);\n}\n\n/**\n * 指定したテキストの幅を計測する\n * @param text 計測するテキスト\n * @param fontSizePx フォントサイズ(ピクセル)\n * @param weight フォントウェイト\n * @returns テキスト幅(ピクセル)\n */\nexport function measureTextWidth(\n text: string,\n fontSizePx: number,\n weight: \"normal\" | \"bold\",\n): number {\n const font = getFont(weight);\n return font.getAdvanceWidth(text, fontSizePx, { kerning: true });\n}\n\n/**\n * フォントの自然な行高さ比率を取得する\n *\n * PowerPoint の lineHeight はフォントサイズではなく、\n * フォントメトリクス(ascent + descent)に対する倍率として適用される。\n * この関数は fontSizePx に対する自然な行高さの比率を返す。\n *\n * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:\n * sTypoAscender, sTypoDescender, sTypoLineGap を使用\n * - 設定されていない場合:\n * usWinAscent, usWinDescent を使用\n *\n * @param weight フォントウェイト\n * @returns fontSizePx に対する行高さの比率(例: 1.448)\n */\nexport function measureFontLineHeightRatio(weight: \"normal\" | \"bold\"): number {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n return 1.0;\n }\n\n const useTypoMetrics = Boolean(os2.fsSelection & (1 << 7));\n\n if (useTypoMetrics) {\n return (os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap) / upm;\n }\n\n return (os2.usWinAscent + os2.usWinDescent) / upm;\n}\n"],"mappings":";;;;AAcA,MAAM,WACH,eAAkE,WACnE;AAGF,MAAM,4BAAY,IAAI,IAAkB;;;;;AAMxC,SAAS,oBAAoB,QAA6B;CAExD,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ;EAC3C,OAAO,OAAO,OAAO,MACnB,OAAO,YACP,OAAO,aAAa,OAAO,UAC7B;CACF;CAEA,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;CAChD,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,MAAM,KAAK,aAAa,WAAW,CAAC;CAEtC,OAAO,MAAM;AACf;;;;;;AAOA,SAAS,QAAQ,QAAiC;CAChD,MAAM,WAAW;CAGjB,MAAM,SAAS,UAAU,IAAI,QAAQ;CACrC,IAAI,QACF,OAAO;CAQT,MAAM,SAAS,oBAHb,WAAW,SAAS,2BAA2B,2BAGR;CACzC,MAAM,OAAO,SAAS,MAAM,MAAM;CAGlC,UAAU,IAAI,UAAU,IAAI;CAE5B,OAAO;AACT;;AAGA,MAAM,qBAAqB,IAAI,IAAI,CAAC,cAAc,CAAC;;;;AAKnD,SAAgB,cAAc,YAA6B;CACzD,OAAO,mBAAmB,IAAI,UAAU;AAC1C;;;;;;;;AASA,SAAgB,iBACd,MACA,YACA,QACQ;CAER,OADa,QAAQ,MACX,EAAE,gBAAgB,MAAM,YAAY,EAAE,SAAS,KAAK,CAAC;AACjE;;;;;;;;;;;;;;;;AAiBA,SAAgB,2BAA2B,QAAmC;CAC5E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KACH,OAAO;CAKT,IAFuB,QAAQ,IAAI,cAAe,GAEjC,GACf,QAAQ,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,gBAAgB;CAGvE,QAAQ,IAAI,cAAc,IAAI,gBAAgB;AAChD"}
1
+ {"version":3,"file":"fontLoader.js","names":[],"sources":["../../src/calcYogaLayout/fontLoader.ts"],"sourcesContent":["/**\n * opentype.js を使用したフォント読み込みモジュール\n * Node.js とブラウザ両方で動作する\n */\n\nimport type { Font } from \"opentype.js\";\nimport * as opentypeModule from \"opentype.js\";\nimport { NOTO_SANS_JP_REGULAR_BASE64 } from \"./fonts/notoSansJPRegular.ts\";\nimport { NOTO_SANS_JP_BOLD_BASE64 } from \"./fonts/notoSansJPBold.ts\";\n\n// opentype.js 2.0 は ESM ビルドで named export のみを提供する一方、\n// CJS UMD ビルドでは module.exports = factory() の動的構造のため\n// Node ESM から取り込むと named exports が静的解析できない。\n// どちらの形でも動くよう default プロパティを優先して unwrap する。\nconst opentype =\n (opentypeModule as unknown as { default?: typeof opentypeModule }).default ??\n opentypeModule;\n\n// フォントキャッシュ\nconst fontCache = new Map<string, Font>();\n\n/**\n * Base64 文字列を ArrayBuffer に変換する\n * Node.js とブラウザ両方で動作する\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Node.js 環境\n if (typeof Buffer !== \"undefined\") {\n const buffer = Buffer.from(base64, \"base64\");\n return buffer.buffer.slice(\n buffer.byteOffset,\n buffer.byteOffset + buffer.byteLength,\n );\n }\n // ブラウザ環境\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * フォントを取得する(キャッシュ付き)\n * @param weight フォントウェイト (\"normal\" or \"bold\")\n * @returns opentype.js の Font オブジェクト\n */\nfunction getFont(weight: \"normal\" | \"bold\"): Font {\n const cacheKey = weight;\n\n // キャッシュがあればそれを返す\n const cached = fontCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Base64 データを選択\n const base64 =\n weight === \"bold\" ? NOTO_SANS_JP_BOLD_BASE64 : NOTO_SANS_JP_REGULAR_BASE64;\n\n // ArrayBuffer に変換してパース\n const buffer = base64ToArrayBuffer(base64);\n const font = opentype.parse(buffer);\n\n // キャッシュに保存\n fontCache.set(cacheKey, font);\n\n return font;\n}\n\n/** バンドル済みフォント名の一覧 */\nconst BUNDLED_FONT_NAMES = new Set([\"Noto Sans JP\"]);\n\n/**\n * 指定されたフォントがバンドル済みかどうかを判定する\n */\nexport function isBundledFont(fontFamily: string): boolean {\n return BUNDLED_FONT_NAMES.has(fontFamily);\n}\n\n/**\n * 指定したテキストの幅を計測する\n * @param text 計測するテキスト\n * @param fontSizePx フォントサイズ(ピクセル)\n * @param weight フォントウェイト\n * @returns テキスト幅(ピクセル)\n */\nexport function measureTextWidth(\n text: string,\n fontSizePx: number,\n weight: \"normal\" | \"bold\",\n): number {\n const font = getFont(weight);\n return font.getAdvanceWidth(text, fontSizePx, { kerning: true });\n}\n\n/**\n * フォントの自然な行高さ比率を取得する\n *\n * PowerPoint の lineHeight はフォントサイズではなく、\n * フォントメトリクス(ascent + descent)に対する倍率として適用される。\n * この関数は fontSizePx に対する自然な行高さの比率を返す。\n *\n * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:\n * sTypoAscender, sTypoDescender, sTypoLineGap を使用\n * - 設定されていない場合:\n * usWinAscent, usWinDescent を使用\n *\n * @param weight フォントウェイト\n * @returns fontSizePx に対する行高さの比率(例: 1.448)\n */\nexport function measureFontLineHeightRatio(weight: \"normal\" | \"bold\"): number {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n return 1.0;\n }\n\n const useTypoMetrics = Boolean(os2.fsSelection & (1 << 7));\n\n if (useTypoMetrics) {\n return (os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap) / upm;\n }\n\n return (os2.usWinAscent + os2.usWinDescent) / upm;\n}\n"],"mappings":";;;;AAcA,MAAM,WACH,eAAkE,WACnE;AAGF,MAAM,4BAAY,IAAI,IAAkB;;;;;AAMxC,SAAS,oBAAoB,QAA6B;CAExD,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ;EAC3C,OAAO,OAAO,OAAO,MACnB,OAAO,YACP,OAAO,aAAa,OAAO,UAC7B;CACF;CAEA,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;CAChD,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,MAAM,KAAK,aAAa,WAAW,CAAC;CAEtC,OAAO,MAAM;AACf;;;;;;AAOA,SAAS,QAAQ,QAAiC;CAChD,MAAM,WAAW;CAGjB,MAAM,SAAS,UAAU,IAAI,QAAQ;CACrC,IAAI,QACF,OAAO;CAQT,MAAM,SAAS,oBAHb,WAAW,SAAS,2BAA2B,2BAGR;CACzC,MAAM,OAAO,SAAS,MAAM,MAAM;CAGlC,UAAU,IAAI,UAAU,IAAI;CAE5B,OAAO;AACT;;AAGA,MAAM,qBAAqB,IAAI,IAAI,CAAC,cAAc,CAAC;;;;AAKnD,SAAgB,cAAc,YAA6B;CACzD,OAAO,mBAAmB,IAAI,UAAU;AAC1C;;;;;;;;AASA,SAAgB,iBACd,MACA,YACA,QACQ;CAER,OADa,QAAQ,MACX,CAAC,CAAC,gBAAgB,MAAM,YAAY,EAAE,SAAS,KAAK,CAAC;AACjE;;;;;;;;;;;;;;;;AAiBA,SAAgB,2BAA2B,QAAmC;CAC5E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KACH,OAAO;CAKT,IAFuB,QAAQ,IAAI,cAAe,GAEjC,GACf,QAAQ,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,gBAAgB;CAGvE,QAAQ,IAAI,cAAc,IAAI,gBAAgB;AAChD"}
@@ -1 +1 @@
1
- {"version":3,"file":"measureText.d.ts","names":[],"sources":["../../src/calcYogaLayout/measureText.ts"],"mappings":";KAYY,mBAAA"}
1
+ {"version":3,"file":"measureText.d.ts","names":[],"sources":["../../src/calcYogaLayout/measureText.ts"],"mappings":";KAaY,mBAAA"}
@@ -33,6 +33,13 @@ function estimateTextWidth(text, fontSizePx) {
33
33
  return width;
34
34
  }
35
35
  /**
36
+ * 計測関数に letterSpacing(文字数 × 字間 px)の加算を合成する
37
+ */
38
+ function withLetterSpacing(measureWidth, letterSpacingPx) {
39
+ if (!letterSpacingPx) return measureWidth;
40
+ return (text) => measureWidth(text) + Array.from(text).length * letterSpacingPx;
41
+ }
42
+ /**
36
43
  * テキストを折り返して行ごとの幅を計算する
37
44
  */
38
45
  function wrapText(text, maxWidthPx, measureWidth) {
@@ -100,14 +107,14 @@ function measureText(text, maxWidthPx, opts, mode = "auto") {
100
107
  */
101
108
  function measureTextWithOpentype(text, maxWidthPx, opts) {
102
109
  const fontWeight = normalizeFontWeight(opts.fontWeight);
103
- return calculateResult(wrapText(text, maxWidthPx, (t) => measureTextWidth(t, opts.fontSizePx, fontWeight)), opts);
110
+ return calculateResult(wrapText(text, maxWidthPx, withLetterSpacing((t) => measureTextWidth(t, opts.fontSizePx, fontWeight), opts.letterSpacingPx)), opts);
104
111
  }
105
112
  /**
106
113
  * フォールバック計算を使ったテキスト計測
107
114
  */
108
115
  function measureTextFallback(text, maxWidthPx, opts) {
109
116
  const { fontSizePx } = opts;
110
- return calculateResult(wrapText(text, maxWidthPx, (t) => estimateTextWidth(t, fontSizePx)), opts);
117
+ return calculateResult(wrapText(text, maxWidthPx, withLetterSpacing((t) => estimateTextWidth(t, fontSizePx), opts.letterSpacingPx)), opts);
111
118
  }
112
119
  function splitForWrap(text) {
113
120
  if (/[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u.test(text)) return Array.from(text);
@@ -1 +1 @@
1
- {"version":3,"file":"measureText.js","names":["measureTextWidthOpentype"],"sources":["../../src/calcYogaLayout/measureText.ts"],"sourcesContent":["import {\n measureTextWidth as measureTextWidthOpentype,\n isBundledFont,\n} from \"./fontLoader.ts\";\n\ntype MeasureOptions = {\n fontFamily: string;\n fontSizePx: number;\n fontWeight?: \"normal\" | \"bold\" | number;\n lineHeight?: number;\n};\n\nexport type TextMeasurementMode = \"opentype\" | \"fallback\" | \"auto\";\n\n/**\n * 文字がCJK(日本語・中国語・韓国語)文字かどうかを判定する\n */\nfunction isCJKChar(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n // CJK統合漢字\n if (code >= 0x4e00 && code <= 0x9fff) return true;\n // CJK統合漢字拡張A\n if (code >= 0x3400 && code <= 0x4dbf) return true;\n // CJK統合漢字拡張B-F\n if (code >= 0x20000 && code <= 0x2ebef) return true;\n // ひらがな\n if (code >= 0x3040 && code <= 0x309f) return true;\n // カタカナ\n if (code >= 0x30a0 && code <= 0x30ff) return true;\n // 全角英数字・記号\n if (code >= 0xff00 && code <= 0xffef) return true;\n // CJK記号\n if (code >= 0x3000 && code <= 0x303f) return true;\n\n return false;\n}\n\n/**\n * フォールバック計算で文字の幅を推定する\n * - CJK文字: 1em(= fontSizePx)\n * - 英数字・半角記号: 0.5em\n */\nfunction estimateCharWidth(char: string, fontSizePx: number): number {\n if (isCJKChar(char)) {\n return fontSizePx; // 1em\n }\n return fontSizePx * 0.5; // 0.5em\n}\n\n/**\n * フォールバック計算でテキスト幅を推定する\n */\nfunction estimateTextWidth(text: string, fontSizePx: number): number {\n let width = 0;\n for (const char of text) {\n width += estimateCharWidth(char, fontSizePx);\n }\n return width;\n}\n\n/**\n * テキスト幅計測関数の型\n */\ntype MeasureTextWidthFn = (text: string) => number;\n\n/**\n * テキストを折り返して行ごとの幅を計算する\n */\nfunction wrapText(\n text: string,\n maxWidthPx: number,\n measureWidth: MeasureTextWidthFn,\n): { widthPx: number }[] {\n const paragraphs = text.split(\"\\n\");\n const lines: { widthPx: number }[] = [];\n\n for (const paragraph of paragraphs) {\n if (paragraph === \"\") {\n lines.push({ widthPx: 0 });\n continue;\n }\n\n const words = splitForWrap(paragraph);\n let current = \"\";\n let currentWidth = 0;\n\n for (const word of words) {\n const candidate = current ? current + word : word;\n const w = measureWidth(candidate);\n\n if (w <= maxWidthPx || !current) {\n current = candidate;\n currentWidth = w;\n } else {\n lines.push({ widthPx: currentWidth });\n current = word;\n currentWidth = measureWidth(word);\n }\n }\n\n if (current) {\n lines.push({ widthPx: currentWidth });\n }\n }\n\n return lines;\n}\n\n/**\n * 行情報から最終的なサイズを計算する\n */\nfunction calculateResult(\n lines: { widthPx: number }[],\n opts: MeasureOptions,\n): { widthPx: number; heightPx: number } {\n const lineHeightRatio = opts.lineHeight ?? 1.3;\n const lineHeightPx = opts.fontSizePx * lineHeightRatio;\n const widthPx = lines.length ? Math.max(...lines.map((l) => l.widthPx)) : 0;\n const heightPx = lines.length * lineHeightPx;\n // 端数切り上げ+余裕分 10px を足す\n return { widthPx: widthPx + 10, heightPx };\n}\n\n/**\n * fontWeight を \"normal\" | \"bold\" に正規化する\n */\nfunction normalizeFontWeight(\n weight: \"normal\" | \"bold\" | number | undefined,\n): \"normal\" | \"bold\" {\n if (weight === \"bold\" || weight === 700) {\n return \"bold\";\n }\n return \"normal\";\n}\n\n/**\n * テキストを折り返し付きでレイアウトし、そのサイズを測定する\n */\nexport function measureText(\n text: string,\n maxWidthPx: number,\n opts: MeasureOptions,\n mode: TextMeasurementMode = \"auto\",\n): {\n widthPx: number;\n heightPx: number;\n} {\n // 計測方法を決定\n // \"opentype\" / \"fallback\" が明示指定された場合はそれを優先\n // \"auto\" の場合はバンドル外フォントならフォールバック計測を使用\n const shouldUseFallback = (() => {\n switch (mode) {\n case \"opentype\":\n return false;\n case \"fallback\":\n return true;\n case \"auto\":\n return !isBundledFont(opts.fontFamily);\n }\n })();\n\n if (shouldUseFallback) {\n return measureTextFallback(text, maxWidthPx, opts);\n }\n\n return measureTextWithOpentype(text, maxWidthPx, opts);\n}\n\n/**\n * opentype.js を使ったテキスト計測\n */\nfunction measureTextWithOpentype(\n text: string,\n maxWidthPx: number,\n opts: MeasureOptions,\n): { widthPx: number; heightPx: number } {\n const fontWeight = normalizeFontWeight(opts.fontWeight);\n const lines = wrapText(text, maxWidthPx, (t) =>\n measureTextWidthOpentype(t, opts.fontSizePx, fontWeight),\n );\n return calculateResult(lines, opts);\n}\n\n/**\n * フォールバック計算を使ったテキスト計測\n */\nfunction measureTextFallback(\n text: string,\n maxWidthPx: number,\n opts: MeasureOptions,\n): { widthPx: number; heightPx: number } {\n const { fontSizePx } = opts;\n const lines = wrapText(text, maxWidthPx, (t) =>\n estimateTextWidth(t, fontSizePx),\n );\n return calculateResult(lines, opts);\n}\n\n// ラップ用の分割ロジック\n// - 英文: 空白で分割しつつ、空白も行末に残す\n// - 日本語: とりあえず 1 文字ずつ(必要なら賢くする)\nfunction splitForWrap(text: string): string[] {\n // 超雑実装:全角ひらがな・カタカナ・漢字が多そうなら 1 文字ずつ、それ以外は空白区切り\n const hasCJK = /[\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Han}]/u.test(\n text,\n );\n\n if (hasCJK) {\n return Array.from(text); // 1 glyph ≒ 1 文字として扱う\n }\n\n // 英文用:単語 + 後続スペースをトークンにする\n const tokens: string[] = [];\n const re = /(\\S+\\s*|\\s+)/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text))) {\n tokens.push(m[0]);\n }\n return tokens;\n}\n"],"mappings":";;;;;AAiBA,SAAS,UAAU,MAAuB;CACxC,MAAM,OAAO,KAAK,YAAY,CAAC;CAC/B,IAAI,SAAS,KAAA,GAAW,OAAO;CAG/B,IAAI,QAAQ,SAAU,QAAQ,OAAQ,OAAO;CAE7C,IAAI,QAAQ,SAAU,QAAQ,OAAQ,OAAO;CAE7C,IAAI,QAAQ,UAAW,QAAQ,QAAS,OAAO;CAE/C,IAAI,QAAQ,SAAU,QAAQ,OAAQ,OAAO;CAE7C,IAAI,QAAQ,SAAU,QAAQ,OAAQ,OAAO;CAE7C,IAAI,QAAQ,SAAU,QAAQ,OAAQ,OAAO;CAE7C,IAAI,QAAQ,SAAU,QAAQ,OAAQ,OAAO;CAE7C,OAAO;AACT;;;;;;AAOA,SAAS,kBAAkB,MAAc,YAA4B;CACnE,IAAI,UAAU,IAAI,GAChB,OAAO;CAET,OAAO,aAAa;AACtB;;;;AAKA,SAAS,kBAAkB,MAAc,YAA4B;CACnE,IAAI,QAAQ;CACZ,KAAK,MAAM,QAAQ,MACjB,SAAS,kBAAkB,MAAM,UAAU;CAE7C,OAAO;AACT;;;;AAUA,SAAS,SACP,MACA,YACA,cACuB;CACvB,MAAM,aAAa,KAAK,MAAM,IAAI;CAClC,MAAM,QAA+B,CAAC;CAEtC,KAAK,MAAM,aAAa,YAAY;EAClC,IAAI,cAAc,IAAI;GACpB,MAAM,KAAK,EAAE,SAAS,EAAE,CAAC;GACzB;EACF;EAEA,MAAM,QAAQ,aAAa,SAAS;EACpC,IAAI,UAAU;EACd,IAAI,eAAe;EAEnB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,YAAY,UAAU,UAAU,OAAO;GAC7C,MAAM,IAAI,aAAa,SAAS;GAEhC,IAAI,KAAK,cAAc,CAAC,SAAS;IAC/B,UAAU;IACV,eAAe;GACjB,OAAO;IACL,MAAM,KAAK,EAAE,SAAS,aAAa,CAAC;IACpC,UAAU;IACV,eAAe,aAAa,IAAI;GAClC;EACF;EAEA,IAAI,SACF,MAAM,KAAK,EAAE,SAAS,aAAa,CAAC;CAExC;CAEA,OAAO;AACT;;;;AAKA,SAAS,gBACP,OACA,MACuC;CACvC,MAAM,kBAAkB,KAAK,cAAc;CAC3C,MAAM,eAAe,KAAK,aAAa;CACvC,MAAM,UAAU,MAAM,SAAS,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,OAAO,CAAC,IAAI;CAC1E,MAAM,WAAW,MAAM,SAAS;CAEhC,OAAO;EAAE,SAAS,UAAU;EAAI;CAAS;AAC3C;;;;AAKA,SAAS,oBACP,QACmB;CACnB,IAAI,WAAW,UAAU,WAAW,KAClC,OAAO;CAET,OAAO;AACT;;;;AAKA,SAAgB,YACd,MACA,YACA,MACA,OAA4B,QAI5B;CAeA,WAXiC;EAC/B,QAAQ,MAAR;GACE,KAAK,YACH,OAAO;GACT,KAAK,YACH,OAAO;GACT,KAAK,QACH,OAAO,CAAC,cAAc,KAAK,UAAU;EACzC;CACF,GAEoB,GAClB,OAAO,oBAAoB,MAAM,YAAY,IAAI;CAGnD,OAAO,wBAAwB,MAAM,YAAY,IAAI;AACvD;;;;AAKA,SAAS,wBACP,MACA,YACA,MACuC;CACvC,MAAM,aAAa,oBAAoB,KAAK,UAAU;CAItD,OAAO,gBAHO,SAAS,MAAM,aAAa,MACxCA,iBAAyB,GAAG,KAAK,YAAY,UAAU,CAE9B,GAAG,IAAI;AACpC;;;;AAKA,SAAS,oBACP,MACA,YACA,MACuC;CACvC,MAAM,EAAE,eAAe;CAIvB,OAAO,gBAHO,SAAS,MAAM,aAAa,MACxC,kBAAkB,GAAG,UAAU,CAEN,GAAG,IAAI;AACpC;AAKA,SAAS,aAAa,MAAwB;CAM5C,IAJe,0DAA0D,KACvE,IAGO,GACP,OAAO,MAAM,KAAK,IAAI;CAIxB,MAAM,SAAmB,CAAC;CAC1B,MAAM,KAAK;CACX,IAAI;CACJ,OAAQ,IAAI,GAAG,KAAK,IAAI,GACtB,OAAO,KAAK,EAAE,EAAE;CAElB,OAAO;AACT"}
1
+ {"version":3,"file":"measureText.js","names":["measureTextWidthOpentype"],"sources":["../../src/calcYogaLayout/measureText.ts"],"sourcesContent":["import {\n measureTextWidth as measureTextWidthOpentype,\n isBundledFont,\n} from \"./fontLoader.ts\";\n\ntype MeasureOptions = {\n fontFamily: string;\n fontSizePx: number;\n fontWeight?: \"normal\" | \"bold\" | number;\n lineHeight?: number;\n letterSpacingPx?: number;\n};\n\nexport type TextMeasurementMode = \"opentype\" | \"fallback\" | \"auto\";\n\n/**\n * 文字がCJK(日本語・中国語・韓国語)文字かどうかを判定する\n */\nfunction isCJKChar(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n // CJK統合漢字\n if (code >= 0x4e00 && code <= 0x9fff) return true;\n // CJK統合漢字拡張A\n if (code >= 0x3400 && code <= 0x4dbf) return true;\n // CJK統合漢字拡張B-F\n if (code >= 0x20000 && code <= 0x2ebef) return true;\n // ひらがな\n if (code >= 0x3040 && code <= 0x309f) return true;\n // カタカナ\n if (code >= 0x30a0 && code <= 0x30ff) return true;\n // 全角英数字・記号\n if (code >= 0xff00 && code <= 0xffef) return true;\n // CJK記号\n if (code >= 0x3000 && code <= 0x303f) return true;\n\n return false;\n}\n\n/**\n * フォールバック計算で文字の幅を推定する\n * - CJK文字: 1em(= fontSizePx)\n * - 英数字・半角記号: 0.5em\n */\nfunction estimateCharWidth(char: string, fontSizePx: number): number {\n if (isCJKChar(char)) {\n return fontSizePx; // 1em\n }\n return fontSizePx * 0.5; // 0.5em\n}\n\n/**\n * フォールバック計算でテキスト幅を推定する\n */\nfunction estimateTextWidth(text: string, fontSizePx: number): number {\n let width = 0;\n for (const char of text) {\n width += estimateCharWidth(char, fontSizePx);\n }\n return width;\n}\n\n/**\n * テキスト幅計測関数の型\n */\ntype MeasureTextWidthFn = (text: string) => number;\n\n/**\n * 計測関数に letterSpacing(文字数 × 字間 px)の加算を合成する\n */\nfunction withLetterSpacing(\n measureWidth: MeasureTextWidthFn,\n letterSpacingPx: number | undefined,\n): MeasureTextWidthFn {\n if (!letterSpacingPx) return measureWidth;\n return (text) =>\n measureWidth(text) + Array.from(text).length * letterSpacingPx;\n}\n\n/**\n * テキストを折り返して行ごとの幅を計算する\n */\nfunction wrapText(\n text: string,\n maxWidthPx: number,\n measureWidth: MeasureTextWidthFn,\n): { widthPx: number }[] {\n const paragraphs = text.split(\"\\n\");\n const lines: { widthPx: number }[] = [];\n\n for (const paragraph of paragraphs) {\n if (paragraph === \"\") {\n lines.push({ widthPx: 0 });\n continue;\n }\n\n const words = splitForWrap(paragraph);\n let current = \"\";\n let currentWidth = 0;\n\n for (const word of words) {\n const candidate = current ? current + word : word;\n const w = measureWidth(candidate);\n\n if (w <= maxWidthPx || !current) {\n current = candidate;\n currentWidth = w;\n } else {\n lines.push({ widthPx: currentWidth });\n current = word;\n currentWidth = measureWidth(word);\n }\n }\n\n if (current) {\n lines.push({ widthPx: currentWidth });\n }\n }\n\n return lines;\n}\n\n/**\n * 行情報から最終的なサイズを計算する\n */\nfunction calculateResult(\n lines: { widthPx: number }[],\n opts: MeasureOptions,\n): { widthPx: number; heightPx: number } {\n const lineHeightRatio = opts.lineHeight ?? 1.3;\n const lineHeightPx = opts.fontSizePx * lineHeightRatio;\n const widthPx = lines.length ? Math.max(...lines.map((l) => l.widthPx)) : 0;\n const heightPx = lines.length * lineHeightPx;\n // 端数切り上げ+余裕分 10px を足す\n return { widthPx: widthPx + 10, heightPx };\n}\n\n/**\n * fontWeight を \"normal\" | \"bold\" に正規化する\n */\nfunction normalizeFontWeight(\n weight: \"normal\" | \"bold\" | number | undefined,\n): \"normal\" | \"bold\" {\n if (weight === \"bold\" || weight === 700) {\n return \"bold\";\n }\n return \"normal\";\n}\n\n/**\n * テキストを折り返し付きでレイアウトし、そのサイズを測定する\n */\nexport function measureText(\n text: string,\n maxWidthPx: number,\n opts: MeasureOptions,\n mode: TextMeasurementMode = \"auto\",\n): {\n widthPx: number;\n heightPx: number;\n} {\n // 計測方法を決定\n // \"opentype\" / \"fallback\" が明示指定された場合はそれを優先\n // \"auto\" の場合はバンドル外フォントならフォールバック計測を使用\n const shouldUseFallback = (() => {\n switch (mode) {\n case \"opentype\":\n return false;\n case \"fallback\":\n return true;\n case \"auto\":\n return !isBundledFont(opts.fontFamily);\n }\n })();\n\n if (shouldUseFallback) {\n return measureTextFallback(text, maxWidthPx, opts);\n }\n\n return measureTextWithOpentype(text, maxWidthPx, opts);\n}\n\n/**\n * opentype.js を使ったテキスト計測\n */\nfunction measureTextWithOpentype(\n text: string,\n maxWidthPx: number,\n opts: MeasureOptions,\n): { widthPx: number; heightPx: number } {\n const fontWeight = normalizeFontWeight(opts.fontWeight);\n const lines = wrapText(\n text,\n maxWidthPx,\n withLetterSpacing(\n (t) => measureTextWidthOpentype(t, opts.fontSizePx, fontWeight),\n opts.letterSpacingPx,\n ),\n );\n return calculateResult(lines, opts);\n}\n\n/**\n * フォールバック計算を使ったテキスト計測\n */\nfunction measureTextFallback(\n text: string,\n maxWidthPx: number,\n opts: MeasureOptions,\n): { widthPx: number; heightPx: number } {\n const { fontSizePx } = opts;\n const lines = wrapText(\n text,\n maxWidthPx,\n withLetterSpacing(\n (t) => estimateTextWidth(t, fontSizePx),\n opts.letterSpacingPx,\n ),\n );\n return calculateResult(lines, opts);\n}\n\n// ラップ用の分割ロジック\n// - 英文: 空白で分割しつつ、空白も行末に残す\n// - 日本語: とりあえず 1 文字ずつ(必要なら賢くする)\nfunction splitForWrap(text: string): string[] {\n // 超雑実装:全角ひらがな・カタカナ・漢字が多そうなら 1 文字ずつ、それ以外は空白区切り\n const hasCJK = /[\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Han}]/u.test(\n text,\n );\n\n if (hasCJK) {\n return Array.from(text); // 1 glyph ≒ 1 文字として扱う\n }\n\n // 英文用:単語 + 後続スペースをトークンにする\n const tokens: string[] = [];\n const re = /(\\S+\\s*|\\s+)/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text))) {\n tokens.push(m[0]);\n }\n return tokens;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  //#region src/diagnostics.d.ts
2
- type DiagnosticCode = "IMAGE_MEASURE_FAILED" | "IMAGE_NOT_PREFETCHED" | "AUTOFIT_OVERFLOW" | "SCALE_BELOW_THRESHOLD" | "MASTER_PPTX_PARSE_FAILED" | "ARROW_REF_NOT_FOUND" | "DUPLICATE_NODE_ID";
2
+ type DiagnosticCode = "IMAGE_MEASURE_FAILED" | "IMAGE_NOT_PREFETCHED" | "AUTOFIT_OVERFLOW" | "SCALE_BELOW_THRESHOLD" | "MASTER_PPTX_PARSE_FAILED" | "ARROW_REF_NOT_FOUND" | "DUPLICATE_NODE_ID" | "PER_SIDE_BORDER_WITH_RADIUS";
3
3
  interface Diagnostic {
4
4
  code: DiagnosticCode;
5
5
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics.d.ts","names":[],"sources":["../src/diagnostics.ts"],"mappings":";KAAY,cAAA;AAAA,UASK,UAAA;EACf,IAAA,EAAM,cAAc;EACpB,OAAA;AAAA;AAAA,cAWW,gBAAA,SAAyB,KAAA;EAAA,SACR,WAAA,EAAa,UAAA;cAAb,WAAA,EAAa,UAAA;AAAA"}
1
+ {"version":3,"file":"diagnostics.d.ts","names":[],"sources":["../src/diagnostics.ts"],"mappings":";KAAY,cAAA;AAAA,UAUK,UAAA;EACf,IAAA,EAAM,cAAc;EACpB,OAAA;AAAA;AAAA,cAWW,gBAAA,SAAyB,KAAA;EAAA,SACR,WAAA,EAAa,UAAA;cAAb,WAAA,EAAa,UAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics.js","names":[],"sources":["../src/diagnostics.ts"],"sourcesContent":["export type DiagnosticCode =\n | \"IMAGE_MEASURE_FAILED\"\n | \"IMAGE_NOT_PREFETCHED\"\n | \"AUTOFIT_OVERFLOW\"\n | \"SCALE_BELOW_THRESHOLD\"\n | \"MASTER_PPTX_PARSE_FAILED\"\n | \"ARROW_REF_NOT_FOUND\"\n | \"DUPLICATE_NODE_ID\";\n\nexport interface Diagnostic {\n code: DiagnosticCode;\n message: string;\n}\n\nexport class DiagnosticCollector {\n readonly items: Diagnostic[] = [];\n\n add(code: DiagnosticCode, message: string): void {\n this.items.push({ code, message });\n }\n}\n\nexport class DiagnosticsError extends Error {\n constructor(public readonly diagnostics: Diagnostic[]) {\n const summary = diagnostics\n .map((d) => `[${d.code}] ${d.message}`)\n .join(\"\\n\");\n super(`Build completed with diagnostics:\\n${summary}`);\n this.name = \"DiagnosticsError\";\n }\n}\n"],"mappings":";AAcA,IAAa,sBAAb,MAAiC;CAC/B,QAA+B,CAAC;CAEhC,IAAI,MAAsB,SAAuB;EAC/C,KAAK,MAAM,KAAK;GAAE;GAAM;EAAQ,CAAC;CACnC;AACF;AAEA,IAAa,mBAAb,cAAsC,MAAM;CACd;CAA5B,YAAY,aAA2C;EACrD,MAAM,UAAU,YACb,KAAK,MAAM,IAAI,EAAE,KAAK,IAAI,EAAE,SAAS,EACrC,KAAK,IAAI;EACZ,MAAM,sCAAsC,SAAS;EAJ3B,KAAA,cAAA;EAK1B,KAAK,OAAO;CACd;AACF"}
1
+ {"version":3,"file":"diagnostics.js","names":[],"sources":["../src/diagnostics.ts"],"sourcesContent":["export type DiagnosticCode =\n | \"IMAGE_MEASURE_FAILED\"\n | \"IMAGE_NOT_PREFETCHED\"\n | \"AUTOFIT_OVERFLOW\"\n | \"SCALE_BELOW_THRESHOLD\"\n | \"MASTER_PPTX_PARSE_FAILED\"\n | \"ARROW_REF_NOT_FOUND\"\n | \"DUPLICATE_NODE_ID\"\n | \"PER_SIDE_BORDER_WITH_RADIUS\";\n\nexport interface Diagnostic {\n code: DiagnosticCode;\n message: string;\n}\n\nexport class DiagnosticCollector {\n readonly items: Diagnostic[] = [];\n\n add(code: DiagnosticCode, message: string): void {\n this.items.push({ code, message });\n }\n}\n\nexport class DiagnosticsError extends Error {\n constructor(public readonly diagnostics: Diagnostic[]) {\n const summary = diagnostics\n .map((d) => `[${d.code}] ${d.message}`)\n .join(\"\\n\");\n super(`Build completed with diagnostics:\\n${summary}`);\n this.name = \"DiagnosticsError\";\n }\n}\n"],"mappings":";AAeA,IAAa,sBAAb,MAAiC;CAC/B,QAA+B,CAAC;CAEhC,IAAI,MAAsB,SAAuB;EAC/C,KAAK,MAAM,KAAK;GAAE;GAAM;EAAQ,CAAC;CACnC;AACF;AAEA,IAAa,mBAAb,cAAsC,MAAM;CACd;CAA5B,YAAY,aAA2C;EACrD,MAAM,UAAU,YACb,KAAK,MAAM,IAAI,EAAE,KAAK,IAAI,EAAE,SAAS,CAAC,CACtC,KAAK,IAAI;EACZ,MAAM,sCAAsC,SAAS;EAJ3B,KAAA,cAAA;EAK1B,KAAK,OAAO;CACd;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"renderIcon.js","names":[],"sources":["../../src/icons/renderIcon.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ICON_DATA } from \"./iconData.ts\";\n\n// @resvg/resvg-wasm を遅延ロードする。\n// バンドラ(webpack / Turbopack)のスタティック解析が require / require.resolve を\n// 追跡してエラーにするのを避けるため、Function コンストラクタで Node.js の require を\n// 取得し、モジュール名は文字列結合で構築する。\n// Turbopack が動的 require / import を Webpack 同等に解析できるようになれば\n// 隠蔽自体が不要になり、apps/website 側の outputFileTracingIncludes 設定もまとめて\n// 撤去できる。\n// 上流 issue: https://github.com/vercel/next.js/issues/85238\ntype ResvgWasm = typeof import(\"@resvg/resvg-wasm\");\nconst RESVG_PKG = [\"@resvg\", \"resvg-wasm\"].join(\"/\");\nlet resvgModule: ResvgWasm | undefined;\nlet wasmInitPromise: Promise<void> | undefined;\n\n// Function コンストラクタを使って require を取得することでバンドラの静的解析から\n// 完全に隠蔽する。実行時は createRequire で生成した require が利用される。\nfunction getNodeRequire(): NodeJS.Require {\n // eslint-disable-next-line @typescript-eslint/no-implied-eval\n const factory = new Function(\n \"url\",\n \"createRequire\",\n \"return createRequire(url)\",\n ) as (\n url: string,\n createRequire: typeof import(\"node:module\").createRequire,\n ) => NodeJS.Require;\n return factory(import.meta.url, createRequire);\n}\n\n/**\n * WASM バイナリのパスを解決する。\n * バンドル環境(esbuild)では同ディレクトリの index_bg.wasm を参照し、\n * 非バンドル環境では createRequire で node_modules から解決する。\n */\nfunction resolveWasmPath(): string {\n const dir = dirname(fileURLToPath(import.meta.url));\n const localPath = join(dir, \"index_bg.wasm\");\n if (existsSync(localPath)) return localPath;\n return getNodeRequire().resolve(`${RESVG_PKG}/index_bg.wasm`);\n}\n\n/**\n * WASM モジュールを初期化し、Resvg クラスを返す。\n * 並行呼び出しでも安全(Promise をキャッシュ)。\n */\nfunction ensureWasmInitialized(): Promise<void> {\n if (!wasmInitPromise) {\n wasmInitPromise = (async () => {\n const mod = getNodeRequire()(RESVG_PKG) as ResvgWasm;\n const wasmPath = resolveWasmPath();\n const wasmBuffer = await readFile(wasmPath);\n await mod.initWasm(wasmBuffer);\n resvgModule = mod;\n })();\n }\n return wasmInitPromise;\n}\n\nfunction getResvg() {\n if (!resvgModule) throw new Error(\"WASM not initialized\");\n return resvgModule.Resvg;\n}\n\nfunction buildIconSvg(name: string, size: number, color: string): string {\n const pathData = ICON_DATA[name];\n if (!pathData) {\n throw new Error(`Unknown icon name: \"${name}\"`);\n }\n 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>`;\n}\n\nexport async function rasterizeIcon(\n name: string,\n size: number,\n color: string,\n cache: Map<string, string>,\n): Promise<string> {\n const key = `${name}|${size}|${color}`;\n const cached = cache.get(key);\n if (cached) return cached;\n\n await ensureWasmInitialized();\n const Resvg = getResvg();\n const svg = buildIconSvg(name, size, color);\n const resvg = new Resvg(svg, { fitTo: { mode: \"width\", value: size } });\n const pngData = resvg.render();\n const pngBuffer = pngData.asPng();\n const result = `image/png;base64,${Buffer.from(pngBuffer).toString(\"base64\")}`;\n cache.set(key, result);\n return result;\n}\n\n/**\n * インライン SVG 文字列を指定サイズでラスタライズし、base64 PNG を返す。\n * color が指定された場合、SVG ルートに stroke / fill 属性を設定する。\n */\nexport async function rasterizeSvgContent(\n svgContent: string,\n width: number,\n color: string | undefined,\n cache: Map<string, string>,\n height?: number,\n): Promise<string> {\n const h = height ?? width;\n const key = `svg:${svgContent}|${width}|${h}|${color ?? \"\"}`;\n const cached = cache.get(key);\n if (cached) return cached;\n\n // SVG に xmlns / width / height を設定し、color があれば stroke / fill を注入\n let svg = svgContent;\n\n // xmlns が無ければ追加\n if (!svg.includes(\"xmlns\")) {\n svg = svg.replace(\"<svg\", '<svg xmlns=\"http://www.w3.org/2000/svg\"');\n }\n\n // width / height を上書き\n svg = svg.replace(/<svg([^>]*)>/, (match, attrs: string) => {\n let newAttrs = attrs\n .replace(/\\bwidth\\s*=\\s*\"[^\"]*\"/g, \"\")\n .replace(/\\bheight\\s*=\\s*\"[^\"]*\"/g, \"\");\n newAttrs += ` width=\"${width}\" height=\"${h}\"`;\n\n // color 指定時は stroke / fill を設定(プリセットアイコンとの一貫性)\n if (color) {\n if (!attrs.includes(\"stroke=\")) {\n newAttrs += ` stroke=\"${color}\"`;\n }\n if (!attrs.includes(\"fill=\")) {\n newAttrs += ` fill=\"none\"`;\n }\n }\n\n return `<svg${newAttrs}>`;\n });\n\n await ensureWasmInitialized();\n const Resvg = getResvg();\n const resvg = new Resvg(svg, { fitTo: { mode: \"width\", value: width } });\n const pngData = resvg.render();\n const pngBuffer = pngData.asPng();\n const result = `image/png;base64,${Buffer.from(pngBuffer).toString(\"base64\")}`;\n cache.set(key, result);\n return result;\n}\n"],"mappings":";;;;;;;AAgBA,MAAM,YAAY,CAAC,UAAU,YAAY,EAAE,KAAK,GAAG;AACnD,IAAI;AACJ,IAAI;AAIJ,SAAS,iBAAiC;CAUxC,OAAO,IARa,SAClB,OACA,iBACA,2BAKW,EAAE,OAAO,KAAK,KAAK,aAAa;AAC/C;;;;;;AAOA,SAAS,kBAA0B;CAEjC,MAAM,YAAY,KADN,QAAQ,cAAc,OAAO,KAAK,GAAG,CACxB,GAAG,eAAe;CAC3C,IAAI,WAAW,SAAS,GAAG,OAAO;CAClC,OAAO,eAAe,EAAE,QAAQ,GAAG,UAAU,eAAe;AAC9D;;;;;AAMA,SAAS,wBAAuC;CAC9C,IAAI,CAAC,iBACH,mBAAmB,YAAY;EAC7B,MAAM,MAAM,eAAe,EAAE,SAAS;EAEtC,MAAM,aAAa,MAAM,SADR,gBACwB,CAAC;EAC1C,MAAM,IAAI,SAAS,UAAU;EAC7B,cAAc;CAChB,GAAG;CAEL,OAAO;AACT;AAEA,SAAS,WAAW;CAClB,IAAI,CAAC,aAAa,MAAM,IAAI,MAAM,sBAAsB;CACxD,OAAO,YAAY;AACrB;AAEA,SAAS,aAAa,MAAc,MAAc,OAAuB;CACvE,MAAM,WAAW,UAAU;CAC3B,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,uBAAuB,KAAK,EAAE;CAEhD,OAAO,kDAAkD,KAAK,YAAY,KAAK,4CAA4C,MAAM,oEAAoE,SAAS;AAChN;AAEA,eAAsB,cACpB,MACA,MACA,OACA,OACiB;CACjB,MAAM,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG;CAC/B,MAAM,SAAS,MAAM,IAAI,GAAG;CAC5B,IAAI,QAAQ,OAAO;CAEnB,MAAM,sBAAsB;CAK5B,MAAM,YADU,KAHF,SAEQ,GADV,aAAa,MAAM,MAAM,KACX,GAAG,EAAE,OAAO;EAAE,MAAM;EAAS,OAAO;CAAK,EAAE,CACjD,EAAE,OACE,EAAE,MAAM;CAChC,MAAM,SAAS,oBAAoB,OAAO,KAAK,SAAS,EAAE,SAAS,QAAQ;CAC3E,MAAM,IAAI,KAAK,MAAM;CACrB,OAAO;AACT;;;;;AAMA,eAAsB,oBACpB,YACA,OACA,OACA,OACA,QACiB;CACjB,MAAM,IAAI,UAAU;CACpB,MAAM,MAAM,OAAO,WAAW,GAAG,MAAM,GAAG,EAAE,GAAG,SAAS;CACxD,MAAM,SAAS,MAAM,IAAI,GAAG;CAC5B,IAAI,QAAQ,OAAO;CAGnB,IAAI,MAAM;CAGV,IAAI,CAAC,IAAI,SAAS,OAAO,GACvB,MAAM,IAAI,QAAQ,QAAQ,2CAAyC;CAIrE,MAAM,IAAI,QAAQ,iBAAiB,OAAO,UAAkB;EAC1D,IAAI,WAAW,MACZ,QAAQ,0BAA0B,EAAE,EACpC,QAAQ,2BAA2B,EAAE;EACxC,YAAY,WAAW,MAAM,YAAY,EAAE;EAG3C,IAAI,OAAO;GACT,IAAI,CAAC,MAAM,SAAS,SAAS,GAC3B,YAAY,YAAY,MAAM;GAEhC,IAAI,CAAC,MAAM,SAAS,OAAO,GACzB,YAAY;EAEhB;EAEA,OAAO,OAAO,SAAS;CACzB,CAAC;CAED,MAAM,sBAAsB;CAI5B,MAAM,YADU,KAFF,SACQ,GAAE,KAAK,EAAE,OAAO;EAAE,MAAM;EAAS,OAAO;CAAM,EAAE,CAClD,EAAE,OACE,EAAE,MAAM;CAChC,MAAM,SAAS,oBAAoB,OAAO,KAAK,SAAS,EAAE,SAAS,QAAQ;CAC3E,MAAM,IAAI,KAAK,MAAM;CACrB,OAAO;AACT"}
1
+ {"version":3,"file":"renderIcon.js","names":[],"sources":["../../src/icons/renderIcon.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ICON_DATA } from \"./iconData.ts\";\n\n// @resvg/resvg-wasm を遅延ロードする。\n// バンドラ(webpack / Turbopack)のスタティック解析が require / require.resolve を\n// 追跡してエラーにするのを避けるため、Function コンストラクタで Node.js の require を\n// 取得し、モジュール名は文字列結合で構築する。\n// Turbopack が動的 require / import を Webpack 同等に解析できるようになれば\n// 隠蔽自体が不要になり、apps/website 側の outputFileTracingIncludes 設定もまとめて\n// 撤去できる。\n// 上流 issue: https://github.com/vercel/next.js/issues/85238\ntype ResvgWasm = typeof import(\"@resvg/resvg-wasm\");\nconst RESVG_PKG = [\"@resvg\", \"resvg-wasm\"].join(\"/\");\nlet resvgModule: ResvgWasm | undefined;\nlet wasmInitPromise: Promise<void> | undefined;\n\n// Function コンストラクタを使って require を取得することでバンドラの静的解析から\n// 完全に隠蔽する。実行時は createRequire で生成した require が利用される。\nfunction getNodeRequire(): NodeJS.Require {\n // eslint-disable-next-line @typescript-eslint/no-implied-eval\n const factory = new Function(\n \"url\",\n \"createRequire\",\n \"return createRequire(url)\",\n ) as (\n url: string,\n createRequire: typeof import(\"node:module\").createRequire,\n ) => NodeJS.Require;\n return factory(import.meta.url, createRequire);\n}\n\n/**\n * WASM バイナリのパスを解決する。\n * バンドル環境(esbuild)では同ディレクトリの index_bg.wasm を参照し、\n * 非バンドル環境では createRequire で node_modules から解決する。\n */\nfunction resolveWasmPath(): string {\n const dir = dirname(fileURLToPath(import.meta.url));\n const localPath = join(dir, \"index_bg.wasm\");\n if (existsSync(localPath)) return localPath;\n return getNodeRequire().resolve(`${RESVG_PKG}/index_bg.wasm`);\n}\n\n/**\n * WASM モジュールを初期化し、Resvg クラスを返す。\n * 並行呼び出しでも安全(Promise をキャッシュ)。\n */\nfunction ensureWasmInitialized(): Promise<void> {\n if (!wasmInitPromise) {\n wasmInitPromise = (async () => {\n const mod = getNodeRequire()(RESVG_PKG) as ResvgWasm;\n const wasmPath = resolveWasmPath();\n const wasmBuffer = await readFile(wasmPath);\n await mod.initWasm(wasmBuffer);\n resvgModule = mod;\n })();\n }\n return wasmInitPromise;\n}\n\nfunction getResvg() {\n if (!resvgModule) throw new Error(\"WASM not initialized\");\n return resvgModule.Resvg;\n}\n\nfunction buildIconSvg(name: string, size: number, color: string): string {\n const pathData = ICON_DATA[name];\n if (!pathData) {\n throw new Error(`Unknown icon name: \"${name}\"`);\n }\n 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>`;\n}\n\nexport async function rasterizeIcon(\n name: string,\n size: number,\n color: string,\n cache: Map<string, string>,\n): Promise<string> {\n const key = `${name}|${size}|${color}`;\n const cached = cache.get(key);\n if (cached) return cached;\n\n await ensureWasmInitialized();\n const Resvg = getResvg();\n const svg = buildIconSvg(name, size, color);\n const resvg = new Resvg(svg, { fitTo: { mode: \"width\", value: size } });\n const pngData = resvg.render();\n const pngBuffer = pngData.asPng();\n const result = `image/png;base64,${Buffer.from(pngBuffer).toString(\"base64\")}`;\n cache.set(key, result);\n return result;\n}\n\n/**\n * インライン SVG 文字列を指定サイズでラスタライズし、base64 PNG を返す。\n * color が指定された場合、SVG ルートに stroke / fill 属性を設定する。\n */\nexport async function rasterizeSvgContent(\n svgContent: string,\n width: number,\n color: string | undefined,\n cache: Map<string, string>,\n height?: number,\n): Promise<string> {\n const h = height ?? width;\n const key = `svg:${svgContent}|${width}|${h}|${color ?? \"\"}`;\n const cached = cache.get(key);\n if (cached) return cached;\n\n // SVG に xmlns / width / height を設定し、color があれば stroke / fill を注入\n let svg = svgContent;\n\n // xmlns が無ければ追加\n if (!svg.includes(\"xmlns\")) {\n svg = svg.replace(\"<svg\", '<svg xmlns=\"http://www.w3.org/2000/svg\"');\n }\n\n // width / height を上書き\n svg = svg.replace(/<svg([^>]*)>/, (match, attrs: string) => {\n let newAttrs = attrs\n .replace(/\\bwidth\\s*=\\s*\"[^\"]*\"/g, \"\")\n .replace(/\\bheight\\s*=\\s*\"[^\"]*\"/g, \"\");\n newAttrs += ` width=\"${width}\" height=\"${h}\"`;\n\n // color 指定時は stroke / fill を設定(プリセットアイコンとの一貫性)\n if (color) {\n if (!attrs.includes(\"stroke=\")) {\n newAttrs += ` stroke=\"${color}\"`;\n }\n if (!attrs.includes(\"fill=\")) {\n newAttrs += ` fill=\"none\"`;\n }\n }\n\n return `<svg${newAttrs}>`;\n });\n\n await ensureWasmInitialized();\n const Resvg = getResvg();\n const resvg = new Resvg(svg, { fitTo: { mode: \"width\", value: width } });\n const pngData = resvg.render();\n const pngBuffer = pngData.asPng();\n const result = `image/png;base64,${Buffer.from(pngBuffer).toString(\"base64\")}`;\n cache.set(key, result);\n return result;\n}\n"],"mappings":";;;;;;;AAgBA,MAAM,YAAY,CAAC,UAAU,YAAY,CAAC,CAAC,KAAK,GAAG;AACnD,IAAI;AACJ,IAAI;AAIJ,SAAS,iBAAiC;CAUxC,OAAO,IARa,SAClB,OACA,iBACA,2BAKW,CAAC,CAAC,OAAO,KAAK,KAAK,aAAa;AAC/C;;;;;;AAOA,SAAS,kBAA0B;CAEjC,MAAM,YAAY,KADN,QAAQ,cAAc,OAAO,KAAK,GAAG,CACxB,GAAG,eAAe;CAC3C,IAAI,WAAW,SAAS,GAAG,OAAO;CAClC,OAAO,eAAe,CAAC,CAAC,QAAQ,GAAG,UAAU,eAAe;AAC9D;;;;;AAMA,SAAS,wBAAuC;CAC9C,IAAI,CAAC,iBACH,mBAAmB,YAAY;EAC7B,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS;EAEtC,MAAM,aAAa,MAAM,SADR,gBACwB,CAAC;EAC1C,MAAM,IAAI,SAAS,UAAU;EAC7B,cAAc;CAChB,EAAA,CAAG;CAEL,OAAO;AACT;AAEA,SAAS,WAAW;CAClB,IAAI,CAAC,aAAa,MAAM,IAAI,MAAM,sBAAsB;CACxD,OAAO,YAAY;AACrB;AAEA,SAAS,aAAa,MAAc,MAAc,OAAuB;CACvE,MAAM,WAAW,UAAU;CAC3B,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,uBAAuB,KAAK,EAAE;CAEhD,OAAO,kDAAkD,KAAK,YAAY,KAAK,4CAA4C,MAAM,oEAAoE,SAAS;AAChN;AAEA,eAAsB,cACpB,MACA,MACA,OACA,OACiB;CACjB,MAAM,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG;CAC/B,MAAM,SAAS,MAAM,IAAI,GAAG;CAC5B,IAAI,QAAQ,OAAO;CAEnB,MAAM,sBAAsB;CAK5B,MAAM,YADU,KAHF,SAEQ,GADV,aAAa,MAAM,MAAM,KACX,GAAG,EAAE,OAAO;EAAE,MAAM;EAAS,OAAO;CAAK,EAAE,CACjD,CAAC,CAAC,OACE,CAAC,CAAC,MAAM;CAChC,MAAM,SAAS,oBAAoB,OAAO,KAAK,SAAS,CAAC,CAAC,SAAS,QAAQ;CAC3E,MAAM,IAAI,KAAK,MAAM;CACrB,OAAO;AACT;;;;;AAMA,eAAsB,oBACpB,YACA,OACA,OACA,OACA,QACiB;CACjB,MAAM,IAAI,UAAU;CACpB,MAAM,MAAM,OAAO,WAAW,GAAG,MAAM,GAAG,EAAE,GAAG,SAAS;CACxD,MAAM,SAAS,MAAM,IAAI,GAAG;CAC5B,IAAI,QAAQ,OAAO;CAGnB,IAAI,MAAM;CAGV,IAAI,CAAC,IAAI,SAAS,OAAO,GACvB,MAAM,IAAI,QAAQ,QAAQ,2CAAyC;CAIrE,MAAM,IAAI,QAAQ,iBAAiB,OAAO,UAAkB;EAC1D,IAAI,WAAW,MACZ,QAAQ,0BAA0B,EAAE,CAAC,CACrC,QAAQ,2BAA2B,EAAE;EACxC,YAAY,WAAW,MAAM,YAAY,EAAE;EAG3C,IAAI,OAAO;GACT,IAAI,CAAC,MAAM,SAAS,SAAS,GAC3B,YAAY,YAAY,MAAM;GAEhC,IAAI,CAAC,MAAM,SAAS,OAAO,GACzB,YAAY;EAEhB;EAEA,OAAO,OAAO,SAAS;CACzB,CAAC;CAED,MAAM,sBAAsB;CAI5B,MAAM,YADU,KAFF,SACQ,GAAE,KAAK,EAAE,OAAO;EAAE,MAAM;EAAS,OAAO;CAAM,EAAE,CAClD,CAAC,CAAC,OACE,CAAC,CAAC,MAAM;CAChC,MAAM,SAAS,oBAAoB,OAAO,KAAK,SAAS,CAAC,CAAC,SAAS,QAAQ;CAC3E,MAAM,IAAI,KAAK,MAAM;CACrB,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseMasterPptx.js","names":[],"sources":["../src/parseMasterPptx.ts"],"sourcesContent":["import { XMLParser } from \"fast-xml-parser\";\nimport type { SlideMasterBackground } from \"./types.ts\";\n\n// JSZip は CJS パッケージのため動的 import で読み込む\nasync function loadJSZip(): Promise<typeof import(\"jszip\")> {\n const mod = await import(\"jszip\");\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n return (mod as any).default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\n\nconst xmlParser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n});\n\n/**\n * MIME タイプを拡張子から判定する\n */\nfunction mimeTypeFromExt(filePath: string): string {\n const ext = filePath.split(\".\").pop()?.toLowerCase();\n switch (ext) {\n case \"png\":\n return \"image/png\";\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \"gif\":\n return \"image/gif\";\n case \"bmp\":\n return \"image/bmp\";\n case \"svg\":\n return \"image/svg+xml\";\n case \"tiff\":\n case \"tif\":\n return \"image/tiff\";\n case \"webp\":\n return \"image/webp\";\n default:\n return \"image/png\";\n }\n}\n\n/**\n * rels XML から rId に対応するファイルパスを取得する\n */\nfunction resolveRelId(relsXml: string, rId: string): string | undefined {\n /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n const parsed = xmlParser.parse(relsXml);\n const relationships = parsed?.Relationships?.Relationship;\n if (!relationships) return undefined;\n\n const rels = Array.isArray(relationships) ? relationships : [relationships];\n for (const rel of rels) {\n if (rel[\"@_Id\"] === rId) {\n return rel[\"@_Target\"] as string;\n }\n }\n /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n return undefined;\n}\n\n/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n/**\n * bgPr 要素から背景情報を抽出する\n */\nasync function extractBackgroundFromBgPr(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n bgPr: any,\n zip: import(\"jszip\"),\n relsPath: string,\n basePath: string,\n): Promise<SlideMasterBackground | undefined> {\n if (!bgPr) return undefined;\n\n // 単色塗りつぶし\n const solidFill = bgPr[\"a:solidFill\"];\n if (solidFill) {\n const srgbClr = solidFill[\"a:srgbClr\"];\n if (srgbClr) {\n const color = (srgbClr[\"@_val\"] as string) ?? undefined;\n if (color) return { color };\n }\n }\n\n // 画像背景\n const blipFill = bgPr[\"a:blipFill\"];\n if (blipFill) {\n const blip = blipFill[\"a:blip\"];\n const rId = blip?.[\"@_r:embed\"] as string | undefined;\n if (!rId) return undefined;\n\n const relsFile = zip.file(relsPath);\n if (!relsFile) return undefined;\n const relsXml = await relsFile.async(\"text\");\n const target = resolveRelId(relsXml, rId);\n if (!target) return undefined;\n\n // target は相対パスなので basePath からの相対パスとして解決\n const imagePath = new URL(\n target,\n `file:///${basePath}dummy`,\n ).pathname.slice(1);\n\n const imageFile = zip.file(imagePath);\n if (!imageFile) return undefined;\n\n const imageData = await imageFile.async(\"base64\");\n const mimeType = mimeTypeFromExt(imagePath);\n return { data: `data:${mimeType};base64,${imageData}` };\n }\n\n return undefined;\n}\n\n/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n/**\n * マスター PPTX のバッファからスライドマスターの背景情報を抽出する。\n *\n * 探索順序:\n * 1. `ppt/slideMasters/slideMaster1.xml` の `p:bg > p:bgPr`\n * 2. 各スライドレイアウト (`ppt/slideLayouts/slideLayoutN.xml`) の `p:bg > p:bgPr`\n *\n * サポートする背景:\n * - 単色塗りつぶし (`a:solidFill` / `a:srgbClr`)\n * - 画像背景 (`a:blipFill` / `a:blip`)\n *\n * @returns 背景情報。背景が設定されていない場合は `undefined`。\n */\nexport async function parseMasterPptx(\n pptxBuffer: ArrayBuffer | Uint8Array,\n): Promise<SlideMasterBackground | undefined> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(pptxBuffer);\n\n /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n // 1. スライドマスター本体を探索\n const masterFile = zip.file(\"ppt/slideMasters/slideMaster1.xml\");\n if (masterFile) {\n const masterXml = await masterFile.async(\"text\");\n const parsed = xmlParser.parse(masterXml);\n const bgPr = parsed?.[\"p:sldMaster\"]?.[\"p:cSld\"]?.[\"p:bg\"]?.[\"p:bgPr\"];\n const result = await extractBackgroundFromBgPr(\n bgPr,\n zip,\n \"ppt/slideMasters/_rels/slideMaster1.xml.rels\",\n \"ppt/slideMasters/\",\n );\n if (result) return result;\n }\n\n // 2. スライドレイアウトを探索\n const layoutFiles = Object.keys(zip.files).filter(\n (f) => f.startsWith(\"ppt/slideLayouts/slideLayout\") && f.endsWith(\".xml\"),\n );\n // 番号順にソート(数値ソートで slideLayout2 < slideLayout10 の順序を保証)\n layoutFiles.sort((a, b) => {\n const numA = parseInt(a.match(/slideLayout(\\d+)\\.xml$/)?.[1] ?? \"0\", 10);\n const numB = parseInt(b.match(/slideLayout(\\d+)\\.xml$/)?.[1] ?? \"0\", 10);\n return numA - numB;\n });\n\n for (const layoutPath of layoutFiles) {\n const layoutFile = zip.file(layoutPath);\n if (!layoutFile) continue;\n\n const layoutXml = await layoutFile.async(\"text\");\n const parsed = xmlParser.parse(layoutXml);\n const bgPr = parsed?.[\"p:sldLayout\"]?.[\"p:cSld\"]?.[\"p:bg\"]?.[\"p:bgPr\"];\n const fileName = layoutPath.split(\"/\").pop()!;\n const relsPath = `ppt/slideLayouts/_rels/${fileName}.rels`;\n const result = await extractBackgroundFromBgPr(\n bgPr,\n zip,\n relsPath,\n \"ppt/slideLayouts/\",\n );\n if (result) return result;\n }\n\n /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"parseMasterPptx.js","names":[],"sources":["../src/parseMasterPptx.ts"],"sourcesContent":["import { XMLParser } from \"fast-xml-parser\";\nimport type { SlideMasterBackground } from \"./types.ts\";\n\n// JSZip は CJS パッケージのため動的 import で読み込む\nasync function loadJSZip(): Promise<typeof import(\"jszip\")> {\n const mod = await import(\"jszip\");\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n return (mod as any).default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\n\nconst xmlParser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n});\n\n/**\n * MIME タイプを拡張子から判定する\n */\nfunction mimeTypeFromExt(filePath: string): string {\n const ext = filePath.split(\".\").pop()?.toLowerCase();\n switch (ext) {\n case \"png\":\n return \"image/png\";\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \"gif\":\n return \"image/gif\";\n case \"bmp\":\n return \"image/bmp\";\n case \"svg\":\n return \"image/svg+xml\";\n case \"tiff\":\n case \"tif\":\n return \"image/tiff\";\n case \"webp\":\n return \"image/webp\";\n default:\n return \"image/png\";\n }\n}\n\n/**\n * rels XML から rId に対応するファイルパスを取得する\n */\nfunction resolveRelId(relsXml: string, rId: string): string | undefined {\n /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n const parsed = xmlParser.parse(relsXml);\n const relationships = parsed?.Relationships?.Relationship;\n if (!relationships) return undefined;\n\n const rels = Array.isArray(relationships) ? relationships : [relationships];\n for (const rel of rels) {\n if (rel[\"@_Id\"] === rId) {\n return rel[\"@_Target\"] as string;\n }\n }\n /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n return undefined;\n}\n\n/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n/**\n * bgPr 要素から背景情報を抽出する\n */\nasync function extractBackgroundFromBgPr(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n bgPr: any,\n zip: import(\"jszip\"),\n relsPath: string,\n basePath: string,\n): Promise<SlideMasterBackground | undefined> {\n if (!bgPr) return undefined;\n\n // 単色塗りつぶし\n const solidFill = bgPr[\"a:solidFill\"];\n if (solidFill) {\n const srgbClr = solidFill[\"a:srgbClr\"];\n if (srgbClr) {\n const color = (srgbClr[\"@_val\"] as string) ?? undefined;\n if (color) return { color };\n }\n }\n\n // 画像背景\n const blipFill = bgPr[\"a:blipFill\"];\n if (blipFill) {\n const blip = blipFill[\"a:blip\"];\n const rId = blip?.[\"@_r:embed\"] as string | undefined;\n if (!rId) return undefined;\n\n const relsFile = zip.file(relsPath);\n if (!relsFile) return undefined;\n const relsXml = await relsFile.async(\"text\");\n const target = resolveRelId(relsXml, rId);\n if (!target) return undefined;\n\n // target は相対パスなので basePath からの相対パスとして解決\n const imagePath = new URL(\n target,\n `file:///${basePath}dummy`,\n ).pathname.slice(1);\n\n const imageFile = zip.file(imagePath);\n if (!imageFile) return undefined;\n\n const imageData = await imageFile.async(\"base64\");\n const mimeType = mimeTypeFromExt(imagePath);\n return { data: `data:${mimeType};base64,${imageData}` };\n }\n\n return undefined;\n}\n\n/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n/**\n * マスター PPTX のバッファからスライドマスターの背景情報を抽出する。\n *\n * 探索順序:\n * 1. `ppt/slideMasters/slideMaster1.xml` の `p:bg > p:bgPr`\n * 2. 各スライドレイアウト (`ppt/slideLayouts/slideLayoutN.xml`) の `p:bg > p:bgPr`\n *\n * サポートする背景:\n * - 単色塗りつぶし (`a:solidFill` / `a:srgbClr`)\n * - 画像背景 (`a:blipFill` / `a:blip`)\n *\n * @returns 背景情報。背景が設定されていない場合は `undefined`。\n */\nexport async function parseMasterPptx(\n pptxBuffer: ArrayBuffer | Uint8Array,\n): Promise<SlideMasterBackground | undefined> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(pptxBuffer);\n\n /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n // 1. スライドマスター本体を探索\n const masterFile = zip.file(\"ppt/slideMasters/slideMaster1.xml\");\n if (masterFile) {\n const masterXml = await masterFile.async(\"text\");\n const parsed = xmlParser.parse(masterXml);\n const bgPr = parsed?.[\"p:sldMaster\"]?.[\"p:cSld\"]?.[\"p:bg\"]?.[\"p:bgPr\"];\n const result = await extractBackgroundFromBgPr(\n bgPr,\n zip,\n \"ppt/slideMasters/_rels/slideMaster1.xml.rels\",\n \"ppt/slideMasters/\",\n );\n if (result) return result;\n }\n\n // 2. スライドレイアウトを探索\n const layoutFiles = Object.keys(zip.files).filter(\n (f) => f.startsWith(\"ppt/slideLayouts/slideLayout\") && f.endsWith(\".xml\"),\n );\n // 番号順にソート(数値ソートで slideLayout2 < slideLayout10 の順序を保証)\n layoutFiles.sort((a, b) => {\n const numA = parseInt(a.match(/slideLayout(\\d+)\\.xml$/)?.[1] ?? \"0\", 10);\n const numB = parseInt(b.match(/slideLayout(\\d+)\\.xml$/)?.[1] ?? \"0\", 10);\n return numA - numB;\n });\n\n for (const layoutPath of layoutFiles) {\n const layoutFile = zip.file(layoutPath);\n if (!layoutFile) continue;\n\n const layoutXml = await layoutFile.async(\"text\");\n const parsed = xmlParser.parse(layoutXml);\n const bgPr = parsed?.[\"p:sldLayout\"]?.[\"p:cSld\"]?.[\"p:bg\"]?.[\"p:bgPr\"];\n const fileName = layoutPath.split(\"/\").pop()!;\n const relsPath = `ppt/slideLayouts/_rels/${fileName}.rels`;\n const result = await extractBackgroundFromBgPr(\n bgPr,\n zip,\n relsPath,\n \"ppt/slideLayouts/\",\n );\n if (result) return result;\n }\n\n /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n return undefined;\n}\n"],"mappings":"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"}
@@ -204,6 +204,21 @@ const UNDERLINE_RULE = {
204
204
  }
205
205
  }]
206
206
  };
207
+ const TEXT_GLOW_RULE = {
208
+ type: "object",
209
+ shape: {
210
+ size: "number",
211
+ opacity: "number",
212
+ color: "string"
213
+ }
214
+ };
215
+ const TEXT_OUTLINE_RULE = {
216
+ type: "object",
217
+ shape: {
218
+ size: "number",
219
+ color: "string"
220
+ }
221
+ };
207
222
  const LINE_ARROW_RULE = {
208
223
  type: "union",
209
224
  options: ["boolean", {
@@ -230,7 +245,8 @@ const FLOW_CONNECTOR_STYLE_RULE = {
230
245
  shape: {
231
246
  color: "string",
232
247
  width: "number",
233
- arrowType: "string"
248
+ arrowType: "string",
249
+ labelColor: "string"
234
250
  }
235
251
  };
236
252
  const IMAGE_SIZING_RULE = {
@@ -247,6 +263,7 @@ const BASE_RULES = {
247
263
  id: "string",
248
264
  w: LENGTH_RULE,
249
265
  h: LENGTH_RULE,
266
+ grow: "number",
250
267
  minW: "number",
251
268
  maxW: "number",
252
269
  minH: "number",
@@ -254,8 +271,13 @@ const BASE_RULES = {
254
271
  padding: PADDING_RULE,
255
272
  margin: PADDING_RULE,
256
273
  backgroundColor: "string",
274
+ backgroundGradient: "string",
257
275
  backgroundImage: BACKGROUND_IMAGE_RULE,
258
276
  border: BORDER_STYLE_RULE,
277
+ borderTop: BORDER_STYLE_RULE,
278
+ borderRight: BORDER_STYLE_RULE,
279
+ borderBottom: BORDER_STYLE_RULE,
280
+ borderLeft: BORDER_STYLE_RULE,
259
281
  borderRadius: "number",
260
282
  opacity: "number",
261
283
  zIndex: "number",
@@ -283,7 +305,11 @@ const NODE_COERCION_MAP = {
283
305
  text: {
284
306
  ...BASE_RULES,
285
307
  text: "string",
286
- ...TEXT_STYLE_RULES
308
+ rotate: "number",
309
+ ...TEXT_STYLE_RULES,
310
+ letterSpacing: "number",
311
+ glow: TEXT_GLOW_RULE,
312
+ outline: TEXT_OUTLINE_RULE
287
313
  },
288
314
  ul: {
289
315
  ...BASE_RULES,
@@ -300,7 +326,8 @@ const NODE_COERCION_MAP = {
300
326
  image: {
301
327
  ...BASE_RULES,
302
328
  src: "string",
303
- sizing: IMAGE_SIZING_RULE
329
+ sizing: IMAGE_SIZING_RULE,
330
+ rotate: "number"
304
331
  },
305
332
  icon: {
306
333
  ...BASE_RULES,
@@ -308,7 +335,8 @@ const NODE_COERCION_MAP = {
308
335
  size: "number",
309
336
  color: "string",
310
337
  variant: "string",
311
- bgColor: "string"
338
+ bgColor: "string",
339
+ rotate: "number"
312
340
  },
313
341
  svg: {
314
342
  ...BASE_RULES,
@@ -325,6 +353,7 @@ const NODE_COERCION_MAP = {
325
353
  ...BASE_RULES,
326
354
  shapeType: "string",
327
355
  text: "string",
356
+ rotate: "number",
328
357
  fill: FILL_STYLE_RULE,
329
358
  line: BORDER_STYLE_RULE,
330
359
  ...TEXT_STYLE_RULES
@@ -342,19 +371,26 @@ const NODE_COERCION_MAP = {
342
371
  timeline: {
343
372
  ...BASE_RULES,
344
373
  direction: "string",
345
- items: "json"
374
+ items: "json",
375
+ dateColor: "string",
376
+ titleColor: "string",
377
+ descriptionColor: "string"
346
378
  },
347
379
  matrix: {
348
380
  ...BASE_RULES,
349
381
  axes: "json",
350
382
  quadrants: "json",
351
- items: "json"
383
+ items: "json",
384
+ axisLabelColor: "string",
385
+ quadrantLabelColor: "string",
386
+ itemLabelColor: "string"
352
387
  },
353
388
  tree: {
354
389
  ...BASE_RULES,
355
390
  layout: "string",
356
391
  nodeShape: "string",
357
392
  data: "json",
393
+ textColor: "string",
358
394
  connectorStyle: TREE_CONNECTOR_STYLE_RULE,
359
395
  nodeWidth: "number",
360
396
  nodeHeight: "number",
@@ -463,7 +499,8 @@ const CHILD_ELEMENT_COERCION_MAP = {
463
499
  label: "string",
464
500
  x: "number",
465
501
  y: "number",
466
- color: "string"
502
+ color: "string",
503
+ textColor: "string"
467
504
  },
468
505
  FlowNode: {
469
506
  id: "string",
@@ -478,7 +515,8 @@ const CHILD_ELEMENT_COERCION_MAP = {
478
515
  from: "string",
479
516
  to: "string",
480
517
  label: "string",
481
- color: "string"
518
+ color: "string",
519
+ labelColor: "string"
482
520
  },
483
521
  Col: { width: "number" },
484
522
  Td: {
@@ -511,7 +549,8 @@ const CHILD_ELEMENT_COERCION_MAP = {
511
549
  I: {},
512
550
  Span: {
513
551
  color: "string",
514
- fontFamily: "string"
552
+ fontFamily: "string",
553
+ letterSpacing: "number"
515
554
  }
516
555
  };
517
556
  //#endregion