@hirokisakabe/pom 9.1.2 → 10.0.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 (65) hide show
  1. package/dist/buildContext.js +2 -2
  2. package/dist/buildContext.js.map +1 -1
  3. package/dist/buildPptx.d.ts +2 -1
  4. package/dist/buildPptx.d.ts.map +1 -1
  5. package/dist/buildPptx.js +4 -3
  6. package/dist/buildPptx.js.map +1 -1
  7. package/dist/calcYogaLayout/calcYogaLayout.js +1 -1
  8. package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
  9. package/dist/index.d.ts +2 -1
  10. package/dist/registry/definitions/icon.js +1 -1
  11. package/dist/registry/definitions/list.js.map +1 -1
  12. package/dist/renderPptx/nodes/chart.js +172 -47
  13. package/dist/renderPptx/nodes/chart.js.map +1 -1
  14. package/dist/renderPptx/nodes/flow.js +4 -1
  15. package/dist/renderPptx/nodes/flow.js.map +1 -1
  16. package/dist/renderPptx/nodes/icon.js +5 -2
  17. package/dist/renderPptx/nodes/icon.js.map +1 -1
  18. package/dist/renderPptx/nodes/list.js +7 -9
  19. package/dist/renderPptx/nodes/list.js.map +1 -1
  20. package/dist/renderPptx/nodes/matrix.js +4 -1
  21. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  22. package/dist/renderPptx/nodes/processArrow.js +8 -2
  23. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  24. package/dist/renderPptx/nodes/pyramid.js +4 -1
  25. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  26. package/dist/renderPptx/nodes/shape.js +5 -2
  27. package/dist/renderPptx/nodes/shape.js.map +1 -1
  28. package/dist/renderPptx/nodes/table.js +89 -59
  29. package/dist/renderPptx/nodes/table.js.map +1 -1
  30. package/dist/renderPptx/nodes/text.js +1 -18
  31. package/dist/renderPptx/nodes/text.js.map +1 -1
  32. package/dist/renderPptx/nodes/timeline.js +9 -3
  33. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  34. package/dist/renderPptx/nodes/tree.js +4 -1
  35. package/dist/renderPptx/nodes/tree.js.map +1 -1
  36. package/dist/renderPptx/pptxAuthoring.js +381 -0
  37. package/dist/renderPptx/pptxAuthoring.js.map +1 -0
  38. package/dist/renderPptx/renderPptx.js +157 -126
  39. package/dist/renderPptx/renderPptx.js.map +1 -1
  40. package/dist/renderPptx/textOptions.js +7 -8
  41. package/dist/renderPptx/textOptions.js.map +1 -1
  42. package/dist/renderPptx/units.js +3 -3
  43. package/dist/renderPptx/units.js.map +1 -1
  44. package/dist/renderPptx/utils/backgroundBorder.js +30 -44
  45. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  46. package/dist/renderPptx/utils/contentArea.js +1 -1
  47. package/dist/renderPptx/utils/contentArea.js.map +1 -1
  48. package/dist/renderPptx/utils/glimpsePicture.js +3 -22
  49. package/dist/renderPptx/utils/glimpsePicture.js.map +1 -1
  50. package/dist/renderPptx/utils/glimpseShape.js +6 -25
  51. package/dist/renderPptx/utils/glimpseShape.js.map +1 -1
  52. package/dist/renderPptx/utils/glimpseTextBox.js +41 -60
  53. package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -1
  54. package/dist/renderPptx/utils/straightLine.js +5 -2
  55. package/dist/renderPptx/utils/straightLine.js.map +1 -1
  56. package/dist/renderPptx/utils/visualStyle.js +3 -33
  57. package/dist/renderPptx/utils/visualStyle.js.map +1 -1
  58. package/dist/renderPptx/writablePptx.d.ts +23 -0
  59. package/dist/renderPptx/writablePptx.d.ts.map +1 -0
  60. package/dist/renderPptx/writablePptx.js +74 -0
  61. package/dist/renderPptx/writablePptx.js.map +1 -0
  62. package/dist/shared/measureImage.js.map +1 -1
  63. package/package.json +4 -5
  64. package/dist/renderPptx/glimpseTextBoxes.js +0 -646
  65. package/dist/renderPptx/glimpseTextBoxes.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"backgroundBorder.js","names":[],"sources":["../../../src/renderPptx/utils/backgroundBorder.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport { getImageData } from \"../../shared/measureImage.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { rectPxToIn } from \"../units.ts\";\nimport {\n BORDER_SIDES,\n convertBorderLine,\n convertShadow,\n hasVisibleBorder,\n resolvePerSideBorders,\n resolveRectRadius,\n type PerSideBorders,\n} from \"./visualStyle.ts\";\nimport { addGlimpsePicture, imageBytesFromSource } from \"./glimpsePicture.ts\";\nimport {\n addGlimpseShape,\n backgroundShapeFill,\n createShapeBoundsInput,\n noShapeOutline,\n noneShapeFill,\n shapeOutline,\n} from \"./glimpseShape.ts\";\n\n/**\n * ノードの背景色・背景画像・ボーダー・影を描画する\n * 全ノードタイプで最初に呼び出される共通処理\n *\n * 描画順序: 背景色 → 背景画像 → ボーダー\n */\nexport function renderBackgroundAndBorder(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const {\n backgroundColor,\n backgroundGradient,\n backgroundImage,\n border,\n borderRadius,\n shadow,\n } = node;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n\n const hasBackground = Boolean(backgroundColor) || Boolean(backgroundGradient);\n const hasBackgroundImage = Boolean(backgroundImage);\n // 辺ごとの指定がある場合、一律 border は各辺へのマージで反映済みのため\n // shape の line としては描画しない\n const hasUniformBorder = !perSideBorders && hasVisibleBorder(border);\n const hasShadow = Boolean(shadow);\n\n if (\n !hasBackground &&\n !hasBackgroundImage &&\n !hasUniformBorder &&\n !perSideBorders &&\n !hasShadow\n ) {\n return;\n }\n\n // borderRadius がある場合は roundRect を使用し、rectRadius を計算\n const shapeType = borderRadius ? \"roundRect\" : \"rect\";\n const legacyShapeType = borderRadius\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n const rectRadius = resolveRectRadius(borderRadius, node.w, node.h);\n\n // backgroundImage がない場合は従来通り1回の addShape で処理\n if (!hasBackgroundImage) {\n if (hasBackground || hasUniformBorder || hasShadow) {\n if (hasShadow && !backgroundGradient) {\n ctx.slide.addShape(legacyShapeType, {\n ...rectPxToIn(node),\n fill: hasBackground\n ? {\n color: backgroundColor,\n transparency:\n node.opacity !== undefined\n ? (1 - node.opacity) * 100\n : undefined,\n }\n : { type: \"none\" },\n line: hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" },\n rectRadius,\n shadow: convertShadow(shadow),\n });\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n const fill = hasBackground\n ? backgroundShapeFill(backgroundColor, backgroundGradient)\n : noneShapeFill();\n\n const line = hasUniformBorder\n ? shapeOutline(border, \"000000\")\n : noShapeOutline();\n\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill,\n outline: line,\n },\n node,\n {\n fillColor: backgroundColor,\n fillOpacity: node.opacity,\n backgroundGradient,\n shadow,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n // backgroundImage がある場合は分割描画: 背景色 → 背景画像 → ボーダー\n\n // 1. 背景色\n if (hasBackground) {\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill: backgroundShapeFill(backgroundColor, backgroundGradient),\n outline: noShapeOutline(),\n },\n node,\n {\n fillColor: backgroundColor,\n fillOpacity: node.opacity,\n backgroundGradient,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n // 2. 背景画像\n if (backgroundImage) {\n const sizing = backgroundImage.sizing ?? \"cover\";\n const cachedData = getImageData(\n backgroundImage.src,\n ctx.buildContext.imageDataCache,\n );\n addGlimpsePicture(\n ctx,\n node,\n imageBytesFromSource(backgroundImage.src, cachedData),\n {\n sizing: { type: sizing, w: node.w, h: node.h },\n },\n );\n }\n\n // 3. ボーダー\n if (hasUniformBorder || hasShadow) {\n if (hasShadow) {\n ctx.slide.addShape(legacyShapeType, {\n ...rectPxToIn(node),\n fill: { type: \"none\" },\n line: hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" },\n rectRadius,\n shadow: convertShadow(shadow),\n });\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill: noneShapeFill(),\n outline: hasUniformBorder\n ? shapeOutline(border, \"000000\")\n : noShapeOutline(),\n },\n node,\n {\n shadow,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n}\n\n/**\n * 辺ごとの border 指定を解決する。borderRadius との併用は角の接続処理が\n * 複雑になるためサポートせず、警告を発して 4 辺一律の border に\n * フォールバックする\n */\nfunction resolveEffectivePerSideBorders(\n node: PositionedNode,\n ctx: RenderContext,\n): PerSideBorders | undefined {\n const perSideBorders = resolvePerSideBorders(node);\n if (perSideBorders && node.borderRadius !== undefined) {\n ctx.buildContext.diagnostics.add(\n \"PER_SIDE_BORDER_WITH_RADIUS\",\n 'borderTop / borderRight / borderBottom / borderLeft cannot be combined with borderRadius — falling back to the uniform \"border\" style',\n );\n return undefined;\n }\n return perSideBorders;\n}\n\n/**\n * ノードの border のみを描画する (背景・影は描画しない)。\n * ルートノードの backgroundColor / backgroundImage を slide.background に\n * 適用した後、border だけを個別に描画するパス用\n */\nexport function renderBorderOnly(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const { border, borderRadius } = node;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n if (perSideBorders) {\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n if (!hasVisibleBorder(border)) return;\n\n const shapeType = borderRadius ? \"roundRect\" : \"rect\";\n\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill: noneShapeFill(),\n outline: shapeOutline(border, \"000000\"),\n },\n node,\n {\n rectRadius: resolveRectRadius(borderRadius, node.w, node.h),\n dashType: border.dashType,\n },\n );\n}\n\n/**\n * 辺ごとの border をノードの各辺に沿った line shape として描画する。\n * shape の line オプション (4 辺一律) では表現できないため、辺ごとに\n * 独立した line shape を追加する\n */\nfunction renderPerSideBorderLines(\n node: PositionedNode,\n perSideBorders: PerSideBorders | undefined,\n ctx: RenderContext,\n): void {\n if (!perSideBorders) return;\n\n const edges = {\n top: { x: node.x, y: node.y, w: node.w, h: 0 },\n right: { x: node.x + node.w, y: node.y, w: 0, h: node.h },\n bottom: { x: node.x, y: node.y + node.h, w: node.w, h: 0 },\n left: { x: node.x, y: node.y, w: 0, h: node.h },\n } as const;\n\n for (const side of BORDER_SIDES) {\n const style = perSideBorders[side];\n if (!style) continue;\n\n addGlimpseShape(\n ctx,\n {\n preset: \"line\",\n ...createShapeBoundsInput(edges[side]),\n fill: noneShapeFill(),\n outline: shapeOutline(style, \"000000\"),\n },\n edges[side],\n {\n dashType: style.dashType,\n },\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"backgroundBorder.js","names":[],"sources":["../../../src/renderPptx/utils/backgroundBorder.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport { getImageData } from \"../../shared/measureImage.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport {\n BORDER_SIDES,\n hasVisibleBorder,\n resolvePerSideBorders,\n resolveRectRadius,\n type PerSideBorders,\n} from \"./visualStyle.ts\";\nimport { addGlimpsePicture, imageBytesFromSource } from \"./glimpsePicture.ts\";\nimport {\n addGlimpseShape,\n backgroundShapeFill,\n createShapeBoundsInput,\n noShapeOutline,\n noneShapeFill,\n shapeOutline,\n} from \"./glimpseShape.ts\";\n\n/**\n * ノードの背景色・背景画像・ボーダー・影を描画する\n * 全ノードタイプで最初に呼び出される共通処理\n *\n * 描画順序: 背景色 → 背景画像 → ボーダー\n */\nexport function renderBackgroundAndBorder(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const {\n backgroundColor,\n backgroundGradient,\n backgroundImage,\n border,\n borderRadius,\n shadow,\n } = node;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n\n const hasBackground = Boolean(backgroundColor) || Boolean(backgroundGradient);\n const hasBackgroundImage = Boolean(backgroundImage);\n // 辺ごとの指定がある場合、一律 border は各辺へのマージで反映済みのため\n // shape の line としては描画しない\n const hasUniformBorder = !perSideBorders && hasVisibleBorder(border);\n const hasShadow = Boolean(shadow);\n\n if (\n !hasBackground &&\n !hasBackgroundImage &&\n !hasUniformBorder &&\n !perSideBorders &&\n !hasShadow\n ) {\n return;\n }\n\n // borderRadius がある場合は roundRect を使用し、rectRadius を計算\n const shapeType = borderRadius ? \"roundRect\" : \"rect\";\n const rectRadius = resolveRectRadius(borderRadius, node.w, node.h);\n\n // backgroundImage がない場合は従来通り1回の addShape で処理\n if (!hasBackgroundImage) {\n if (hasBackground || hasUniformBorder || hasShadow) {\n const fill = hasBackground\n ? backgroundShapeFill(backgroundColor, backgroundGradient)\n : noneShapeFill();\n\n const line = hasUniformBorder\n ? shapeOutline(border, \"000000\")\n : noShapeOutline();\n\n addGlimpseShape(\n ctx,\n {\n geometry: { kind: \"preset\", preset: shapeType },\n ...createShapeBoundsInput(node),\n fill,\n outline: line,\n },\n node,\n {\n fillColor: backgroundColor,\n fillOpacity: node.opacity,\n backgroundGradient,\n shadow,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n // backgroundImage がある場合は分割描画: 背景色 → 背景画像 → ボーダー\n\n // 1. 背景色\n if (hasBackground) {\n addGlimpseShape(\n ctx,\n {\n geometry: { kind: \"preset\", preset: shapeType },\n ...createShapeBoundsInput(node),\n fill: backgroundShapeFill(backgroundColor, backgroundGradient),\n outline: noShapeOutline(),\n },\n node,\n {\n fillColor: backgroundColor,\n fillOpacity: node.opacity,\n backgroundGradient,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n // 2. 背景画像\n if (backgroundImage) {\n const sizing = backgroundImage.sizing ?? \"cover\";\n const cachedData = getImageData(\n backgroundImage.src,\n ctx.buildContext.imageDataCache,\n );\n addGlimpsePicture(\n ctx,\n node,\n imageBytesFromSource(backgroundImage.src, cachedData),\n {\n sizing: { type: sizing, w: node.w, h: node.h },\n },\n );\n }\n\n // 3. ボーダー\n if (hasUniformBorder || hasShadow) {\n addGlimpseShape(\n ctx,\n {\n geometry: { kind: \"preset\", preset: shapeType },\n ...createShapeBoundsInput(node),\n fill: noneShapeFill(),\n outline: hasUniformBorder\n ? shapeOutline(border, \"000000\")\n : noShapeOutline(),\n },\n node,\n {\n shadow,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n}\n\n/**\n * 辺ごとの border 指定を解決する。borderRadius との併用は角の接続処理が\n * 複雑になるためサポートせず、警告を発して 4 辺一律の border に\n * フォールバックする\n */\nfunction resolveEffectivePerSideBorders(\n node: PositionedNode,\n ctx: RenderContext,\n): PerSideBorders | undefined {\n const perSideBorders = resolvePerSideBorders(node);\n if (perSideBorders && node.borderRadius !== undefined) {\n ctx.buildContext.diagnostics.add(\n \"PER_SIDE_BORDER_WITH_RADIUS\",\n 'borderTop / borderRight / borderBottom / borderLeft cannot be combined with borderRadius — falling back to the uniform \"border\" style',\n );\n return undefined;\n }\n return perSideBorders;\n}\n\n/**\n * ノードの border のみを描画する (背景・影は描画しない)。\n * ルートノードの backgroundColor / backgroundImage を slide.background に\n * 適用した後、border だけを個別に描画するパス用\n */\nexport function renderBorderOnly(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const { border, borderRadius } = node;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n if (perSideBorders) {\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n if (!hasVisibleBorder(border)) return;\n\n const shapeType = borderRadius ? \"roundRect\" : \"rect\";\n\n addGlimpseShape(\n ctx,\n {\n geometry: { kind: \"preset\", preset: shapeType },\n ...createShapeBoundsInput(node),\n fill: noneShapeFill(),\n outline: shapeOutline(border, \"000000\"),\n },\n node,\n {\n rectRadius: resolveRectRadius(borderRadius, node.w, node.h),\n dashType: border.dashType,\n },\n );\n}\n\n/**\n * 辺ごとの border をノードの各辺に沿った line shape として描画する。\n * shape の line オプション (4 辺一律) では表現できないため、辺ごとに\n * 独立した line shape を追加する\n */\nfunction renderPerSideBorderLines(\n node: PositionedNode,\n perSideBorders: PerSideBorders | undefined,\n ctx: RenderContext,\n): void {\n if (!perSideBorders) return;\n\n const edges = {\n top: { x: node.x, y: node.y, w: node.w, h: 0 },\n right: { x: node.x + node.w, y: node.y, w: 0, h: node.h },\n bottom: { x: node.x, y: node.y + node.h, w: node.w, h: 0 },\n left: { x: node.x, y: node.y, w: 0, h: node.h },\n } as const;\n\n for (const side of BORDER_SIDES) {\n const style = perSideBorders[side];\n if (!style) continue;\n\n addGlimpseShape(\n ctx,\n {\n geometry: { kind: \"preset\", preset: \"line\" },\n ...createShapeBoundsInput(edges[side]),\n fill: noneShapeFill(),\n outline: shapeOutline(style, \"000000\"),\n },\n edges[side],\n {\n dashType: style.dashType,\n },\n );\n }\n}\n"],"mappings":"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"}
@@ -22,7 +22,7 @@ function getContentArea(node) {
22
22
  };
23
23
  }
24
24
  /**
25
- * コンテンツ描画領域を pptxgenjs の位置オプション (inch 単位の x/y/w/h)
25
+ * コンテンツ描画領域を互換用の位置オプション (inch 単位の x/y/w/h)
26
26
  * として返す。コンテンツを領域いっぱいに描画する renderer はこれを
27
27
  * addShape / addText 等のオプションへ spread するだけでよい。
28
28
  */
@@ -1 +1 @@
1
- {"version":3,"file":"contentArea.js","names":[],"sources":["../../../src/renderPptx/utils/contentArea.ts"],"sourcesContent":["import {\n resolveBoxSpacing,\n type BoxSpacingInput,\n} from \"../../shared/boxSpacing.ts\";\nimport { rectPxToIn } from \"../units.ts\";\n\ninterface ContentArea {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/**\n * ノードの padding を考慮したコンテンツ描画領域を計算する。\n * background/border はノード全体の領域 (node.x/y/w/h) に描画し、\n * コンテンツはこの関数で返される領域に描画する。\n */\nexport function getContentArea(node: {\n x: number;\n y: number;\n w: number;\n h: number;\n padding?: BoxSpacingInput;\n}): ContentArea {\n if (node.padding === undefined) {\n return { x: node.x, y: node.y, w: node.w, h: node.h };\n }\n\n const { top, right, bottom, left } = resolveBoxSpacing(node.padding);\n\n return {\n x: node.x + left,\n y: node.y + top,\n w: Math.max(0, node.w - left - right),\n h: Math.max(0, node.h - top - bottom),\n };\n}\n\n/**\n * コンテンツ描画領域を pptxgenjs の位置オプション (inch 単位の x/y/w/h)\n * として返す。コンテンツを領域いっぱいに描画する renderer はこれを\n * addShape / addText 等のオプションへ spread するだけでよい。\n */\nexport function getContentAreaIn(\n node: Parameters<typeof getContentArea>[0],\n): ReturnType<typeof rectPxToIn> {\n return rectPxToIn(getContentArea(node));\n}\n\n/**\n * ノードの x/y/w/h を指定領域 (通常はコンテンツ領域) で置き換えた\n * 仮想ノードを返す。padding を解決済みの領域を基準に下位の描画関数へ\n * ノードを渡すために使う。\n */\nexport function withContentBounds<\n T extends { x: number; y: number; w: number; h: number },\n>(node: T, bounds: ContentArea): T {\n return { ...node, x: bounds.x, y: bounds.y, w: bounds.w, h: bounds.h };\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,eAAe,MAMf;CACd,IAAI,KAAK,YAAY,KAAA,GACnB,OAAO;EAAE,GAAG,KAAK;EAAG,GAAG,KAAK;EAAG,GAAG,KAAK;EAAG,GAAG,KAAK;CAAE;CAGtD,MAAM,EAAE,KAAK,OAAO,QAAQ,SAAS,kBAAkB,KAAK,OAAO;CAEnE,OAAO;EACL,GAAG,KAAK,IAAI;EACZ,GAAG,KAAK,IAAI;EACZ,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,KAAK;EACpC,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,MAAM;CACtC;AACF;;;;;;AAOA,SAAgB,iBACd,MAC+B;CAC/B,OAAO,WAAW,eAAe,IAAI,CAAC;AACxC;;;;;;AAOA,SAAgB,kBAEd,MAAS,QAAwB;CACjC,OAAO;EAAE,GAAG;EAAM,GAAG,OAAO;EAAG,GAAG,OAAO;EAAG,GAAG,OAAO;EAAG,GAAG,OAAO;CAAE;AACvE"}
1
+ {"version":3,"file":"contentArea.js","names":[],"sources":["../../../src/renderPptx/utils/contentArea.ts"],"sourcesContent":["import {\n resolveBoxSpacing,\n type BoxSpacingInput,\n} from \"../../shared/boxSpacing.ts\";\nimport { rectPxToIn } from \"../units.ts\";\n\ninterface ContentArea {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/**\n * ノードの padding を考慮したコンテンツ描画領域を計算する。\n * background/border はノード全体の領域 (node.x/y/w/h) に描画し、\n * コンテンツはこの関数で返される領域に描画する。\n */\nexport function getContentArea(node: {\n x: number;\n y: number;\n w: number;\n h: number;\n padding?: BoxSpacingInput;\n}): ContentArea {\n if (node.padding === undefined) {\n return { x: node.x, y: node.y, w: node.w, h: node.h };\n }\n\n const { top, right, bottom, left } = resolveBoxSpacing(node.padding);\n\n return {\n x: node.x + left,\n y: node.y + top,\n w: Math.max(0, node.w - left - right),\n h: Math.max(0, node.h - top - bottom),\n };\n}\n\n/**\n * コンテンツ描画領域を互換用の位置オプション (inch 単位の x/y/w/h)\n * として返す。コンテンツを領域いっぱいに描画する renderer はこれを\n * addShape / addText 等のオプションへ spread するだけでよい。\n */\nexport function getContentAreaIn(\n node: Parameters<typeof getContentArea>[0],\n): ReturnType<typeof rectPxToIn> {\n return rectPxToIn(getContentArea(node));\n}\n\n/**\n * ノードの x/y/w/h を指定領域 (通常はコンテンツ領域) で置き換えた\n * 仮想ノードを返す。padding を解決済みの領域を基準に下位の描画関数へ\n * ノードを渡すために使う。\n */\nexport function withContentBounds<\n T extends { x: number; y: number; w: number; h: number },\n>(node: T, bounds: ContentArea): T {\n return { ...node, x: bounds.x, y: bounds.y, w: bounds.w, h: bounds.h };\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,eAAe,MAMf;CACd,IAAI,KAAK,YAAY,KAAA,GACnB,OAAO;EAAE,GAAG,KAAK;EAAG,GAAG,KAAK;EAAG,GAAG,KAAK;EAAG,GAAG,KAAK;CAAE;CAGtD,MAAM,EAAE,KAAK,OAAO,QAAQ,SAAS,kBAAkB,KAAK,OAAO;CAEnE,OAAO;EACL,GAAG,KAAK,IAAI;EACZ,GAAG,KAAK,IAAI;EACZ,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,KAAK;EACpC,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,MAAM;CACtC;AACF;;;;;;AAOA,SAAgB,iBACd,MAC+B;CAC/B,OAAO,WAAW,eAAe,IAAI,CAAC;AACxC;;;;;;AAOA,SAAgB,kBAEd,MAAS,QAAwB;CACjC,OAAO;EAAE,GAAG;EAAM,GAAG,OAAO;EAAG,GAAG,OAAO;EAAG,GAAG,OAAO;EAAG,GAAG,OAAO;CAAE;AACvE"}
@@ -1,18 +1,8 @@
1
- import { pxToEmu, rectPxToIn } from "../units.js";
1
+ import { pxToEmu } from "../units.js";
2
2
  import * as fs from "fs";
3
3
  import { asEmu, asOoxmlAngle, asOoxmlPercent } from "@pptx-glimpse/document";
4
4
  //#region src/renderPptx/utils/glimpsePicture.ts
5
- const TRANSPARENT_MARKER_STYLE = {
6
- fill: {
7
- color: "FFFFFF",
8
- transparency: 100
9
- },
10
- line: {
11
- color: "FFFFFF",
12
- transparency: 100
13
- }
14
- };
15
- const FALLBACK_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAFgwJ/lU9qJwAAAABJRU5ErkJggg==";
5
+ const FALLBACK_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
16
6
  function dataToBytes(data) {
17
7
  const base64 = data.includes(",") ? data.split(",").at(-1) : data;
18
8
  return new Uint8Array(Buffer.from(base64 ?? "", "base64"));
@@ -110,19 +100,10 @@ function addGlimpsePicture(ctx, content, bytes, options) {
110
100
  rotation: createPictureRotationInput(options?.rotate),
111
101
  crop: sizingResult.crop
112
102
  };
113
- const marker = ctx.buildContext.glimpseTextBoxes.registerPicture(input, {
103
+ ctx.buildContext.pptxAuthoring.registerPicture(input, {
114
104
  name: options?.name,
115
105
  shadow: options?.shadow
116
106
  });
117
- ctx.slide.addShape(ctx.pptx.ShapeType.rect, {
118
- ...rectPxToIn({
119
- ...sizingResult.bounds,
120
- w: Math.max(sizingResult.bounds.w, 1),
121
- h: Math.max(sizingResult.bounds.h, 1)
122
- }),
123
- ...TRANSPARENT_MARKER_STYLE,
124
- objectName: marker
125
- });
126
107
  }
127
108
  //#endregion
128
109
  export { addGlimpsePicture, imageBytesFromSource };
@@ -1 +1 @@
1
- {"version":3,"file":"glimpsePicture.js","names":[],"sources":["../../../src/renderPptx/utils/glimpsePicture.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport {\n asEmu,\n asOoxmlAngle,\n asOoxmlPercent,\n type AddPictureCropInput,\n type AddPictureInput,\n} from \"@pptx-glimpse/document\";\nimport type { ShadowStyle } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToEmu, rectPxToIn } from \"../units.ts\";\n\ntype PictureBoundsPx = { x: number; y: number; w: number; h: number };\ntype ImageSizing = {\n type: \"contain\" | \"cover\" | \"crop\";\n w?: number;\n h?: number;\n x?: number;\n y?: number;\n};\n\nconst TRANSPARENT_MARKER_STYLE = {\n fill: { color: \"FFFFFF\", transparency: 100 },\n line: { color: \"FFFFFF\", transparency: 100 },\n} as const;\nconst FALLBACK_PNG =\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAFgwJ/lU9qJwAAAABJRU5ErkJggg==\";\n\nfunction dataToBytes(data: string): Uint8Array {\n const base64 = data.includes(\",\") ? data.split(\",\").at(-1) : data;\n return new Uint8Array(Buffer.from(base64 ?? \"\", \"base64\"));\n}\n\nfunction isPngOrJpeg(bytes: Uint8Array): boolean {\n const isPng =\n bytes[0] === 0x89 &&\n bytes[1] === 0x50 &&\n bytes[2] === 0x4e &&\n bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;\n return isPng || isJpeg;\n}\n\nfunction supportedOrFallback(bytes: Uint8Array): Uint8Array {\n return isPngOrJpeg(bytes) ? bytes : dataToBytes(FALLBACK_PNG);\n}\n\nexport function imageBytesFromSource(src: string, data?: string): Uint8Array {\n if (data) return supportedOrFallback(dataToBytes(data));\n if (src.startsWith(\"data:\")) return supportedOrFallback(dataToBytes(src));\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\")) {\n return dataToBytes(FALLBACK_PNG);\n }\n try {\n return supportedOrFallback(new Uint8Array(fs.readFileSync(src)));\n } catch {\n return dataToBytes(FALLBACK_PNG);\n }\n}\n\nfunction positiveEmu(valuePx: number) {\n return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));\n}\n\nfunction createPictureRotationInput(rotate: number | undefined) {\n return rotate !== undefined\n ? asOoxmlAngle(Math.round(rotate * 60000))\n : undefined;\n}\n\nfunction pct(value: number) {\n return asOoxmlPercent(Math.round(value));\n}\n\nfunction createSizingCrop(\n sizing: ImageSizing,\n content: PictureBoundsPx,\n): { bounds: PictureBoundsPx; crop?: AddPictureCropInput } {\n const box = {\n x: sizing.x ?? 0,\n y: sizing.y ?? 0,\n w: sizing.w ?? content.w,\n h: sizing.h ?? content.h,\n };\n const img = { w: content.w, h: content.h };\n\n if (sizing.type === \"crop\") {\n const cropBase = { w: content.w, h: content.h };\n return {\n bounds: { ...content, w: box.w, h: box.h },\n crop: {\n left: pct(100000 * (box.x / cropBase.w)),\n right: pct(100000 * ((cropBase.w - (box.x + box.w)) / cropBase.w)),\n top: pct(100000 * (box.y / cropBase.h)),\n bottom: pct(100000 * ((cropBase.h - (box.y + box.h)) / cropBase.h)),\n },\n };\n }\n\n const imgRatio = img.h / img.w;\n const boxRatio = box.h / box.w;\n const isCoverBoxBased = boxRatio > imgRatio;\n const isContainWidthBased = boxRatio > imgRatio;\n const width =\n sizing.type === \"cover\"\n ? isCoverBoxBased\n ? box.h / imgRatio\n : box.w\n : isContainWidthBased\n ? box.w\n : box.h / imgRatio;\n const height =\n sizing.type === \"cover\"\n ? isCoverBoxBased\n ? box.h\n : box.w * imgRatio\n : isContainWidthBased\n ? box.w * imgRatio\n : box.h;\n const horizontal = 100000 * 0.5 * (1 - box.w / width);\n const vertical = 100000 * 0.5 * (1 - box.h / height);\n\n return {\n bounds: { ...content, w: box.w, h: box.h },\n crop: {\n left: pct(horizontal),\n right: pct(horizontal),\n top: pct(vertical),\n bottom: pct(vertical),\n },\n };\n}\n\nexport function addGlimpsePicture(\n ctx: RenderContext,\n content: PictureBoundsPx,\n bytes: Uint8Array,\n options?: {\n rotate?: number;\n sizing?: ImageSizing;\n name?: string;\n shadow?: ShadowStyle;\n },\n): void {\n const sizingResult = options?.sizing\n ? createSizingCrop(options.sizing, content)\n : { bounds: content, crop: undefined };\n const input: AddPictureInput = {\n bytes,\n offsetX: asEmu(Math.round(pxToEmu(content.x))),\n offsetY: asEmu(Math.round(pxToEmu(content.y))),\n width: positiveEmu(sizingResult.bounds.w),\n height: positiveEmu(sizingResult.bounds.h),\n rotation: createPictureRotationInput(options?.rotate),\n crop: sizingResult.crop,\n };\n const marker = ctx.buildContext.glimpseTextBoxes.registerPicture(input, {\n name: options?.name,\n shadow: options?.shadow,\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.rect, {\n ...rectPxToIn({\n ...sizingResult.bounds,\n w: Math.max(sizingResult.bounds.w, 1),\n h: Math.max(sizingResult.bounds.h, 1),\n }),\n ...TRANSPARENT_MARKER_STYLE,\n objectName: marker,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"glimpsePicture.js","names":[],"sources":["../../../src/renderPptx/utils/glimpsePicture.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport {\n asEmu,\n asOoxmlAngle,\n asOoxmlPercent,\n type AddPictureCropInput,\n type AddPictureInput,\n} from \"@pptx-glimpse/document\";\nimport type { ShadowStyle } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToEmu } from \"../units.ts\";\n\ntype PictureBoundsPx = { x: number; y: number; w: number; h: number };\ntype ImageSizing = {\n type: \"contain\" | \"cover\" | \"crop\";\n w?: number;\n h?: number;\n x?: number;\n y?: number;\n};\n\nconst FALLBACK_PNG =\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=\";\n\nfunction dataToBytes(data: string): Uint8Array {\n const base64 = data.includes(\",\") ? data.split(\",\").at(-1) : data;\n return new Uint8Array(Buffer.from(base64 ?? \"\", \"base64\"));\n}\n\nfunction isPngOrJpeg(bytes: Uint8Array): boolean {\n const isPng =\n bytes[0] === 0x89 &&\n bytes[1] === 0x50 &&\n bytes[2] === 0x4e &&\n bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;\n return isPng || isJpeg;\n}\n\nfunction supportedOrFallback(bytes: Uint8Array): Uint8Array {\n return isPngOrJpeg(bytes) ? bytes : dataToBytes(FALLBACK_PNG);\n}\n\nexport function imageBytesFromSource(src: string, data?: string): Uint8Array {\n if (data) return supportedOrFallback(dataToBytes(data));\n if (src.startsWith(\"data:\")) return supportedOrFallback(dataToBytes(src));\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\")) {\n return dataToBytes(FALLBACK_PNG);\n }\n try {\n return supportedOrFallback(new Uint8Array(fs.readFileSync(src)));\n } catch {\n return dataToBytes(FALLBACK_PNG);\n }\n}\n\nfunction positiveEmu(valuePx: number) {\n return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));\n}\n\nfunction createPictureRotationInput(rotate: number | undefined) {\n return rotate !== undefined\n ? asOoxmlAngle(Math.round(rotate * 60000))\n : undefined;\n}\n\nfunction pct(value: number) {\n return asOoxmlPercent(Math.round(value));\n}\n\nfunction createSizingCrop(\n sizing: ImageSizing,\n content: PictureBoundsPx,\n): { bounds: PictureBoundsPx; crop?: AddPictureCropInput } {\n const box = {\n x: sizing.x ?? 0,\n y: sizing.y ?? 0,\n w: sizing.w ?? content.w,\n h: sizing.h ?? content.h,\n };\n const img = { w: content.w, h: content.h };\n\n if (sizing.type === \"crop\") {\n const cropBase = { w: content.w, h: content.h };\n return {\n bounds: { ...content, w: box.w, h: box.h },\n crop: {\n left: pct(100000 * (box.x / cropBase.w)),\n right: pct(100000 * ((cropBase.w - (box.x + box.w)) / cropBase.w)),\n top: pct(100000 * (box.y / cropBase.h)),\n bottom: pct(100000 * ((cropBase.h - (box.y + box.h)) / cropBase.h)),\n },\n };\n }\n\n const imgRatio = img.h / img.w;\n const boxRatio = box.h / box.w;\n const isCoverBoxBased = boxRatio > imgRatio;\n const isContainWidthBased = boxRatio > imgRatio;\n const width =\n sizing.type === \"cover\"\n ? isCoverBoxBased\n ? box.h / imgRatio\n : box.w\n : isContainWidthBased\n ? box.w\n : box.h / imgRatio;\n const height =\n sizing.type === \"cover\"\n ? isCoverBoxBased\n ? box.h\n : box.w * imgRatio\n : isContainWidthBased\n ? box.w * imgRatio\n : box.h;\n const horizontal = 100000 * 0.5 * (1 - box.w / width);\n const vertical = 100000 * 0.5 * (1 - box.h / height);\n\n return {\n bounds: { ...content, w: box.w, h: box.h },\n crop: {\n left: pct(horizontal),\n right: pct(horizontal),\n top: pct(vertical),\n bottom: pct(vertical),\n },\n };\n}\n\nexport function addGlimpsePicture(\n ctx: RenderContext,\n content: PictureBoundsPx,\n bytes: Uint8Array,\n options?: {\n rotate?: number;\n sizing?: ImageSizing;\n name?: string;\n shadow?: ShadowStyle;\n },\n): void {\n const sizingResult = options?.sizing\n ? createSizingCrop(options.sizing, content)\n : { bounds: content, crop: undefined };\n const input: AddPictureInput = {\n bytes,\n offsetX: asEmu(Math.round(pxToEmu(content.x))),\n offsetY: asEmu(Math.round(pxToEmu(content.y))),\n width: positiveEmu(sizingResult.bounds.w),\n height: positiveEmu(sizingResult.bounds.h),\n rotation: createPictureRotationInput(options?.rotate),\n crop: sizingResult.crop,\n };\n ctx.buildContext.pptxAuthoring.registerPicture(input, {\n name: options?.name,\n shadow: options?.shadow,\n });\n}\n"],"mappings":"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"}
@@ -1,18 +1,8 @@
1
- import { pxToEmu, rectPxToIn } from "../units.js";
2
1
  import { parseGradient } from "../../shared/gradient.js";
3
- import { toColorInput } from "../glimpseTextBoxes.js";
2
+ import { pxToEmu } from "../units.js";
3
+ import { toColorInput } from "../pptxAuthoring.js";
4
4
  import { asEmu, asOoxmlAngle } from "@pptx-glimpse/document";
5
5
  //#region src/renderPptx/utils/glimpseShape.ts
6
- const TRANSPARENT_MARKER_STYLE = {
7
- fill: {
8
- color: "FFFFFF",
9
- transparency: 100
10
- },
11
- line: {
12
- color: "FFFFFF",
13
- transparency: 100
14
- }
15
- };
16
6
  function positiveEmu(valuePx) {
17
7
  return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));
18
8
  }
@@ -68,20 +58,11 @@ function arrowEndpoint(type) {
68
58
  length: "med"
69
59
  };
70
60
  }
71
- function addGlimpseShape(ctx, input, markerBounds, options) {
72
- const marker = ctx.buildContext.glimpseTextBoxes.registerShape(input, {
61
+ function addGlimpseShape(ctx, input, bounds, options) {
62
+ ctx.buildContext.pptxAuthoring.registerShape(input, {
73
63
  ...options,
74
- zeroWidth: markerBounds.w === 0,
75
- zeroHeight: markerBounds.h === 0
76
- });
77
- ctx.slide.addShape(ctx.pptx.ShapeType.rect, {
78
- ...rectPxToIn({
79
- ...markerBounds,
80
- w: Math.max(markerBounds.w, 1),
81
- h: Math.max(markerBounds.h, 1)
82
- }),
83
- ...TRANSPARENT_MARKER_STYLE,
84
- objectName: marker
64
+ zeroWidth: bounds.w === 0,
65
+ zeroHeight: bounds.h === 0
85
66
  });
86
67
  }
87
68
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"glimpseShape.js","names":[],"sources":["../../../src/renderPptx/utils/glimpseShape.ts"],"sourcesContent":["import {\n asEmu,\n asOoxmlAngle,\n type AddShapeFillInput,\n type AddShapeInput,\n type AddShapeOutlineInput,\n type SourceArrowEndpoint,\n type SourceDashStyle,\n} from \"@pptx-glimpse/document\";\nimport type { BorderStyle, ShadowStyle, TextGlow } from \"../../types.ts\";\nimport { parseGradient } from \"../../shared/gradient.ts\";\nimport {\n toColorInput,\n type CustomGeometryXmlInput,\n} from \"../glimpseTextBoxes.ts\";\nimport { pxToEmu, rectPxToIn } from \"../units.ts\";\nimport type { RenderContext } from \"../types.ts\";\n\ntype ShapeBoundsPx = { x: number; y: number; w: number; h: number };\n\nexport type GlimpseShapeStyleOptions = {\n fillColor?: string;\n fillOpacity?: number;\n backgroundGradient?: string;\n outlineGradient?: string;\n outlineOpacity?: number;\n glow?: TextGlow;\n shadow?: ShadowStyle;\n rectRadius?: number;\n dashType?: BorderStyle[\"dashType\"];\n flipH?: boolean;\n flipV?: boolean;\n zeroWidth?: boolean;\n zeroHeight?: boolean;\n customGeometry?: CustomGeometryXmlInput;\n};\n\nconst TRANSPARENT_MARKER_STYLE = {\n fill: { color: \"FFFFFF\", transparency: 100 },\n line: { color: \"FFFFFF\", transparency: 100 },\n} as const;\n\nfunction positiveEmu(valuePx: number) {\n return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));\n}\n\nexport function createShapeBoundsInput(bounds: ShapeBoundsPx) {\n return {\n offsetX: asEmu(Math.round(pxToEmu(bounds.x))),\n offsetY: asEmu(Math.round(pxToEmu(bounds.y))),\n width: positiveEmu(bounds.w),\n height: positiveEmu(bounds.h),\n };\n}\n\nexport function createShapeRotationInput(rotate: number | undefined) {\n return rotate !== undefined\n ? asOoxmlAngle(Math.round(rotate * 60000))\n : undefined;\n}\n\nexport function solidShapeFill(\n color: string | undefined,\n): AddShapeFillInput | undefined {\n const clean = toColorInput(color);\n return clean ? { kind: \"solid\", color: clean } : undefined;\n}\n\nexport function noneShapeFill(): AddShapeFillInput {\n return { kind: \"none\" };\n}\n\nfunction gradientFallbackColor(value: string): string | undefined {\n const gradient = parseGradient(value);\n return gradient?.value.stops[0]?.color;\n}\n\nexport function backgroundShapeFill(\n backgroundColor: string | undefined,\n backgroundGradient: string | undefined,\n): AddShapeFillInput | undefined {\n if (backgroundGradient) {\n const color = gradientFallbackColor(backgroundGradient);\n return solidShapeFill(color);\n }\n return solidShapeFill(backgroundColor);\n}\n\nfunction dashStyle(\n dashType: BorderStyle[\"dashType\"],\n): SourceDashStyle | undefined {\n return dashType as SourceDashStyle | undefined;\n}\n\nexport function shapeOutline(\n border: BorderStyle | undefined,\n fallbackColor?: string,\n): AddShapeOutlineInput | undefined {\n if (!border) return undefined;\n const fill = solidShapeFill(border.color ?? fallbackColor);\n if (\n border.width === undefined &&\n fill === undefined &&\n border.dashType === undefined\n ) {\n return undefined;\n }\n return {\n width:\n border.width !== undefined\n ? asEmu(Math.round(pxToEmu(border.width)))\n : undefined,\n fill,\n dash: dashStyle(border.dashType),\n };\n}\n\nexport function noShapeOutline(): AddShapeOutlineInput {\n return { fill: noneShapeFill() };\n}\n\nexport function arrowEndpoint(\n type:\n \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\" | undefined,\n): SourceArrowEndpoint | undefined {\n if (type === undefined || type === \"none\") return undefined;\n return { type, width: \"med\", length: \"med\" };\n}\n\nexport function addGlimpseShape(\n ctx: RenderContext,\n input: AddShapeInput,\n markerBounds: ShapeBoundsPx,\n options?: GlimpseShapeStyleOptions & { name?: string },\n): void {\n const marker = ctx.buildContext.glimpseTextBoxes.registerShape(input, {\n ...options,\n zeroWidth: markerBounds.w === 0,\n zeroHeight: markerBounds.h === 0,\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.rect, {\n ...rectPxToIn({\n ...markerBounds,\n w: Math.max(markerBounds.w, 1),\n h: Math.max(markerBounds.h, 1),\n }),\n ...TRANSPARENT_MARKER_STYLE,\n objectName: marker,\n });\n}\n"],"mappings":";;;;;AAqCA,MAAM,2BAA2B;CAC/B,MAAM;EAAE,OAAO;EAAU,cAAc;CAAI;CAC3C,MAAM;EAAE,OAAO;EAAU,cAAc;CAAI;AAC7C;AAEA,SAAS,YAAY,SAAiB;CACpC,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;AACxD;AAEA,SAAgB,uBAAuB,QAAuB;CAC5D,OAAO;EACL,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,OAAO,YAAY,OAAO,CAAC;EAC3B,QAAQ,YAAY,OAAO,CAAC;CAC9B;AACF;AAEA,SAAgB,yBAAyB,QAA4B;CACnE,OAAO,WAAW,KAAA,IACd,aAAa,KAAK,MAAM,SAAS,GAAK,CAAC,IACvC,KAAA;AACN;AAEA,SAAgB,eACd,OAC+B;CAC/B,MAAM,QAAQ,aAAa,KAAK;CAChC,OAAO,QAAQ;EAAE,MAAM;EAAS,OAAO;CAAM,IAAI,KAAA;AACnD;AAEA,SAAgB,gBAAmC;CACjD,OAAO,EAAE,MAAM,OAAO;AACxB;AAEA,SAAS,sBAAsB,OAAmC;CAEhE,OADiB,cAAc,KACjB,CAAC,EAAE,MAAM,MAAM,EAAE,EAAE;AACnC;AAEA,SAAgB,oBACd,iBACA,oBAC+B;CAC/B,IAAI,oBAEF,OAAO,eADO,sBAAsB,kBACV,CAAC;CAE7B,OAAO,eAAe,eAAe;AACvC;AAEA,SAAS,UACP,UAC6B;CAC7B,OAAO;AACT;AAEA,SAAgB,aACd,QACA,eACkC;CAClC,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,MAAM,OAAO,eAAe,OAAO,SAAS,aAAa;CACzD,IACE,OAAO,UAAU,KAAA,KACjB,SAAS,KAAA,KACT,OAAO,aAAa,KAAA,GAEpB;CAEF,OAAO;EACL,OACE,OAAO,UAAU,KAAA,IACb,MAAM,KAAK,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC,IACvC,KAAA;EACN;EACA,MAAM,UAAU,OAAO,QAAQ;CACjC;AACF;AAEA,SAAgB,iBAAuC;CACrD,OAAO,EAAE,MAAM,cAAc,EAAE;AACjC;AAEA,SAAgB,cACd,MAEiC;CACjC,IAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,OAAO,KAAA;CAClD,OAAO;EAAE;EAAM,OAAO;EAAO,QAAQ;CAAM;AAC7C;AAEA,SAAgB,gBACd,KACA,OACA,cACA,SACM;CACN,MAAM,SAAS,IAAI,aAAa,iBAAiB,cAAc,OAAO;EACpE,GAAG;EACH,WAAW,aAAa,MAAM;EAC9B,YAAY,aAAa,MAAM;CACjC,CAAC;CACD,IAAI,MAAM,SAAS,IAAI,KAAK,UAAU,MAAM;EAC1C,GAAG,WAAW;GACZ,GAAG;GACH,GAAG,KAAK,IAAI,aAAa,GAAG,CAAC;GAC7B,GAAG,KAAK,IAAI,aAAa,GAAG,CAAC;EAC/B,CAAC;EACD,GAAG;EACH,YAAY;CACd,CAAC;AACH"}
1
+ {"version":3,"file":"glimpseShape.js","names":[],"sources":["../../../src/renderPptx/utils/glimpseShape.ts"],"sourcesContent":["import {\n asEmu,\n asOoxmlAngle,\n type AddShapeFillInput,\n type AddShapeInput,\n type AddShapeOutlineInput,\n type SourceArrowEndpoint,\n type SourceDashStyle,\n} from \"@pptx-glimpse/document\";\nimport type { BorderStyle, ShadowStyle, TextGlow } from \"../../types.ts\";\nimport { parseGradient } from \"../../shared/gradient.ts\";\nimport { toColorInput, type CustomGeometryXmlInput } from \"../pptxAuthoring.ts\";\nimport { pxToEmu } from \"../units.ts\";\nimport type { RenderContext } from \"../types.ts\";\n\ntype ShapeBoundsPx = { x: number; y: number; w: number; h: number };\n\nexport type GlimpseShapeStyleOptions = {\n fillColor?: string;\n fillOpacity?: number;\n backgroundGradient?: string;\n outlineGradient?: string;\n outlineOpacity?: number;\n glow?: TextGlow;\n shadow?: ShadowStyle;\n rectRadius?: number;\n dashType?: BorderStyle[\"dashType\"];\n flipH?: boolean;\n flipV?: boolean;\n zeroWidth?: boolean;\n zeroHeight?: boolean;\n customGeometry?: CustomGeometryXmlInput;\n};\n\nfunction positiveEmu(valuePx: number) {\n return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));\n}\n\nexport function createShapeBoundsInput(bounds: ShapeBoundsPx) {\n return {\n offsetX: asEmu(Math.round(pxToEmu(bounds.x))),\n offsetY: asEmu(Math.round(pxToEmu(bounds.y))),\n width: positiveEmu(bounds.w),\n height: positiveEmu(bounds.h),\n };\n}\n\nexport function createShapeRotationInput(rotate: number | undefined) {\n return rotate !== undefined\n ? asOoxmlAngle(Math.round(rotate * 60000))\n : undefined;\n}\n\nexport function solidShapeFill(\n color: string | undefined,\n): AddShapeFillInput | undefined {\n const clean = toColorInput(color);\n return clean ? { kind: \"solid\", color: clean } : undefined;\n}\n\nexport function noneShapeFill(): AddShapeFillInput {\n return { kind: \"none\" };\n}\n\nfunction gradientFallbackColor(value: string): string | undefined {\n const gradient = parseGradient(value);\n return gradient?.value.stops[0]?.color;\n}\n\nexport function backgroundShapeFill(\n backgroundColor: string | undefined,\n backgroundGradient: string | undefined,\n): AddShapeFillInput | undefined {\n if (backgroundGradient) {\n const color = gradientFallbackColor(backgroundGradient);\n return solidShapeFill(color);\n }\n return solidShapeFill(backgroundColor);\n}\n\nfunction dashStyle(\n dashType: BorderStyle[\"dashType\"],\n): SourceDashStyle | undefined {\n return dashType;\n}\n\nexport function shapeOutline(\n border: BorderStyle | undefined,\n fallbackColor?: string,\n): AddShapeOutlineInput | undefined {\n if (!border) return undefined;\n const fill = solidShapeFill(border.color ?? fallbackColor);\n if (\n border.width === undefined &&\n fill === undefined &&\n border.dashType === undefined\n ) {\n return undefined;\n }\n return {\n width:\n border.width !== undefined\n ? asEmu(Math.round(pxToEmu(border.width)))\n : undefined,\n fill,\n dash: dashStyle(border.dashType),\n };\n}\n\nexport function noShapeOutline(): AddShapeOutlineInput {\n return { fill: noneShapeFill() };\n}\n\nexport function arrowEndpoint(\n type:\n \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\" | undefined,\n): SourceArrowEndpoint | undefined {\n if (type === undefined || type === \"none\") return undefined;\n return { type, width: \"med\", length: \"med\" };\n}\n\nexport function addGlimpseShape(\n ctx: RenderContext,\n input: AddShapeInput,\n bounds: ShapeBoundsPx,\n options?: GlimpseShapeStyleOptions & { name?: string },\n): void {\n ctx.buildContext.pptxAuthoring.registerShape(input, {\n ...options,\n zeroWidth: bounds.w === 0,\n zeroHeight: bounds.h === 0,\n });\n}\n"],"mappings":";;;;;AAkCA,SAAS,YAAY,SAAiB;CACpC,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;AACxD;AAEA,SAAgB,uBAAuB,QAAuB;CAC5D,OAAO;EACL,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,OAAO,YAAY,OAAO,CAAC;EAC3B,QAAQ,YAAY,OAAO,CAAC;CAC9B;AACF;AAEA,SAAgB,yBAAyB,QAA4B;CACnE,OAAO,WAAW,KAAA,IACd,aAAa,KAAK,MAAM,SAAS,GAAK,CAAC,IACvC,KAAA;AACN;AAEA,SAAgB,eACd,OAC+B;CAC/B,MAAM,QAAQ,aAAa,KAAK;CAChC,OAAO,QAAQ;EAAE,MAAM;EAAS,OAAO;CAAM,IAAI,KAAA;AACnD;AAEA,SAAgB,gBAAmC;CACjD,OAAO,EAAE,MAAM,OAAO;AACxB;AAEA,SAAS,sBAAsB,OAAmC;CAEhE,OADiB,cAAc,KACjB,CAAC,EAAE,MAAM,MAAM,EAAE,EAAE;AACnC;AAEA,SAAgB,oBACd,iBACA,oBAC+B;CAC/B,IAAI,oBAEF,OAAO,eADO,sBAAsB,kBACV,CAAC;CAE7B,OAAO,eAAe,eAAe;AACvC;AAEA,SAAS,UACP,UAC6B;CAC7B,OAAO;AACT;AAEA,SAAgB,aACd,QACA,eACkC;CAClC,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,MAAM,OAAO,eAAe,OAAO,SAAS,aAAa;CACzD,IACE,OAAO,UAAU,KAAA,KACjB,SAAS,KAAA,KACT,OAAO,aAAa,KAAA,GAEpB;CAEF,OAAO;EACL,OACE,OAAO,UAAU,KAAA,IACb,MAAM,KAAK,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC,IACvC,KAAA;EACN;EACA,MAAM,UAAU,OAAO,QAAQ;CACjC;AACF;AAEA,SAAgB,iBAAuC;CACrD,OAAO,EAAE,MAAM,cAAc,EAAE;AACjC;AAEA,SAAgB,cACd,MAEiC;CACjC,IAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,OAAO,KAAA;CAClD,OAAO;EAAE;EAAM,OAAO;EAAO,QAAQ;CAAM;AAC7C;AAEA,SAAgB,gBACd,KACA,OACA,QACA,SACM;CACN,IAAI,aAAa,cAAc,cAAc,OAAO;EAClD,GAAG;EACH,WAAW,OAAO,MAAM;EACxB,YAAY,OAAO,MAAM;CAC3B,CAAC;AACH"}
@@ -1,17 +1,7 @@
1
- import { pxToEmu, pxToPt, rectPxToIn } from "../units.js";
2
- import { toColorInput } from "../glimpseTextBoxes.js";
3
- import { asEmu, asHundredthPt, asOoxmlAngle, asPt } from "@pptx-glimpse/document";
1
+ import { pxToEmu, pxToPt } from "../units.js";
2
+ import { toColorInput } from "../pptxAuthoring.js";
3
+ import { asEmu, asHundredthPt, asOoxmlAngle, asOoxmlPercent, asPt } from "@pptx-glimpse/document";
4
4
  //#region src/renderPptx/utils/glimpseTextBox.ts
5
- const TRANSPARENT_MARKER_STYLE = {
6
- fill: {
7
- color: "FFFFFF",
8
- transparency: 100
9
- },
10
- line: {
11
- color: "FFFFFF",
12
- transparency: 100
13
- }
14
- };
15
5
  function toUnderlineInput(underline) {
16
6
  if (underline === void 0 || underline === false) return void 0;
17
7
  if (underline === true) return true;
@@ -29,7 +19,13 @@ function createGlimpseRunProperties(style) {
29
19
  italic: style.italic,
30
20
  underline: toUnderlineInput(style.underline),
31
21
  strike: style.strike,
32
- baseline: style.subscript ? "subscript" : style.superscript ? "superscript" : void 0,
22
+ baseline: style.subscript ? {
23
+ type: "percent",
24
+ value: asOoxmlPercent(-4e4)
25
+ } : style.superscript ? {
26
+ type: "percent",
27
+ value: asOoxmlPercent(3e4)
28
+ } : void 0,
33
29
  highlight: toColorInput(style.highlight)
34
30
  };
35
31
  }
@@ -62,65 +58,50 @@ function textBoxInput(bounds, options, paragraphs) {
62
58
  marginRight: margin,
63
59
  marginTop: margin,
64
60
  marginBottom: margin,
65
- anchor: options.valign
61
+ anchor: options.valign,
62
+ autoFit: options.autoFit ? "shape" : void 0
66
63
  },
67
64
  paragraphs
68
65
  };
69
66
  }
70
- function withShapeAutofit(xml) {
71
- const selfClosing = xml.replace(/<a:bodyPr\b([^>]*)\/>/, (_match, attrs) => `<a:bodyPr${attrs}><a:spAutoFit/></a:bodyPr>`);
72
- if (selfClosing !== xml) return selfClosing;
73
- return xml.replace(/(<a:bodyPr\b[^>]*>)([\s\S]*?)(<\/a:bodyPr>)/, (_match, open, body, close) => {
74
- if (body.includes("<a:spAutoFit/>")) return `${open}${body}${close}`;
75
- return `${open}<a:spAutoFit/>${body}${close}`;
76
- });
77
- }
78
- function composeXmlTransform(autoFit, transform) {
79
- if (!autoFit) return transform;
80
- return (xml) => {
81
- const fitted = withShapeAutofit(xml);
82
- return transform ? transform(fitted) : fitted;
67
+ function withListProperties(paragraphs, options) {
68
+ if (!options.bullet) return paragraphs;
69
+ const bullet = options.bullet.kind === "bullet" ? {
70
+ type: "character",
71
+ character: "",
72
+ size: asOoxmlPercent(1e5)
73
+ } : {
74
+ type: "auto-number",
75
+ scheme: isSupportedAutoNumScheme(options.bullet.scheme) ? options.bullet.scheme : "arabicPeriod",
76
+ startAt: options.bullet.startAt ?? 1,
77
+ fontFace: "+mj-lt",
78
+ size: asOoxmlPercent(1e5)
83
79
  };
80
+ return paragraphs.map((paragraph) => ({
81
+ ...paragraph,
82
+ properties: {
83
+ ...paragraph.properties,
84
+ marginLeft: asEmu(342900),
85
+ indent: asEmu(-342900),
86
+ lineSpacing: {
87
+ type: "percent",
88
+ value: asOoxmlPercent(Math.round((options.lineHeight ?? 1.3) * 1e5))
89
+ },
90
+ bullet
91
+ }
92
+ }));
84
93
  }
85
94
  function addGlimpseTextBox(ctx, bounds, options = {}) {
86
- const paragraphs = options.paragraphs ?? createGlimpseParagraphs(options.text ?? "", options, {
95
+ const paragraphs = withListProperties(options.paragraphs ?? createGlimpseParagraphs(options.text ?? "", options, {
87
96
  align: options.align,
88
97
  lineHeight: options.lineHeight
89
- });
90
- const marker = ctx.buildContext.glimpseTextBoxes.registerTextBox(textBoxInput(bounds, options, paragraphs), {
91
- hyperlinks: options.hyperlinks,
92
- xmlTransform: composeXmlTransform(options.autoFit, options.xmlTransform)
93
- });
94
- ctx.slide.addShape(ctx.pptx.ShapeType.rect, {
95
- ...rectPxToIn({
96
- ...bounds,
97
- w: Math.max(bounds.w, 1),
98
- h: Math.max(bounds.h, 1)
99
- }),
100
- ...TRANSPARENT_MARKER_STYLE,
101
- objectName: marker
102
- });
103
- }
104
- function bulletXml(options) {
105
- if (options.kind === "bullet") return "<a:buSzPct val=\"100000\"/><a:buChar char=\"&#x2022;\"/>";
106
- return `<a:buSzPct val="100000"/><a:buFont typeface="+mj-lt"/><a:buAutoNum type="${isSupportedAutoNumScheme(options.scheme) ? options.scheme : "arabicPeriod"}" startAt="${options.startAt ?? 1}"/>`;
98
+ }), options);
99
+ ctx.buildContext.pptxAuthoring.registerTextBox(textBoxInput(bounds, options, paragraphs), { hyperlinks: options.hyperlinks });
107
100
  }
108
101
  function isSupportedAutoNumScheme(value) {
109
102
  return value === "arabicPeriod" || value === "arabicParenR" || value === "romanUcPeriod" || value === "romanLcPeriod" || value === "alphaUcPeriod" || value === "alphaLcPeriod" || value === "alphaLcParenR" || value === "alphaUcParenR" || value === "arabicPlain";
110
103
  }
111
- function withoutParagraphIndentAttrs(attrs) {
112
- return attrs.replace(/\s(?:indent|marL)="[^"]*"/g, "");
113
- }
114
- function listBulletXmlTransform(options) {
115
- const paragraphBody = `${`<a:lnSpc><a:spcPct val="${Math.round((options.lineHeight ?? 1.3) * 1e5)}"/></a:lnSpc>`}${bulletXml(options)}`;
116
- return (xml) => {
117
- return xml.replace(/<a:pPr([^>]*)\/>/g, (_match, attrs) => `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL="342900" indent="-342900">${paragraphBody}</a:pPr>`).replace(/<a:pPr([^>]*)>([\s\S]*?)<\/a:pPr>/g, (match, attrs, body) => {
118
- if (!body.includes("<a:buNone/>")) return match;
119
- return `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL="342900" indent="-342900">${body.replace(/(?:<a:lnSpc>[\s\S]*?<\/a:lnSpc>)?<a:buNone\/>/, paragraphBody)}</a:pPr>`;
120
- });
121
- };
122
- }
123
104
  //#endregion
124
- export { addGlimpseTextBox, createGlimpseParagraphs, createGlimpseRunProperties, listBulletXmlTransform };
105
+ export { addGlimpseTextBox, createGlimpseParagraphs, createGlimpseRunProperties };
125
106
 
126
107
  //# sourceMappingURL=glimpseTextBox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"glimpseTextBox.js","names":[],"sources":["../../../src/renderPptx/utils/glimpseTextBox.ts"],"sourcesContent":["import {\n asEmu,\n asHundredthPt,\n asOoxmlAngle,\n asPt,\n type AddTextBoxInput,\n type AddTextBoxParagraphInput,\n type AddTextBoxRunPropertiesInput,\n type SourceAutoNumScheme,\n} from \"@pptx-glimpse/document\";\nimport type { Underline } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToEmu, pxToPt, rectPxToIn } from \"../units.ts\";\nimport { toColorInput } from \"../glimpseTextBoxes.ts\";\n\ntype TextBoundsPx = { x: number; y: number; w: number; h: number };\n\ntype TextBoxVerticalAlign = \"top\" | \"middle\" | \"bottom\";\n\nexport type GlimpseTextRunStyle = {\n fontSize?: number;\n fontFace?: string;\n color?: string;\n bold?: boolean;\n italic?: boolean;\n underline?: Underline;\n strike?: boolean;\n subscript?: boolean;\n superscript?: boolean;\n highlight?: string;\n};\n\nexport type GlimpseTextBoxOptions = GlimpseTextRunStyle & {\n text?: string;\n align?: \"left\" | \"center\" | \"right\";\n valign?: TextBoxVerticalAlign;\n lineHeight?: number;\n rotate?: number;\n autoFit?: boolean;\n margin?: number;\n};\n\nexport type ListBulletOptions =\n | { kind: \"bullet\" }\n | {\n kind: \"number\";\n scheme?: string;\n startAt?: number;\n };\n\nconst TRANSPARENT_MARKER_STYLE = {\n fill: { color: \"FFFFFF\", transparency: 100 },\n line: { color: \"FFFFFF\", transparency: 100 },\n} as const;\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\nexport function createGlimpseRunProperties(\n style: GlimpseTextRunStyle,\n): AddTextBoxRunPropertiesInput {\n return {\n fontFace: style.fontFace ?? \"Noto Sans JP\",\n fontSize: asPt(pxToPt(style.fontSize ?? 24)),\n color: toColorInput(style.color),\n bold: style.bold,\n italic: style.italic,\n underline: toUnderlineInput(style.underline),\n strike: style.strike,\n baseline: style.subscript\n ? \"subscript\"\n : style.superscript\n ? \"superscript\"\n : undefined,\n highlight: toColorInput(style.highlight),\n };\n}\n\nfunction createGlimpseParagraph(\n text: string,\n style: GlimpseTextRunStyle,\n options: Pick<GlimpseTextBoxOptions, \"align\" | \"lineHeight\"> = {},\n): AddTextBoxParagraphInput {\n const fontSizePx = style.fontSize ?? 24;\n return {\n properties: {\n align: options.align,\n lineSpacing:\n options.lineHeight !== undefined\n ? asHundredthPt(\n Math.round(pxToPt(fontSizePx * options.lineHeight) * 100),\n )\n : undefined,\n },\n runs: [{ text, properties: createGlimpseRunProperties(style) }],\n };\n}\n\nexport function createGlimpseParagraphs(\n text: string,\n style: GlimpseTextRunStyle,\n options: Pick<GlimpseTextBoxOptions, \"align\" | \"lineHeight\"> = {},\n): AddTextBoxParagraphInput[] {\n return text\n .replace(/\\r*\\n/g, \"\\n\")\n .split(\"\\n\")\n .map((line) => createGlimpseParagraph(line, style, options));\n}\n\nfunction textBoxInput(\n bounds: TextBoundsPx,\n options: GlimpseTextBoxOptions,\n paragraphs: readonly AddTextBoxParagraphInput[],\n): AddTextBoxInput {\n const margin =\n options.margin !== undefined\n ? asEmu(Math.round(pxToEmu(options.margin)))\n : undefined;\n return {\n offsetX: asEmu(Math.round(pxToEmu(bounds.x))),\n offsetY: asEmu(Math.round(pxToEmu(bounds.y))),\n width: asEmu(Math.max(1, Math.round(pxToEmu(bounds.w)))),\n height: asEmu(Math.max(1, Math.round(pxToEmu(bounds.h)))),\n rotation:\n options.rotate !== undefined\n ? asOoxmlAngle(Math.round(options.rotate * 60000))\n : undefined,\n body: {\n marginLeft: margin,\n marginRight: margin,\n marginTop: margin,\n marginBottom: margin,\n anchor: options.valign,\n },\n paragraphs,\n };\n}\n\nfunction withShapeAutofit(xml: string): string {\n const selfClosing = xml.replace(\n /<a:bodyPr\\b([^>]*)\\/>/,\n (_match, attrs: string) => `<a:bodyPr${attrs}><a:spAutoFit/></a:bodyPr>`,\n );\n if (selfClosing !== xml) return selfClosing;\n return xml.replace(\n /(<a:bodyPr\\b[^>]*>)([\\s\\S]*?)(<\\/a:bodyPr>)/,\n (_match, open: string, body: string, close: string) => {\n if (body.includes(\"<a:spAutoFit/>\")) return `${open}${body}${close}`;\n return `${open}<a:spAutoFit/>${body}${close}`;\n },\n );\n}\n\nfunction composeXmlTransform(\n autoFit: boolean | undefined,\n transform: ((xml: string) => string) | undefined,\n) {\n if (!autoFit) return transform;\n return (xml: string) => {\n const fitted = withShapeAutofit(xml);\n return transform ? transform(fitted) : fitted;\n };\n}\n\nexport function addGlimpseTextBox(\n ctx: RenderContext,\n bounds: TextBoundsPx,\n options: GlimpseTextBoxOptions & {\n paragraphs?: readonly AddTextBoxParagraphInput[];\n hyperlinks?: readonly (string | undefined)[];\n xmlTransform?: (xml: string) => string;\n } = {},\n): void {\n const paragraphs =\n options.paragraphs ??\n createGlimpseParagraphs(options.text ?? \"\", options, {\n align: options.align,\n lineHeight: options.lineHeight,\n });\n const marker = ctx.buildContext.glimpseTextBoxes.registerTextBox(\n textBoxInput(bounds, options, paragraphs),\n {\n hyperlinks: options.hyperlinks,\n xmlTransform: composeXmlTransform(options.autoFit, options.xmlTransform),\n },\n );\n ctx.slide.addShape(ctx.pptx.ShapeType.rect, {\n ...rectPxToIn({\n ...bounds,\n w: Math.max(bounds.w, 1),\n h: Math.max(bounds.h, 1),\n }),\n ...TRANSPARENT_MARKER_STYLE,\n objectName: marker,\n });\n}\n\nfunction bulletXml(options: ListBulletOptions): string {\n if (options.kind === \"bullet\") {\n return '<a:buSzPct val=\"100000\"/><a:buChar char=\"&#x2022;\"/>';\n }\n const scheme = isSupportedAutoNumScheme(options.scheme)\n ? options.scheme\n : \"arabicPeriod\";\n return `<a:buSzPct val=\"100000\"/><a:buFont typeface=\"+mj-lt\"/><a:buAutoNum type=\"${scheme}\" startAt=\"${\n options.startAt ?? 1\n }\"/>`;\n}\n\nfunction isSupportedAutoNumScheme(\n value: string | undefined,\n): value is SourceAutoNumScheme {\n return (\n value === \"arabicPeriod\" ||\n value === \"arabicParenR\" ||\n value === \"romanUcPeriod\" ||\n value === \"romanLcPeriod\" ||\n value === \"alphaUcPeriod\" ||\n value === \"alphaLcPeriod\" ||\n value === \"alphaLcParenR\" ||\n value === \"alphaUcParenR\" ||\n value === \"arabicPlain\"\n );\n}\n\nfunction withoutParagraphIndentAttrs(attrs: string): string {\n return attrs.replace(/\\s(?:indent|marL)=\"[^\"]*\"/g, \"\");\n}\n\nexport function listBulletXmlTransform(\n options: ListBulletOptions & { lineHeight?: number },\n) {\n const lineSpacing = `<a:lnSpc><a:spcPct val=\"${Math.round(\n (options.lineHeight ?? 1.3) * 100000,\n )}\"/></a:lnSpc>`;\n const paragraphBody = `${lineSpacing}${bulletXml(options)}`;\n return (xml: string): string => {\n const expanded = xml.replace(\n /<a:pPr([^>]*)\\/>/g,\n (_match, attrs: string) =>\n `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL=\"342900\" indent=\"-342900\">${paragraphBody}</a:pPr>`,\n );\n return expanded.replace(\n /<a:pPr([^>]*)>([\\s\\S]*?)<\\/a:pPr>/g,\n (match: string, attrs: string, body: string) => {\n if (!body.includes(\"<a:buNone/>\")) return match;\n return `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL=\"342900\" indent=\"-342900\">${body.replace(\n /(?:<a:lnSpc>[\\s\\S]*?<\\/a:lnSpc>)?<a:buNone\\/>/,\n paragraphBody,\n )}</a:pPr>`;\n },\n );\n };\n}\n\nexport function listLineSpacingXmlTransform(options: { lineHeight?: number }) {\n const lineSpacing = `<a:lnSpc><a:spcPct val=\"${Math.round(\n (options.lineHeight ?? 1.3) * 100000,\n )}\"/></a:lnSpc>`;\n const paragraphBody = `${lineSpacing}<a:buNone/>`;\n return (xml: string): string => {\n const expanded = xml.replace(\n /<a:pPr([^>]*)\\/>/g,\n (_match, attrs: string) => `<a:pPr${attrs}>${paragraphBody}</a:pPr>`,\n );\n return expanded.replace(\n /<a:pPr([^>]*)>([\\s\\S]*?)<\\/a:pPr>/g,\n (_match: string, attrs: string, body: string) => {\n const nextBody = body\n .replace(/<a:lnSpc>[\\s\\S]*?<\\/a:lnSpc>/, \"\")\n .replace(/<a:buNone\\/>/, \"\");\n return `<a:pPr${attrs}>${paragraphBody}${nextBody}</a:pPr>`;\n },\n );\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"glimpseTextBox.js","names":[],"sources":["../../../src/renderPptx/utils/glimpseTextBox.ts"],"sourcesContent":["import {\n asEmu,\n asHundredthPt,\n asOoxmlAngle,\n asOoxmlPercent,\n asPt,\n type AddTextBoxInput,\n type AddTextBoxParagraphInput,\n type AddTextBoxRunPropertiesInput,\n type SourceAutoNumScheme,\n} from \"@pptx-glimpse/document\";\nimport type { Underline } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToEmu, pxToPt } from \"../units.ts\";\nimport { toColorInput } from \"../pptxAuthoring.ts\";\n\ntype TextBoundsPx = { x: number; y: number; w: number; h: number };\n\ntype TextBoxVerticalAlign = \"top\" | \"middle\" | \"bottom\";\n\nexport type GlimpseTextRunStyle = {\n fontSize?: number;\n fontFace?: string;\n color?: string;\n bold?: boolean;\n italic?: boolean;\n underline?: Underline;\n strike?: boolean;\n subscript?: boolean;\n superscript?: boolean;\n highlight?: string;\n};\n\nexport type GlimpseTextBoxOptions = GlimpseTextRunStyle & {\n text?: string;\n align?: \"left\" | \"center\" | \"right\";\n valign?: TextBoxVerticalAlign;\n lineHeight?: number;\n rotate?: number;\n autoFit?: boolean;\n margin?: number;\n bullet?: ListBulletOptions;\n};\n\nexport type ListBulletOptions =\n | { kind: \"bullet\" }\n | {\n kind: \"number\";\n scheme?: string;\n startAt?: number;\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\nexport function createGlimpseRunProperties(\n style: GlimpseTextRunStyle,\n): AddTextBoxRunPropertiesInput {\n return {\n fontFace: style.fontFace ?? \"Noto Sans JP\",\n fontSize: asPt(pxToPt(style.fontSize ?? 24)),\n color: toColorInput(style.color),\n bold: style.bold,\n italic: style.italic,\n underline: toUnderlineInput(style.underline),\n strike: style.strike,\n baseline: style.subscript\n ? { type: \"percent\", value: asOoxmlPercent(-40000) }\n : style.superscript\n ? { type: \"percent\", value: asOoxmlPercent(30000) }\n : undefined,\n highlight: toColorInput(style.highlight),\n };\n}\n\nfunction createGlimpseParagraph(\n text: string,\n style: GlimpseTextRunStyle,\n options: Pick<GlimpseTextBoxOptions, \"align\" | \"lineHeight\"> = {},\n): AddTextBoxParagraphInput {\n const fontSizePx = style.fontSize ?? 24;\n return {\n properties: {\n align: options.align,\n lineSpacing:\n options.lineHeight !== undefined\n ? asHundredthPt(\n Math.round(pxToPt(fontSizePx * options.lineHeight) * 100),\n )\n : undefined,\n },\n runs: [{ text, properties: createGlimpseRunProperties(style) }],\n };\n}\n\nexport function createGlimpseParagraphs(\n text: string,\n style: GlimpseTextRunStyle,\n options: Pick<GlimpseTextBoxOptions, \"align\" | \"lineHeight\"> = {},\n): AddTextBoxParagraphInput[] {\n return text\n .replace(/\\r*\\n/g, \"\\n\")\n .split(\"\\n\")\n .map((line) => createGlimpseParagraph(line, style, options));\n}\n\nfunction textBoxInput(\n bounds: TextBoundsPx,\n options: GlimpseTextBoxOptions,\n paragraphs: readonly AddTextBoxParagraphInput[],\n): AddTextBoxInput {\n const margin =\n options.margin !== undefined\n ? asEmu(Math.round(pxToEmu(options.margin)))\n : undefined;\n return {\n offsetX: asEmu(Math.round(pxToEmu(bounds.x))),\n offsetY: asEmu(Math.round(pxToEmu(bounds.y))),\n width: asEmu(Math.max(1, Math.round(pxToEmu(bounds.w)))),\n height: asEmu(Math.max(1, Math.round(pxToEmu(bounds.h)))),\n rotation:\n options.rotate !== undefined\n ? asOoxmlAngle(Math.round(options.rotate * 60000))\n : undefined,\n body: {\n marginLeft: margin,\n marginRight: margin,\n marginTop: margin,\n marginBottom: margin,\n anchor: options.valign,\n autoFit: options.autoFit ? \"shape\" : undefined,\n },\n paragraphs,\n };\n}\n\nfunction withListProperties(\n paragraphs: readonly AddTextBoxParagraphInput[],\n options: GlimpseTextBoxOptions,\n): readonly AddTextBoxParagraphInput[] {\n if (!options.bullet) return paragraphs;\n const bullet =\n options.bullet.kind === \"bullet\"\n ? {\n type: \"character\" as const,\n character: \"•\",\n size: asOoxmlPercent(100000),\n }\n : {\n type: \"auto-number\" as const,\n scheme: isSupportedAutoNumScheme(options.bullet.scheme)\n ? options.bullet.scheme\n : \"arabicPeriod\",\n startAt: options.bullet.startAt ?? 1,\n fontFace: \"+mj-lt\",\n size: asOoxmlPercent(100000),\n };\n return paragraphs.map((paragraph) => ({\n ...paragraph,\n properties: {\n ...paragraph.properties,\n marginLeft: asEmu(342900),\n indent: asEmu(-342900),\n lineSpacing: {\n type: \"percent\",\n value: asOoxmlPercent(Math.round((options.lineHeight ?? 1.3) * 100000)),\n },\n bullet,\n },\n }));\n}\n\nexport function addGlimpseTextBox(\n ctx: RenderContext,\n bounds: TextBoundsPx,\n options: GlimpseTextBoxOptions & {\n paragraphs?: readonly AddTextBoxParagraphInput[];\n hyperlinks?: readonly (string | undefined)[];\n } = {},\n): void {\n const paragraphs = withListProperties(\n options.paragraphs ??\n createGlimpseParagraphs(options.text ?? \"\", options, {\n align: options.align,\n lineHeight: options.lineHeight,\n }),\n options,\n );\n ctx.buildContext.pptxAuthoring.registerTextBox(\n textBoxInput(bounds, options, paragraphs),\n {\n hyperlinks: options.hyperlinks,\n },\n );\n}\n\nfunction isSupportedAutoNumScheme(\n value: string | undefined,\n): value is SourceAutoNumScheme {\n return (\n value === \"arabicPeriod\" ||\n value === \"arabicParenR\" ||\n value === \"romanUcPeriod\" ||\n value === \"romanLcPeriod\" ||\n value === \"alphaUcPeriod\" ||\n value === \"alphaLcPeriod\" ||\n value === \"alphaLcParenR\" ||\n value === \"alphaUcParenR\" ||\n value === \"arabicPlain\"\n );\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { addGlimpseShape, arrowEndpoint, createShapeBoundsInput, noneShapeFill,
3
3
  import { asEmu } from "@pptx-glimpse/document";
4
4
  //#region src/renderPptx/utils/straightLine.ts
5
5
  /**
6
- * boolean | LineArrowOptions から pptxgenjs の arrow type を取得
6
+ * boolean | LineArrowOptions から arrow type を取得
7
7
  */
8
8
  function resolveArrowType(arrow) {
9
9
  if (arrow === void 0) return;
@@ -22,7 +22,10 @@ function addStraightLine(ctx, { x1, y1, x2, y2 }, { color, lineWidth, dashType,
22
22
  const flipH = x2 < x1;
23
23
  const flipV = y2 < y1;
24
24
  addGlimpseShape(ctx, {
25
- preset: "line",
25
+ geometry: {
26
+ kind: "preset",
27
+ preset: "line"
28
+ },
26
29
  ...createShapeBoundsInput({
27
30
  x: minX,
28
31
  y: minY,
@@ -1 +1 @@
1
- {"version":3,"file":"straightLine.js","names":[],"sources":["../../../src/renderPptx/utils/straightLine.ts"],"sourcesContent":["/**\n * 2 点間の直線を pptxgenjs の line shape に変換する共通描画処理。\n *\n * pptxgenjs の line shape は左上座標 (x, y) + サイズ (w, h) で表現され、\n * 線の向き (始点→終点) は flipH / flipV で表すため、端点座標からの\n * 変換ロジックを Line / Arrow ノードで共有する。\n */\nimport type { LineArrow, LineNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { asEmu } from \"@pptx-glimpse/document\";\nimport { pxToEmu } from \"../units.ts\";\nimport {\n addGlimpseShape,\n arrowEndpoint,\n createShapeBoundsInput,\n noneShapeFill,\n solidShapeFill,\n} from \"./glimpseShape.ts\";\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\ntype StraightLinePoints = { x1: number; y1: number; x2: number; y2: number };\n\ntype StraightLineStyle = Pick<\n LineNode,\n \"color\" | \"lineWidth\" | \"dashType\" | \"beginArrow\" | \"endArrow\"\n> & {\n lineGradient?: string;\n lineGradientOpacity?: number;\n};\n\n/**\n * 始点 (x1, y1) から終点 (x2, y2) への直線を描画する\n */\nexport function addStraightLine(\n ctx: RenderContext,\n { x1, y1, x2, y2 }: StraightLinePoints,\n {\n color,\n lineWidth,\n dashType,\n beginArrow,\n endArrow,\n lineGradient,\n lineGradientOpacity,\n }: StraightLineStyle,\n): void {\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 addGlimpseShape(\n ctx,\n {\n preset: \"line\",\n ...createShapeBoundsInput({ x: minX, y: minY, w: lineW, h: lineH }),\n fill: noneShapeFill(),\n outline: {\n fill: solidShapeFill(color ?? \"000000\"),\n width:\n lineWidth !== undefined\n ? asEmu(Math.round(pxToEmu(lineWidth)))\n : asEmu(12700),\n dash: dashType === \"lgDashDotDot\" ? undefined : dashType,\n headEnd: arrowEndpoint(resolveArrowType(beginArrow)),\n tailEnd: arrowEndpoint(resolveArrowType(endArrow)),\n },\n },\n { x: minX, y: minY, w: lineW, h: lineH },\n {\n flipH,\n flipV,\n dashType,\n outlineGradient: lineGradient,\n outlineOpacity: lineGradientOpacity,\n },\n );\n}\n"],"mappings":";;;;;;;AAsBA,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,gBACd,KACA,EAAE,IAAI,IAAI,IAAI,MACd,EACE,OACA,WACA,UACA,YACA,UACA,cACA,uBAEI;CACN,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,gBACE,KACA;EACE,QAAQ;EACR,GAAG,uBAAuB;GAAE,GAAG;GAAM,GAAG;GAAM,GAAG;GAAO,GAAG;EAAM,CAAC;EAClE,MAAM,cAAc;EACpB,SAAS;GACP,MAAM,eAAe,SAAS,QAAQ;GACtC,OACE,cAAc,KAAA,IACV,MAAM,KAAK,MAAM,QAAQ,SAAS,CAAC,CAAC,IACpC,MAAM,KAAK;GACjB,MAAM,aAAa,iBAAiB,KAAA,IAAY;GAChD,SAAS,cAAc,iBAAiB,UAAU,CAAC;GACnD,SAAS,cAAc,iBAAiB,QAAQ,CAAC;EACnD;CACF,GACA;EAAE,GAAG;EAAM,GAAG;EAAM,GAAG;EAAO,GAAG;CAAM,GACvC;EACE;EACA;EACA;EACA,iBAAiB;EACjB,gBAAgB;CAClB,CACF;AACF"}
1
+ {"version":3,"file":"straightLine.js","names":[],"sources":["../../../src/renderPptx/utils/straightLine.ts"],"sourcesContent":["/**\n * 2 点間の直線を line shape に変換する共通描画処理。\n *\n * line shape は左上座標 (x, y) + サイズ (w, h) で表現され、\n * 線の向き (始点→終点) は flipH / flipV で表すため、端点座標からの\n * 変換ロジックを Line / Arrow ノードで共有する。\n */\nimport type { LineArrow, LineNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { asEmu } from \"@pptx-glimpse/document\";\nimport { pxToEmu } from \"../units.ts\";\nimport {\n addGlimpseShape,\n arrowEndpoint,\n createShapeBoundsInput,\n noneShapeFill,\n solidShapeFill,\n} from \"./glimpseShape.ts\";\n\n/**\n * boolean | LineArrowOptions から 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\ntype StraightLinePoints = { x1: number; y1: number; x2: number; y2: number };\n\ntype StraightLineStyle = Pick<\n LineNode,\n \"color\" | \"lineWidth\" | \"dashType\" | \"beginArrow\" | \"endArrow\"\n> & {\n lineGradient?: string;\n lineGradientOpacity?: number;\n};\n\n/**\n * 始点 (x1, y1) から終点 (x2, y2) への直線を描画する\n */\nexport function addStraightLine(\n ctx: RenderContext,\n { x1, y1, x2, y2 }: StraightLinePoints,\n {\n color,\n lineWidth,\n dashType,\n beginArrow,\n endArrow,\n lineGradient,\n lineGradientOpacity,\n }: StraightLineStyle,\n): void {\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 addGlimpseShape(\n ctx,\n {\n geometry: { kind: \"preset\", preset: \"line\" },\n ...createShapeBoundsInput({ x: minX, y: minY, w: lineW, h: lineH }),\n fill: noneShapeFill(),\n outline: {\n fill: solidShapeFill(color ?? \"000000\"),\n width:\n lineWidth !== undefined\n ? asEmu(Math.round(pxToEmu(lineWidth)))\n : asEmu(12700),\n dash: dashType === \"lgDashDotDot\" ? undefined : dashType,\n headEnd: arrowEndpoint(resolveArrowType(beginArrow)),\n tailEnd: arrowEndpoint(resolveArrowType(endArrow)),\n },\n },\n { x: minX, y: minY, w: lineW, h: lineH },\n {\n flipH,\n flipV,\n dashType,\n outlineGradient: lineGradient,\n outlineOpacity: lineGradientOpacity,\n },\n );\n}\n"],"mappings":";;;;;;;AAsBA,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,gBACd,KACA,EAAE,IAAI,IAAI,IAAI,MACd,EACE,OACA,WACA,UACA,YACA,UACA,cACA,uBAEI;CACN,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,gBACE,KACA;EACE,UAAU;GAAE,MAAM;GAAU,QAAQ;EAAO;EAC3C,GAAG,uBAAuB;GAAE,GAAG;GAAM,GAAG;GAAM,GAAG;GAAO,GAAG;EAAM,CAAC;EAClE,MAAM,cAAc;EACpB,SAAS;GACP,MAAM,eAAe,SAAS,QAAQ;GACtC,OACE,cAAc,KAAA,IACV,MAAM,KAAK,MAAM,QAAQ,SAAS,CAAC,CAAC,IACpC,MAAM,KAAK;GACjB,MAAM,aAAa,iBAAiB,KAAA,IAAY;GAChD,SAAS,cAAc,iBAAiB,UAAU,CAAC;GACnD,SAAS,cAAc,iBAAiB,QAAQ,CAAC;EACnD;CACF,GACA;EAAE,GAAG;EAAM,GAAG;EAAM,GAAG;EAAO,GAAG;CAAM,GACvC;EACE;EACA;EACA;EACA,iBAAiB;EACjB,gBAAgB;CAClB,CACF;AACF"}
@@ -1,28 +1,12 @@
1
- import { pxToPt } from "../units.js";
2
1
  //#region src/renderPptx/utils/visualStyle.ts
3
2
  /**
4
- * 色文字列から "#" を 1 つ取り除き、pptxgenjs が受け付ける HEX 文字列にする。
3
+ * 色文字列から "#" を 1 つ取り除き、OOXML 向けの HEX 文字列にする。
5
4
  * undefined はそのまま返すため `stripHash(color) ?? fallback` の形で使える。
6
5
  */
7
6
  function stripHash(color) {
8
7
  return color?.replace("#", "");
9
8
  }
10
9
  /**
11
- * ShadowStyle を pptxgenjs の shadow オプションに変換する
12
- * type 未指定時は outer をデフォルトとする
13
- */
14
- function convertShadow(shadow) {
15
- if (!shadow) return void 0;
16
- return {
17
- type: shadow.type ?? "outer",
18
- opacity: shadow.opacity,
19
- blur: shadow.blur,
20
- angle: shadow.angle,
21
- offset: shadow.offset,
22
- color: shadow.color
23
- };
24
- }
25
- /**
26
10
  * border が描画対象となる指定 (color / width / dashType のいずれか) を
27
11
  * 持つかを判定する
28
12
  */
@@ -64,27 +48,13 @@ function resolvePerSideBorders(style) {
64
48
  return result;
65
49
  }
66
50
  /**
67
- * BorderStyle を pptxgenjs の line オプションに変換する
68
- * width はユーザー入力 px、pptxgenjs の line.width は pt
69
- *
70
- * @param fallbackColor color 未指定時に使う色。省略時は color を
71
- * undefined のまま渡し pptxgenjs のデフォルトに任せる
72
- */
73
- function convertBorderLine(border, fallbackColor) {
74
- return {
75
- color: border.color ?? fallbackColor,
76
- width: border.width !== void 0 ? pxToPt(border.width) : void 0,
77
- dashType: border.dashType
78
- };
79
- }
80
- /**
81
51
  * borderRadius (px) をノードサイズに対する 0-1 の正規化値
82
- * (pptxgenjs roundRect の rectRadius) に変換する
52
+ * (roundRect の rectRadius) に変換する
83
53
  */
84
54
  function resolveRectRadius(borderRadius, w, h) {
85
55
  return borderRadius ? Math.min(borderRadius / Math.min(w, h) * 2, 1) : void 0;
86
56
  }
87
57
  //#endregion
88
- export { BORDER_SIDES, convertBorderLine, convertShadow, hasVisibleBorder, resolvePerSideBorders, resolveRectRadius, stripHash };
58
+ export { BORDER_SIDES, hasVisibleBorder, resolvePerSideBorders, resolveRectRadius, stripHash };
89
59
 
90
60
  //# sourceMappingURL=visualStyle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"visualStyle.js","names":[],"sources":["../../../src/renderPptx/utils/visualStyle.ts"],"sourcesContent":["/**\n * 見た目属性 (fill / border / shadow / opacity / borderRadius) を\n * pptxgenjs のオプションへ変換する共通 resolver 群。\n *\n * ノードごとに変換ロジックが重複・乖離すると、XML 上は同じ指定でも\n * 描画結果が異なるリスクがあるため、属性値 → pptxgenjs オプションの\n * 純粋変換をここに集約する。\n *\n * 共通化対象の属性カテゴリ:\n * - fill (背景色 + opacity): resolveBackgroundFill / opacityToTransparency\n * - border / line (枠線): hasVisibleBorder / convertBorderLine\n * - shadow (影): convertShadow\n * - borderRadius (角丸): resolveRectRadius\n *\n * 既存 helper との責務境界:\n * - utils/glimpseShape.ts: backgroundGradient を writer 生成 XML の native\n * gradFill として出力する。\n * - utils/backgroundBorder.ts: 背景色 → 背景画像 → ボーダーの描画順序の\n * オーケストレーション。個々の属性値の変換は本モジュールに委譲する。\n * - textOptions.ts: テキスト系属性 (font / underline / strike など) の変換。\n */\nimport type { BorderStyle, ShadowStyle } from \"../../types.ts\";\nimport { pxToPt } from \"../units.ts\";\n\n/**\n * 色文字列から \"#\" を 1 つ取り除き、pptxgenjs が受け付ける HEX 文字列にする。\n * undefined はそのまま返すため `stripHash(color) ?? fallback` の形で使える。\n */\nexport function stripHash(color: string | undefined): string | undefined {\n return color?.replace(\"#\", \"\");\n}\n\n/**\n * ShadowStyle pptxgenjs の shadow オプションに変換する\n * type 未指定時は outer をデフォルトとする\n */\nexport function convertShadow(shadow: ShadowStyle | undefined):\n | {\n type: \"outer\" | \"inner\";\n opacity?: number;\n blur?: number;\n angle?: number;\n offset?: number;\n color?: string;\n }\n | undefined {\n if (!shadow) return undefined;\n return {\n type: shadow.type ?? \"outer\",\n opacity: shadow.opacity,\n blur: shadow.blur,\n angle: shadow.angle,\n offset: shadow.offset,\n color: shadow.color,\n };\n}\n\n/**\n * border が描画対象となる指定 (color / width / dashType のいずれか) を\n * 持つかを判定する\n */\nexport function hasVisibleBorder(\n border: BorderStyle | undefined,\n): border is BorderStyle {\n return Boolean(\n border &&\n (border.color !== undefined ||\n border.width !== undefined ||\n border.dashType !== undefined),\n );\n}\n\nexport const BORDER_SIDES = [\"top\", \"right\", \"bottom\", \"left\"] as const;\nexport type BorderSide = (typeof BORDER_SIDES)[number];\n\nexport type PerSideBorders = Partial<Record<BorderSide, BorderStyle>>;\n\n/**\n * 4 辺一律の border と辺ごとの borderTop / borderRight / borderBottom /\n * borderLeft をマージし、描画対象となる辺ごとの BorderStyle を返す。\n *\n * - 辺ごとの指定が 1 つも無い場合は undefined を返し、呼び出し側は\n * 従来の 4 辺一律描画 (shape の line オプション) にフォールバックする\n * - 辺ごとの指定がある場合、各辺は border をベースに辺ごとの指定で\n * フィールド単位に上書きした BorderStyle になる (辺ごとの指定が優先)\n * - マージ結果が描画対象とならない辺 (指定なし) は結果に含まれない\n */\nexport function resolvePerSideBorders(style: {\n border?: BorderStyle;\n borderTop?: BorderStyle;\n borderRight?: BorderStyle;\n borderBottom?: BorderStyle;\n borderLeft?: BorderStyle;\n}): PerSideBorders | undefined {\n const overrides: Partial<Record<BorderSide, BorderStyle | undefined>> = {\n top: style.borderTop,\n right: style.borderRight,\n bottom: style.borderBottom,\n left: style.borderLeft,\n };\n\n const hasPerSideOverride = Object.values(overrides).some(hasVisibleBorder);\n if (!hasPerSideOverride) return undefined;\n\n const result: PerSideBorders = {};\n for (const side of BORDER_SIDES) {\n const merged = { ...style.border, ...overrides[side] };\n if (hasVisibleBorder(merged)) {\n result[side] = merged;\n }\n }\n return result;\n}\n\n/**\n * BorderStyle pptxgenjs の line オプションに変換する\n * width はユーザー入力 px、pptxgenjs line.width は pt\n *\n * @param fallbackColor color 未指定時に使う色。省略時は color を\n * undefined のまま渡し pptxgenjs のデフォルトに任せる\n */\nexport function convertBorderLine(\n border: BorderStyle,\n fallbackColor?: string,\n): { color?: string; width?: number; dashType?: BorderStyle[\"dashType\"] } {\n return {\n color: border.color ?? fallbackColor,\n width: border.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border.dashType,\n };\n}\n\n/**\n * ノードの opacity (0-1、1 = 不透明) を pptxgenjs の\n * transparency (0-100、100 = 透明) に変換する\n */\nexport function opacityToTransparency(\n opacity: number | undefined,\n): number | undefined {\n return opacity !== undefined ? (1 - opacity) * 100 : undefined;\n}\n\n/**\n * backgroundColor / opacity / 互換用のグラデーション fallback 色から\n * pptxgenjs の fill オプションを解決する\n *\n * gradientMarker は旧 pptxgenjs 経路との互換 helper 用に残している。\n * writer 経路の backgroundGradient は utils/glimpseShape.ts 側で native\n * gradFill に変換する。\n */\nexport function resolveBackgroundFill(\n backgroundColor: string | undefined,\n opacity: number | undefined,\n gradientMarker: string | undefined,\n): { color?: string; transparency?: number } {\n if (gradientMarker) return { color: gradientMarker };\n return {\n color: backgroundColor,\n transparency: opacityToTransparency(opacity),\n };\n}\n\n/**\n * borderRadius (px) をノードサイズに対する 0-1 の正規化値\n * (pptxgenjs roundRect の rectRadius) に変換する\n */\nexport function resolveRectRadius(\n borderRadius: number | undefined,\n w: number,\n h: number,\n): number | undefined {\n return borderRadius\n ? Math.min((borderRadius / Math.min(w, h)) * 2, 1)\n : undefined;\n}\n"],"mappings":";;;;;;AA4BA,SAAgB,UAAU,OAA+C;CACvE,OAAO,OAAO,QAAQ,KAAK,EAAE;AAC/B;;;;;AAMA,SAAgB,cAAc,QAShB;CACZ,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO;EACL,MAAM,OAAO,QAAQ;EACrB,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,OAAO,OAAO;CAChB;AACF;;;;;AAMA,SAAgB,iBACd,QACuB;CACvB,OAAO,QACL,WACC,OAAO,UAAU,KAAA,KAChB,OAAO,UAAU,KAAA,KACjB,OAAO,aAAa,KAAA,EACxB;AACF;AAEA,MAAa,eAAe;CAAC;CAAO;CAAS;CAAU;AAAM;;;;;;;;;;;AAe7D,SAAgB,sBAAsB,OAMP;CAC7B,MAAM,YAAkE;EACtE,KAAK,MAAM;EACX,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,MAAM,MAAM;CACd;CAGA,IAAI,CADuB,OAAO,OAAO,SAAS,CAAC,CAAC,KAAK,gBACnC,GAAG,OAAO,KAAA;CAEhC,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,SAAS;GAAE,GAAG,MAAM;GAAQ,GAAG,UAAU;EAAM;EACrD,IAAI,iBAAiB,MAAM,GACzB,OAAO,QAAQ;CAEnB;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,kBACd,QACA,eACwE;CACxE,OAAO;EACL,OAAO,OAAO,SAAS;EACvB,OAAO,OAAO,UAAU,KAAA,IAAY,OAAO,OAAO,KAAK,IAAI,KAAA;EAC3D,UAAU,OAAO;CACnB;AACF;;;;;AAoCA,SAAgB,kBACd,cACA,GACA,GACoB;CACpB,OAAO,eACH,KAAK,IAAK,eAAe,KAAK,IAAI,GAAG,CAAC,IAAK,GAAG,CAAC,IAC/C,KAAA;AACN"}
1
+ {"version":3,"file":"visualStyle.js","names":[],"sources":["../../../src/renderPptx/utils/visualStyle.ts"],"sourcesContent":["/**\n * 見た目属性 (fill / border / shadow / opacity / borderRadius) を\n * PPTX 描画用の中間オプションへ変換する共通 resolver 群。\n *\n * ノードごとに変換ロジックが重複・乖離すると、XML 上は同じ指定でも\n * 描画結果が異なるリスクがあるため、属性値から描画オプションへの\n * 純粋変換をここに集約する。\n *\n * 共通化対象の属性カテゴリ:\n * - fill (背景色 + opacity): resolveBackgroundFill / opacityToTransparency\n * - border / line (枠線): hasVisibleBorder / convertBorderLine\n * - shadow (影): convertShadow\n * - borderRadius (角丸): resolveRectRadius\n *\n * 既存 helper との責務境界:\n * - utils/glimpseShape.ts: backgroundGradient を writer 生成 XML の native\n * gradFill として出力する。\n * - utils/backgroundBorder.ts: 背景色 → 背景画像 → ボーダーの描画順序の\n * オーケストレーション。個々の属性値の変換は本モジュールに委譲する。\n * - textOptions.ts: テキスト系属性 (font / underline / strike など) の変換。\n */\nimport type { BorderStyle, ShadowStyle } from \"../../types.ts\";\nimport { pxToPt } from \"../units.ts\";\n\n/**\n * 色文字列から \"#\" を 1 つ取り除き、OOXML 向けの HEX 文字列にする。\n * undefined はそのまま返すため `stripHash(color) ?? fallback` の形で使える。\n */\nexport function stripHash(color: string | undefined): string | undefined {\n return color?.replace(\"#\", \"\");\n}\n\n/**\n * ShadowStyle を描画用の shadow オプションに変換する\n * type 未指定時は outer をデフォルトとする\n */\nexport function convertShadow(shadow: ShadowStyle | undefined):\n | {\n type: \"outer\" | \"inner\";\n opacity?: number;\n blur?: number;\n angle?: number;\n offset?: number;\n color?: string;\n }\n | undefined {\n if (!shadow) return undefined;\n return {\n type: shadow.type ?? \"outer\",\n opacity: shadow.opacity,\n blur: shadow.blur,\n angle: shadow.angle,\n offset: shadow.offset,\n color: shadow.color,\n };\n}\n\n/**\n * border が描画対象となる指定 (color / width / dashType のいずれか) を\n * 持つかを判定する\n */\nexport function hasVisibleBorder(\n border: BorderStyle | undefined,\n): border is BorderStyle {\n return Boolean(\n border &&\n (border.color !== undefined ||\n border.width !== undefined ||\n border.dashType !== undefined),\n );\n}\n\nexport const BORDER_SIDES = [\"top\", \"right\", \"bottom\", \"left\"] as const;\nexport type BorderSide = (typeof BORDER_SIDES)[number];\n\nexport type PerSideBorders = Partial<Record<BorderSide, BorderStyle>>;\n\n/**\n * 4 辺一律の border と辺ごとの borderTop / borderRight / borderBottom /\n * borderLeft をマージし、描画対象となる辺ごとの BorderStyle を返す。\n *\n * - 辺ごとの指定が 1 つも無い場合は undefined を返し、呼び出し側は\n * 従来の 4 辺一律描画 (shape の line オプション) にフォールバックする\n * - 辺ごとの指定がある場合、各辺は border をベースに辺ごとの指定で\n * フィールド単位に上書きした BorderStyle になる (辺ごとの指定が優先)\n * - マージ結果が描画対象とならない辺 (指定なし) は結果に含まれない\n */\nexport function resolvePerSideBorders(style: {\n border?: BorderStyle;\n borderTop?: BorderStyle;\n borderRight?: BorderStyle;\n borderBottom?: BorderStyle;\n borderLeft?: BorderStyle;\n}): PerSideBorders | undefined {\n const overrides: Partial<Record<BorderSide, BorderStyle | undefined>> = {\n top: style.borderTop,\n right: style.borderRight,\n bottom: style.borderBottom,\n left: style.borderLeft,\n };\n\n const hasPerSideOverride = Object.values(overrides).some(hasVisibleBorder);\n if (!hasPerSideOverride) return undefined;\n\n const result: PerSideBorders = {};\n for (const side of BORDER_SIDES) {\n const merged = { ...style.border, ...overrides[side] };\n if (hasVisibleBorder(merged)) {\n result[side] = merged;\n }\n }\n return result;\n}\n\n/**\n * BorderStyle を描画用の line オプションに変換する\n * width はユーザー入力 px、描画用の line.width は pt\n *\n * @param fallbackColor color 未指定時に使う色。省略時は color を\n * undefined のまま渡し呼び出し側のデフォルトに任せる\n */\nexport function convertBorderLine(\n border: BorderStyle,\n fallbackColor?: string,\n): { color?: string; width?: number; dashType?: BorderStyle[\"dashType\"] } {\n return {\n color: border.color ?? fallbackColor,\n width: border.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border.dashType,\n };\n}\n\n/**\n * ノードの opacity (0-1、1 = 不透明) を描画用の\n * transparency (0-100、100 = 透明) に変換する\n */\nexport function opacityToTransparency(\n opacity: number | undefined,\n): number | undefined {\n return opacity !== undefined ? (1 - opacity) * 100 : undefined;\n}\n\n/**\n * backgroundColor / opacity / 互換用のグラデーション fallback 色から\n * fill オプションを解決する\n *\n * gradientMarker は既存 helper の互換引数として残している。\n * backgroundGradient は utils/glimpseShape.ts 側で native\n * gradFill に変換する。\n */\nexport function resolveBackgroundFill(\n backgroundColor: string | undefined,\n opacity: number | undefined,\n gradientMarker: string | undefined,\n): { color?: string; transparency?: number } {\n if (gradientMarker) return { color: gradientMarker };\n return {\n color: backgroundColor,\n transparency: opacityToTransparency(opacity),\n };\n}\n\n/**\n * borderRadius (px) をノードサイズに対する 0-1 の正規化値\n * (roundRect の rectRadius) に変換する\n */\nexport function resolveRectRadius(\n borderRadius: number | undefined,\n w: number,\n h: number,\n): number | undefined {\n return borderRadius\n ? Math.min((borderRadius / Math.min(w, h)) * 2, 1)\n : undefined;\n}\n"],"mappings":";;;;;AA4BA,SAAgB,UAAU,OAA+C;CACvE,OAAO,OAAO,QAAQ,KAAK,EAAE;AAC/B;;;;;AA+BA,SAAgB,iBACd,QACuB;CACvB,OAAO,QACL,WACC,OAAO,UAAU,KAAA,KAChB,OAAO,UAAU,KAAA,KACjB,OAAO,aAAa,KAAA,EACxB;AACF;AAEA,MAAa,eAAe;CAAC;CAAO;CAAS;CAAU;AAAM;;;;;;;;;;;AAe7D,SAAgB,sBAAsB,OAMP;CAC7B,MAAM,YAAkE;EACtE,KAAK,MAAM;EACX,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,MAAM,MAAM;CACd;CAGA,IAAI,CADuB,OAAO,OAAO,SAAS,CAAC,CAAC,KAAK,gBACnC,GAAG,OAAO,KAAA;CAEhC,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,SAAS;GAAE,GAAG,MAAM;GAAQ,GAAG,UAAU;EAAM;EACrD,IAAI,iBAAiB,MAAM,GACzB,OAAO,QAAQ;CAEnB;CACA,OAAO;AACT;;;;;AAsDA,SAAgB,kBACd,cACA,GACA,GACoB;CACpB,OAAO,eACH,KAAK,IAAK,eAAe,KAAK,IAAI,GAAG,CAAC,IAAK,GAAG,CAAC,IAC/C,KAAA;AACN"}
@@ -0,0 +1,23 @@
1
+ import { Buffer } from "node:buffer";
2
+
3
+ //#region src/renderPptx/writablePptx.d.ts
4
+ type PptxOutputType = "arraybuffer" | "base64" | "binarystring" | "blob" | "nodebuffer" | "uint8array";
5
+ type PptxWriteOptions = {
6
+ outputType?: PptxOutputType;
7
+ };
8
+ type PptxWriteFileOptions = {
9
+ fileName?: string;
10
+ };
11
+ type PptxWriteOutput<T extends PptxOutputType | undefined> = T extends "arraybuffer" ? ArrayBuffer : T extends "base64" | "binarystring" ? string : T extends "nodebuffer" ? Buffer : T extends "uint8array" ? Uint8Array : Blob;
12
+ interface WritablePptx {
13
+ write(): Promise<Blob>;
14
+ write<T extends PptxOutputType>(options: PptxWriteOptions & {
15
+ outputType: T;
16
+ }): Promise<PptxWriteOutput<T>>;
17
+ write(options: PptxWriteOptions): Promise<PptxWriteOutput<PptxOutputType | undefined>>;
18
+ stream(): Promise<Uint8Array>;
19
+ writeFile(options?: PptxWriteFileOptions | string): Promise<string>;
20
+ }
21
+ //#endregion
22
+ export { PptxOutputType, PptxWriteFileOptions, PptxWriteOptions, PptxWriteOutput, WritablePptx };
23
+ //# sourceMappingURL=writablePptx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writablePptx.d.ts","names":[],"sources":["../../src/renderPptx/writablePptx.ts"],"mappings":";;;KAGY,cAAA;AAAA,KAQA,gBAAA;EACV,UAAA,GAAa,cAAc;AAAA;AAAA,KAGjB,oBAAA;EACV,QAAQ;AAAA;AAAA,KAGE,eAAA,WAA0B,cAAA,gBACpC,CAAA,yBACI,WAAA,GACA,CAAA,8CAEE,CAAA,wBACE,MAAA,GACA,CAAA,wBACE,UAAA,GACA,IAAA;AAAA,UAEK,YAAA;EACf,KAAA,IAAS,OAAA,CAAQ,IAAA;EACjB,KAAA,WAAgB,cAAA,EACd,OAAA,EAAS,gBAAA;IAAqB,UAAA,EAAY,CAAA;EAAA,IACzC,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAC3B,KAAA,CACE,OAAA,EAAS,gBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,cAAA;EAC3B,MAAA,IAAU,OAAA,CAAQ,UAAA;EAClB,SAAA,CAAU,OAAA,GAAU,oBAAA,YAAgC,OAAA;AAAA"}