@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xmlChildRules.js","names":[],"sources":["../../src/registry/xmlChildRules.ts"],"sourcesContent":["/**\n * parseXml / serializeXml が共有する XML child handling のルール定義。\n *\n * - インライン装飾タグ (B/I/A/U/S/Mark/Span) と TextRun property の対応\n * - ノードごとの XML child element 受け入れルール (XmlChildRule)\n *\n * parse 側 (parseXml.ts) と serialize 側 (serializeXml.ts) の双方がこの\n * モジュールを参照することで、タグ名・対応 property・許容 child のズレを防ぐ。\n * 依存方向は parseXml / serializeXml → registry の一方向に保つ(このモジュール\n * からは parseXml / serializeXml を import しない)。\n */\n\n// ===== インライン装飾 (B/I/A/U/S/Mark/Span) =====\n\n/** Text / Shape / Li / Td 内のインライン装飾を表すテキスト run */\nexport interface TextRun {\n text: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strike?: boolean;\n highlight?: string;\n color?: string;\n href?: string;\n fontFamily?: string;\n letterSpacing?: number;\n}\n\n/**\n * boolean 装飾タグ ↔ TextRun property の対応。\n * 配列の順序は serialize 時のネスト順(先頭が最も外側)。\n */\nexport const INLINE_BOOLEAN_FORMATS: readonly {\n readonly tag: string;\n readonly property: \"bold\" | \"italic\" | \"underline\" | \"strike\";\n}[] = [\n { tag: \"B\", property: \"bold\" },\n { tag: \"I\", property: \"italic\" },\n { tag: \"U\", property: \"underline\" },\n { tag: \"S\", property: \"strike\" },\n];\n\n/** ハイパーリンクタグ。href 属性 → TextRun.href */\nexport const INLINE_LINK_TAG = \"A\";\n\n/** ハイライトタグ。color 属性 → TextRun.highlight(省略時は既定色) */\nexport const INLINE_MARK_TAG = \"Mark\";\n\n/** Mark の color 属性省略時に適用される既定のハイライト色 */\nexport const MARK_DEFAULT_HIGHLIGHT_COLOR = \"FFFF00\";\n\n/** 文字スタイルタグ。color / fontFamily / letterSpacing 属性 → TextRun の同名 property */\nexport const INLINE_SPAN_TAG = \"Span\";\n\n/** インライン装飾として許容されるタグの一覧(エラーメッセージの列挙順) */\nexport const INLINE_FORMAT_TAG_LIST: readonly string[] = [\n \"B\",\n \"I\",\n \"A\",\n \"U\",\n \"S\",\n \"Mark\",\n \"Span\",\n];\n\n/** インライン装飾タグの集合(child element 判定用) */\nexport const INLINE_FORMAT_TAGS: ReadonlySet<string> = new Set(\n INLINE_FORMAT_TAG_LIST,\n);\n\n// ===== XML child element 受け入れルール =====\n\n/**\n * インライン装飾タグ (B/I/A/U/S/Mark/Span) のみを child として受け付け、\n * runs / text property へ変換するルール(Text 用)\n */\nexport interface InlineRunsChildRule {\n kind: \"inline-runs\";\n}\n\n/** 単一種類の child tag の繰り返しを受け取り、配列 property へ変換するルール */\nexport interface RepeatedChildRule {\n kind: \"repeated\";\n /** 受け付ける child tag 名 */\n childTag: string;\n /** 変換結果を格納する property 名 */\n property: string;\n /** 各 item が text content / インライン装飾タグを受け付けるか(Li 用) */\n allowsItemText?: boolean;\n}\n\n/**\n * parseXml 側の専用 converter で処理する複雑な child 構造\n * (ネスト構造や複数 property への振り分けを伴うもの)\n */\nexport interface NodeSpecificChildRule {\n kind: \"node-specific\";\n /** 直下で受け付ける child tag 名の一覧(unknown child エラーメッセージに使用) */\n expectedTags: readonly string[];\n}\n\nexport type XmlChildRule =\n | InlineRunsChildRule\n | RepeatedChildRule\n | NodeSpecificChildRule;\n\n/**\n * unknown child エラーメッセージ用に期待タグ一覧を整形する。\n * 例: [\"A\"] → \"<A>\" / [\"A\",\"B\"] → \"<A> or <B>\" / [\"A\",\"B\",\"C\"] → \"<A>, <B>, or <C>\"\n */\nexport function formatExpectedTags(tags: readonly string[]): string {\n const wrapped = tags.map((tag) => `<${tag}>`);\n if (wrapped.length === 1) return wrapped[0];\n if (wrapped.length === 2) return `${wrapped[0]} or ${wrapped[1]}`;\n return `${wrapped.slice(0, -1).join(\", \")}, or ${wrapped[wrapped.length - 1]}`;\n}\n"],"mappings":";;;;;AAgCA,MAAa,yBAGP;CACJ;EAAE,KAAK;EAAK,UAAU;CAAO;CAC7B;EAAE,KAAK;EAAK,UAAU;CAAS;CAC/B;EAAE,KAAK;EAAK,UAAU;CAAY;CAClC;EAAE,KAAK;EAAK,UAAU;CAAS;AACjC;;AAMA,MAAa,kBAAkB;;AAG/B,MAAa,+BAA+B;;AAG5C,MAAa,kBAAkB;;AAG/B,MAAa,yBAA4C;CACvD;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,qBAA0C,IAAI,IACzD,sBACF;;;;;AA0CA,SAAgB,mBAAmB,MAAiC;CAClE,MAAM,UAAU,KAAK,KAAK,QAAQ,IAAI,IAAI,EAAE;CAC5C,IAAI,QAAQ,WAAW,GAAG,OAAO,QAAQ;CACzC,IAAI,QAAQ,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,QAAQ;CAC7D,OAAO,GAAG,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,OAAO,QAAQ,QAAQ,SAAS;AAC5E"}
@@ -0,0 +1,139 @@
1
+ import { parseLinearGradient } from "../shared/gradient.js";
2
+ //#region src/renderPptx/gradientFills.ts
3
+ async function loadJSZip() {
4
+ const mod = await import("jszip");
5
+ return mod.default ?? mod;
6
+ }
7
+ /** マーカー色の探索開始値。reserveColors で予約済みの色はスキップされる */
8
+ const MARKER_BASE = 1014333;
9
+ var GradientFillRegistry = class {
10
+ reserved = /* @__PURE__ */ new Set();
11
+ markerBySpec = /* @__PURE__ */ new Map();
12
+ registered = [];
13
+ nextCandidate = MARKER_BASE;
14
+ /**
15
+ * テキスト中に現れる 6桁 HEX をマーカー候補から除外する。
16
+ * 入力 XML 由来のユーザー指定色とマーカーの衝突を防ぐため、
17
+ * register より前に入力 XML 文字列を渡しておく。
18
+ */
19
+ reserveColors(text) {
20
+ for (const match of text.matchAll(/[0-9a-fA-F]{6}/g)) this.reserved.add(match[0].toUpperCase());
21
+ }
22
+ /** グラデーションを登録し、対応するマーカー色を返す */
23
+ register(gradient, opacity) {
24
+ const specKey = JSON.stringify({
25
+ gradient,
26
+ opacity
27
+ });
28
+ const existing = this.markerBySpec.get(specKey);
29
+ if (existing) return existing;
30
+ let marker;
31
+ do {
32
+ marker = this.nextCandidate.toString(16).toUpperCase().padStart(6, "0");
33
+ this.nextCandidate = (this.nextCandidate + 1) % 16777216;
34
+ } while (this.reserved.has(marker));
35
+ this.reserved.add(marker);
36
+ this.markerBySpec.set(specKey, marker);
37
+ this.registered.push({
38
+ marker,
39
+ gradient,
40
+ opacity
41
+ });
42
+ return marker;
43
+ }
44
+ get isEmpty() {
45
+ return this.registered.length === 0;
46
+ }
47
+ get entries() {
48
+ return this.registered;
49
+ }
50
+ };
51
+ /**
52
+ * backgroundGradient 属性値をパースしてレジストリに登録し、マーカー色を返す。
53
+ * パースできない場合 (スキーマ検証済みのため通常発生しない) は undefined を返す。
54
+ */
55
+ function registerBackgroundGradient(value, opacity, registry) {
56
+ const gradient = parseLinearGradient(value);
57
+ if (!gradient) return void 0;
58
+ return registry.register(gradient, opacity);
59
+ }
60
+ /**
61
+ * LinearGradient を DrawingML の `<a:gradFill>` 要素に変換する
62
+ *
63
+ * - カラーストップ位置: % → 1/1000 % (0-100000)
64
+ * - 角度: CSS 基準 (0deg = 上向き) → DrawingML 基準 (0 = 右向き、1/60000 度)
65
+ */
66
+ function buildGradFillXml(gradient, opacity) {
67
+ const alphaXml = opacity !== void 0 ? `<a:alpha val="${Math.round(opacity * 1e5)}"/>` : "";
68
+ const gsXml = gradient.stops.map((stop) => {
69
+ return `<a:gs pos="${Math.round(stop.position * 1e3)}">${alphaXml ? `<a:srgbClr val="${stop.color}">${alphaXml}</a:srgbClr>` : `<a:srgbClr val="${stop.color}"/>`}</a:gs>`;
70
+ }).join("");
71
+ const dmlAngle = ((gradient.angle - 90) % 360 + 360) % 360;
72
+ return `<a:gradFill flip="none" rotWithShape="1"><a:gsLst>${gsXml}</a:gsLst><a:lin ang="${Math.round(dmlAngle * 6e4)}" scaled="0"/></a:gradFill>`;
73
+ }
74
+ /**
75
+ * 出力 zip 内のスライド XML のマーカー solidFill を gradFill に置換する
76
+ */
77
+ async function applyGradientFills(data, registry) {
78
+ const zip = await (await loadJSZip()).loadAsync(data);
79
+ const slidePaths = Object.keys(zip.files).filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path));
80
+ for (const path of slidePaths) {
81
+ const file = zip.file(path);
82
+ if (!file) continue;
83
+ let xml = await file.async("text");
84
+ let replaced = false;
85
+ for (const { marker, gradient, opacity } of registry.entries) {
86
+ const target = `<a:solidFill><a:srgbClr val="${marker}"/></a:solidFill>`;
87
+ if (!xml.includes(target)) continue;
88
+ xml = xml.replaceAll(target, buildGradFillXml(gradient, opacity));
89
+ replaced = true;
90
+ }
91
+ if (replaced) zip.file(path, xml);
92
+ }
93
+ return zip;
94
+ }
95
+ /**
96
+ * pptx インスタンスの write / writeFile をラップし、
97
+ * 出力時にグラデーション後処理を適用する。
98
+ *
99
+ * pptxgenjs の write と同じ outputType / compression の挙動を再現する。
100
+ * writeFile は Node 環境のみ後処理対象 (ブラウザでは pptxgenjs がダウンロード
101
+ * 処理を行うため、元の実装にフォールバックする)。
102
+ */
103
+ function patchPptxWriteForGradientFills(pptx, registry) {
104
+ if (registry.isEmpty) return;
105
+ const originalWrite = pptx.write.bind(pptx);
106
+ const originalWriteFile = pptx.writeFile.bind(pptx);
107
+ const patchedWrite = async (rawProps) => {
108
+ const props = typeof rawProps === "string" ? { outputType: rawProps } : rawProps;
109
+ const zip = await applyGradientFills(await originalWrite({ outputType: "uint8array" }), registry);
110
+ const outputType = props?.outputType;
111
+ if (outputType === "STREAM") return zip.generateAsync({
112
+ type: "nodebuffer",
113
+ compression: props?.compression ? "DEFLATE" : "STORE"
114
+ });
115
+ if (outputType) return zip.generateAsync({ type: outputType });
116
+ return zip.generateAsync({
117
+ type: "blob",
118
+ compression: props?.compression ? "DEFLATE" : "STORE"
119
+ });
120
+ };
121
+ pptx.write = patchedWrite;
122
+ const patchedWriteFile = async (rawProps) => {
123
+ const props = typeof rawProps === "string" ? { fileName: rawProps } : rawProps;
124
+ if (!(typeof process !== "undefined" && Boolean(process.versions?.node))) return originalWriteFile(props);
125
+ const rawName = props?.fileName ?? "Presentation.pptx";
126
+ const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
127
+ const buffer = await patchedWrite({
128
+ outputType: "nodebuffer",
129
+ compression: props?.compression
130
+ });
131
+ await (await import("fs")).promises.writeFile(fileName, buffer);
132
+ return fileName;
133
+ };
134
+ pptx.writeFile = patchedWriteFile;
135
+ }
136
+ //#endregion
137
+ export { GradientFillRegistry, patchPptxWriteForGradientFills, registerBackgroundGradient };
138
+
139
+ //# sourceMappingURL=gradientFills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradientFills.js","names":[],"sources":["../../src/renderPptx/gradientFills.ts"],"sourcesContent":["/**\n * グラデーション塗りの実現\n *\n * pptxgenjs はグラデーション塗りを未サポート\n * (https://github.com/gitbrent/PptxGenJS/issues/102) のため、以下の方式で実現する:\n *\n * 1. レンダリング時: グラデーション指定のある shape をビルド内で一意なマーカー色の\n * 単色塗り (solidFill) として描画し、レジストリに登録する\n * 2. 出力時: pptx.write() / writeFile() をラップし、出力 zip 内のスライド XML の\n * `<a:solidFill><a:srgbClr val=\"マーカー色\"/></a:solidFill>` を\n * DrawingML ネイティブの `<a:gradFill>` に置換する\n *\n * 置換は pptxgenjs が生成する固定パターンに対する完全一致の文字列置換で行う。\n * スライド XML 全体をパーサーで往復させると無関係な要素の表現が変わり得るため、\n * 意図的に文字列置換を採用している。\n */\nimport type { LinearGradient } from \"../shared/gradient.ts\";\nimport { parseLinearGradient } from \"../shared/gradient.ts\";\n\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\ntype WriteProps = NonNullable<Parameters<PptxGenJSInstance[\"write\"]>[0]>;\ntype WriteFileProps = NonNullable<\n Parameters<PptxGenJSInstance[\"writeFile\"]>[0]\n>;\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\ninterface RegisteredGradientFill {\n /** マーカー色 (6桁大文字 HEX、# なし) */\n marker: string;\n gradient: LinearGradient;\n /** 0-1。指定時は各カラーストップに alpha として反映する */\n opacity?: number;\n}\n\n/** マーカー色の探索開始値。reserveColors で予約済みの色はスキップされる */\nconst MARKER_BASE = 0x0f7a3d;\n\nexport class GradientFillRegistry {\n private readonly reserved = new Set<string>();\n private readonly markerBySpec = new Map<string, string>();\n private readonly registered: RegisteredGradientFill[] = [];\n private nextCandidate = MARKER_BASE;\n\n /**\n * テキスト中に現れる 6桁 HEX をマーカー候補から除外する。\n * 入力 XML 由来のユーザー指定色とマーカーの衝突を防ぐため、\n * register より前に入力 XML 文字列を渡しておく。\n */\n reserveColors(text: string): void {\n for (const match of text.matchAll(/[0-9a-fA-F]{6}/g)) {\n this.reserved.add(match[0].toUpperCase());\n }\n }\n\n /** グラデーションを登録し、対応するマーカー色を返す */\n register(gradient: LinearGradient, opacity?: number): string {\n const specKey = JSON.stringify({ gradient, opacity });\n const existing = this.markerBySpec.get(specKey);\n if (existing) return existing;\n\n let marker: string;\n do {\n marker = this.nextCandidate.toString(16).toUpperCase().padStart(6, \"0\");\n this.nextCandidate = (this.nextCandidate + 1) % 0x1000000;\n } while (this.reserved.has(marker));\n this.reserved.add(marker);\n\n this.markerBySpec.set(specKey, marker);\n this.registered.push({ marker, gradient, opacity });\n return marker;\n }\n\n get isEmpty(): boolean {\n return this.registered.length === 0;\n }\n\n get entries(): readonly RegisteredGradientFill[] {\n return this.registered;\n }\n}\n\n/**\n * backgroundGradient 属性値をパースしてレジストリに登録し、マーカー色を返す。\n * パースできない場合 (スキーマ検証済みのため通常発生しない) は undefined を返す。\n */\nexport function registerBackgroundGradient(\n value: string,\n opacity: number | undefined,\n registry: GradientFillRegistry,\n): string | undefined {\n const gradient = parseLinearGradient(value);\n if (!gradient) return undefined;\n return registry.register(gradient, opacity);\n}\n\n/**\n * LinearGradient を DrawingML の `<a:gradFill>` 要素に変換する\n *\n * - カラーストップ位置: % → 1/1000 % (0-100000)\n * - 角度: CSS 基準 (0deg = 上向き) → DrawingML 基準 (0 = 右向き、1/60000 度)\n */\nfunction buildGradFillXml(gradient: LinearGradient, opacity?: number): string {\n const alphaXml =\n opacity !== undefined\n ? `<a:alpha val=\"${Math.round(opacity * 100000)}\"/>`\n : \"\";\n const gsXml = gradient.stops\n .map((stop) => {\n const pos = Math.round(stop.position * 1000);\n const srgbClr = alphaXml\n ? `<a:srgbClr val=\"${stop.color}\">${alphaXml}</a:srgbClr>`\n : `<a:srgbClr val=\"${stop.color}\"/>`;\n return `<a:gs pos=\"${pos}\">${srgbClr}</a:gs>`;\n })\n .join(\"\");\n const dmlAngle = (((gradient.angle - 90) % 360) + 360) % 360;\n const ang = Math.round(dmlAngle * 60000);\n return `<a:gradFill flip=\"none\" rotWithShape=\"1\"><a:gsLst>${gsXml}</a:gsLst><a:lin ang=\"${ang}\" scaled=\"0\"/></a:gradFill>`;\n}\n\n/**\n * 出力 zip 内のスライド XML のマーカー solidFill を gradFill に置換する\n */\nexport async function applyGradientFills(\n data: Uint8Array | ArrayBuffer,\n registry: GradientFillRegistry,\n): Promise<import(\"jszip\")> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(data);\n\n const slidePaths = Object.keys(zip.files).filter((path) =>\n /^ppt\\/slides\\/slide\\d+\\.xml$/.test(path),\n );\n for (const path of slidePaths) {\n const file = zip.file(path);\n if (!file) continue;\n let xml = await file.async(\"text\");\n let replaced = false;\n for (const { marker, gradient, opacity } of registry.entries) {\n const target = `<a:solidFill><a:srgbClr val=\"${marker}\"/></a:solidFill>`;\n if (!xml.includes(target)) continue;\n xml = xml.replaceAll(target, buildGradFillXml(gradient, opacity));\n replaced = true;\n }\n if (replaced) {\n zip.file(path, xml);\n }\n }\n return zip;\n}\n\n/**\n * pptx インスタンスの write / writeFile をラップし、\n * 出力時にグラデーション後処理を適用する。\n *\n * pptxgenjs の write と同じ outputType / compression の挙動を再現する。\n * writeFile は Node 環境のみ後処理対象 (ブラウザでは pptxgenjs がダウンロード\n * 処理を行うため、元の実装にフォールバックする)。\n */\nexport function patchPptxWriteForGradientFills(\n pptx: PptxGenJSInstance,\n registry: GradientFillRegistry,\n): void {\n if (registry.isEmpty) return;\n\n const originalWrite = pptx.write.bind(pptx);\n const originalWriteFile = pptx.writeFile.bind(pptx);\n\n const patchedWrite = async (rawProps?: WriteProps | string) => {\n // DEPRECATED: pptxgenjs は write(outputType) の文字列 overload を\n // ランタイムでは今も受け付けるため、同様に正規化する\n const props: WriteProps | undefined =\n typeof rawProps === \"string\"\n ? ({ outputType: rawProps } as WriteProps)\n : rawProps;\n const data = (await originalWrite({\n outputType: \"uint8array\",\n })) as Uint8Array;\n const zip = await applyGradientFills(data, registry);\n\n const outputType = props?.outputType;\n if (outputType === \"STREAM\") {\n return zip.generateAsync({\n type: \"nodebuffer\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n if (outputType) {\n return zip.generateAsync({ type: outputType });\n }\n return zip.generateAsync({\n type: \"blob\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n };\n pptx.write = patchedWrite;\n\n const patchedWriteFile = async (rawProps?: WriteFileProps | string) => {\n // DEPRECATED: pptxgenjs は writeFile(fileName) の文字列 overload を\n // ランタイムでは今も受け付けるため、同様に正規化する\n const props: WriteFileProps | undefined =\n typeof rawProps === \"string\" ? { fileName: rawProps } : rawProps;\n const isNode =\n typeof process !== \"undefined\" && Boolean(process.versions?.node);\n if (!isNode) {\n return originalWriteFile(props);\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const buffer = (await patchedWrite({\n outputType: \"nodebuffer\",\n compression: props?.compression,\n })) as Buffer;\n const fs = await import(\"fs\");\n await fs.promises.writeFile(fileName, buffer);\n return fileName;\n };\n pptx.writeFile = patchedWriteFile;\n}\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- import { pxToIn, pxToPt } from "../units.js";
2
- import { resolveArrowType } from "./line.js";
1
+ import { addStraightLine } from "../utils/straightLine.js";
3
2
  //#region src/renderPptx/nodes/arrow.ts
4
3
  function renderArrowNode(node, ctx) {
5
4
  const fromBounds = ctx.idPositionMap.get(node.from);
@@ -12,32 +11,12 @@ function renderArrowNode(node, ctx) {
12
11
  ctx.buildContext.diagnostics.add("ARROW_REF_NOT_FOUND", `Arrow: "to" ID "${node.to}" not found`);
13
12
  return;
14
13
  }
15
- const x1 = fromBounds.x + fromBounds.w / 2;
16
- const y1 = fromBounds.y + fromBounds.h / 2;
17
- const x2 = toBounds.x + toBounds.w / 2;
18
- const y2 = toBounds.y + toBounds.h / 2;
19
- const minX = Math.min(x1, x2);
20
- const minY = Math.min(y1, y2);
21
- const lineW = Math.abs(x2 - x1);
22
- const lineH = Math.abs(y2 - y1);
23
- const flipH = x2 < x1;
24
- const flipV = y2 < y1;
25
- const { color, lineWidth, dashType, beginArrow, endArrow } = node;
26
- ctx.slide.addShape(ctx.pptx.ShapeType.line, {
27
- x: pxToIn(minX),
28
- y: pxToIn(minY),
29
- w: pxToIn(lineW),
30
- h: pxToIn(lineH),
31
- flipH,
32
- flipV,
33
- line: {
34
- color: color ?? "000000",
35
- width: lineWidth !== void 0 ? pxToPt(lineWidth) : 1,
36
- dashType,
37
- beginArrowType: resolveArrowType(beginArrow),
38
- endArrowType: resolveArrowType(endArrow)
39
- }
40
- });
14
+ addStraightLine(ctx, {
15
+ x1: fromBounds.x + fromBounds.w / 2,
16
+ y1: fromBounds.y + fromBounds.h / 2,
17
+ x2: toBounds.x + toBounds.w / 2,
18
+ y2: toBounds.y + toBounds.h / 2
19
+ }, node);
41
20
  }
42
21
  //#endregion
43
22
  export { renderArrowNode };
@@ -1 +1 @@
1
- {"version":3,"file":"arrow.js","names":[],"sources":["../../../src/renderPptx/nodes/arrow.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\nimport { resolveArrowType } from \"./line.ts\";\n\ntype ArrowPositionedNode = Extract<PositionedNode, { type: \"arrow\" }>;\n\nexport function renderArrowNode(\n node: ArrowPositionedNode,\n ctx: RenderContext,\n): void {\n const fromBounds = ctx.idPositionMap.get(node.from);\n const toBounds = ctx.idPositionMap.get(node.to);\n\n if (!fromBounds) {\n ctx.buildContext.diagnostics.add(\n \"ARROW_REF_NOT_FOUND\",\n `Arrow: \"from\" ID \"${node.from}\" not found`,\n );\n return;\n }\n if (!toBounds) {\n ctx.buildContext.diagnostics.add(\n \"ARROW_REF_NOT_FOUND\",\n `Arrow: \"to\" ID \"${node.to}\" not found`,\n );\n return;\n }\n\n const x1 = fromBounds.x + fromBounds.w / 2;\n const y1 = fromBounds.y + fromBounds.h / 2;\n const x2 = toBounds.x + toBounds.w / 2;\n const y2 = toBounds.y + toBounds.h / 2;\n\n const minX = Math.min(x1, x2);\n const minY = Math.min(y1, y2);\n const lineW = Math.abs(x2 - x1);\n const lineH = Math.abs(y2 - y1);\n const flipH = x2 < x1;\n const flipV = y2 < y1;\n\n const { color, lineWidth, dashType, beginArrow, endArrow } = node;\n\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(minX),\n y: pxToIn(minY),\n w: pxToIn(lineW),\n h: pxToIn(lineH),\n flipH,\n flipV,\n line: {\n color: color ?? \"000000\",\n width: lineWidth !== undefined ? pxToPt(lineWidth) : 1,\n dashType,\n beginArrowType: resolveArrowType(beginArrow),\n endArrowType: resolveArrowType(endArrow),\n },\n });\n}\n"],"mappings":";;;AAOA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,aAAa,IAAI,cAAc,IAAI,KAAK,IAAI;CAClD,MAAM,WAAW,IAAI,cAAc,IAAI,KAAK,EAAE;CAE9C,IAAI,CAAC,YAAY;EACf,IAAI,aAAa,YAAY,IAC3B,uBACA,qBAAqB,KAAK,KAAK,YACjC;EACA;CACF;CACA,IAAI,CAAC,UAAU;EACb,IAAI,aAAa,YAAY,IAC3B,uBACA,mBAAmB,KAAK,GAAG,YAC7B;EACA;CACF;CAEA,MAAM,KAAK,WAAW,IAAI,WAAW,IAAI;CACzC,MAAM,KAAK,WAAW,IAAI,WAAW,IAAI;CACzC,MAAM,KAAK,SAAS,IAAI,SAAS,IAAI;CACrC,MAAM,KAAK,SAAS,IAAI,SAAS,IAAI;CAErC,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAC9B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAC9B,MAAM,QAAQ,KAAK;CACnB,MAAM,QAAQ,KAAK;CAEnB,MAAM,EAAE,OAAO,WAAW,UAAU,YAAY,aAAa;CAE7D,IAAI,MAAM,SAAS,IAAI,KAAK,UAAU,MAAM;EAC1C,GAAG,OAAO,IAAI;EACd,GAAG,OAAO,IAAI;EACd,GAAG,OAAO,KAAK;EACf,GAAG,OAAO,KAAK;EACf;EACA;EACA,MAAM;GACJ,OAAO,SAAS;GAChB,OAAO,cAAc,KAAA,IAAY,OAAO,SAAS,IAAI;GACrD;GACA,gBAAgB,iBAAiB,UAAU;GAC3C,cAAc,iBAAiB,QAAQ;EACzC;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"arrow.js","names":[],"sources":["../../../src/renderPptx/nodes/arrow.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { addStraightLine } from \"../utils/straightLine.ts\";\n\ntype ArrowPositionedNode = Extract<PositionedNode, { type: \"arrow\" }>;\n\nexport function renderArrowNode(\n node: ArrowPositionedNode,\n ctx: RenderContext,\n): void {\n const fromBounds = ctx.idPositionMap.get(node.from);\n const toBounds = ctx.idPositionMap.get(node.to);\n\n if (!fromBounds) {\n ctx.buildContext.diagnostics.add(\n \"ARROW_REF_NOT_FOUND\",\n `Arrow: \"from\" ID \"${node.from}\" not found`,\n );\n return;\n }\n if (!toBounds) {\n ctx.buildContext.diagnostics.add(\n \"ARROW_REF_NOT_FOUND\",\n `Arrow: \"to\" ID \"${node.to}\" not found`,\n );\n return;\n }\n\n // 参照ノードの中心同士を結ぶ\n addStraightLine(\n ctx,\n {\n x1: fromBounds.x + fromBounds.w / 2,\n y1: fromBounds.y + fromBounds.h / 2,\n x2: toBounds.x + toBounds.w / 2,\n y2: toBounds.y + toBounds.h / 2,\n },\n node,\n );\n}\n"],"mappings":";;AAMA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,aAAa,IAAI,cAAc,IAAI,KAAK,IAAI;CAClD,MAAM,WAAW,IAAI,cAAc,IAAI,KAAK,EAAE;CAE9C,IAAI,CAAC,YAAY;EACf,IAAI,aAAa,YAAY,IAC3B,uBACA,qBAAqB,KAAK,KAAK,YACjC;EACA;CACF;CACA,IAAI,CAAC,UAAU;EACb,IAAI,aAAa,YAAY,IAC3B,uBACA,mBAAmB,KAAK,GAAG,YAC7B;EACA;CACF;CAGA,gBACE,KACA;EACE,IAAI,WAAW,IAAI,WAAW,IAAI;EAClC,IAAI,WAAW,IAAI,WAAW,IAAI;EAClC,IAAI,SAAS,IAAI,SAAS,IAAI;EAC9B,IAAI,SAAS,IAAI,SAAS,IAAI;CAChC,GACA,IACF;AACF"}
@@ -1,5 +1,4 @@
1
- import { pxToIn } from "../units.js";
2
- import { getContentArea } from "../utils/contentArea.js";
1
+ import { getContentAreaIn } from "../utils/contentArea.js";
3
2
  //#region src/renderPptx/nodes/chart.ts
4
3
  function renderChartNode(node, ctx) {
5
4
  const chartData = node.data.map((d) => ({
@@ -7,12 +6,8 @@ function renderChartNode(node, ctx) {
7
6
  labels: d.labels,
8
7
  values: d.values
9
8
  }));
10
- const content = getContentArea(node);
11
9
  const chartOptions = {
12
- x: pxToIn(content.x),
13
- y: pxToIn(content.y),
14
- w: pxToIn(content.w),
15
- h: pxToIn(content.h),
10
+ ...getContentAreaIn(node),
16
11
  showLegend: node.showLegend ?? false,
17
12
  showTitle: node.showTitle ?? false,
18
13
  title: node.title,
@@ -1 +1 @@
1
- {"version":3,"file":"chart.js","names":[],"sources":["../../../src/renderPptx/nodes/chart.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn } from \"../units.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\n\ntype ChartPositionedNode = Extract<PositionedNode, { type: \"chart\" }>;\n\nexport function renderChartNode(\n node: ChartPositionedNode,\n ctx: RenderContext,\n): void {\n const chartData = node.data.map((d) => ({\n name: d.name,\n labels: d.labels,\n values: d.values,\n }));\n\n const content = getContentArea(node);\n const chartOptions: Record<string, unknown> = {\n x: pxToIn(content.x),\n y: pxToIn(content.y),\n w: pxToIn(content.w),\n h: pxToIn(content.h),\n showLegend: node.showLegend ?? false,\n showTitle: node.showTitle ?? false,\n title: node.title,\n chartColors: node.chartColors,\n };\n\n // radar専用オプション\n if (node.chartType === \"radar\" && node.radarStyle) {\n chartOptions.radarStyle = node.radarStyle;\n }\n\n ctx.slide.addChart(node.chartType, chartData, chartOptions);\n}\n"],"mappings":";;;AAOA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,YAAY,KAAK,KAAK,KAAK,OAAO;EACtC,MAAM,EAAE;EACR,QAAQ,EAAE;EACV,QAAQ,EAAE;CACZ,EAAE;CAEF,MAAM,UAAU,eAAe,IAAI;CACnC,MAAM,eAAwC;EAC5C,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,YAAY,KAAK,cAAc;EAC/B,WAAW,KAAK,aAAa;EAC7B,OAAO,KAAK;EACZ,aAAa,KAAK;CACpB;CAGA,IAAI,KAAK,cAAc,WAAW,KAAK,YACrC,aAAa,aAAa,KAAK;CAGjC,IAAI,MAAM,SAAS,KAAK,WAAW,WAAW,YAAY;AAC5D"}
1
+ {"version":3,"file":"chart.js","names":[],"sources":["../../../src/renderPptx/nodes/chart.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { getContentAreaIn } from \"../utils/contentArea.ts\";\n\ntype ChartPositionedNode = Extract<PositionedNode, { type: \"chart\" }>;\n\nexport function renderChartNode(\n node: ChartPositionedNode,\n ctx: RenderContext,\n): void {\n const chartData = node.data.map((d) => ({\n name: d.name,\n labels: d.labels,\n values: d.values,\n }));\n\n const chartOptions: Record<string, unknown> = {\n ...getContentAreaIn(node),\n showLegend: node.showLegend ?? false,\n showTitle: node.showTitle ?? false,\n title: node.title,\n chartColors: node.chartColors,\n };\n\n // radar専用オプション\n if (node.chartType === \"radar\" && node.radarStyle) {\n chartOptions.radarStyle = node.radarStyle;\n }\n\n ctx.slide.addChart(node.chartType, chartData, chartOptions);\n}\n"],"mappings":";;AAMA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,YAAY,KAAK,KAAK,KAAK,OAAO;EACtC,MAAM,EAAE;EACR,QAAQ,EAAE;EACV,QAAQ,EAAE;CACZ,EAAE;CAEF,MAAM,eAAwC;EAC5C,GAAG,iBAAiB,IAAI;EACxB,YAAY,KAAK,cAAc;EAC/B,WAAW,KAAK,aAAa;EAC7B,OAAO,KAAK;EACZ,aAAa,KAAK;CACpB;CAGA,IAAI,KAAK,cAAc,WAAW,KAAK,YACrC,aAAa,aAAa,KAAK;CAGjC,IAAI,MAAM,SAAS,KAAK,WAAW,WAAW,YAAY;AAC5D"}
@@ -1,7 +1,8 @@
1
1
  import { pxToIn, pxToPt } from "../units.js";
2
- import { getContentArea } from "../utils/contentArea.js";
2
+ import { withContentBounds } from "../utils/contentArea.js";
3
+ import { stripHash } from "../utils/visualStyle.js";
3
4
  import { measureFlow } from "../../calcYogaLayout/measureCompositeNodes.js";
4
- import { calcScaleFactor } from "../utils/scaleToFit.js";
5
+ import { resolveScaledContentArea } from "../utils/scaleToFit.js";
5
6
  //#region src/renderPptx/nodes/flow.ts
6
7
  function renderFlowNode(node, ctx) {
7
8
  const direction = node.direction ?? "horizontal";
@@ -10,21 +11,13 @@ function renderFlowNode(node, ctx) {
10
11
  const nodeGap = node.nodeGap ?? 80;
11
12
  const connectorStyle = node.connectorStyle ?? {};
12
13
  const defaultColor = "1D4ED8";
13
- const content = getContentArea(node);
14
- const intrinsic = measureFlow(node);
15
- const scaleFactor = calcScaleFactor(content.w, content.h, intrinsic.width, intrinsic.height, "flow", ctx.buildContext.diagnostics);
14
+ const { content, scaleFactor } = resolveScaledContentArea(node, measureFlow(node), ctx);
16
15
  const scaledNodeWidth = nodeWidth * scaleFactor;
17
16
  const scaledNodeHeight = nodeHeight * scaleFactor;
18
17
  const scaledNodeGap = nodeGap * scaleFactor;
19
18
  const layouts = /* @__PURE__ */ new Map();
20
19
  const nodeCount = node.nodes.length;
21
- const contentNode = {
22
- ...node,
23
- x: content.x,
24
- y: content.y,
25
- w: content.w,
26
- h: content.h
27
- };
20
+ const contentNode = withContentBounds(node, content);
28
21
  if (direction === "horizontal") calculateHorizontalLayout(contentNode, layouts, nodeCount, scaledNodeWidth, scaledNodeHeight, scaledNodeGap, scaleFactor);
29
22
  else calculateVerticalLayout(contentNode, layouts, nodeCount, scaledNodeWidth, scaledNodeHeight, scaledNodeGap, scaleFactor);
30
23
  for (const conn of node.connections) {
@@ -44,7 +37,7 @@ function renderFlowNode(node, ctx) {
44
37
  h: pxToIn(labelH),
45
38
  fontSize: pxToPt(10 * scaleFactor),
46
39
  fontFace: "Noto Sans JP",
47
- color: "64748B",
40
+ color: stripHash(conn.labelColor) ?? stripHash(connectorStyle.labelColor) ?? "64748B",
48
41
  align: "center",
49
42
  valign: "middle"
50
43
  });
@@ -1 +1 @@
1
- {"version":3,"file":"flow.js","names":[],"sources":["../../../src/renderPptx/nodes/flow.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\nimport { measureFlow } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { calcScaleFactor } from \"../utils/scaleToFit.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\n\ntype FlowPositionedNode = Extract<PositionedNode, { type: \"flow\" }>;\n\ninterface FlowNodeLayout {\n id: string;\n x: number;\n y: number;\n width: number;\n height: number;\n item: FlowPositionedNode[\"nodes\"][0];\n}\n\nexport function renderFlowNode(\n node: FlowPositionedNode,\n ctx: RenderContext,\n): void {\n const direction = node.direction ?? \"horizontal\";\n const nodeWidth = node.nodeWidth ?? 120;\n const nodeHeight = node.nodeHeight ?? 60;\n const nodeGap = node.nodeGap ?? 80;\n const connectorStyle = node.connectorStyle ?? {};\n const defaultColor = \"1D4ED8\";\n\n // スケール係数を計算(コンテンツ領域基準)\n const content = getContentArea(node);\n const intrinsic = measureFlow(node);\n const scaleFactor = calcScaleFactor(\n content.w,\n content.h,\n intrinsic.width,\n intrinsic.height,\n \"flow\",\n ctx.buildContext.diagnostics,\n );\n\n const scaledNodeWidth = nodeWidth * scaleFactor;\n const scaledNodeHeight = nodeHeight * scaleFactor;\n const scaledNodeGap = nodeGap * scaleFactor;\n\n const layouts = new Map<string, FlowNodeLayout>();\n const nodeCount = node.nodes.length;\n\n // コンテンツ領域を使用するための仮想ノードを作成\n const contentNode = {\n ...node,\n x: content.x,\n y: content.y,\n w: content.w,\n h: content.h,\n };\n\n // ノードのレイアウトを計算\n if (direction === \"horizontal\") {\n calculateHorizontalLayout(\n contentNode,\n layouts,\n nodeCount,\n scaledNodeWidth,\n scaledNodeHeight,\n scaledNodeGap,\n scaleFactor,\n );\n } else {\n calculateVerticalLayout(\n contentNode,\n layouts,\n nodeCount,\n scaledNodeWidth,\n scaledNodeHeight,\n scaledNodeGap,\n scaleFactor,\n );\n }\n\n // 接続線を描画(ノードより先に描画して背面に配置)\n for (const conn of node.connections) {\n const fromLayout = layouts.get(conn.from);\n const toLayout = layouts.get(conn.to);\n\n if (!fromLayout || !toLayout) continue;\n\n const lineColor = conn.color ?? connectorStyle.color ?? \"333333\";\n const lineWidth = (connectorStyle.width ?? 2) * scaleFactor;\n const arrowType = connectorStyle.arrowType ?? \"triangle\";\n\n drawConnection(\n ctx,\n direction,\n fromLayout,\n toLayout,\n lineColor,\n lineWidth,\n arrowType,\n );\n\n // ラベルを描画\n if (conn.label) {\n const labelX =\n (fromLayout.x +\n fromLayout.width / 2 +\n toLayout.x +\n toLayout.width / 2) /\n 2;\n const labelY =\n (fromLayout.y +\n fromLayout.height / 2 +\n toLayout.y +\n toLayout.height / 2) /\n 2;\n\n const labelW = 60 * scaleFactor;\n const labelH = 20 * scaleFactor;\n\n ctx.slide.addText(conn.label, {\n x: pxToIn(labelX - labelW / 2),\n y: pxToIn(labelY - labelH / 2),\n w: pxToIn(labelW),\n h: pxToIn(labelH),\n fontSize: pxToPt(10 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color: \"64748B\",\n align: \"center\",\n valign: \"middle\",\n });\n }\n }\n\n // ノードを描画\n for (const item of node.nodes) {\n const layout = layouts.get(item.id);\n if (!layout) continue;\n\n const fillColor = item.color ?? defaultColor;\n const textColor = item.textColor ?? \"FFFFFF\";\n\n // 図形を描画\n ctx.slide.addText(item.text, {\n x: pxToIn(layout.x),\n y: pxToIn(layout.y),\n w: pxToIn(layout.width),\n h: pxToIn(layout.height),\n shape: item.shape,\n fill: { color: fillColor },\n line: { color: \"333333\", width: pxToPt(1 * scaleFactor) },\n fontSize: pxToPt(14 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color: textColor,\n align: \"center\",\n valign: \"middle\",\n });\n }\n}\n\nfunction calculateHorizontalLayout(\n node: FlowPositionedNode,\n layouts: Map<string, FlowNodeLayout>,\n nodeCount: number,\n nodeWidth: number,\n nodeHeight: number,\n nodeGap: number,\n scaleFactor: number,\n): void {\n const totalWidth = nodeCount * nodeWidth + (nodeCount - 1) * nodeGap;\n const startX = node.x + (node.w - totalWidth) / 2;\n const centerY = node.y + node.h / 2;\n\n node.nodes.forEach((item, index) => {\n const w = (item.width ?? nodeWidth / scaleFactor) * scaleFactor;\n const h = (item.height ?? nodeHeight / scaleFactor) * scaleFactor;\n layouts.set(item.id, {\n id: item.id,\n x: startX + index * (nodeWidth + nodeGap) + (nodeWidth - w) / 2,\n y: centerY - h / 2,\n width: w,\n height: h,\n item,\n });\n });\n}\n\nfunction calculateVerticalLayout(\n node: FlowPositionedNode,\n layouts: Map<string, FlowNodeLayout>,\n nodeCount: number,\n nodeWidth: number,\n nodeHeight: number,\n nodeGap: number,\n scaleFactor: number,\n): void {\n const totalHeight = nodeCount * nodeHeight + (nodeCount - 1) * nodeGap;\n const startY = node.y + (node.h - totalHeight) / 2;\n const centerX = node.x + node.w / 2;\n\n node.nodes.forEach((item, index) => {\n const w = (item.width ?? nodeWidth / scaleFactor) * scaleFactor;\n const h = (item.height ?? nodeHeight / scaleFactor) * scaleFactor;\n layouts.set(item.id, {\n id: item.id,\n x: centerX - w / 2,\n y: startY + index * (nodeHeight + nodeGap) + (nodeHeight - h) / 2,\n width: w,\n height: h,\n item,\n });\n });\n}\n\nfunction drawConnection(\n ctx: RenderContext,\n direction: \"horizontal\" | \"vertical\",\n fromLayout: FlowNodeLayout,\n toLayout: FlowNodeLayout,\n lineColor: string,\n lineWidth: number,\n arrowType: \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\",\n): void {\n let startX: number, startY: number, endX: number, endY: number;\n\n if (direction === \"horizontal\") {\n // 水平: 右端から左端へ\n startX = fromLayout.x + fromLayout.width;\n startY = fromLayout.y + fromLayout.height / 2;\n endX = toLayout.x;\n endY = toLayout.y + toLayout.height / 2;\n } else {\n // 垂直: 下端から上端へ\n startX = fromLayout.x + fromLayout.width / 2;\n startY = fromLayout.y + fromLayout.height;\n endX = toLayout.x + toLayout.width / 2;\n endY = toLayout.y;\n }\n\n // 直線接続(シンプルなケース)\n const isHorizontalLine = Math.abs(startY - endY) < 1;\n const isVerticalLine = Math.abs(startX - endX) < 1;\n\n if (isHorizontalLine || isVerticalLine) {\n // 直線で描画\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(Math.min(startX, endX)),\n y: pxToIn(Math.min(startY, endY)),\n w: pxToIn(Math.abs(endX - startX)),\n h: pxToIn(Math.abs(endY - startY)),\n line: {\n color: lineColor,\n width: pxToPt(lineWidth),\n endArrowType: arrowType,\n },\n });\n } else {\n // L字型接続\n drawLShapedConnection(\n ctx,\n direction,\n startX,\n startY,\n endX,\n endY,\n lineColor,\n lineWidth,\n arrowType,\n );\n }\n}\n\nfunction drawLShapedConnection(\n ctx: RenderContext,\n direction: \"horizontal\" | \"vertical\",\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n lineColor: string,\n lineWidth: number,\n arrowType: \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\",\n): void {\n const midX = (startX + endX) / 2;\n const midY = (startY + endY) / 2;\n\n if (direction === \"horizontal\") {\n // 水平→垂直→水平\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(startX),\n y: pxToIn(startY),\n w: pxToIn(midX - startX),\n h: 0,\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(midX),\n y: pxToIn(Math.min(startY, endY)),\n w: 0,\n h: pxToIn(Math.abs(endY - startY)),\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(midX),\n y: pxToIn(endY),\n w: pxToIn(endX - midX),\n h: 0,\n line: {\n color: lineColor,\n width: pxToPt(lineWidth),\n endArrowType: arrowType,\n },\n });\n } else {\n // 垂直→水平→垂直\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(startX),\n y: pxToIn(startY),\n w: 0,\n h: pxToIn(midY - startY),\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(Math.min(startX, endX)),\n y: pxToIn(midY),\n w: pxToIn(Math.abs(endX - startX)),\n h: 0,\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(endX),\n y: pxToIn(midY),\n w: 0,\n h: pxToIn(endY - midY),\n line: {\n color: lineColor,\n width: pxToPt(lineWidth),\n endArrowType: arrowType,\n },\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"flow.js","names":[],"sources":["../../../src/renderPptx/nodes/flow.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { stripHash } from \"../utils/visualStyle.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\nimport { measureFlow } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { resolveScaledContentArea } from \"../utils/scaleToFit.ts\";\nimport { withContentBounds } from \"../utils/contentArea.ts\";\n\ntype FlowPositionedNode = Extract<PositionedNode, { type: \"flow\" }>;\n\ninterface FlowNodeLayout {\n id: string;\n x: number;\n y: number;\n width: number;\n height: number;\n item: FlowPositionedNode[\"nodes\"][0];\n}\n\nexport function renderFlowNode(\n node: FlowPositionedNode,\n ctx: RenderContext,\n): void {\n const direction = node.direction ?? \"horizontal\";\n const nodeWidth = node.nodeWidth ?? 120;\n const nodeHeight = node.nodeHeight ?? 60;\n const nodeGap = node.nodeGap ?? 80;\n const connectorStyle = node.connectorStyle ?? {};\n const defaultColor = \"1D4ED8\";\n\n // スケール係数を計算(コンテンツ領域基準)\n const { content, scaleFactor } = resolveScaledContentArea(\n node,\n measureFlow(node),\n ctx,\n );\n\n const scaledNodeWidth = nodeWidth * scaleFactor;\n const scaledNodeHeight = nodeHeight * scaleFactor;\n const scaledNodeGap = nodeGap * scaleFactor;\n\n const layouts = new Map<string, FlowNodeLayout>();\n const nodeCount = node.nodes.length;\n\n // コンテンツ領域を使用するための仮想ノードを作成\n const contentNode = withContentBounds(node, content);\n\n // ノードのレイアウトを計算\n if (direction === \"horizontal\") {\n calculateHorizontalLayout(\n contentNode,\n layouts,\n nodeCount,\n scaledNodeWidth,\n scaledNodeHeight,\n scaledNodeGap,\n scaleFactor,\n );\n } else {\n calculateVerticalLayout(\n contentNode,\n layouts,\n nodeCount,\n scaledNodeWidth,\n scaledNodeHeight,\n scaledNodeGap,\n scaleFactor,\n );\n }\n\n // 接続線を描画(ノードより先に描画して背面に配置)\n for (const conn of node.connections) {\n const fromLayout = layouts.get(conn.from);\n const toLayout = layouts.get(conn.to);\n\n if (!fromLayout || !toLayout) continue;\n\n const lineColor = conn.color ?? connectorStyle.color ?? \"333333\";\n const lineWidth = (connectorStyle.width ?? 2) * scaleFactor;\n const arrowType = connectorStyle.arrowType ?? \"triangle\";\n\n drawConnection(\n ctx,\n direction,\n fromLayout,\n toLayout,\n lineColor,\n lineWidth,\n arrowType,\n );\n\n // ラベルを描画\n if (conn.label) {\n const labelX =\n (fromLayout.x +\n fromLayout.width / 2 +\n toLayout.x +\n toLayout.width / 2) /\n 2;\n const labelY =\n (fromLayout.y +\n fromLayout.height / 2 +\n toLayout.y +\n toLayout.height / 2) /\n 2;\n\n const labelW = 60 * scaleFactor;\n const labelH = 20 * scaleFactor;\n\n ctx.slide.addText(conn.label, {\n x: pxToIn(labelX - labelW / 2),\n y: pxToIn(labelY - labelH / 2),\n w: pxToIn(labelW),\n h: pxToIn(labelH),\n fontSize: pxToPt(10 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color:\n stripHash(conn.labelColor) ??\n stripHash(connectorStyle.labelColor) ??\n \"64748B\",\n align: \"center\",\n valign: \"middle\",\n });\n }\n }\n\n // ノードを描画\n for (const item of node.nodes) {\n const layout = layouts.get(item.id);\n if (!layout) continue;\n\n const fillColor = item.color ?? defaultColor;\n const textColor = item.textColor ?? \"FFFFFF\";\n\n // 図形を描画\n ctx.slide.addText(item.text, {\n x: pxToIn(layout.x),\n y: pxToIn(layout.y),\n w: pxToIn(layout.width),\n h: pxToIn(layout.height),\n shape: item.shape,\n fill: { color: fillColor },\n line: { color: \"333333\", width: pxToPt(1 * scaleFactor) },\n fontSize: pxToPt(14 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color: textColor,\n align: \"center\",\n valign: \"middle\",\n });\n }\n}\n\nfunction calculateHorizontalLayout(\n node: FlowPositionedNode,\n layouts: Map<string, FlowNodeLayout>,\n nodeCount: number,\n nodeWidth: number,\n nodeHeight: number,\n nodeGap: number,\n scaleFactor: number,\n): void {\n const totalWidth = nodeCount * nodeWidth + (nodeCount - 1) * nodeGap;\n const startX = node.x + (node.w - totalWidth) / 2;\n const centerY = node.y + node.h / 2;\n\n node.nodes.forEach((item, index) => {\n const w = (item.width ?? nodeWidth / scaleFactor) * scaleFactor;\n const h = (item.height ?? nodeHeight / scaleFactor) * scaleFactor;\n layouts.set(item.id, {\n id: item.id,\n x: startX + index * (nodeWidth + nodeGap) + (nodeWidth - w) / 2,\n y: centerY - h / 2,\n width: w,\n height: h,\n item,\n });\n });\n}\n\nfunction calculateVerticalLayout(\n node: FlowPositionedNode,\n layouts: Map<string, FlowNodeLayout>,\n nodeCount: number,\n nodeWidth: number,\n nodeHeight: number,\n nodeGap: number,\n scaleFactor: number,\n): void {\n const totalHeight = nodeCount * nodeHeight + (nodeCount - 1) * nodeGap;\n const startY = node.y + (node.h - totalHeight) / 2;\n const centerX = node.x + node.w / 2;\n\n node.nodes.forEach((item, index) => {\n const w = (item.width ?? nodeWidth / scaleFactor) * scaleFactor;\n const h = (item.height ?? nodeHeight / scaleFactor) * scaleFactor;\n layouts.set(item.id, {\n id: item.id,\n x: centerX - w / 2,\n y: startY + index * (nodeHeight + nodeGap) + (nodeHeight - h) / 2,\n width: w,\n height: h,\n item,\n });\n });\n}\n\nfunction drawConnection(\n ctx: RenderContext,\n direction: \"horizontal\" | \"vertical\",\n fromLayout: FlowNodeLayout,\n toLayout: FlowNodeLayout,\n lineColor: string,\n lineWidth: number,\n arrowType: \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\",\n): void {\n let startX: number, startY: number, endX: number, endY: number;\n\n if (direction === \"horizontal\") {\n // 水平: 右端から左端へ\n startX = fromLayout.x + fromLayout.width;\n startY = fromLayout.y + fromLayout.height / 2;\n endX = toLayout.x;\n endY = toLayout.y + toLayout.height / 2;\n } else {\n // 垂直: 下端から上端へ\n startX = fromLayout.x + fromLayout.width / 2;\n startY = fromLayout.y + fromLayout.height;\n endX = toLayout.x + toLayout.width / 2;\n endY = toLayout.y;\n }\n\n // 直線接続(シンプルなケース)\n const isHorizontalLine = Math.abs(startY - endY) < 1;\n const isVerticalLine = Math.abs(startX - endX) < 1;\n\n if (isHorizontalLine || isVerticalLine) {\n // 直線で描画\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(Math.min(startX, endX)),\n y: pxToIn(Math.min(startY, endY)),\n w: pxToIn(Math.abs(endX - startX)),\n h: pxToIn(Math.abs(endY - startY)),\n line: {\n color: lineColor,\n width: pxToPt(lineWidth),\n endArrowType: arrowType,\n },\n });\n } else {\n // L字型接続\n drawLShapedConnection(\n ctx,\n direction,\n startX,\n startY,\n endX,\n endY,\n lineColor,\n lineWidth,\n arrowType,\n );\n }\n}\n\nfunction drawLShapedConnection(\n ctx: RenderContext,\n direction: \"horizontal\" | \"vertical\",\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n lineColor: string,\n lineWidth: number,\n arrowType: \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\",\n): void {\n const midX = (startX + endX) / 2;\n const midY = (startY + endY) / 2;\n\n if (direction === \"horizontal\") {\n // 水平→垂直→水平\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(startX),\n y: pxToIn(startY),\n w: pxToIn(midX - startX),\n h: 0,\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(midX),\n y: pxToIn(Math.min(startY, endY)),\n w: 0,\n h: pxToIn(Math.abs(endY - startY)),\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(midX),\n y: pxToIn(endY),\n w: pxToIn(endX - midX),\n h: 0,\n line: {\n color: lineColor,\n width: pxToPt(lineWidth),\n endArrowType: arrowType,\n },\n });\n } else {\n // 垂直→水平→垂直\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(startX),\n y: pxToIn(startY),\n w: 0,\n h: pxToIn(midY - startY),\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(Math.min(startX, endX)),\n y: pxToIn(midY),\n w: pxToIn(Math.abs(endX - startX)),\n h: 0,\n line: { color: lineColor, width: pxToPt(lineWidth) },\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(endX),\n y: pxToIn(midY),\n w: 0,\n h: pxToIn(endY - midY),\n line: {\n color: lineColor,\n width: pxToPt(lineWidth),\n endArrowType: arrowType,\n },\n });\n }\n}\n"],"mappings":"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"}
@@ -16,7 +16,8 @@ function renderIconNode(node, ctx) {
16
16
  color: colorValue,
17
17
  width: 1.5
18
18
  },
19
- rectRadius: isCircle ? void 0 : .1
19
+ rectRadius: isCircle ? void 0 : .1,
20
+ rotate: node.rotate
20
21
  };
21
22
  ctx.slide.addShape(shapeType, shapeOptions);
22
23
  }
@@ -25,7 +26,8 @@ function renderIconNode(node, ctx) {
25
26
  x: pxToIn(node.iconX ?? node.x),
26
27
  y: pxToIn(node.iconY ?? node.y),
27
28
  w: pxToIn(node.iconW ?? node.w),
28
- h: pxToIn(node.iconH ?? node.h)
29
+ h: pxToIn(node.iconH ?? node.h),
30
+ rotate: node.rotate
29
31
  });
30
32
  }
31
33
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"icon.js","names":[],"sources":["../../../src/renderPptx/nodes/icon.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn } from \"../units.ts\";\n\ntype IconPositionedNode = Extract<PositionedNode, { type: \"icon\" }>;\n\nexport function renderIconNode(\n node: IconPositionedNode,\n ctx: RenderContext,\n): void {\n // variant 指定時は背景図形を描画\n if (node.variant) {\n const isCircle = node.variant.startsWith(\"circle\");\n const isFilled = node.variant.endsWith(\"-filled\");\n const bgColor = node.bgColor ?? \"#E0E0E0\";\n const colorValue = bgColor.replace(/^#/, \"\");\n\n const shapeType = isCircle ? \"ellipse\" : \"roundRect\";\n const shapeOptions: Record<string, unknown> = {\n x: pxToIn(node.bgX ?? node.x),\n y: pxToIn(node.bgY ?? node.y),\n w: pxToIn(node.bgW ?? node.w),\n h: pxToIn(node.bgH ?? node.h),\n fill: isFilled ? { color: colorValue } : { type: \"none\" as const },\n line: isFilled ? undefined : { color: colorValue, width: 1.5 },\n rectRadius: isCircle ? undefined : 0.1,\n };\n\n ctx.slide.addShape(shapeType, shapeOptions);\n }\n\n ctx.slide.addImage({\n data: node.iconImageData,\n x: pxToIn(node.iconX ?? node.x),\n y: pxToIn(node.iconY ?? node.y),\n w: pxToIn(node.iconW ?? node.w),\n h: pxToIn(node.iconH ?? node.h),\n });\n}\n"],"mappings":";;AAMA,SAAgB,eACd,MACA,KACM;CAEN,IAAI,KAAK,SAAS;EAChB,MAAM,WAAW,KAAK,QAAQ,WAAW,QAAQ;EACjD,MAAM,WAAW,KAAK,QAAQ,SAAS,SAAS;EAEhD,MAAM,cADU,KAAK,WAAW,WACL,QAAQ,MAAM,EAAE;EAE3C,MAAM,YAAY,WAAW,YAAY;EACzC,MAAM,eAAwC;GAC5C,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,MAAM,WAAW,EAAE,OAAO,WAAW,IAAI,EAAE,MAAM,OAAgB;GACjE,MAAM,WAAW,KAAA,IAAY;IAAE,OAAO;IAAY,OAAO;GAAI;GAC7D,YAAY,WAAW,KAAA,IAAY;EACrC;EAEA,IAAI,MAAM,SAAS,WAAW,YAAY;CAC5C;CAEA,IAAI,MAAM,SAAS;EACjB,MAAM,KAAK;EACX,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;EAC9B,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;EAC9B,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;EAC9B,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;CAChC,CAAC;AACH"}
1
+ {"version":3,"file":"icon.js","names":[],"sources":["../../../src/renderPptx/nodes/icon.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn } from \"../units.ts\";\n\ntype IconPositionedNode = Extract<PositionedNode, { type: \"icon\" }>;\n\nexport function renderIconNode(\n node: IconPositionedNode,\n ctx: RenderContext,\n): void {\n // variant 指定時は背景図形を描画\n if (node.variant) {\n const isCircle = node.variant.startsWith(\"circle\");\n const isFilled = node.variant.endsWith(\"-filled\");\n const bgColor = node.bgColor ?? \"#E0E0E0\";\n const colorValue = bgColor.replace(/^#/, \"\");\n\n const shapeType = isCircle ? \"ellipse\" : \"roundRect\";\n const shapeOptions: Record<string, unknown> = {\n x: pxToIn(node.bgX ?? node.x),\n y: pxToIn(node.bgY ?? node.y),\n w: pxToIn(node.bgW ?? node.w),\n h: pxToIn(node.bgH ?? node.h),\n fill: isFilled ? { color: colorValue } : { type: \"none\" as const },\n line: isFilled ? undefined : { color: colorValue, width: 1.5 },\n rectRadius: isCircle ? undefined : 0.1,\n rotate: node.rotate,\n };\n\n ctx.slide.addShape(shapeType, shapeOptions);\n }\n\n ctx.slide.addImage({\n data: node.iconImageData,\n x: pxToIn(node.iconX ?? node.x),\n y: pxToIn(node.iconY ?? node.y),\n w: pxToIn(node.iconW ?? node.w),\n h: pxToIn(node.iconH ?? node.h),\n rotate: node.rotate,\n });\n}\n"],"mappings":";;AAMA,SAAgB,eACd,MACA,KACM;CAEN,IAAI,KAAK,SAAS;EAChB,MAAM,WAAW,KAAK,QAAQ,WAAW,QAAQ;EACjD,MAAM,WAAW,KAAK,QAAQ,SAAS,SAAS;EAEhD,MAAM,cADU,KAAK,WAAW,UAAA,CACL,QAAQ,MAAM,EAAE;EAE3C,MAAM,YAAY,WAAW,YAAY;EACzC,MAAM,eAAwC;GAC5C,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;GAC5B,MAAM,WAAW,EAAE,OAAO,WAAW,IAAI,EAAE,MAAM,OAAgB;GACjE,MAAM,WAAW,KAAA,IAAY;IAAE,OAAO;IAAY,OAAO;GAAI;GAC7D,YAAY,WAAW,KAAA,IAAY;GACnC,QAAQ,KAAK;EACf;EAEA,IAAI,MAAM,SAAS,WAAW,YAAY;CAC5C;CAEA,IAAI,MAAM,SAAS;EACjB,MAAM,KAAK;EACX,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;EAC9B,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;EAC9B,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;EAC9B,GAAG,OAAO,KAAK,SAAS,KAAK,CAAC;EAC9B,QAAQ,KAAK;CACf,CAAC;AACH"}
@@ -1,21 +1,13 @@
1
- import { pxToIn } from "../units.js";
1
+ import { pxToIn, rectPxToIn } from "../units.js";
2
2
  import { getContentArea } from "../utils/contentArea.js";
3
+ import { convertShadow } from "../utils/visualStyle.js";
3
4
  //#region src/renderPptx/nodes/image.ts
4
5
  function renderImageNode(node, ctx) {
5
6
  const content = getContentArea(node);
6
7
  const imageOptions = {
7
- x: pxToIn(content.x),
8
- y: pxToIn(content.y),
9
- w: pxToIn(content.w),
10
- h: pxToIn(content.h),
11
- shadow: node.shadow ? {
12
- type: node.shadow.type ?? "outer",
13
- opacity: node.shadow.opacity,
14
- blur: node.shadow.blur,
15
- angle: node.shadow.angle,
16
- offset: node.shadow.offset,
17
- color: node.shadow.color
18
- } : void 0
8
+ ...rectPxToIn(content),
9
+ shadow: convertShadow(node.shadow),
10
+ rotate: node.rotate
19
11
  };
20
12
  if (node.sizing) imageOptions.sizing = {
21
13
  type: node.sizing.type,
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","names":[],"sources":["../../../src/renderPptx/nodes/image.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn } from \"../units.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\n\ntype ImagePositionedNode = Extract<PositionedNode, { type: \"image\" }>;\n\nexport function renderImageNode(\n node: ImagePositionedNode,\n ctx: RenderContext,\n): void {\n const content = getContentArea(node);\n const imageOptions: Record<string, unknown> = {\n x: pxToIn(content.x),\n y: pxToIn(content.y),\n w: pxToIn(content.w),\n h: pxToIn(content.h),\n shadow: node.shadow\n ? {\n type: node.shadow.type ?? (\"outer\" as const),\n opacity: node.shadow.opacity,\n blur: node.shadow.blur,\n angle: node.shadow.angle,\n offset: node.shadow.offset,\n color: node.shadow.color,\n }\n : undefined,\n };\n\n if (node.sizing) {\n imageOptions.sizing = {\n type: node.sizing.type,\n w: pxToIn(node.sizing.w ?? content.w),\n h: pxToIn(node.sizing.h ?? content.h),\n ...(node.sizing.x !== undefined && { x: pxToIn(node.sizing.x) }),\n ...(node.sizing.y !== undefined && { y: pxToIn(node.sizing.y) }),\n };\n }\n\n if (node.imageData) {\n // Base64 データがある場合は data プロパティを使用(リモート画像)\n ctx.slide.addImage({ ...imageOptions, data: node.imageData });\n } else {\n // ローカルパスの場合は path プロパティを使用\n ctx.slide.addImage({ ...imageOptions, path: node.src });\n }\n}\n"],"mappings":";;;AAOA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,UAAU,eAAe,IAAI;CACnC,MAAM,eAAwC;EAC5C,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,QAAQ,KAAK,SACT;GACE,MAAM,KAAK,OAAO,QAAS;GAC3B,SAAS,KAAK,OAAO;GACrB,MAAM,KAAK,OAAO;GAClB,OAAO,KAAK,OAAO;GACnB,QAAQ,KAAK,OAAO;GACpB,OAAO,KAAK,OAAO;EACrB,IACA,KAAA;CACN;CAEA,IAAI,KAAK,QACP,aAAa,SAAS;EACpB,MAAM,KAAK,OAAO;EAClB,GAAG,OAAO,KAAK,OAAO,KAAK,QAAQ,CAAC;EACpC,GAAG,OAAO,KAAK,OAAO,KAAK,QAAQ,CAAC;EACpC,GAAI,KAAK,OAAO,MAAM,KAAA,KAAa,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,EAAE;EAC9D,GAAI,KAAK,OAAO,MAAM,KAAA,KAAa,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,EAAE;CAChE;CAGF,IAAI,KAAK,WAEP,IAAI,MAAM,SAAS;EAAE,GAAG;EAAc,MAAM,KAAK;CAAU,CAAC;MAG5D,IAAI,MAAM,SAAS;EAAE,GAAG;EAAc,MAAM,KAAK;CAAI,CAAC;AAE1D"}
1
+ {"version":3,"file":"image.js","names":[],"sources":["../../../src/renderPptx/nodes/image.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, rectPxToIn } from \"../units.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\nimport { convertShadow } from \"../utils/visualStyle.ts\";\n\ntype ImagePositionedNode = Extract<PositionedNode, { type: \"image\" }>;\n\nexport function renderImageNode(\n node: ImagePositionedNode,\n ctx: RenderContext,\n): void {\n const content = getContentArea(node);\n const imageOptions: Record<string, unknown> = {\n ...rectPxToIn(content),\n shadow: convertShadow(node.shadow),\n rotate: node.rotate,\n };\n\n if (node.sizing) {\n imageOptions.sizing = {\n type: node.sizing.type,\n w: pxToIn(node.sizing.w ?? content.w),\n h: pxToIn(node.sizing.h ?? content.h),\n ...(node.sizing.x !== undefined && { x: pxToIn(node.sizing.x) }),\n ...(node.sizing.y !== undefined && { y: pxToIn(node.sizing.y) }),\n };\n }\n\n if (node.imageData) {\n // Base64 データがある場合は data プロパティを使用(リモート画像)\n ctx.slide.addImage({ ...imageOptions, data: node.imageData });\n } else {\n // ローカルパスの場合は path プロパティを使用\n ctx.slide.addImage({ ...imageOptions, path: node.src });\n }\n}\n"],"mappings":";;;;AAQA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,UAAU,eAAe,IAAI;CACnC,MAAM,eAAwC;EAC5C,GAAG,WAAW,OAAO;EACrB,QAAQ,cAAc,KAAK,MAAM;EACjC,QAAQ,KAAK;CACf;CAEA,IAAI,KAAK,QACP,aAAa,SAAS;EACpB,MAAM,KAAK,OAAO;EAClB,GAAG,OAAO,KAAK,OAAO,KAAK,QAAQ,CAAC;EACpC,GAAG,OAAO,KAAK,OAAO,KAAK,QAAQ,CAAC;EACpC,GAAI,KAAK,OAAO,MAAM,KAAA,KAAa,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,EAAE;EAC9D,GAAI,KAAK,OAAO,MAAM,KAAA,KAAa,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,EAAE;CAChE;CAGF,IAAI,KAAK,WAEP,IAAI,MAAM,SAAS;EAAE,GAAG;EAAc,MAAM,KAAK;CAAU,CAAC;MAG5D,IAAI,MAAM,SAAS;EAAE,GAAG;EAAc,MAAM,KAAK;CAAI,CAAC;AAE1D"}
@@ -1,39 +1,15 @@
1
- import { pxToIn, pxToPt } from "../units.js";
1
+ import { addStraightLine } from "../utils/straightLine.js";
2
2
  //#region src/renderPptx/nodes/line.ts
3
- /**
4
- * boolean | LineArrowOptions から pptxgenjs の arrow type を取得
5
- */
6
- function resolveArrowType(arrow) {
7
- if (arrow === void 0) return;
8
- if (arrow === false) return "none";
9
- if (arrow === true) return "triangle";
10
- return arrow.type ?? "triangle";
11
- }
12
3
  function renderLineNode(node, ctx) {
13
- const { x1, y1, x2, y2, color, lineWidth, dashType, beginArrow, endArrow } = node;
14
- const minX = Math.min(x1, x2);
15
- const minY = Math.min(y1, y2);
16
- const lineW = Math.abs(x2 - x1);
17
- const lineH = Math.abs(y2 - y1);
18
- const flipH = x2 < x1;
19
- const flipV = y2 < y1;
20
- ctx.slide.addShape(ctx.pptx.ShapeType.line, {
21
- x: pxToIn(minX),
22
- y: pxToIn(minY),
23
- w: pxToIn(lineW),
24
- h: pxToIn(lineH),
25
- flipH,
26
- flipV,
27
- line: {
28
- color: color ?? "000000",
29
- width: lineWidth !== void 0 ? pxToPt(lineWidth) : 1,
30
- dashType,
31
- beginArrowType: resolveArrowType(beginArrow),
32
- endArrowType: resolveArrowType(endArrow)
33
- }
34
- });
4
+ const { x1, y1, x2, y2 } = node;
5
+ addStraightLine(ctx, {
6
+ x1,
7
+ y1,
8
+ x2,
9
+ y2
10
+ }, node);
35
11
  }
36
12
  //#endregion
37
- export { renderLineNode, resolveArrowType };
13
+ export { renderLineNode };
38
14
 
39
15
  //# sourceMappingURL=line.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"line.js","names":[],"sources":["../../../src/renderPptx/nodes/line.ts"],"sourcesContent":["import type { PositionedNode, LineArrow } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\n\ntype LinePositionedNode = Extract<PositionedNode, { type: \"line\" }>;\n\n/**\n * boolean | LineArrowOptions から pptxgenjs の arrow type を取得\n */\nexport function resolveArrowType(\n arrow: LineArrow | undefined,\n): \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\" | undefined {\n if (arrow === undefined) {\n return undefined;\n }\n if (arrow === false) {\n return \"none\";\n }\n if (arrow === true) {\n return \"triangle\"; // デフォルト\n }\n return arrow.type ?? \"triangle\";\n}\n\nexport function renderLineNode(\n node: LinePositionedNode,\n ctx: RenderContext,\n): void {\n const { x1, y1, x2, y2, color, lineWidth, dashType, beginArrow, endArrow } =\n node;\n\n // pptxgenjs の line シェイプは x, y, w, h で描画\n // x, y は左上座標、w, h で方向と長さを指定\n const minX = Math.min(x1, x2);\n const minY = Math.min(y1, y2);\n const lineW = Math.abs(x2 - x1);\n const lineH = Math.abs(y2 - y1);\n\n // 線の方向を判定して flip を決定\n // flipH: 右から左へ向かう線\n // flipV: 下から上へ向かう線\n const flipH = x2 < x1;\n const flipV = y2 < y1;\n\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(minX),\n y: pxToIn(minY),\n w: pxToIn(lineW),\n h: pxToIn(lineH),\n flipH,\n flipV,\n line: {\n color: color ?? \"000000\",\n width: lineWidth !== undefined ? pxToPt(lineWidth) : 1,\n dashType: dashType,\n beginArrowType: resolveArrowType(beginArrow),\n endArrowType: resolveArrowType(endArrow),\n },\n });\n}\n"],"mappings":";;;;;AASA,SAAgB,iBACd,OAC4E;CAC5E,IAAI,UAAU,KAAA,GACZ;CAEF,IAAI,UAAU,OACZ,OAAO;CAET,IAAI,UAAU,MACZ,OAAO;CAET,OAAO,MAAM,QAAQ;AACvB;AAEA,SAAgB,eACd,MACA,KACM;CACN,MAAM,EAAE,IAAI,IAAI,IAAI,IAAI,OAAO,WAAW,UAAU,YAAY,aAC9D;CAIF,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAC9B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAK9B,MAAM,QAAQ,KAAK;CACnB,MAAM,QAAQ,KAAK;CAEnB,IAAI,MAAM,SAAS,IAAI,KAAK,UAAU,MAAM;EAC1C,GAAG,OAAO,IAAI;EACd,GAAG,OAAO,IAAI;EACd,GAAG,OAAO,KAAK;EACf,GAAG,OAAO,KAAK;EACf;EACA;EACA,MAAM;GACJ,OAAO,SAAS;GAChB,OAAO,cAAc,KAAA,IAAY,OAAO,SAAS,IAAI;GAC3C;GACV,gBAAgB,iBAAiB,UAAU;GAC3C,cAAc,iBAAiB,QAAQ;EACzC;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"line.js","names":[],"sources":["../../../src/renderPptx/nodes/line.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { addStraightLine } from \"../utils/straightLine.ts\";\n\ntype LinePositionedNode = Extract<PositionedNode, { type: \"line\" }>;\n\nexport function renderLineNode(\n node: LinePositionedNode,\n ctx: RenderContext,\n): void {\n const { x1, y1, x2, y2 } = node;\n addStraightLine(ctx, { x1, y1, x2, y2 }, node);\n}\n"],"mappings":";;AAMA,SAAgB,eACd,MACA,KACM;CACN,MAAM,EAAE,IAAI,IAAI,IAAI,OAAO;CAC3B,gBAAgB,KAAK;EAAE;EAAI;EAAI;EAAI;CAAG,GAAG,IAAI;AAC/C"}
@@ -1,5 +1,5 @@
1
- import { pxToIn, pxToPt } from "../units.js";
2
- import { getContentArea } from "../utils/contentArea.js";
1
+ import { pxToPt } from "../units.js";
2
+ import { getContentAreaIn } from "../utils/contentArea.js";
3
3
  import { convertStrike, convertUnderline } from "../textOptions.js";
4
4
  //#region src/renderPptx/nodes/list.ts
5
5
  function resolveStyle(li, parent) {
@@ -68,14 +68,11 @@ function renderUlNode(node, ctx) {
68
68
  const fontSizePx = node.fontSize ?? 24;
69
69
  const fontFamily = node.fontFamily ?? "Noto Sans JP";
70
70
  const lineHeight = node.lineHeight ?? 1.3;
71
- const content = getContentArea(node);
71
+ const contentIn = getContentAreaIn(node);
72
72
  if (hasItemStyleOverride(node.items)) {
73
73
  const textItems = buildListTextItems(node.items, node, true);
74
74
  ctx.slide.addText(textItems, {
75
- x: pxToIn(content.x),
76
- y: pxToIn(content.y),
77
- w: pxToIn(content.w),
78
- h: pxToIn(content.h),
75
+ ...contentIn,
79
76
  align: node.textAlign ?? "left",
80
77
  valign: "top",
81
78
  margin: 0,
@@ -84,10 +81,7 @@ function renderUlNode(node, ctx) {
84
81
  } else {
85
82
  const text = node.items.map((li) => li.text).join("\n");
86
83
  ctx.slide.addText(text, {
87
- x: pxToIn(content.x),
88
- y: pxToIn(content.y),
89
- w: pxToIn(content.w),
90
- h: pxToIn(content.h),
84
+ ...contentIn,
91
85
  fontSize: pxToPt(fontSizePx),
92
86
  fontFace: fontFamily,
93
87
  align: node.textAlign ?? "left",
@@ -108,17 +102,14 @@ function renderOlNode(node, ctx) {
108
102
  const fontSizePx = node.fontSize ?? 24;
109
103
  const fontFamily = node.fontFamily ?? "Noto Sans JP";
110
104
  const lineHeight = node.lineHeight ?? 1.3;
111
- const content = getContentArea(node);
105
+ const contentIn = getContentAreaIn(node);
112
106
  const bulletOptions = { type: "number" };
113
107
  if (node.numberType !== void 0) bulletOptions.numberType = node.numberType;
114
108
  if (node.numberStartAt !== void 0) bulletOptions.numberStartAt = node.numberStartAt;
115
109
  if (hasItemStyleOverride(node.items)) {
116
110
  const textItems = buildListTextItems(node.items, node, bulletOptions);
117
111
  ctx.slide.addText(textItems, {
118
- x: pxToIn(content.x),
119
- y: pxToIn(content.y),
120
- w: pxToIn(content.w),
121
- h: pxToIn(content.h),
112
+ ...contentIn,
122
113
  align: node.textAlign ?? "left",
123
114
  valign: "top",
124
115
  margin: 0,
@@ -127,10 +118,7 @@ function renderOlNode(node, ctx) {
127
118
  } else {
128
119
  const text = node.items.map((li) => li.text).join("\n");
129
120
  ctx.slide.addText(text, {
130
- x: pxToIn(content.x),
131
- y: pxToIn(content.y),
132
- w: pxToIn(content.w),
133
- h: pxToIn(content.h),
121
+ ...contentIn,
134
122
  fontSize: pxToPt(fontSizePx),
135
123
  fontFace: fontFamily,
136
124
  align: node.textAlign ?? "left",