@hirokisakabe/pom 9.1.0 → 9.1.2

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 (52) hide show
  1. package/dist/buildContext.js +2 -4
  2. package/dist/buildContext.js.map +1 -1
  3. package/dist/buildPptx.d.ts.map +1 -1
  4. package/dist/buildPptx.js +2 -6
  5. package/dist/buildPptx.js.map +1 -1
  6. package/dist/extractSlideMastersAsPptx.js +1 -1
  7. package/dist/renderPptx/glimpseTextBoxes.js +646 -0
  8. package/dist/renderPptx/glimpseTextBoxes.js.map +1 -0
  9. package/dist/renderPptx/nodes/flow.js +95 -90
  10. package/dist/renderPptx/nodes/flow.js.map +1 -1
  11. package/dist/renderPptx/nodes/icon.js +33 -24
  12. package/dist/renderPptx/nodes/icon.js.map +1 -1
  13. package/dist/renderPptx/nodes/image.js +5 -22
  14. package/dist/renderPptx/nodes/image.js.map +1 -1
  15. package/dist/renderPptx/nodes/list.js +96 -111
  16. package/dist/renderPptx/nodes/list.js.map +1 -1
  17. package/dist/renderPptx/nodes/matrix.js +92 -70
  18. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  19. package/dist/renderPptx/nodes/processArrow.js +68 -35
  20. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  21. package/dist/renderPptx/nodes/pyramid.js +34 -16
  22. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  23. package/dist/renderPptx/nodes/shape.js +63 -37
  24. package/dist/renderPptx/nodes/shape.js.map +1 -1
  25. package/dist/renderPptx/nodes/svg.js +2 -5
  26. package/dist/renderPptx/nodes/svg.js.map +1 -1
  27. package/dist/renderPptx/nodes/text.js +16 -45
  28. package/dist/renderPptx/nodes/text.js.map +1 -1
  29. package/dist/renderPptx/nodes/timeline.js +108 -74
  30. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  31. package/dist/renderPptx/nodes/tree.js +78 -79
  32. package/dist/renderPptx/nodes/tree.js.map +1 -1
  33. package/dist/renderPptx/renderPptx.js +4 -5
  34. package/dist/renderPptx/renderPptx.js.map +1 -1
  35. package/dist/renderPptx/utils/backgroundBorder.js +85 -49
  36. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  37. package/dist/renderPptx/utils/glimpsePicture.js +130 -0
  38. package/dist/renderPptx/utils/glimpsePicture.js.map +1 -0
  39. package/dist/renderPptx/utils/glimpseShape.js +90 -0
  40. package/dist/renderPptx/utils/glimpseShape.js.map +1 -0
  41. package/dist/renderPptx/utils/glimpseTextBox.js +126 -0
  42. package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -0
  43. package/dist/renderPptx/utils/straightLine.js +29 -14
  44. package/dist/renderPptx/utils/straightLine.js.map +1 -1
  45. package/dist/renderPptx/utils/visualStyle.js +1 -24
  46. package/dist/renderPptx/utils/visualStyle.js.map +1 -1
  47. package/dist/types.js +1 -1
  48. package/package.json +4 -4
  49. package/dist/renderPptx/glowEffects.js +0 -152
  50. package/dist/renderPptx/glowEffects.js.map +0 -1
  51. package/dist/renderPptx/gradientFills.js +0 -167
  52. package/dist/renderPptx/gradientFills.js.map +0 -1
@@ -1,9 +1,10 @@
1
- import { pxToIn, pxToPt } from "../units.js";
1
+ import { pxToIn } from "../units.js";
2
2
  import { withContentBounds } from "../utils/contentArea.js";
3
- import { convertStrike, convertUnderline } from "../textOptions.js";
4
- import { stripHash } from "../utils/visualStyle.js";
3
+ import { addGlimpseTextBox } from "../utils/glimpseTextBox.js";
4
+ import { addGlimpseShape, createShapeBoundsInput, noShapeOutline, solidShapeFill } from "../utils/glimpseShape.js";
5
5
  import { ARROW_DEPTH_RATIO } from "../../shared/processArrowConstants.js";
6
6
  import { measureProcessArrow } from "../../calcYogaLayout/measureCompositeNodes.js";
7
+ import { stripHash } from "../utils/visualStyle.js";
7
8
  import { resolveScaledContentArea } from "../utils/scaleToFit.js";
8
9
  //#region src/renderPptx/nodes/processArrow.ts
9
10
  function renderProcessArrowNode(node, ctx) {
@@ -85,29 +86,45 @@ function renderHorizontalProcessArrow(node, ctx, steps, stepCount, itemWidth, it
85
86
  },
86
87
  { close: true }
87
88
  ];
88
- ctx.slide.addShape("custGeom", {
89
- x: pxToIn(stepX),
90
- y: pxToIn(stepY),
91
- w: pxToIn(itemWidth),
92
- h: pxToIn(itemHeight),
93
- points,
94
- fill: { color: fillColor },
95
- line: { type: "none" }
89
+ addGlimpseShape(ctx, {
90
+ preset: "rect",
91
+ ...createShapeBoundsInput({
92
+ x: stepX,
93
+ y: stepY,
94
+ w: itemWidth,
95
+ h: itemHeight
96
+ }),
97
+ fill: solidShapeFill(fillColor),
98
+ outline: noShapeOutline()
99
+ }, {
100
+ x: stepX,
101
+ y: stepY,
102
+ w: itemWidth,
103
+ h: itemHeight
104
+ }, {
105
+ fillColor,
106
+ customGeometry: {
107
+ width: pxToIn(itemWidth),
108
+ height: pxToIn(itemHeight),
109
+ points
110
+ }
96
111
  });
97
112
  const textOffsetLeft = isFirst ? 0 : arrowDepth;
98
113
  const textWidth = Math.max(1, itemWidth - arrowDepth - textOffsetLeft);
99
- ctx.slide.addText(step.label, {
100
- x: pxToIn(stepX + textOffsetLeft),
101
- y: pxToIn(stepY),
102
- w: pxToIn(textWidth),
103
- h: pxToIn(itemHeight),
104
- fontSize: pxToPt((node.fontSize ?? 14) * scaleFactor),
114
+ addGlimpseTextBox(ctx, {
115
+ x: stepX + textOffsetLeft,
116
+ y: stepY,
117
+ w: textWidth,
118
+ h: itemHeight
119
+ }, {
120
+ text: step.label,
121
+ fontSize: (node.fontSize ?? 14) * scaleFactor,
105
122
  fontFace: node.fontFamily ?? "Noto Sans JP",
106
123
  color: textColor,
107
124
  bold: node.bold ?? false,
108
125
  italic: node.italic,
109
- underline: convertUnderline(node.underline),
110
- strike: convertStrike(node.strike),
126
+ underline: node.underline,
127
+ strike: node.strike,
111
128
  highlight: node.highlight,
112
129
  align: "center",
113
130
  valign: "middle"
@@ -173,29 +190,45 @@ function renderVerticalProcessArrow(node, ctx, steps, stepCount, itemWidth, item
173
190
  },
174
191
  { close: true }
175
192
  ];
176
- ctx.slide.addShape("custGeom", {
177
- x: pxToIn(stepX),
178
- y: pxToIn(stepY),
179
- w: pxToIn(itemWidth),
180
- h: pxToIn(itemHeight),
181
- points,
182
- fill: { color: fillColor },
183
- line: { type: "none" }
193
+ addGlimpseShape(ctx, {
194
+ preset: "rect",
195
+ ...createShapeBoundsInput({
196
+ x: stepX,
197
+ y: stepY,
198
+ w: itemWidth,
199
+ h: itemHeight
200
+ }),
201
+ fill: solidShapeFill(fillColor),
202
+ outline: noShapeOutline()
203
+ }, {
204
+ x: stepX,
205
+ y: stepY,
206
+ w: itemWidth,
207
+ h: itemHeight
208
+ }, {
209
+ fillColor,
210
+ customGeometry: {
211
+ width: pxToIn(itemWidth),
212
+ height: pxToIn(itemHeight),
213
+ points
214
+ }
184
215
  });
185
216
  const textOffsetTop = isFirst ? 0 : arrowDepth;
186
217
  const textHeight = Math.max(1, itemHeight - arrowDepth - textOffsetTop);
187
- ctx.slide.addText(step.label, {
188
- x: pxToIn(stepX),
189
- y: pxToIn(stepY + textOffsetTop),
190
- w: pxToIn(itemWidth),
191
- h: pxToIn(textHeight),
192
- fontSize: pxToPt((node.fontSize ?? 14) * scaleFactor),
218
+ addGlimpseTextBox(ctx, {
219
+ x: stepX,
220
+ y: stepY + textOffsetTop,
221
+ w: itemWidth,
222
+ h: textHeight
223
+ }, {
224
+ text: step.label,
225
+ fontSize: (node.fontSize ?? 14) * scaleFactor,
193
226
  fontFace: node.fontFamily ?? "Noto Sans JP",
194
227
  color: textColor,
195
228
  bold: node.bold ?? false,
196
229
  italic: node.italic,
197
- underline: convertUnderline(node.underline),
198
- strike: convertStrike(node.strike),
230
+ underline: node.underline,
231
+ strike: node.strike,
199
232
  highlight: node.highlight,
200
233
  align: "center",
201
234
  valign: "middle"
@@ -1 +1 @@
1
- {"version":3,"file":"processArrow.js","names":[],"sources":["../../../src/renderPptx/nodes/processArrow.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 { convertUnderline, convertStrike } from \"../textOptions.ts\";\nimport { measureProcessArrow } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { resolveScaledContentArea } from \"../utils/scaleToFit.ts\";\nimport {\n ARROW_DEPTH_RATIO,\n DEFAULT_PROCESS_ARROW_ITEM_WIDTH,\n DEFAULT_PROCESS_ARROW_ITEM_HEIGHT,\n} from \"../../shared/processArrowConstants.ts\";\nimport { withContentBounds } from \"../utils/contentArea.ts\";\n\ntype ProcessArrowPositionedNode = Extract<\n PositionedNode,\n { type: \"processArrow\" }\n>;\n\nexport function renderProcessArrowNode(\n node: ProcessArrowPositionedNode,\n ctx: RenderContext,\n): void {\n const direction = node.direction ?? \"horizontal\";\n const steps = node.steps;\n const stepCount = steps.length;\n\n if (stepCount === 0) return;\n\n const defaultColor = \"4472C4\"; // PowerPoint標準の青\n const defaultTextColor = \"FFFFFF\";\n const itemWidth = node.itemWidth ?? DEFAULT_PROCESS_ARROW_ITEM_WIDTH;\n const itemHeight = node.itemHeight ?? DEFAULT_PROCESS_ARROW_ITEM_HEIGHT;\n const arrowDepth = itemHeight * ARROW_DEPTH_RATIO;\n const gap = node.gap ?? -arrowDepth;\n\n // スケール係数を計算(コンテンツ領域基準)\n const { content, scaleFactor } = resolveScaledContentArea(\n node,\n measureProcessArrow(node),\n ctx,\n );\n\n const scaledItemWidth = itemWidth * scaleFactor;\n const scaledItemHeight = itemHeight * scaleFactor;\n const scaledGap = gap * scaleFactor;\n const scaledArrowDepth = arrowDepth * scaleFactor;\n\n // コンテンツ領域を使用するための仮想ノードを作成\n const contentNode = withContentBounds(node, content);\n\n if (direction === \"horizontal\") {\n renderHorizontalProcessArrow(\n contentNode,\n ctx,\n steps,\n stepCount,\n scaledItemWidth,\n scaledItemHeight,\n scaledGap,\n scaledArrowDepth,\n defaultColor,\n defaultTextColor,\n scaleFactor,\n );\n } else {\n renderVerticalProcessArrow(\n contentNode,\n ctx,\n steps,\n stepCount,\n scaledItemWidth,\n scaledItemHeight,\n scaledGap,\n scaledArrowDepth,\n defaultColor,\n defaultTextColor,\n scaleFactor,\n );\n }\n}\n\nfunction renderHorizontalProcessArrow(\n node: ProcessArrowPositionedNode,\n ctx: RenderContext,\n steps: ProcessArrowPositionedNode[\"steps\"],\n stepCount: number,\n itemWidth: number,\n itemHeight: number,\n gap: number,\n arrowDepth: number,\n defaultColor: string,\n defaultTextColor: string,\n scaleFactor: number,\n): void {\n const totalWidth = stepCount * itemWidth + (stepCount - 1) * gap;\n const startX = node.x + (node.w - totalWidth) / 2;\n const centerY = node.y + node.h / 2;\n\n steps.forEach((step, index) => {\n const stepX = startX + index * (itemWidth + gap);\n const stepY = centerY - itemHeight / 2;\n const fillColor = stripHash(step.color) ?? defaultColor;\n const textColor = stripHash(step.textColor) ?? defaultTextColor;\n\n // custGeom でシェブロン形状を描画\n const isFirst = index === 0;\n const points = isFirst\n ? [\n // homePlate 風: 左辺フラット、右辺が矢印\n { x: 0, y: 0 },\n { x: pxToIn(itemWidth - arrowDepth), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight / 2) },\n { x: pxToIn(itemWidth - arrowDepth), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight) },\n { close: true as const },\n ]\n : [\n // chevron 風: 左辺に切り欠き、右辺が矢印\n { x: 0, y: 0 },\n { x: pxToIn(itemWidth - arrowDepth), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight / 2) },\n { x: pxToIn(itemWidth - arrowDepth), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight) },\n { x: pxToIn(arrowDepth), y: pxToIn(itemHeight / 2) },\n { close: true as const },\n ];\n\n ctx.slide.addShape(\"custGeom\" as never, {\n x: pxToIn(stepX),\n y: pxToIn(stepY),\n w: pxToIn(itemWidth),\n h: pxToIn(itemHeight),\n points,\n fill: { color: fillColor },\n line: { type: \"none\" as const },\n });\n\n // テキストを図形の中央(矢印部分を除いた領域)に配置\n const textOffsetLeft = isFirst ? 0 : arrowDepth;\n const textWidth = Math.max(1, itemWidth - arrowDepth - textOffsetLeft);\n\n ctx.slide.addText(step.label, {\n x: pxToIn(stepX + textOffsetLeft),\n y: pxToIn(stepY),\n w: pxToIn(textWidth),\n h: pxToIn(itemHeight),\n fontSize: pxToPt((node.fontSize ?? 14) * scaleFactor),\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n color: textColor,\n bold: node.bold ?? false,\n italic: node.italic,\n underline: convertUnderline(node.underline),\n strike: convertStrike(node.strike),\n highlight: node.highlight,\n align: \"center\",\n valign: \"middle\",\n });\n });\n}\n\nfunction renderVerticalProcessArrow(\n node: ProcessArrowPositionedNode,\n ctx: RenderContext,\n steps: ProcessArrowPositionedNode[\"steps\"],\n stepCount: number,\n itemWidth: number,\n itemHeight: number,\n gap: number,\n arrowDepth: number,\n defaultColor: string,\n defaultTextColor: string,\n scaleFactor: number,\n): void {\n const totalHeight = stepCount * itemHeight + (stepCount - 1) * gap;\n const startY = node.y + (node.h - totalHeight) / 2;\n const centerX = node.x + node.w / 2;\n\n steps.forEach((step, index) => {\n const stepX = centerX - itemWidth / 2;\n const stepY = startY + index * (itemHeight + gap);\n const fillColor = stripHash(step.color) ?? defaultColor;\n const textColor = stripHash(step.textColor) ?? defaultTextColor;\n\n const isFirst = index === 0;\n const points = isFirst\n ? [\n // rect 風: 上辺フラット、下辺が矢印\n { x: 0, y: 0 },\n { x: pxToIn(itemWidth), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight - arrowDepth) },\n { x: pxToIn(itemWidth / 2), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight - arrowDepth) },\n { close: true as const },\n ]\n : [\n // pentagon 風: 上辺に切り欠き、下辺が矢印\n { x: pxToIn(itemWidth / 2), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(arrowDepth) },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight - arrowDepth) },\n { x: pxToIn(itemWidth / 2), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight - arrowDepth) },\n { x: 0, y: pxToIn(arrowDepth) },\n { close: true as const },\n ];\n\n ctx.slide.addShape(\"custGeom\" as never, {\n x: pxToIn(stepX),\n y: pxToIn(stepY),\n w: pxToIn(itemWidth),\n h: pxToIn(itemHeight),\n points,\n fill: { color: fillColor },\n line: { type: \"none\" as const },\n });\n\n // テキストを図形の中央(矢印部分を除いた領域)に配置\n const textOffsetTop = isFirst ? 0 : arrowDepth;\n const textHeight = Math.max(1, itemHeight - arrowDepth - textOffsetTop);\n\n ctx.slide.addText(step.label, {\n x: pxToIn(stepX),\n y: pxToIn(stepY + textOffsetTop),\n w: pxToIn(itemWidth),\n h: pxToIn(textHeight),\n fontSize: pxToPt((node.fontSize ?? 14) * scaleFactor),\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n color: textColor,\n bold: node.bold ?? false,\n italic: node.italic,\n underline: convertUnderline(node.underline),\n strike: convertStrike(node.strike),\n highlight: node.highlight,\n align: \"center\",\n valign: \"middle\",\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"processArrow.js","names":[],"sources":["../../../src/renderPptx/nodes/processArrow.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { stripHash } from \"../utils/visualStyle.ts\";\nimport { pxToIn } from \"../units.ts\";\nimport { measureProcessArrow } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { resolveScaledContentArea } from \"../utils/scaleToFit.ts\";\nimport {\n ARROW_DEPTH_RATIO,\n DEFAULT_PROCESS_ARROW_ITEM_WIDTH,\n DEFAULT_PROCESS_ARROW_ITEM_HEIGHT,\n} from \"../../shared/processArrowConstants.ts\";\nimport { withContentBounds } from \"../utils/contentArea.ts\";\nimport {\n addGlimpseShape,\n createShapeBoundsInput,\n noShapeOutline,\n solidShapeFill,\n} from \"../utils/glimpseShape.ts\";\nimport { addGlimpseTextBox } from \"../utils/glimpseTextBox.ts\";\n\ntype ProcessArrowPositionedNode = Extract<\n PositionedNode,\n { type: \"processArrow\" }\n>;\n\nexport function renderProcessArrowNode(\n node: ProcessArrowPositionedNode,\n ctx: RenderContext,\n): void {\n const direction = node.direction ?? \"horizontal\";\n const steps = node.steps;\n const stepCount = steps.length;\n\n if (stepCount === 0) return;\n\n const defaultColor = \"4472C4\"; // PowerPoint標準の青\n const defaultTextColor = \"FFFFFF\";\n const itemWidth = node.itemWidth ?? DEFAULT_PROCESS_ARROW_ITEM_WIDTH;\n const itemHeight = node.itemHeight ?? DEFAULT_PROCESS_ARROW_ITEM_HEIGHT;\n const arrowDepth = itemHeight * ARROW_DEPTH_RATIO;\n const gap = node.gap ?? -arrowDepth;\n\n // スケール係数を計算(コンテンツ領域基準)\n const { content, scaleFactor } = resolveScaledContentArea(\n node,\n measureProcessArrow(node),\n ctx,\n );\n\n const scaledItemWidth = itemWidth * scaleFactor;\n const scaledItemHeight = itemHeight * scaleFactor;\n const scaledGap = gap * scaleFactor;\n const scaledArrowDepth = arrowDepth * scaleFactor;\n\n // コンテンツ領域を使用するための仮想ノードを作成\n const contentNode = withContentBounds(node, content);\n\n if (direction === \"horizontal\") {\n renderHorizontalProcessArrow(\n contentNode,\n ctx,\n steps,\n stepCount,\n scaledItemWidth,\n scaledItemHeight,\n scaledGap,\n scaledArrowDepth,\n defaultColor,\n defaultTextColor,\n scaleFactor,\n );\n } else {\n renderVerticalProcessArrow(\n contentNode,\n ctx,\n steps,\n stepCount,\n scaledItemWidth,\n scaledItemHeight,\n scaledGap,\n scaledArrowDepth,\n defaultColor,\n defaultTextColor,\n scaleFactor,\n );\n }\n}\n\nfunction renderHorizontalProcessArrow(\n node: ProcessArrowPositionedNode,\n ctx: RenderContext,\n steps: ProcessArrowPositionedNode[\"steps\"],\n stepCount: number,\n itemWidth: number,\n itemHeight: number,\n gap: number,\n arrowDepth: number,\n defaultColor: string,\n defaultTextColor: string,\n scaleFactor: number,\n): void {\n const totalWidth = stepCount * itemWidth + (stepCount - 1) * gap;\n const startX = node.x + (node.w - totalWidth) / 2;\n const centerY = node.y + node.h / 2;\n\n steps.forEach((step, index) => {\n const stepX = startX + index * (itemWidth + gap);\n const stepY = centerY - itemHeight / 2;\n const fillColor = stripHash(step.color) ?? defaultColor;\n const textColor = stripHash(step.textColor) ?? defaultTextColor;\n\n // custGeom でシェブロン形状を描画\n const isFirst = index === 0;\n const points = isFirst\n ? [\n // homePlate 風: 左辺フラット、右辺が矢印\n { x: 0, y: 0 },\n { x: pxToIn(itemWidth - arrowDepth), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight / 2) },\n { x: pxToIn(itemWidth - arrowDepth), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight) },\n { close: true as const },\n ]\n : [\n // chevron 風: 左辺に切り欠き、右辺が矢印\n { x: 0, y: 0 },\n { x: pxToIn(itemWidth - arrowDepth), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight / 2) },\n { x: pxToIn(itemWidth - arrowDepth), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight) },\n { x: pxToIn(arrowDepth), y: pxToIn(itemHeight / 2) },\n { close: true as const },\n ];\n\n addGlimpseShape(\n ctx,\n {\n preset: \"rect\",\n ...createShapeBoundsInput({\n x: stepX,\n y: stepY,\n w: itemWidth,\n h: itemHeight,\n }),\n fill: solidShapeFill(fillColor),\n outline: noShapeOutline(),\n },\n { x: stepX, y: stepY, w: itemWidth, h: itemHeight },\n {\n fillColor,\n customGeometry: {\n width: pxToIn(itemWidth),\n height: pxToIn(itemHeight),\n points,\n },\n },\n );\n\n // テキストを図形の中央(矢印部分を除いた領域)に配置\n const textOffsetLeft = isFirst ? 0 : arrowDepth;\n const textWidth = Math.max(1, itemWidth - arrowDepth - textOffsetLeft);\n\n addGlimpseTextBox(\n ctx,\n {\n x: stepX + textOffsetLeft,\n y: stepY,\n w: textWidth,\n h: itemHeight,\n },\n {\n text: step.label,\n fontSize: (node.fontSize ?? 14) * scaleFactor,\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n color: textColor,\n bold: node.bold ?? false,\n italic: node.italic,\n underline: node.underline,\n strike: node.strike,\n highlight: node.highlight,\n align: \"center\",\n valign: \"middle\",\n },\n );\n });\n}\n\nfunction renderVerticalProcessArrow(\n node: ProcessArrowPositionedNode,\n ctx: RenderContext,\n steps: ProcessArrowPositionedNode[\"steps\"],\n stepCount: number,\n itemWidth: number,\n itemHeight: number,\n gap: number,\n arrowDepth: number,\n defaultColor: string,\n defaultTextColor: string,\n scaleFactor: number,\n): void {\n const totalHeight = stepCount * itemHeight + (stepCount - 1) * gap;\n const startY = node.y + (node.h - totalHeight) / 2;\n const centerX = node.x + node.w / 2;\n\n steps.forEach((step, index) => {\n const stepX = centerX - itemWidth / 2;\n const stepY = startY + index * (itemHeight + gap);\n const fillColor = stripHash(step.color) ?? defaultColor;\n const textColor = stripHash(step.textColor) ?? defaultTextColor;\n\n const isFirst = index === 0;\n const points = isFirst\n ? [\n // rect 風: 上辺フラット、下辺が矢印\n { x: 0, y: 0 },\n { x: pxToIn(itemWidth), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight - arrowDepth) },\n { x: pxToIn(itemWidth / 2), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight - arrowDepth) },\n { close: true as const },\n ]\n : [\n // pentagon 風: 上辺に切り欠き、下辺が矢印\n { x: pxToIn(itemWidth / 2), y: 0 },\n { x: pxToIn(itemWidth), y: pxToIn(arrowDepth) },\n { x: pxToIn(itemWidth), y: pxToIn(itemHeight - arrowDepth) },\n { x: pxToIn(itemWidth / 2), y: pxToIn(itemHeight) },\n { x: 0, y: pxToIn(itemHeight - arrowDepth) },\n { x: 0, y: pxToIn(arrowDepth) },\n { close: true as const },\n ];\n\n addGlimpseShape(\n ctx,\n {\n preset: \"rect\",\n ...createShapeBoundsInput({\n x: stepX,\n y: stepY,\n w: itemWidth,\n h: itemHeight,\n }),\n fill: solidShapeFill(fillColor),\n outline: noShapeOutline(),\n },\n { x: stepX, y: stepY, w: itemWidth, h: itemHeight },\n {\n fillColor,\n customGeometry: {\n width: pxToIn(itemWidth),\n height: pxToIn(itemHeight),\n points,\n },\n },\n );\n\n // テキストを図形の中央(矢印部分を除いた領域)に配置\n const textOffsetTop = isFirst ? 0 : arrowDepth;\n const textHeight = Math.max(1, itemHeight - arrowDepth - textOffsetTop);\n\n addGlimpseTextBox(\n ctx,\n {\n x: stepX,\n y: stepY + textOffsetTop,\n w: itemWidth,\n h: textHeight,\n },\n {\n text: step.label,\n fontSize: (node.fontSize ?? 14) * scaleFactor,\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n color: textColor,\n bold: node.bold ?? false,\n italic: node.italic,\n underline: node.underline,\n strike: node.strike,\n highlight: node.highlight,\n align: \"center\",\n valign: \"middle\",\n },\n );\n });\n}\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
- import { pxToIn, pxToPt } from "../units.js";
2
- import { stripHash } from "../utils/visualStyle.js";
1
+ import { pxToIn } from "../units.js";
2
+ import { addGlimpseTextBox } from "../utils/glimpseTextBox.js";
3
+ import { addGlimpseShape, createShapeBoundsInput, noShapeOutline, solidShapeFill } from "../utils/glimpseShape.js";
3
4
  import { measurePyramid } from "../../calcYogaLayout/measureCompositeNodes.js";
5
+ import { stripHash } from "../utils/visualStyle.js";
4
6
  import { resolveScaledContentArea } from "../utils/scaleToFit.js";
5
7
  //#region src/renderPptx/nodes/pyramid.ts
6
8
  function renderPyramidNode(node, ctx) {
@@ -58,21 +60,37 @@ function renderPyramidNode(node, ctx) {
58
60
  },
59
61
  { close: true }
60
62
  ];
61
- ctx.slide.addShape("custGeom", {
62
- x: pxToIn(bboxX),
63
- y: pxToIn(layerY),
64
- w: pxToIn(bboxW),
65
- h: pxToIn(layerHeight),
66
- points,
67
- fill: { color: fillColor },
68
- line: { type: "none" }
63
+ addGlimpseShape(ctx, {
64
+ preset: "rect",
65
+ ...createShapeBoundsInput({
66
+ x: bboxX,
67
+ y: layerY,
68
+ w: bboxW,
69
+ h: layerHeight
70
+ }),
71
+ fill: solidShapeFill(fillColor),
72
+ outline: noShapeOutline()
73
+ }, {
74
+ x: bboxX,
75
+ y: layerY,
76
+ w: bboxW,
77
+ h: layerHeight
78
+ }, {
79
+ fillColor,
80
+ customGeometry: {
81
+ width: pxToIn(bboxW),
82
+ height: pxToIn(layerHeight),
83
+ points
84
+ }
69
85
  });
70
- ctx.slide.addText(level.label, {
71
- x: pxToIn(bboxX),
72
- y: pxToIn(layerY),
73
- w: pxToIn(bboxW),
74
- h: pxToIn(layerHeight),
75
- fontSize: pxToPt((node.fontSize ?? 14) * scaleFactor),
86
+ addGlimpseTextBox(ctx, {
87
+ x: bboxX,
88
+ y: layerY,
89
+ w: bboxW,
90
+ h: layerHeight
91
+ }, {
92
+ text: level.label,
93
+ fontSize: (node.fontSize ?? 14) * scaleFactor,
76
94
  fontFace: node.fontFamily ?? "Noto Sans JP",
77
95
  color: textColor,
78
96
  bold: node.bold ?? false,
@@ -1 +1 @@
1
- {"version":3,"file":"pyramid.js","names":[],"sources":["../../../src/renderPptx/nodes/pyramid.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 { measurePyramid } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { resolveScaledContentArea } from \"../utils/scaleToFit.ts\";\n\ntype PyramidPositionedNode = Extract<PositionedNode, { type: \"pyramid\" }>;\n\nexport function renderPyramidNode(\n node: PyramidPositionedNode,\n ctx: RenderContext,\n): void {\n const direction = node.direction ?? \"up\";\n const levels = node.levels;\n const levelCount = levels.length;\n\n if (levelCount === 0) return;\n\n const defaultColor = \"4472C4\";\n const defaultTextColor = \"FFFFFF\";\n\n // スケール係数を計算(コンテンツ領域基準)\n const { content, scaleFactor } = resolveScaledContentArea(\n node,\n measurePyramid(node),\n ctx,\n );\n\n const baseWidth = 400 * scaleFactor;\n const layerHeight = 50 * scaleFactor;\n const gap = 2 * scaleFactor;\n\n const totalHeight = levelCount * layerHeight + (levelCount - 1) * gap;\n const startX = content.x + (content.w - baseWidth) / 2;\n const startY = content.y + (content.h - totalHeight) / 2;\n\n for (let i = 0; i < levelCount; i++) {\n const level = levels[i];\n const fillColor = stripHash(level.color) ?? defaultColor;\n const textColor = stripHash(level.textColor) ?? defaultTextColor;\n\n const layerY = startY + i * (layerHeight + gap);\n\n // direction=\"up\": i=0 が最上段(最も狭い=三角形)、i=levelCount-1 が最下段(最も広い)\n // direction=\"down\": i=0 が最上段(最も広い)、i=levelCount-1 が最下段(最も狭い=三角形)\n // 頂点層の上辺は幅0(三角形)、それ以外は台形\n let topWidthRatio: number;\n let bottomWidthRatio: number;\n\n if (direction === \"up\") {\n topWidthRatio = i / levelCount;\n bottomWidthRatio = (i + 1) / levelCount;\n } else {\n topWidthRatio = (levelCount - i) / levelCount;\n bottomWidthRatio = (levelCount - i - 1) / levelCount;\n }\n\n const topWidth = baseWidth * topWidthRatio;\n const bottomWidth = baseWidth * bottomWidthRatio;\n\n const topLeftX = startX + (baseWidth - topWidth) / 2;\n const topRightX = topLeftX + topWidth;\n const bottomLeftX = startX + (baseWidth - bottomWidth) / 2;\n const bottomRightX = bottomLeftX + bottomWidth;\n\n // custGeom のバウンディングボックス\n const bboxX = Math.min(topLeftX, bottomLeftX);\n const bboxW = Math.max(topRightX, bottomRightX) - bboxX;\n\n // points はバウンディングボックス内の相対インチ座標\n const points = [\n { x: pxToIn(topLeftX - bboxX), y: 0 },\n { x: pxToIn(topRightX - bboxX), y: 0 },\n { x: pxToIn(bottomRightX - bboxX), y: pxToIn(layerHeight) },\n { x: pxToIn(bottomLeftX - bboxX), y: pxToIn(layerHeight) },\n { close: true as const },\n ];\n\n // 図形を描画(頂点層は三角形、それ以外は台形)\n ctx.slide.addShape(\"custGeom\" as never, {\n x: pxToIn(bboxX),\n y: pxToIn(layerY),\n w: pxToIn(bboxW),\n h: pxToIn(layerHeight),\n points,\n fill: { color: fillColor },\n line: { type: \"none\" as const },\n });\n\n // テキストを図形の中央に重ねて描画\n ctx.slide.addText(level.label, {\n x: pxToIn(bboxX),\n y: pxToIn(layerY),\n w: pxToIn(bboxW),\n h: pxToIn(layerHeight),\n fontSize: pxToPt((node.fontSize ?? 14) * scaleFactor),\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n color: textColor,\n bold: node.bold ?? false,\n align: \"center\",\n valign: \"middle\",\n autoFit: true,\n });\n }\n}\n"],"mappings":";;;;;AASA,SAAgB,kBACd,MACA,KACM;CACN,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,SAAS,KAAK;CACpB,MAAM,aAAa,OAAO;CAE1B,IAAI,eAAe,GAAG;CAEtB,MAAM,eAAe;CACrB,MAAM,mBAAmB;CAGzB,MAAM,EAAE,SAAS,gBAAgB,yBAC/B,MACA,eAAe,IAAI,GACnB,GACF;CAEA,MAAM,YAAY,MAAM;CACxB,MAAM,cAAc,KAAK;CACzB,MAAM,MAAM,IAAI;CAEhB,MAAM,cAAc,aAAa,eAAe,aAAa,KAAK;CAClE,MAAM,SAAS,QAAQ,KAAK,QAAQ,IAAI,aAAa;CACrD,MAAM,SAAS,QAAQ,KAAK,QAAQ,IAAI,eAAe;CAEvD,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK;EACnC,MAAM,QAAQ,OAAO;EACrB,MAAM,YAAY,UAAU,MAAM,KAAK,KAAK;EAC5C,MAAM,YAAY,UAAU,MAAM,SAAS,KAAK;EAEhD,MAAM,SAAS,SAAS,KAAK,cAAc;EAK3C,IAAI;EACJ,IAAI;EAEJ,IAAI,cAAc,MAAM;GACtB,gBAAgB,IAAI;GACpB,oBAAoB,IAAI,KAAK;EAC/B,OAAO;GACL,iBAAiB,aAAa,KAAK;GACnC,oBAAoB,aAAa,IAAI,KAAK;EAC5C;EAEA,MAAM,WAAW,YAAY;EAC7B,MAAM,cAAc,YAAY;EAEhC,MAAM,WAAW,UAAU,YAAY,YAAY;EACnD,MAAM,YAAY,WAAW;EAC7B,MAAM,cAAc,UAAU,YAAY,eAAe;EACzD,MAAM,eAAe,cAAc;EAGnC,MAAM,QAAQ,KAAK,IAAI,UAAU,WAAW;EAC5C,MAAM,QAAQ,KAAK,IAAI,WAAW,YAAY,IAAI;EAGlD,MAAM,SAAS;GACb;IAAE,GAAG,OAAO,WAAW,KAAK;IAAG,GAAG;GAAE;GACpC;IAAE,GAAG,OAAO,YAAY,KAAK;IAAG,GAAG;GAAE;GACrC;IAAE,GAAG,OAAO,eAAe,KAAK;IAAG,GAAG,OAAO,WAAW;GAAE;GAC1D;IAAE,GAAG,OAAO,cAAc,KAAK;IAAG,GAAG,OAAO,WAAW;GAAE;GACzD,EAAE,OAAO,KAAc;EACzB;EAGA,IAAI,MAAM,SAAS,YAAqB;GACtC,GAAG,OAAO,KAAK;GACf,GAAG,OAAO,MAAM;GAChB,GAAG,OAAO,KAAK;GACf,GAAG,OAAO,WAAW;GACrB;GACA,MAAM,EAAE,OAAO,UAAU;GACzB,MAAM,EAAE,MAAM,OAAgB;EAChC,CAAC;EAGD,IAAI,MAAM,QAAQ,MAAM,OAAO;GAC7B,GAAG,OAAO,KAAK;GACf,GAAG,OAAO,MAAM;GAChB,GAAG,OAAO,KAAK;GACf,GAAG,OAAO,WAAW;GACrB,UAAU,QAAQ,KAAK,YAAY,MAAM,WAAW;GACpD,UAAU,KAAK,cAAc;GAC7B,OAAO;GACP,MAAM,KAAK,QAAQ;GACnB,OAAO;GACP,QAAQ;GACR,SAAS;EACX,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"pyramid.js","names":[],"sources":["../../../src/renderPptx/nodes/pyramid.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { stripHash } from \"../utils/visualStyle.ts\";\nimport { pxToIn } from \"../units.ts\";\nimport { measurePyramid } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { resolveScaledContentArea } from \"../utils/scaleToFit.ts\";\nimport {\n addGlimpseShape,\n createShapeBoundsInput,\n noShapeOutline,\n solidShapeFill,\n} from \"../utils/glimpseShape.ts\";\nimport { addGlimpseTextBox } from \"../utils/glimpseTextBox.ts\";\n\ntype PyramidPositionedNode = Extract<PositionedNode, { type: \"pyramid\" }>;\n\nexport function renderPyramidNode(\n node: PyramidPositionedNode,\n ctx: RenderContext,\n): void {\n const direction = node.direction ?? \"up\";\n const levels = node.levels;\n const levelCount = levels.length;\n\n if (levelCount === 0) return;\n\n const defaultColor = \"4472C4\";\n const defaultTextColor = \"FFFFFF\";\n\n // スケール係数を計算(コンテンツ領域基準)\n const { content, scaleFactor } = resolveScaledContentArea(\n node,\n measurePyramid(node),\n ctx,\n );\n\n const baseWidth = 400 * scaleFactor;\n const layerHeight = 50 * scaleFactor;\n const gap = 2 * scaleFactor;\n\n const totalHeight = levelCount * layerHeight + (levelCount - 1) * gap;\n const startX = content.x + (content.w - baseWidth) / 2;\n const startY = content.y + (content.h - totalHeight) / 2;\n\n for (let i = 0; i < levelCount; i++) {\n const level = levels[i];\n const fillColor = stripHash(level.color) ?? defaultColor;\n const textColor = stripHash(level.textColor) ?? defaultTextColor;\n\n const layerY = startY + i * (layerHeight + gap);\n\n // direction=\"up\": i=0 が最上段(最も狭い=三角形)、i=levelCount-1 が最下段(最も広い)\n // direction=\"down\": i=0 が最上段(最も広い)、i=levelCount-1 が最下段(最も狭い=三角形)\n // 頂点層の上辺は幅0(三角形)、それ以外は台形\n let topWidthRatio: number;\n let bottomWidthRatio: number;\n\n if (direction === \"up\") {\n topWidthRatio = i / levelCount;\n bottomWidthRatio = (i + 1) / levelCount;\n } else {\n topWidthRatio = (levelCount - i) / levelCount;\n bottomWidthRatio = (levelCount - i - 1) / levelCount;\n }\n\n const topWidth = baseWidth * topWidthRatio;\n const bottomWidth = baseWidth * bottomWidthRatio;\n\n const topLeftX = startX + (baseWidth - topWidth) / 2;\n const topRightX = topLeftX + topWidth;\n const bottomLeftX = startX + (baseWidth - bottomWidth) / 2;\n const bottomRightX = bottomLeftX + bottomWidth;\n\n // custGeom のバウンディングボックス\n const bboxX = Math.min(topLeftX, bottomLeftX);\n const bboxW = Math.max(topRightX, bottomRightX) - bboxX;\n\n // points はバウンディングボックス内の相対インチ座標\n const points = [\n { x: pxToIn(topLeftX - bboxX), y: 0 },\n { x: pxToIn(topRightX - bboxX), y: 0 },\n { x: pxToIn(bottomRightX - bboxX), y: pxToIn(layerHeight) },\n { x: pxToIn(bottomLeftX - bboxX), y: pxToIn(layerHeight) },\n { close: true as const },\n ];\n\n addGlimpseShape(\n ctx,\n {\n preset: \"rect\",\n ...createShapeBoundsInput({\n x: bboxX,\n y: layerY,\n w: bboxW,\n h: layerHeight,\n }),\n fill: solidShapeFill(fillColor),\n outline: noShapeOutline(),\n },\n { x: bboxX, y: layerY, w: bboxW, h: layerHeight },\n {\n fillColor,\n customGeometry: {\n width: pxToIn(bboxW),\n height: pxToIn(layerHeight),\n points,\n },\n },\n );\n\n // テキストを図形の中央に重ねて描画\n addGlimpseTextBox(\n ctx,\n {\n x: bboxX,\n y: layerY,\n w: bboxW,\n h: layerHeight,\n },\n {\n text: level.label,\n fontSize: (node.fontSize ?? 14) * scaleFactor,\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n color: textColor,\n bold: node.bold ?? false,\n align: \"center\",\n valign: \"middle\",\n autoFit: true,\n },\n );\n }\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,kBACd,MACA,KACM;CACN,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,SAAS,KAAK;CACpB,MAAM,aAAa,OAAO;CAE1B,IAAI,eAAe,GAAG;CAEtB,MAAM,eAAe;CACrB,MAAM,mBAAmB;CAGzB,MAAM,EAAE,SAAS,gBAAgB,yBAC/B,MACA,eAAe,IAAI,GACnB,GACF;CAEA,MAAM,YAAY,MAAM;CACxB,MAAM,cAAc,KAAK;CACzB,MAAM,MAAM,IAAI;CAEhB,MAAM,cAAc,aAAa,eAAe,aAAa,KAAK;CAClE,MAAM,SAAS,QAAQ,KAAK,QAAQ,IAAI,aAAa;CACrD,MAAM,SAAS,QAAQ,KAAK,QAAQ,IAAI,eAAe;CAEvD,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK;EACnC,MAAM,QAAQ,OAAO;EACrB,MAAM,YAAY,UAAU,MAAM,KAAK,KAAK;EAC5C,MAAM,YAAY,UAAU,MAAM,SAAS,KAAK;EAEhD,MAAM,SAAS,SAAS,KAAK,cAAc;EAK3C,IAAI;EACJ,IAAI;EAEJ,IAAI,cAAc,MAAM;GACtB,gBAAgB,IAAI;GACpB,oBAAoB,IAAI,KAAK;EAC/B,OAAO;GACL,iBAAiB,aAAa,KAAK;GACnC,oBAAoB,aAAa,IAAI,KAAK;EAC5C;EAEA,MAAM,WAAW,YAAY;EAC7B,MAAM,cAAc,YAAY;EAEhC,MAAM,WAAW,UAAU,YAAY,YAAY;EACnD,MAAM,YAAY,WAAW;EAC7B,MAAM,cAAc,UAAU,YAAY,eAAe;EACzD,MAAM,eAAe,cAAc;EAGnC,MAAM,QAAQ,KAAK,IAAI,UAAU,WAAW;EAC5C,MAAM,QAAQ,KAAK,IAAI,WAAW,YAAY,IAAI;EAGlD,MAAM,SAAS;GACb;IAAE,GAAG,OAAO,WAAW,KAAK;IAAG,GAAG;GAAE;GACpC;IAAE,GAAG,OAAO,YAAY,KAAK;IAAG,GAAG;GAAE;GACrC;IAAE,GAAG,OAAO,eAAe,KAAK;IAAG,GAAG,OAAO,WAAW;GAAE;GAC1D;IAAE,GAAG,OAAO,cAAc,KAAK;IAAG,GAAG,OAAO,WAAW;GAAE;GACzD,EAAE,OAAO,KAAc;EACzB;EAEA,gBACE,KACA;GACE,QAAQ;GACR,GAAG,uBAAuB;IACxB,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;GACL,CAAC;GACD,MAAM,eAAe,SAAS;GAC9B,SAAS,eAAe;EAC1B,GACA;GAAE,GAAG;GAAO,GAAG;GAAQ,GAAG;GAAO,GAAG;EAAY,GAChD;GACE;GACA,gBAAgB;IACd,OAAO,OAAO,KAAK;IACnB,QAAQ,OAAO,WAAW;IAC1B;GACF;EACF,CACF;EAGA,kBACE,KACA;GACE,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;EACL,GACA;GACE,MAAM,MAAM;GACZ,WAAW,KAAK,YAAY,MAAM;GAClC,UAAU,KAAK,cAAc;GAC7B,OAAO;GACP,MAAM,KAAK,QAAQ;GACnB,OAAO;GACP,QAAQ;GACR,SAAS;EACX,CACF;CACF;AACF"}
@@ -1,7 +1,8 @@
1
- import { pxToPt } from "../units.js";
2
- import { getContentAreaIn } from "../utils/contentArea.js";
3
- import { convertStrike, convertUnderline } from "../textOptions.js";
4
- import { convertBorderLine, convertShadow } from "../utils/visualStyle.js";
1
+ import { pxToEmu, pxToPt } from "../units.js";
2
+ import { getContentArea } from "../utils/contentArea.js";
3
+ import { toColorInput } from "../glimpseTextBoxes.js";
4
+ import { addGlimpseShape, createShapeBoundsInput, createShapeRotationInput, noneShapeFill, shapeOutline, solidShapeFill } from "../utils/glimpseShape.js";
5
+ import { asEmu, asHundredthPt, asPt } from "@pptx-glimpse/document";
5
6
  //#region src/renderPptx/nodes/shape.ts
6
7
  /**
7
8
  * outline (Text と同じ書式の `outline.size` / `outline.color`) と
@@ -22,41 +23,66 @@ function resolveShapeLine(line, outline) {
22
23
  dashType: line?.dashType
23
24
  };
24
25
  }
25
- function renderShapeNode(node, ctx) {
26
- const lineSpec = resolveShapeLine(node.line, node.outline);
27
- const glowMarker = node.glow ? ctx.buildContext.glowEffects.register(node.glow) : void 0;
28
- const shapeOptions = {
29
- ...getContentAreaIn(node),
30
- fill: node.fill ? {
31
- color: node.fill.color,
32
- transparency: node.fill.transparency
33
- } : void 0,
34
- line: lineSpec ? convertBorderLine(lineSpec) : void 0,
35
- shadow: convertShadow(node.shadow),
36
- rotate: node.rotate,
37
- objectName: glowMarker
26
+ function toUnderlineInput(underline) {
27
+ if (underline === void 0 || underline === false) return void 0;
28
+ if (underline === true) return true;
29
+ return {
30
+ style: underline.style,
31
+ color: toColorInput(underline.color)
32
+ };
33
+ }
34
+ function buildShapeTextProperties(node) {
35
+ return {
36
+ fontFace: node.fontFamily ?? "Noto Sans JP",
37
+ fontSize: asPt(pxToPt(node.fontSize ?? 24)),
38
+ color: toColorInput(node.color),
39
+ bold: node.bold,
40
+ italic: node.italic,
41
+ underline: toUnderlineInput(node.underline),
42
+ strike: node.strike,
43
+ baseline: node.subscript ? "subscript" : node.superscript ? "superscript" : void 0,
44
+ highlight: toColorInput(node.highlight)
38
45
  };
39
- if (node.text) {
40
- const fontSizePx = node.fontSize ?? 24;
41
- const lineHeight = node.lineHeight ?? 1.3;
42
- ctx.slide.addText(node.text, {
43
- ...shapeOptions,
44
- shape: node.shapeType,
45
- fontSize: pxToPt(fontSizePx),
46
- fontFace: node.fontFamily ?? "Noto Sans JP",
47
- color: node.color,
48
- bold: node.bold,
49
- italic: node.italic,
50
- underline: convertUnderline(node.underline),
51
- strike: convertStrike(node.strike),
52
- subscript: node.subscript,
53
- superscript: node.superscript,
54
- highlight: node.highlight,
46
+ }
47
+ function buildShapeParagraphs(node) {
48
+ if (!node.text) return void 0;
49
+ const fontSizePx = node.fontSize ?? 24;
50
+ const lineHeight = node.lineHeight ?? 1.3;
51
+ const properties = buildShapeTextProperties(node);
52
+ return node.text.replace(/\r*\n/g, "\n").split("\n").map((text) => ({
53
+ properties: {
55
54
  align: node.textAlign ?? "center",
56
- valign: "middle",
57
- lineSpacing: pxToPt(fontSizePx * lineHeight)
58
- });
59
- } else ctx.slide.addShape(node.shapeType, shapeOptions);
55
+ lineSpacing: asHundredthPt(Math.round(pxToPt(fontSizePx * lineHeight) * 100))
56
+ },
57
+ runs: [{
58
+ text,
59
+ properties
60
+ }]
61
+ }));
62
+ }
63
+ function renderShapeNode(node, ctx) {
64
+ const lineSpec = resolveShapeLine(node.line, node.outline);
65
+ const boundsPx = getContentArea(node);
66
+ const fillOpacity = node.fill?.transparency !== void 0 ? 1 - node.fill.transparency / 100 : void 0;
67
+ addGlimpseShape(ctx, {
68
+ preset: node.shapeType,
69
+ ...createShapeBoundsInput(boundsPx),
70
+ rotation: createShapeRotationInput(node.rotate),
71
+ fill: node.fill?.color ? solidShapeFill(node.fill.color) : noneShapeFill(),
72
+ outline: shapeOutline(lineSpec),
73
+ effects: node.glow ? { glow: {
74
+ radius: asEmu(Math.round(pxToEmu(node.glow.size ?? 8))),
75
+ color: toColorInput(node.glow.color ?? "FFFFFF")
76
+ } } : void 0,
77
+ body: node.text ? { anchor: "middle" } : void 0,
78
+ paragraphs: buildShapeParagraphs(node)
79
+ }, boundsPx, {
80
+ fillColor: node.fill?.color,
81
+ fillOpacity,
82
+ glow: node.glow,
83
+ shadow: node.shadow,
84
+ dashType: lineSpec?.dashType
85
+ });
60
86
  }
61
87
  //#endregion
62
88
  export { renderShapeNode };
@@ -1 +1 @@
1
- {"version":3,"file":"shape.js","names":[],"sources":["../../../src/renderPptx/nodes/shape.ts"],"sourcesContent":["import type { BorderStyle, PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToPt } from \"../units.ts\";\nimport { convertUnderline, convertStrike } from \"../textOptions.ts\";\nimport { getContentAreaIn } from \"../utils/contentArea.ts\";\nimport { convertBorderLine, convertShadow } from \"../utils/visualStyle.ts\";\n\ntype ShapePositionedNode = Extract<PositionedNode, { type: \"shape\" }>;\n\n/**\n * outline (Text と同じ書式の `outline.size` / `outline.color`) と\n * 既存 `line` 属性 (`line.color` / `line.width` / `line.dashType`) を\n * 1 つの BorderStyle にマージする。\n *\n * フィールド単位のマージで、`outline` の指定があるフィールドは `line` を\n * 上書きするが、`outline` 側で省略されたフィールドは `line` の値を引き継ぎ、\n * `line` にも値が無い場合は Text outline と同じ既定値 (`width: 1pt 相当` /\n * `color: FFFFFF`) を採用する。`dashType` は `outline` に対応フィールドが\n * 無いため `line.dashType` をそのまま使う。\n */\nfunction resolveShapeLine(\n line: BorderStyle | undefined,\n outline: { size?: number; color?: string } | undefined,\n): BorderStyle | undefined {\n if (!outline) return line;\n return {\n color: outline.color ?? line?.color ?? \"FFFFFF\",\n width: outline.size ?? line?.width ?? 1,\n dashType: line?.dashType,\n };\n}\n\nexport function renderShapeNode(\n node: ShapePositionedNode,\n ctx: RenderContext,\n): void {\n const lineSpec = resolveShapeLine(node.line, node.outline);\n const glowMarker = node.glow\n ? ctx.buildContext.glowEffects.register(node.glow)\n : undefined;\n\n const shapeOptions = {\n ...getContentAreaIn(node),\n fill: node.fill\n ? {\n color: node.fill.color,\n transparency: node.fill.transparency,\n }\n : undefined,\n line: lineSpec ? convertBorderLine(lineSpec) : undefined,\n shadow: convertShadow(node.shadow),\n rotate: node.rotate,\n objectName: glowMarker,\n };\n\n if (node.text) {\n const fontSizePx = node.fontSize ?? 24;\n const lineHeight = node.lineHeight ?? 1.3;\n // テキストがある場合:addTextでshapeを指定\n ctx.slide.addText(node.text, {\n ...shapeOptions,\n shape: node.shapeType,\n fontSize: pxToPt(fontSizePx),\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n color: node.color,\n bold: node.bold,\n italic: node.italic,\n underline: convertUnderline(node.underline),\n strike: convertStrike(node.strike),\n subscript: node.subscript,\n superscript: node.superscript,\n highlight: node.highlight,\n align: node.textAlign ?? \"center\",\n valign: \"middle\" as const,\n // Text と同じく行送りを固定値 (spcPts) で指定し、計測高さ\n // (行数 × fontSize × lineHeight) と実描画の行高さを一致させる (#846)。\n // valign middle のためテキストブロックは枠内中央に配置され、\n // Text のような描画 y 補正は不要\n lineSpacing: pxToPt(fontSizePx * lineHeight),\n });\n } else {\n // テキストがない場合:addShapeを使用\n ctx.slide.addShape(node.shapeType, shapeOptions);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,SAAS,iBACP,MACA,SACyB;CACzB,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EACL,OAAO,QAAQ,SAAS,MAAM,SAAS;EACvC,OAAO,QAAQ,QAAQ,MAAM,SAAS;EACtC,UAAU,MAAM;CAClB;AACF;AAEA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,WAAW,iBAAiB,KAAK,MAAM,KAAK,OAAO;CACzD,MAAM,aAAa,KAAK,OACpB,IAAI,aAAa,YAAY,SAAS,KAAK,IAAI,IAC/C,KAAA;CAEJ,MAAM,eAAe;EACnB,GAAG,iBAAiB,IAAI;EACxB,MAAM,KAAK,OACP;GACE,OAAO,KAAK,KAAK;GACjB,cAAc,KAAK,KAAK;EAC1B,IACA,KAAA;EACJ,MAAM,WAAW,kBAAkB,QAAQ,IAAI,KAAA;EAC/C,QAAQ,cAAc,KAAK,MAAM;EACjC,QAAQ,KAAK;EACb,YAAY;CACd;CAEA,IAAI,KAAK,MAAM;EACb,MAAM,aAAa,KAAK,YAAY;EACpC,MAAM,aAAa,KAAK,cAAc;EAEtC,IAAI,MAAM,QAAQ,KAAK,MAAM;GAC3B,GAAG;GACH,OAAO,KAAK;GACZ,UAAU,OAAO,UAAU;GAC3B,UAAU,KAAK,cAAc;GAC7B,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,WAAW,iBAAiB,KAAK,SAAS;GAC1C,QAAQ,cAAc,KAAK,MAAM;GACjC,WAAW,KAAK;GAChB,aAAa,KAAK;GAClB,WAAW,KAAK;GAChB,OAAO,KAAK,aAAa;GACzB,QAAQ;GAKR,aAAa,OAAO,aAAa,UAAU;EAC7C,CAAC;CACH,OAEE,IAAI,MAAM,SAAS,KAAK,WAAW,YAAY;AAEnD"}
1
+ {"version":3,"file":"shape.js","names":[],"sources":["../../../src/renderPptx/nodes/shape.ts"],"sourcesContent":["import {\n asEmu,\n asHundredthPt,\n asPt,\n type AddShapeParagraphInput,\n type AddShapeRunPropertiesInput,\n} from \"@pptx-glimpse/document\";\nimport type { BorderStyle, PositionedNode, Underline } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToPt } from \"../units.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\nimport { pxToEmu } from \"../units.ts\";\nimport { toColorInput } from \"../glimpseTextBoxes.ts\";\nimport {\n addGlimpseShape,\n createShapeBoundsInput,\n createShapeRotationInput,\n noneShapeFill,\n shapeOutline,\n solidShapeFill,\n} from \"../utils/glimpseShape.ts\";\n\ntype ShapePositionedNode = Extract<PositionedNode, { type: \"shape\" }>;\n\n/**\n * outline (Text と同じ書式の `outline.size` / `outline.color`) と\n * 既存 `line` 属性 (`line.color` / `line.width` / `line.dashType`) を\n * 1 つの BorderStyle にマージする。\n *\n * フィールド単位のマージで、`outline` の指定があるフィールドは `line` を\n * 上書きするが、`outline` 側で省略されたフィールドは `line` の値を引き継ぎ、\n * `line` にも値が無い場合は Text outline と同じ既定値 (`width: 1pt 相当` /\n * `color: FFFFFF`) を採用する。`dashType` は `outline` に対応フィールドが\n * 無いため `line.dashType` をそのまま使う。\n */\nfunction resolveShapeLine(\n line: BorderStyle | undefined,\n outline: { size?: number; color?: string } | undefined,\n): BorderStyle | undefined {\n if (!outline) return line;\n return {\n color: outline.color ?? line?.color ?? \"FFFFFF\",\n width: outline.size ?? line?.width ?? 1,\n dashType: line?.dashType,\n };\n}\n\nfunction toUnderlineInput(underline: Underline | undefined) {\n if (underline === undefined || underline === false) return undefined;\n if (underline === true) return true;\n return {\n style: underline.style,\n color: toColorInput(underline.color),\n };\n}\n\nfunction buildShapeTextProperties(\n node: ShapePositionedNode,\n): AddShapeRunPropertiesInput {\n return {\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\n fontSize: asPt(pxToPt(node.fontSize ?? 24)),\n color: toColorInput(node.color),\n bold: node.bold,\n italic: node.italic,\n underline: toUnderlineInput(node.underline),\n strike: node.strike,\n baseline: node.subscript\n ? \"subscript\"\n : node.superscript\n ? \"superscript\"\n : undefined,\n highlight: toColorInput(node.highlight),\n };\n}\n\nfunction buildShapeParagraphs(\n node: ShapePositionedNode,\n): AddShapeParagraphInput[] | undefined {\n if (!node.text) return undefined;\n const fontSizePx = node.fontSize ?? 24;\n const lineHeight = node.lineHeight ?? 1.3;\n const properties = buildShapeTextProperties(node);\n return node.text\n .replace(/\\r*\\n/g, \"\\n\")\n .split(\"\\n\")\n .map((text) => ({\n properties: {\n align: node.textAlign ?? \"center\",\n lineSpacing: asHundredthPt(\n Math.round(pxToPt(fontSizePx * lineHeight) * 100),\n ),\n },\n runs: [{ text, properties }],\n }));\n}\n\nexport function renderShapeNode(\n node: ShapePositionedNode,\n ctx: RenderContext,\n): void {\n const lineSpec = resolveShapeLine(node.line, node.outline);\n const boundsPx = getContentArea(node);\n const fillOpacity =\n node.fill?.transparency !== undefined\n ? 1 - node.fill.transparency / 100\n : undefined;\n\n addGlimpseShape(\n ctx,\n {\n preset: node.shapeType,\n ...createShapeBoundsInput(boundsPx),\n rotation: createShapeRotationInput(node.rotate),\n fill: node.fill?.color\n ? solidShapeFill(node.fill.color)\n : noneShapeFill(),\n outline: shapeOutline(lineSpec),\n effects: node.glow\n ? {\n glow: {\n radius: asEmu(Math.round(pxToEmu(node.glow.size ?? 8))),\n color: toColorInput(node.glow.color ?? \"FFFFFF\")!,\n },\n }\n : undefined,\n body: node.text\n ? {\n anchor: \"middle\",\n }\n : undefined,\n paragraphs: buildShapeParagraphs(node),\n },\n boundsPx,\n {\n fillColor: node.fill?.color,\n fillOpacity,\n glow: node.glow,\n shadow: node.shadow,\n dashType: lineSpec?.dashType,\n },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmCA,SAAS,iBACP,MACA,SACyB;CACzB,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EACL,OAAO,QAAQ,SAAS,MAAM,SAAS;EACvC,OAAO,QAAQ,QAAQ,MAAM,SAAS;EACtC,UAAU,MAAM;CAClB;AACF;AAEA,SAAS,iBAAiB,WAAkC;CAC1D,IAAI,cAAc,KAAA,KAAa,cAAc,OAAO,OAAO,KAAA;CAC3D,IAAI,cAAc,MAAM,OAAO;CAC/B,OAAO;EACL,OAAO,UAAU;EACjB,OAAO,aAAa,UAAU,KAAK;CACrC;AACF;AAEA,SAAS,yBACP,MAC4B;CAC5B,OAAO;EACL,UAAU,KAAK,cAAc;EAC7B,UAAU,KAAK,OAAO,KAAK,YAAY,EAAE,CAAC;EAC1C,OAAO,aAAa,KAAK,KAAK;EAC9B,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,WAAW,iBAAiB,KAAK,SAAS;EAC1C,QAAQ,KAAK;EACb,UAAU,KAAK,YACX,cACA,KAAK,cACH,gBACA,KAAA;EACN,WAAW,aAAa,KAAK,SAAS;CACxC;AACF;AAEA,SAAS,qBACP,MACsC;CACtC,IAAI,CAAC,KAAK,MAAM,OAAO,KAAA;CACvB,MAAM,aAAa,KAAK,YAAY;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,aAAa,yBAAyB,IAAI;CAChD,OAAO,KAAK,KACT,QAAQ,UAAU,IAAI,CAAC,CACvB,MAAM,IAAI,CAAC,CACX,KAAK,UAAU;EACd,YAAY;GACV,OAAO,KAAK,aAAa;GACzB,aAAa,cACX,KAAK,MAAM,OAAO,aAAa,UAAU,IAAI,GAAG,CAClD;EACF;EACA,MAAM,CAAC;GAAE;GAAM;EAAW,CAAC;CAC7B,EAAE;AACN;AAEA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,WAAW,iBAAiB,KAAK,MAAM,KAAK,OAAO;CACzD,MAAM,WAAW,eAAe,IAAI;CACpC,MAAM,cACJ,KAAK,MAAM,iBAAiB,KAAA,IACxB,IAAI,KAAK,KAAK,eAAe,MAC7B,KAAA;CAEN,gBACE,KACA;EACE,QAAQ,KAAK;EACb,GAAG,uBAAuB,QAAQ;EAClC,UAAU,yBAAyB,KAAK,MAAM;EAC9C,MAAM,KAAK,MAAM,QACb,eAAe,KAAK,KAAK,KAAK,IAC9B,cAAc;EAClB,SAAS,aAAa,QAAQ;EAC9B,SAAS,KAAK,OACV,EACE,MAAM;GACJ,QAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC;GACtD,OAAO,aAAa,KAAK,KAAK,SAAS,QAAQ;EACjD,EACF,IACA,KAAA;EACJ,MAAM,KAAK,OACP,EACE,QAAQ,SACV,IACA,KAAA;EACJ,YAAY,qBAAqB,IAAI;CACvC,GACA,UACA;EACE,WAAW,KAAK,MAAM;EACtB;EACA,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,UAAU,UAAU;CACtB,CACF;AACF"}
@@ -1,10 +1,7 @@
1
- import { rectPxToIn } from "../units.js";
1
+ import { addGlimpsePicture, imageBytesFromSource } from "../utils/glimpsePicture.js";
2
2
  //#region src/renderPptx/nodes/svg.ts
3
3
  function renderSvgNode(node, ctx) {
4
- ctx.slide.addImage({
5
- data: node.iconImageData,
6
- ...rectPxToIn(node)
7
- });
4
+ addGlimpsePicture(ctx, node, imageBytesFromSource("", node.iconImageData));
8
5
  }
9
6
  //#endregion
10
7
  export { renderSvgNode };
@@ -1 +1 @@
1
- {"version":3,"file":"svg.js","names":[],"sources":["../../../src/renderPptx/nodes/svg.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { rectPxToIn } from \"../units.ts\";\n\ntype SvgPositionedNode = Extract<PositionedNode, { type: \"svg\" }>;\n\nexport function renderSvgNode(\n node: SvgPositionedNode,\n ctx: RenderContext,\n): void {\n ctx.slide.addImage({\n data: node.iconImageData,\n ...rectPxToIn(node),\n });\n}\n"],"mappings":";;AAMA,SAAgB,cACd,MACA,KACM;CACN,IAAI,MAAM,SAAS;EACjB,MAAM,KAAK;EACX,GAAG,WAAW,IAAI;CACpB,CAAC;AACH"}
1
+ {"version":3,"file":"svg.js","names":[],"sources":["../../../src/renderPptx/nodes/svg.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport {\n addGlimpsePicture,\n imageBytesFromSource,\n} from \"../utils/glimpsePicture.ts\";\n\ntype SvgPositionedNode = Extract<PositionedNode, { type: \"svg\" }>;\n\nexport function renderSvgNode(\n node: SvgPositionedNode,\n ctx: RenderContext,\n): void {\n addGlimpsePicture(ctx, node, imageBytesFromSource(\"\", node.iconImageData));\n}\n"],"mappings":";;AASA,SAAgB,cACd,MACA,KACM;CACN,kBAAkB,KAAK,MAAM,qBAAqB,IAAI,KAAK,aAAa,CAAC;AAC3E"}
@@ -1,51 +1,22 @@
1
- import { pxToPt } from "../units.js";
2
- import { convertGlow, convertOutline, convertStrike, convertUnderline, createTextOptions, resolveSubSup } from "../textOptions.js";
3
- import { registerTextGradient } from "../gradientFills.js";
1
+ import { createTextOptions } from "../textOptions.js";
4
2
  //#region src/renderPptx/nodes/text.ts
5
3
  function renderTextNode(node, ctx) {
6
4
  const textOptions = createTextOptions(node);
7
- const textGradientMarker = node.textGradient ? registerTextGradient(node.textGradient, ctx.buildContext.gradientFills) : void 0;
8
- if (node.runs && node.runs.length > 0) {
9
- const fontSizePx = node.fontSize ?? 24;
10
- const fontFamily = node.fontFamily ?? "Noto Sans JP";
11
- const textItems = node.runs.map((run) => {
12
- const letterSpacingPx = run.letterSpacing ?? node.letterSpacing;
13
- const runFontSizePx = run.fontSize ?? fontSizePx;
14
- const subSup = resolveSubSup(run, node);
15
- return {
16
- text: run.text,
17
- options: {
18
- fontSize: pxToPt(runFontSizePx),
19
- fontFace: run.fontFamily ?? fontFamily,
20
- color: textGradientMarker ?? run.color ?? node.color,
21
- bold: run.bold ?? node.bold,
22
- italic: run.italic ?? node.italic,
23
- underline: convertUnderline(run.underline ?? node.underline),
24
- strike: convertStrike(run.strike ?? node.strike),
25
- subscript: subSup.subscript,
26
- superscript: subSup.superscript,
27
- highlight: run.highlight ?? node.highlight,
28
- glow: convertGlow(node.glow),
29
- outline: convertOutline(node.outline),
30
- charSpacing: letterSpacingPx !== void 0 ? pxToPt(letterSpacingPx) : void 0,
31
- ...run.href ? { hyperlink: { url: run.href } } : {}
32
- }
33
- };
34
- });
35
- ctx.slide.addText(textItems, {
36
- x: textOptions.x,
37
- y: textOptions.y,
38
- w: textOptions.w,
39
- h: textOptions.h,
40
- rotate: textOptions.rotate,
41
- align: textOptions.align,
42
- valign: textOptions.valign,
43
- margin: textOptions.margin,
44
- lineSpacing: textOptions.lineSpacing
45
- });
46
- } else ctx.slide.addText(node.text ?? "", {
47
- ...textOptions,
48
- color: textGradientMarker ?? textOptions.color
5
+ const marker = ctx.buildContext.glimpseTextBoxes.register(node);
6
+ ctx.slide.addShape(ctx.pptx.ShapeType.rect, {
7
+ x: textOptions.x,
8
+ y: textOptions.y,
9
+ w: textOptions.w,
10
+ h: textOptions.h,
11
+ fill: {
12
+ color: "FFFFFF",
13
+ transparency: 100
14
+ },
15
+ line: {
16
+ color: "FFFFFF",
17
+ transparency: 100
18
+ },
19
+ objectName: marker
49
20
  });
50
21
  }
51
22
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","names":[],"sources":["../../../src/renderPptx/nodes/text.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport {\n createTextOptions,\n convertUnderline,\n convertStrike,\n convertGlow,\n convertOutline,\n resolveSubSup,\n} from \"../textOptions.ts\";\nimport { registerTextGradient } from \"../gradientFills.ts\";\nimport { pxToPt } from \"../units.ts\";\n\ntype TextPositionedNode = Extract<PositionedNode, { type: \"text\" }>;\n\nexport function renderTextNode(\n node: TextPositionedNode,\n ctx: RenderContext,\n): void {\n const textOptions = createTextOptions(node);\n\n // textGradient はマーカー色で text run color に適用し、\n // 出力時の後処理で gradFill に置換される (gradientFills.ts 参照)。\n // node 単位の指定として全 run の color を上書きする (run 単位指定はスコープ外)。\n const textGradientMarker = node.textGradient\n ? registerTextGradient(node.textGradient, ctx.buildContext.gradientFills)\n : undefined;\n\n if (node.runs && node.runs.length > 0) {\n const fontSizePx = node.fontSize ?? 24;\n const fontFamily = node.fontFamily ?? \"Noto Sans JP\";\n const textItems = node.runs.map((run) => {\n const letterSpacingPx = run.letterSpacing ?? node.letterSpacing;\n const runFontSizePx = run.fontSize ?? fontSizePx;\n const subSup = resolveSubSup(run, node);\n return {\n text: run.text,\n options: {\n fontSize: pxToPt(runFontSizePx),\n fontFace: run.fontFamily ?? fontFamily,\n color: textGradientMarker ?? run.color ?? node.color,\n bold: run.bold ?? node.bold,\n italic: run.italic ?? node.italic,\n underline: convertUnderline(run.underline ?? node.underline),\n strike: convertStrike(run.strike ?? node.strike),\n subscript: subSup.subscript,\n superscript: subSup.superscript,\n highlight: run.highlight ?? node.highlight,\n // glow / outline はノード単位指定のみ (run 単位はスコープ外)\n glow: convertGlow(node.glow),\n outline: convertOutline(node.outline),\n charSpacing:\n letterSpacingPx !== undefined ? pxToPt(letterSpacingPx) : undefined,\n ...(run.href ? { hyperlink: { url: run.href } } : {}),\n },\n };\n });\n ctx.slide.addText(textItems, {\n x: textOptions.x,\n y: textOptions.y,\n w: textOptions.w,\n h: textOptions.h,\n rotate: textOptions.rotate,\n align: textOptions.align,\n valign: textOptions.valign,\n margin: textOptions.margin,\n lineSpacing: textOptions.lineSpacing,\n });\n } else {\n ctx.slide.addText(node.text ?? \"\", {\n ...textOptions,\n color: textGradientMarker ?? textOptions.color,\n });\n }\n}\n"],"mappings":";;;;AAeA,SAAgB,eACd,MACA,KACM;CACN,MAAM,cAAc,kBAAkB,IAAI;CAK1C,MAAM,qBAAqB,KAAK,eAC5B,qBAAqB,KAAK,cAAc,IAAI,aAAa,aAAa,IACtE,KAAA;CAEJ,IAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;EACrC,MAAM,aAAa,KAAK,YAAY;EACpC,MAAM,aAAa,KAAK,cAAc;EACtC,MAAM,YAAY,KAAK,KAAK,KAAK,QAAQ;GACvC,MAAM,kBAAkB,IAAI,iBAAiB,KAAK;GAClD,MAAM,gBAAgB,IAAI,YAAY;GACtC,MAAM,SAAS,cAAc,KAAK,IAAI;GACtC,OAAO;IACL,MAAM,IAAI;IACV,SAAS;KACP,UAAU,OAAO,aAAa;KAC9B,UAAU,IAAI,cAAc;KAC5B,OAAO,sBAAsB,IAAI,SAAS,KAAK;KAC/C,MAAM,IAAI,QAAQ,KAAK;KACvB,QAAQ,IAAI,UAAU,KAAK;KAC3B,WAAW,iBAAiB,IAAI,aAAa,KAAK,SAAS;KAC3D,QAAQ,cAAc,IAAI,UAAU,KAAK,MAAM;KAC/C,WAAW,OAAO;KAClB,aAAa,OAAO;KACpB,WAAW,IAAI,aAAa,KAAK;KAEjC,MAAM,YAAY,KAAK,IAAI;KAC3B,SAAS,eAAe,KAAK,OAAO;KACpC,aACE,oBAAoB,KAAA,IAAY,OAAO,eAAe,IAAI,KAAA;KAC5D,GAAI,IAAI,OAAO,EAAE,WAAW,EAAE,KAAK,IAAI,KAAK,EAAE,IAAI,CAAC;IACrD;GACF;EACF,CAAC;EACD,IAAI,MAAM,QAAQ,WAAW;GAC3B,GAAG,YAAY;GACf,GAAG,YAAY;GACf,GAAG,YAAY;GACf,GAAG,YAAY;GACf,QAAQ,YAAY;GACpB,OAAO,YAAY;GACnB,QAAQ,YAAY;GACpB,QAAQ,YAAY;GACpB,aAAa,YAAY;EAC3B,CAAC;CACH,OACE,IAAI,MAAM,QAAQ,KAAK,QAAQ,IAAI;EACjC,GAAG;EACH,OAAO,sBAAsB,YAAY;CAC3C,CAAC;AAEL"}
1
+ {"version":3,"file":"text.js","names":[],"sources":["../../../src/renderPptx/nodes/text.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { createTextOptions } from \"../textOptions.ts\";\n\ntype TextPositionedNode = Extract<PositionedNode, { type: \"text\" }>;\n\nexport function renderTextNode(\n node: TextPositionedNode,\n ctx: RenderContext,\n): void {\n const textOptions = createTextOptions(node);\n const marker = ctx.buildContext.glimpseTextBoxes.register(node);\n\n ctx.slide.addShape(ctx.pptx.ShapeType.rect, {\n x: textOptions.x,\n y: textOptions.y,\n w: textOptions.w,\n h: textOptions.h,\n fill: { color: \"FFFFFF\", transparency: 100 },\n line: { color: \"FFFFFF\", transparency: 100 },\n objectName: marker,\n });\n}\n"],"mappings":";;AAMA,SAAgB,eACd,MACA,KACM;CACN,MAAM,cAAc,kBAAkB,IAAI;CAC1C,MAAM,SAAS,IAAI,aAAa,iBAAiB,SAAS,IAAI;CAE9D,IAAI,MAAM,SAAS,IAAI,KAAK,UAAU,MAAM;EAC1C,GAAG,YAAY;EACf,GAAG,YAAY;EACf,GAAG,YAAY;EACf,GAAG,YAAY;EACf,MAAM;GAAE,OAAO;GAAU,cAAc;EAAI;EAC3C,MAAM;GAAE,OAAO;GAAU,cAAc;EAAI;EAC3C,YAAY;CACd,CAAC;AACH"}