@hirokisakabe/pom 9.1.1 → 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 (73) hide show
  1. package/dist/buildContext.js +2 -6
  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 -9
  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 +98 -90
  15. package/dist/renderPptx/nodes/flow.js.map +1 -1
  16. package/dist/renderPptx/nodes/icon.js +36 -24
  17. package/dist/renderPptx/nodes/icon.js.map +1 -1
  18. package/dist/renderPptx/nodes/image.js +5 -22
  19. package/dist/renderPptx/nodes/image.js.map +1 -1
  20. package/dist/renderPptx/nodes/list.js +94 -111
  21. package/dist/renderPptx/nodes/list.js.map +1 -1
  22. package/dist/renderPptx/nodes/matrix.js +95 -70
  23. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  24. package/dist/renderPptx/nodes/processArrow.js +74 -35
  25. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  26. package/dist/renderPptx/nodes/pyramid.js +37 -16
  27. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  28. package/dist/renderPptx/nodes/shape.js +66 -37
  29. package/dist/renderPptx/nodes/shape.js.map +1 -1
  30. package/dist/renderPptx/nodes/svg.js +2 -5
  31. package/dist/renderPptx/nodes/svg.js.map +1 -1
  32. package/dist/renderPptx/nodes/table.js +89 -59
  33. package/dist/renderPptx/nodes/table.js.map +1 -1
  34. package/dist/renderPptx/nodes/text.js +1 -18
  35. package/dist/renderPptx/nodes/text.js.map +1 -1
  36. package/dist/renderPptx/nodes/timeline.js +114 -74
  37. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  38. package/dist/renderPptx/nodes/tree.js +81 -79
  39. package/dist/renderPptx/nodes/tree.js.map +1 -1
  40. package/dist/renderPptx/pptxAuthoring.js +381 -0
  41. package/dist/renderPptx/pptxAuthoring.js.map +1 -0
  42. package/dist/renderPptx/renderPptx.js +158 -128
  43. package/dist/renderPptx/renderPptx.js.map +1 -1
  44. package/dist/renderPptx/textOptions.js +7 -8
  45. package/dist/renderPptx/textOptions.js.map +1 -1
  46. package/dist/renderPptx/units.js +3 -3
  47. package/dist/renderPptx/units.js.map +1 -1
  48. package/dist/renderPptx/utils/backgroundBorder.js +70 -48
  49. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  50. package/dist/renderPptx/utils/contentArea.js +1 -1
  51. package/dist/renderPptx/utils/contentArea.js.map +1 -1
  52. package/dist/renderPptx/utils/glimpsePicture.js +111 -0
  53. package/dist/renderPptx/utils/glimpsePicture.js.map +1 -0
  54. package/dist/renderPptx/utils/glimpseShape.js +71 -0
  55. package/dist/renderPptx/utils/glimpseShape.js.map +1 -0
  56. package/dist/renderPptx/utils/glimpseTextBox.js +107 -0
  57. package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -0
  58. package/dist/renderPptx/utils/straightLine.js +33 -15
  59. package/dist/renderPptx/utils/straightLine.js.map +1 -1
  60. package/dist/renderPptx/utils/visualStyle.js +3 -56
  61. package/dist/renderPptx/utils/visualStyle.js.map +1 -1
  62. package/dist/renderPptx/writablePptx.d.ts +23 -0
  63. package/dist/renderPptx/writablePptx.d.ts.map +1 -0
  64. package/dist/renderPptx/writablePptx.js +74 -0
  65. package/dist/renderPptx/writablePptx.js.map +1 -0
  66. package/dist/shared/measureImage.js.map +1 -1
  67. package/package.json +4 -5
  68. package/dist/renderPptx/glimpseTextBoxes.js +0 -347
  69. package/dist/renderPptx/glimpseTextBoxes.js.map +0 -1
  70. package/dist/renderPptx/glowEffects.js +0 -157
  71. package/dist/renderPptx/glowEffects.js.map +0 -1
  72. package/dist/renderPptx/gradientFills.js +0 -154
  73. package/dist/renderPptx/gradientFills.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { getImageData } from "../../shared/measureImage.js";
2
- import { pxToIn, rectPxToIn } from "../units.js";
3
- import { BORDER_SIDES, convertBorderLine, convertShadow, hasVisibleBorder, resolveBackgroundFill, resolvePerSideBorders, resolveRectRadius } from "./visualStyle.js";
4
- import { registerBackgroundGradient } from "../gradientFills.js";
2
+ import { addGlimpsePicture, imageBytesFromSource } from "./glimpsePicture.js";
3
+ import { addGlimpseShape, backgroundShapeFill, createShapeBoundsInput, noShapeOutline, noneShapeFill, shapeOutline } from "./glimpseShape.js";
4
+ import { BORDER_SIDES, hasVisibleBorder, resolvePerSideBorders, resolveRectRadius } from "./visualStyle.js";
5
5
  //#region src/renderPptx/utils/backgroundBorder.ts
6
6
  /**
7
7
  * ノードの背景色・背景画像・ボーダー・影を描画する
@@ -11,62 +11,74 @@ import { registerBackgroundGradient } from "../gradientFills.js";
11
11
  */
12
12
  function renderBackgroundAndBorder(node, ctx) {
13
13
  const { backgroundColor, backgroundGradient, backgroundImage, border, borderRadius, shadow } = node;
14
- const gradientMarker = backgroundGradient ? registerBackgroundGradient(backgroundGradient, node.opacity, ctx.buildContext.gradientFills) : void 0;
15
14
  const perSideBorders = resolveEffectivePerSideBorders(node, ctx);
16
- const hasBackground = Boolean(backgroundColor) || Boolean(gradientMarker);
15
+ const hasBackground = Boolean(backgroundColor) || Boolean(backgroundGradient);
17
16
  const hasBackgroundImage = Boolean(backgroundImage);
18
17
  const hasUniformBorder = !perSideBorders && hasVisibleBorder(border);
19
18
  const hasShadow = Boolean(shadow);
20
19
  if (!hasBackground && !hasBackgroundImage && !hasUniformBorder && !perSideBorders && !hasShadow) return;
21
- const shapeType = borderRadius ? ctx.pptx.ShapeType.roundRect : ctx.pptx.ShapeType.rect;
20
+ const shapeType = borderRadius ? "roundRect" : "rect";
22
21
  const rectRadius = resolveRectRadius(borderRadius, node.w, node.h);
23
22
  if (!hasBackgroundImage) {
24
23
  if (hasBackground || hasUniformBorder || hasShadow) {
25
- const fill = hasBackground ? resolveBackgroundFill(backgroundColor, node.opacity, gradientMarker) : { type: "none" };
26
- const line = hasUniformBorder ? convertBorderLine(border, "000000") : { type: "none" };
27
- ctx.slide.addShape(shapeType, {
28
- ...rectPxToIn(node),
24
+ const fill = hasBackground ? backgroundShapeFill(backgroundColor, backgroundGradient) : noneShapeFill();
25
+ const line = hasUniformBorder ? shapeOutline(border, "000000") : noShapeOutline();
26
+ addGlimpseShape(ctx, {
27
+ geometry: {
28
+ kind: "preset",
29
+ preset: shapeType
30
+ },
31
+ ...createShapeBoundsInput(node),
29
32
  fill,
30
- line,
33
+ outline: line
34
+ }, node, {
35
+ fillColor: backgroundColor,
36
+ fillOpacity: node.opacity,
37
+ backgroundGradient,
38
+ shadow,
31
39
  rectRadius,
32
- shadow: convertShadow(shadow)
40
+ dashType: border?.dashType
33
41
  });
34
42
  }
35
43
  renderPerSideBorderLines(node, perSideBorders, ctx);
36
44
  return;
37
45
  }
38
- if (hasBackground) ctx.slide.addShape(shapeType, {
39
- ...rectPxToIn(node),
40
- fill: resolveBackgroundFill(backgroundColor, node.opacity, gradientMarker),
41
- line: { type: "none" },
42
- rectRadius
46
+ if (hasBackground) addGlimpseShape(ctx, {
47
+ geometry: {
48
+ kind: "preset",
49
+ preset: shapeType
50
+ },
51
+ ...createShapeBoundsInput(node),
52
+ fill: backgroundShapeFill(backgroundColor, backgroundGradient),
53
+ outline: noShapeOutline()
54
+ }, node, {
55
+ fillColor: backgroundColor,
56
+ fillOpacity: node.opacity,
57
+ backgroundGradient,
58
+ rectRadius,
59
+ dashType: border?.dashType
43
60
  });
44
61
  if (backgroundImage) {
45
62
  const sizing = backgroundImage.sizing ?? "cover";
46
- const imageOptions = {
47
- ...rectPxToIn(node),
48
- sizing: {
49
- type: sizing,
50
- w: pxToIn(node.w),
51
- h: pxToIn(node.h)
52
- }
53
- };
54
63
  const cachedData = getImageData(backgroundImage.src, ctx.buildContext.imageDataCache);
55
- if (cachedData) ctx.slide.addImage({
56
- ...imageOptions,
57
- data: cachedData
58
- });
59
- else ctx.slide.addImage({
60
- ...imageOptions,
61
- path: backgroundImage.src
62
- });
64
+ addGlimpsePicture(ctx, node, imageBytesFromSource(backgroundImage.src, cachedData), { sizing: {
65
+ type: sizing,
66
+ w: node.w,
67
+ h: node.h
68
+ } });
63
69
  }
64
- if (hasUniformBorder || hasShadow) ctx.slide.addShape(shapeType, {
65
- ...rectPxToIn(node),
66
- fill: { type: "none" },
67
- line: hasUniformBorder ? convertBorderLine(border, "000000") : { type: "none" },
70
+ if (hasUniformBorder || hasShadow) addGlimpseShape(ctx, {
71
+ geometry: {
72
+ kind: "preset",
73
+ preset: shapeType
74
+ },
75
+ ...createShapeBoundsInput(node),
76
+ fill: noneShapeFill(),
77
+ outline: hasUniformBorder ? shapeOutline(border, "000000") : noShapeOutline()
78
+ }, node, {
79
+ shadow,
68
80
  rectRadius,
69
- shadow: convertShadow(shadow)
81
+ dashType: border?.dashType
70
82
  });
71
83
  renderPerSideBorderLines(node, perSideBorders, ctx);
72
84
  }
@@ -96,12 +108,17 @@ function renderBorderOnly(node, ctx) {
96
108
  return;
97
109
  }
98
110
  if (!hasVisibleBorder(border)) return;
99
- const shapeType = borderRadius ? ctx.pptx.ShapeType.roundRect : ctx.pptx.ShapeType.rect;
100
- ctx.slide.addShape(shapeType, {
101
- ...rectPxToIn(node),
102
- fill: { type: "none" },
103
- line: convertBorderLine(border, "000000"),
104
- rectRadius: resolveRectRadius(borderRadius, node.w, node.h)
111
+ addGlimpseShape(ctx, {
112
+ geometry: {
113
+ kind: "preset",
114
+ preset: borderRadius ? "roundRect" : "rect"
115
+ },
116
+ ...createShapeBoundsInput(node),
117
+ fill: noneShapeFill(),
118
+ outline: shapeOutline(border, "000000")
119
+ }, node, {
120
+ rectRadius: resolveRectRadius(borderRadius, node.w, node.h),
121
+ dashType: border.dashType
105
122
  });
106
123
  }
107
124
  /**
@@ -140,10 +157,15 @@ function renderPerSideBorderLines(node, perSideBorders, ctx) {
140
157
  for (const side of BORDER_SIDES) {
141
158
  const style = perSideBorders[side];
142
159
  if (!style) continue;
143
- ctx.slide.addShape(ctx.pptx.ShapeType.line, {
144
- ...rectPxToIn(edges[side]),
145
- line: convertBorderLine(style, "000000")
146
- });
160
+ addGlimpseShape(ctx, {
161
+ geometry: {
162
+ kind: "preset",
163
+ preset: "line"
164
+ },
165
+ ...createShapeBoundsInput(edges[side]),
166
+ fill: noneShapeFill(),
167
+ outline: shapeOutline(style, "000000")
168
+ }, edges[side], { dashType: style.dashType });
147
169
  }
148
170
  }
149
171
  //#endregion
@@ -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 { registerBackgroundGradient } from \"../gradientFills.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, rectPxToIn } from \"../units.ts\";\nimport {\n BORDER_SIDES,\n convertShadow,\n convertBorderLine,\n hasVisibleBorder,\n resolveBackgroundFill,\n resolvePerSideBorders,\n resolveRectRadius,\n type PerSideBorders,\n} from \"./visualStyle.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 // backgroundGradient はマーカー色の solidFill として描画し、\n // 出力時の後処理で gradFill に置換される (gradientFills.ts 参照)。\n // opacity はマーカー側ではなく gradFill のカラーストップの alpha で表現する\n const gradientMarker = backgroundGradient\n ? registerBackgroundGradient(\n backgroundGradient,\n node.opacity,\n ctx.buildContext.gradientFills,\n )\n : undefined;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n\n const hasBackground = Boolean(backgroundColor) || Boolean(gradientMarker);\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\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n\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 ? resolveBackgroundFill(backgroundColor, node.opacity, gradientMarker)\n : { type: \"none\" as const };\n\n const line = hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" as const };\n\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill,\n line,\n rectRadius,\n shadow: convertShadow(shadow),\n });\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n // backgroundImage がある場合は分割描画: 背景色 → 背景画像 → ボーダー\n\n // 1. 背景色\n if (hasBackground) {\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill: resolveBackgroundFill(\n backgroundColor,\n node.opacity,\n gradientMarker,\n ),\n line: { type: \"none\" as const },\n rectRadius,\n });\n }\n\n // 2. 背景画像\n if (backgroundImage) {\n const sizing = backgroundImage.sizing ?? \"cover\";\n const imageOptions: Record<string, unknown> = {\n ...rectPxToIn(node),\n sizing: {\n type: sizing,\n w: pxToIn(node.w),\n h: pxToIn(node.h),\n },\n };\n\n const cachedData = getImageData(\n backgroundImage.src,\n ctx.buildContext.imageDataCache,\n );\n if (cachedData) {\n ctx.slide.addImage({ ...imageOptions, data: cachedData });\n } else {\n ctx.slide.addImage({ ...imageOptions, path: backgroundImage.src });\n }\n }\n\n // 3. ボーダー\n if (hasUniformBorder || hasShadow) {\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill: { type: \"none\" as const },\n line: hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" as const },\n rectRadius,\n shadow: convertShadow(shadow),\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\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill: { type: \"none\" as const },\n line: convertBorderLine(border, \"000000\"),\n rectRadius: resolveRectRadius(borderRadius, node.w, node.h),\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 ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n ...rectPxToIn(edges[side]),\n line: convertBorderLine(style, \"000000\"),\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"}
@@ -0,0 +1,111 @@
1
+ import { pxToEmu } from "../units.js";
2
+ import * as fs from "fs";
3
+ import { asEmu, asOoxmlAngle, asOoxmlPercent } from "@pptx-glimpse/document";
4
+ //#region src/renderPptx/utils/glimpsePicture.ts
5
+ const FALLBACK_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
6
+ function dataToBytes(data) {
7
+ const base64 = data.includes(",") ? data.split(",").at(-1) : data;
8
+ return new Uint8Array(Buffer.from(base64 ?? "", "base64"));
9
+ }
10
+ function isPngOrJpeg(bytes) {
11
+ const isPng = bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
12
+ const isJpeg = bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
13
+ return isPng || isJpeg;
14
+ }
15
+ function supportedOrFallback(bytes) {
16
+ return isPngOrJpeg(bytes) ? bytes : dataToBytes(FALLBACK_PNG);
17
+ }
18
+ function imageBytesFromSource(src, data) {
19
+ if (data) return supportedOrFallback(dataToBytes(data));
20
+ if (src.startsWith("data:")) return supportedOrFallback(dataToBytes(src));
21
+ if (src.startsWith("http://") || src.startsWith("https://")) return dataToBytes(FALLBACK_PNG);
22
+ try {
23
+ return supportedOrFallback(new Uint8Array(fs.readFileSync(src)));
24
+ } catch {
25
+ return dataToBytes(FALLBACK_PNG);
26
+ }
27
+ }
28
+ function positiveEmu(valuePx) {
29
+ return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));
30
+ }
31
+ function createPictureRotationInput(rotate) {
32
+ return rotate !== void 0 ? asOoxmlAngle(Math.round(rotate * 6e4)) : void 0;
33
+ }
34
+ function pct(value) {
35
+ return asOoxmlPercent(Math.round(value));
36
+ }
37
+ function createSizingCrop(sizing, content) {
38
+ const box = {
39
+ x: sizing.x ?? 0,
40
+ y: sizing.y ?? 0,
41
+ w: sizing.w ?? content.w,
42
+ h: sizing.h ?? content.h
43
+ };
44
+ const img = {
45
+ w: content.w,
46
+ h: content.h
47
+ };
48
+ if (sizing.type === "crop") {
49
+ const cropBase = {
50
+ w: content.w,
51
+ h: content.h
52
+ };
53
+ return {
54
+ bounds: {
55
+ ...content,
56
+ w: box.w,
57
+ h: box.h
58
+ },
59
+ crop: {
60
+ left: pct(1e5 * (box.x / cropBase.w)),
61
+ right: pct(1e5 * ((cropBase.w - (box.x + box.w)) / cropBase.w)),
62
+ top: pct(1e5 * (box.y / cropBase.h)),
63
+ bottom: pct(1e5 * ((cropBase.h - (box.y + box.h)) / cropBase.h))
64
+ }
65
+ };
66
+ }
67
+ const imgRatio = img.h / img.w;
68
+ const boxRatio = box.h / box.w;
69
+ const isCoverBoxBased = boxRatio > imgRatio;
70
+ const isContainWidthBased = boxRatio > imgRatio;
71
+ const width = sizing.type === "cover" ? isCoverBoxBased ? box.h / imgRatio : box.w : isContainWidthBased ? box.w : box.h / imgRatio;
72
+ const height = sizing.type === "cover" ? isCoverBoxBased ? box.h : box.w * imgRatio : isContainWidthBased ? box.w * imgRatio : box.h;
73
+ const horizontal = 1e5 * .5 * (1 - box.w / width);
74
+ const vertical = 1e5 * .5 * (1 - box.h / height);
75
+ return {
76
+ bounds: {
77
+ ...content,
78
+ w: box.w,
79
+ h: box.h
80
+ },
81
+ crop: {
82
+ left: pct(horizontal),
83
+ right: pct(horizontal),
84
+ top: pct(vertical),
85
+ bottom: pct(vertical)
86
+ }
87
+ };
88
+ }
89
+ function addGlimpsePicture(ctx, content, bytes, options) {
90
+ const sizingResult = options?.sizing ? createSizingCrop(options.sizing, content) : {
91
+ bounds: content,
92
+ crop: void 0
93
+ };
94
+ const input = {
95
+ bytes,
96
+ offsetX: asEmu(Math.round(pxToEmu(content.x))),
97
+ offsetY: asEmu(Math.round(pxToEmu(content.y))),
98
+ width: positiveEmu(sizingResult.bounds.w),
99
+ height: positiveEmu(sizingResult.bounds.h),
100
+ rotation: createPictureRotationInput(options?.rotate),
101
+ crop: sizingResult.crop
102
+ };
103
+ ctx.buildContext.pptxAuthoring.registerPicture(input, {
104
+ name: options?.name,
105
+ shadow: options?.shadow
106
+ });
107
+ }
108
+ //#endregion
109
+ export { addGlimpsePicture, imageBytesFromSource };
110
+
111
+ //# sourceMappingURL=glimpsePicture.js.map
@@ -0,0 +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 } 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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"}
@@ -0,0 +1,71 @@
1
+ import { parseGradient } from "../../shared/gradient.js";
2
+ import { pxToEmu } from "../units.js";
3
+ import { toColorInput } from "../pptxAuthoring.js";
4
+ import { asEmu, asOoxmlAngle } from "@pptx-glimpse/document";
5
+ //#region src/renderPptx/utils/glimpseShape.ts
6
+ function positiveEmu(valuePx) {
7
+ return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));
8
+ }
9
+ function createShapeBoundsInput(bounds) {
10
+ return {
11
+ offsetX: asEmu(Math.round(pxToEmu(bounds.x))),
12
+ offsetY: asEmu(Math.round(pxToEmu(bounds.y))),
13
+ width: positiveEmu(bounds.w),
14
+ height: positiveEmu(bounds.h)
15
+ };
16
+ }
17
+ function createShapeRotationInput(rotate) {
18
+ return rotate !== void 0 ? asOoxmlAngle(Math.round(rotate * 6e4)) : void 0;
19
+ }
20
+ function solidShapeFill(color) {
21
+ const clean = toColorInput(color);
22
+ return clean ? {
23
+ kind: "solid",
24
+ color: clean
25
+ } : void 0;
26
+ }
27
+ function noneShapeFill() {
28
+ return { kind: "none" };
29
+ }
30
+ function gradientFallbackColor(value) {
31
+ return parseGradient(value)?.value.stops[0]?.color;
32
+ }
33
+ function backgroundShapeFill(backgroundColor, backgroundGradient) {
34
+ if (backgroundGradient) return solidShapeFill(gradientFallbackColor(backgroundGradient));
35
+ return solidShapeFill(backgroundColor);
36
+ }
37
+ function dashStyle(dashType) {
38
+ return dashType;
39
+ }
40
+ function shapeOutline(border, fallbackColor) {
41
+ if (!border) return void 0;
42
+ const fill = solidShapeFill(border.color ?? fallbackColor);
43
+ if (border.width === void 0 && fill === void 0 && border.dashType === void 0) return;
44
+ return {
45
+ width: border.width !== void 0 ? asEmu(Math.round(pxToEmu(border.width))) : void 0,
46
+ fill,
47
+ dash: dashStyle(border.dashType)
48
+ };
49
+ }
50
+ function noShapeOutline() {
51
+ return { fill: noneShapeFill() };
52
+ }
53
+ function arrowEndpoint(type) {
54
+ if (type === void 0 || type === "none") return void 0;
55
+ return {
56
+ type,
57
+ width: "med",
58
+ length: "med"
59
+ };
60
+ }
61
+ function addGlimpseShape(ctx, input, bounds, options) {
62
+ ctx.buildContext.pptxAuthoring.registerShape(input, {
63
+ ...options,
64
+ zeroWidth: bounds.w === 0,
65
+ zeroHeight: bounds.h === 0
66
+ });
67
+ }
68
+ //#endregion
69
+ export { addGlimpseShape, arrowEndpoint, backgroundShapeFill, createShapeBoundsInput, createShapeRotationInput, noShapeOutline, noneShapeFill, shapeOutline, solidShapeFill };
70
+
71
+ //# sourceMappingURL=glimpseShape.js.map
@@ -0,0 +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 { 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"}
@@ -0,0 +1,107 @@
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
+ //#region src/renderPptx/utils/glimpseTextBox.ts
5
+ function toUnderlineInput(underline) {
6
+ if (underline === void 0 || underline === false) return void 0;
7
+ if (underline === true) return true;
8
+ return {
9
+ style: underline.style,
10
+ color: toColorInput(underline.color)
11
+ };
12
+ }
13
+ function createGlimpseRunProperties(style) {
14
+ return {
15
+ fontFace: style.fontFace ?? "Noto Sans JP",
16
+ fontSize: asPt(pxToPt(style.fontSize ?? 24)),
17
+ color: toColorInput(style.color),
18
+ bold: style.bold,
19
+ italic: style.italic,
20
+ underline: toUnderlineInput(style.underline),
21
+ strike: style.strike,
22
+ baseline: style.subscript ? {
23
+ type: "percent",
24
+ value: asOoxmlPercent(-4e4)
25
+ } : style.superscript ? {
26
+ type: "percent",
27
+ value: asOoxmlPercent(3e4)
28
+ } : void 0,
29
+ highlight: toColorInput(style.highlight)
30
+ };
31
+ }
32
+ function createGlimpseParagraph(text, style, options = {}) {
33
+ const fontSizePx = style.fontSize ?? 24;
34
+ return {
35
+ properties: {
36
+ align: options.align,
37
+ lineSpacing: options.lineHeight !== void 0 ? asHundredthPt(Math.round(pxToPt(fontSizePx * options.lineHeight) * 100)) : void 0
38
+ },
39
+ runs: [{
40
+ text,
41
+ properties: createGlimpseRunProperties(style)
42
+ }]
43
+ };
44
+ }
45
+ function createGlimpseParagraphs(text, style, options = {}) {
46
+ return text.replace(/\r*\n/g, "\n").split("\n").map((line) => createGlimpseParagraph(line, style, options));
47
+ }
48
+ function textBoxInput(bounds, options, paragraphs) {
49
+ const margin = options.margin !== void 0 ? asEmu(Math.round(pxToEmu(options.margin))) : void 0;
50
+ return {
51
+ offsetX: asEmu(Math.round(pxToEmu(bounds.x))),
52
+ offsetY: asEmu(Math.round(pxToEmu(bounds.y))),
53
+ width: asEmu(Math.max(1, Math.round(pxToEmu(bounds.w)))),
54
+ height: asEmu(Math.max(1, Math.round(pxToEmu(bounds.h)))),
55
+ rotation: options.rotate !== void 0 ? asOoxmlAngle(Math.round(options.rotate * 6e4)) : void 0,
56
+ body: {
57
+ marginLeft: margin,
58
+ marginRight: margin,
59
+ marginTop: margin,
60
+ marginBottom: margin,
61
+ anchor: options.valign,
62
+ autoFit: options.autoFit ? "shape" : void 0
63
+ },
64
+ paragraphs
65
+ };
66
+ }
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)
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
+ }));
93
+ }
94
+ function addGlimpseTextBox(ctx, bounds, options = {}) {
95
+ const paragraphs = withListProperties(options.paragraphs ?? createGlimpseParagraphs(options.text ?? "", options, {
96
+ align: options.align,
97
+ lineHeight: options.lineHeight
98
+ }), options);
99
+ ctx.buildContext.pptxAuthoring.registerTextBox(textBoxInput(bounds, options, paragraphs), { hyperlinks: options.hyperlinks });
100
+ }
101
+ function isSupportedAutoNumScheme(value) {
102
+ return value === "arabicPeriod" || value === "arabicParenR" || value === "romanUcPeriod" || value === "romanLcPeriod" || value === "alphaUcPeriod" || value === "alphaLcPeriod" || value === "alphaLcParenR" || value === "alphaUcParenR" || value === "arabicPlain";
103
+ }
104
+ //#endregion
105
+ export { addGlimpseTextBox, createGlimpseParagraphs, createGlimpseRunProperties };
106
+
107
+ //# sourceMappingURL=glimpseTextBox.js.map
@@ -0,0 +1 @@
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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"}