@hirokisakabe/pom 10.0.0 → 10.2.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.
- package/README.md +1 -1
- package/dist/autoFit/autoFit.js +55 -15
- package/dist/autoFit/autoFit.js.map +1 -1
- package/dist/autoFit/strategies/reduceFontSize.js.map +1 -1
- package/dist/buildContext.js +4 -4
- package/dist/buildContext.js.map +1 -1
- package/dist/buildPptx.d.ts +3 -2
- package/dist/buildPptx.d.ts.map +1 -1
- package/dist/buildPptx.js +1 -1
- package/dist/buildPptx.js.map +1 -1
- package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
- package/dist/calcYogaLayout/fontLoader.d.ts +13 -0
- package/dist/calcYogaLayout/fontLoader.d.ts.map +1 -0
- package/dist/calcYogaLayout/fontLoader.js +70 -3
- package/dist/calcYogaLayout/fontLoader.js.map +1 -1
- package/dist/calcYogaLayout/measureText.d.ts.map +1 -1
- package/dist/calcYogaLayout/measureText.js +6 -6
- package/dist/calcYogaLayout/measureText.js.map +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/extractSlideMastersAsPptx.d.ts.map +1 -1
- package/dist/extractThemeTokensFromPptx.d.ts +0 -1
- package/dist/extractThemeTokensFromPptx.d.ts.map +1 -1
- package/dist/icons/renderIcon.js +12 -10
- package/dist/icons/renderIcon.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/parseXml/coercionRules.js.map +1 -1
- package/dist/parseXml/parseXml.d.ts +0 -1
- package/dist/parseXml/parseXml.d.ts.map +1 -1
- package/dist/parseXml/parseXml.js.map +1 -1
- package/dist/parseXml/serializeXml.d.ts +0 -1
- package/dist/parseXml/serializeXml.d.ts.map +1 -1
- package/dist/parseXml/serializeXml.js.map +1 -1
- package/dist/registry/definitions/icon.js +1 -1
- package/dist/registry/definitions/list.js +1 -1
- package/dist/registry/definitions/list.js.map +1 -1
- package/dist/registry/definitions/shape.js +1 -1
- package/dist/registry/definitions/shape.js.map +1 -1
- package/dist/registry/definitions/text.js +1 -1
- package/dist/registry/definitions/text.js.map +1 -1
- package/dist/renderPptx/authoringContext.js +101 -0
- package/dist/renderPptx/authoringContext.js.map +1 -0
- package/dist/renderPptx/{pptxAuthoring.js → glimpseAdapter.js} +83 -152
- package/dist/renderPptx/glimpseAdapter.js.map +1 -0
- package/dist/renderPptx/nodes/arrow.js +49 -7
- package/dist/renderPptx/nodes/arrow.js.map +1 -1
- package/dist/renderPptx/nodes/chart.js +2 -2
- package/dist/renderPptx/nodes/chart.js.map +1 -1
- package/dist/renderPptx/nodes/icon.js +1 -1
- package/dist/renderPptx/nodes/icon.js.map +1 -1
- package/dist/renderPptx/nodes/list.js +11 -19
- package/dist/renderPptx/nodes/list.js.map +1 -1
- package/dist/renderPptx/nodes/shape.js +18 -19
- package/dist/renderPptx/nodes/shape.js.map +1 -1
- package/dist/renderPptx/nodes/table.js +12 -21
- package/dist/renderPptx/nodes/table.js.map +1 -1
- package/dist/renderPptx/nodes/text.js +16 -1
- package/dist/renderPptx/nodes/text.js.map +1 -1
- package/dist/renderPptx/renderPptx.js +93 -40
- package/dist/renderPptx/renderPptx.js.map +1 -1
- package/dist/renderPptx/utils/connectorSites.js +58 -0
- package/dist/renderPptx/utils/connectorSites.js.map +1 -0
- package/dist/renderPptx/utils/glimpsePicture.js +2 -4
- package/dist/renderPptx/utils/glimpsePicture.js.map +1 -1
- package/dist/renderPptx/utils/glimpseShape.js +3 -3
- package/dist/renderPptx/utils/glimpseShape.js.map +1 -1
- package/dist/renderPptx/utils/glimpseTextBox.js +3 -30
- package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -1
- package/dist/renderPptx/utils/straightLine.js.map +1 -1
- package/dist/renderPptx/writablePptx.d.ts +1 -1
- package/dist/renderPptx/writablePptx.d.ts.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/package.json +10 -6
- package/dist/renderPptx/pptxAuthoring.js.map +0 -1
|
@@ -2,29 +2,37 @@ import { getImageData } from "../shared/measureImage.js";
|
|
|
2
2
|
import { resolveBoxSpacing } from "../shared/boxSpacing.js";
|
|
3
3
|
import { getNodeDef } from "../registry/nodeRegistry.js";
|
|
4
4
|
import { pxToEmu, pxToPt } from "./units.js";
|
|
5
|
-
import { toColorInput } from "./
|
|
6
|
-
import
|
|
5
|
+
import { createGlimpseRunProperties, enrichPictureInput, enrichShapeInput, toColorInput, toSlideBackgroundGradient } from "./glimpseAdapter.js";
|
|
6
|
+
import "./utils/glimpseTextBox.js";
|
|
7
7
|
import { imageBytesFromSource } from "./utils/glimpsePicture.js";
|
|
8
8
|
import { createShapeBoundsInput, noneShapeFill, shapeOutline, solidShapeFill } from "./utils/glimpseShape.js";
|
|
9
9
|
import "../registry/index.js";
|
|
10
10
|
import { renderBackgroundAndBorder, renderBorderOnly } from "./utils/backgroundBorder.js";
|
|
11
|
+
import { PptxAuthoringContext } from "./authoringContext.js";
|
|
11
12
|
import { createWritablePptx } from "./writablePptx.js";
|
|
12
|
-
import {
|
|
13
|
+
import { asEmu, asPt, createPptx } from "@pptx-glimpse/document";
|
|
13
14
|
//#region src/renderPptx/renderPptx.ts
|
|
14
|
-
function
|
|
15
|
-
const
|
|
15
|
+
function buildIdMaps(node, diagnostics) {
|
|
16
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
16
18
|
function traverse(n) {
|
|
17
|
-
if (n.id) if (
|
|
18
|
-
else
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if (n.id) if (positions.has(n.id)) diagnostics.add("DUPLICATE_NODE_ID", `Duplicate node id "${n.id}" — only the first occurrence will be used for Arrow references`);
|
|
20
|
+
else {
|
|
21
|
+
positions.set(n.id, {
|
|
22
|
+
x: n.x,
|
|
23
|
+
y: n.y,
|
|
24
|
+
w: n.w,
|
|
25
|
+
h: n.h
|
|
26
|
+
});
|
|
27
|
+
nodes.set(n.id, n);
|
|
28
|
+
}
|
|
24
29
|
if (n.type === "vstack" || n.type === "hstack" || n.type === "layer") for (const child of n.children) traverse(child);
|
|
25
30
|
}
|
|
26
31
|
traverse(node);
|
|
27
|
-
return
|
|
32
|
+
return {
|
|
33
|
+
positions,
|
|
34
|
+
nodes
|
|
35
|
+
};
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
30
38
|
* zIndex でソートして描画順を制御する(安定ソート)
|
|
@@ -71,13 +79,13 @@ function masterTextRunProperties(obj) {
|
|
|
71
79
|
fontSize: obj.fontSize === void 0 ? void 0 : properties.fontSize
|
|
72
80
|
};
|
|
73
81
|
}
|
|
74
|
-
function addMasterContent(buildContext, master) {
|
|
75
|
-
const target =
|
|
82
|
+
function addMasterContent(buildContext, authoring, master) {
|
|
83
|
+
const target = authoring.source.slideMasters[0]?.handle;
|
|
76
84
|
if (!target) throw new Error("createPptx did not create a slide master handle");
|
|
77
|
-
|
|
85
|
+
authoring.selectTarget(target);
|
|
78
86
|
for (const obj of master.objects ?? []) switch (obj.type) {
|
|
79
87
|
case "text":
|
|
80
|
-
|
|
88
|
+
authoring.addTextBox({
|
|
81
89
|
...masterBounds(obj),
|
|
82
90
|
body: {
|
|
83
91
|
anchor: "middle",
|
|
@@ -101,13 +109,13 @@ function addMasterContent(buildContext, master) {
|
|
|
101
109
|
});
|
|
102
110
|
break;
|
|
103
111
|
case "image":
|
|
104
|
-
|
|
112
|
+
authoring.addPicture(enrichPictureInput({
|
|
105
113
|
...masterBounds(obj),
|
|
106
114
|
bytes: imageBytesFromSource(obj.src, getImageData(obj.src, buildContext.imageDataCache))
|
|
107
|
-
});
|
|
115
|
+
}, void 0));
|
|
108
116
|
break;
|
|
109
117
|
case "rect":
|
|
110
|
-
|
|
118
|
+
authoring.addShape(enrichShapeInput({
|
|
111
119
|
...masterBounds(obj),
|
|
112
120
|
geometry: {
|
|
113
121
|
kind: "preset",
|
|
@@ -118,10 +126,10 @@ function addMasterContent(buildContext, master) {
|
|
|
118
126
|
}, {
|
|
119
127
|
fillColor: obj.fill?.color,
|
|
120
128
|
fillOpacity: obj.fill?.transparency === void 0 ? void 0 : 1 - obj.fill.transparency / 100
|
|
121
|
-
});
|
|
129
|
+
}));
|
|
122
130
|
break;
|
|
123
131
|
case "line":
|
|
124
|
-
|
|
132
|
+
authoring.addShape({
|
|
125
133
|
...masterBounds(obj),
|
|
126
134
|
geometry: {
|
|
127
135
|
kind: "preset",
|
|
@@ -137,7 +145,7 @@ function addMasterContent(buildContext, master) {
|
|
|
137
145
|
}
|
|
138
146
|
if (master.slideNumber) {
|
|
139
147
|
const value = master.slideNumber;
|
|
140
|
-
|
|
148
|
+
authoring.addSlideNumber({
|
|
141
149
|
offsetX: asEmu(Math.round(pxToEmu(value.x))),
|
|
142
150
|
offsetY: asEmu(Math.round(pxToEmu(value.y))),
|
|
143
151
|
width: value.w === void 0 ? asEmu(8e5) : asEmu(Math.round(pxToEmu(value.w))),
|
|
@@ -147,7 +155,7 @@ function addMasterContent(buildContext, master) {
|
|
|
147
155
|
fontSize: value.fontSize ? asPt(pxToPt(value.fontSize)) : void 0,
|
|
148
156
|
color: toColorInput(value.color)
|
|
149
157
|
}
|
|
150
|
-
})
|
|
158
|
+
});
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
161
|
/**
|
|
@@ -159,7 +167,7 @@ function addMasterContent(buildContext, master) {
|
|
|
159
167
|
*/
|
|
160
168
|
function renderPptx(pages, slidePx, buildContext, master) {
|
|
161
169
|
const margin = master?.margin === void 0 ? void 0 : resolveBoxSpacing(master.margin);
|
|
162
|
-
|
|
170
|
+
const authoring = new PptxAuthoringContext(createPptx({
|
|
163
171
|
slideSize: {
|
|
164
172
|
width: asEmu(Math.round(pxToEmu(slidePx.w))),
|
|
165
173
|
height: asEmu(Math.round(pxToEmu(slidePx.h)))
|
|
@@ -177,40 +185,60 @@ function renderPptx(pages, slidePx, buildContext, master) {
|
|
|
177
185
|
left: asEmu(Math.round(pxToEmu(margin.left)))
|
|
178
186
|
} : void 0
|
|
179
187
|
}
|
|
180
|
-
});
|
|
181
|
-
buildContext
|
|
182
|
-
if (master) addMasterContent(buildContext, master);
|
|
188
|
+
}), margin !== void 0);
|
|
189
|
+
if (master) addMasterContent(buildContext, authoring, master);
|
|
183
190
|
for (const [pageIndex, data] of pages.entries()) {
|
|
184
191
|
if (pageIndex > 0) {
|
|
185
|
-
const layoutPartPath =
|
|
192
|
+
const layoutPartPath = authoring.source.slideLayouts[0]?.partPath;
|
|
186
193
|
if (!layoutPartPath) throw new Error("createPptx did not create a slide layout");
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
const slideHandle = authoring.addEmptySlideFromLayout({ layoutPartPath });
|
|
195
|
+
authoring.selectTarget(slideHandle);
|
|
196
|
+
} else {
|
|
197
|
+
const slideHandle = authoring.source.slides[pageIndex]?.handle;
|
|
198
|
+
if (!slideHandle) throw new Error(`slide handle was not found: ${pageIndex + 1}`);
|
|
199
|
+
authoring.selectTarget(slideHandle);
|
|
189
200
|
}
|
|
190
|
-
const
|
|
191
|
-
if (!slideHandle) throw new Error(`slide handle was not found: ${pageIndex + 1}`);
|
|
192
|
-
buildContext.pptxAuthoring.selectSlide(slideHandle);
|
|
201
|
+
const idMaps = buildIdMaps(data, buildContext.diagnostics);
|
|
193
202
|
const ctx = {
|
|
194
203
|
buildContext,
|
|
195
|
-
|
|
204
|
+
authoring,
|
|
205
|
+
idPositionMap: idMaps.positions,
|
|
206
|
+
idNodeMap: idMaps.nodes,
|
|
207
|
+
connectorTargetMap: /* @__PURE__ */ new Map()
|
|
196
208
|
};
|
|
209
|
+
const deferredArrows = [];
|
|
197
210
|
const isLinelike = data.type === "line" || data.type === "arrow";
|
|
198
211
|
const rootBackgroundColor = !isLinelike ? data.backgroundColor : void 0;
|
|
199
212
|
const rootBackgroundGradient = !isLinelike ? data.backgroundGradient : void 0;
|
|
200
213
|
const rootHasOpacity = !isLinelike && "opacity" in data && data.opacity !== void 0;
|
|
201
|
-
const
|
|
202
|
-
|
|
214
|
+
const rootGradient = rootBackgroundGradient && !rootHasOpacity ? toSlideBackgroundGradient(rootBackgroundGradient) : void 0;
|
|
215
|
+
const rootGradientApplied = rootGradient !== void 0;
|
|
216
|
+
if (rootGradient) authoring.setSlideBackground(rootGradient);
|
|
217
|
+
if (!rootGradientApplied && rootBackgroundColor && !rootBackgroundGradient && !rootHasOpacity) authoring.setSlideBackground({
|
|
218
|
+
kind: "solid",
|
|
219
|
+
color: toColorInput(rootBackgroundColor)
|
|
220
|
+
});
|
|
203
221
|
const rootBackgroundImage = !isLinelike ? data.backgroundImage : void 0;
|
|
204
222
|
if (rootBackgroundImage) {
|
|
205
223
|
const cachedData = getImageData(rootBackgroundImage.src, buildContext.imageDataCache);
|
|
206
|
-
|
|
224
|
+
authoring.setSlideBackground({
|
|
225
|
+
kind: "image",
|
|
226
|
+
bytes: imageBytesFromSource(rootBackgroundImage.src, cachedData)
|
|
227
|
+
});
|
|
207
228
|
}
|
|
208
229
|
/**
|
|
209
230
|
* node をスライドにレンダリングする
|
|
210
231
|
* @param isRoot ルートノードかどうか(ルートノードの background は slide.background で処理済み)
|
|
211
232
|
*/
|
|
212
233
|
function renderNode(node, isRoot = false) {
|
|
213
|
-
if (node.type
|
|
234
|
+
if (node.type === "arrow") {
|
|
235
|
+
deferredArrows.push({
|
|
236
|
+
node,
|
|
237
|
+
insertionIndex: authoring.currentTargetShapeHandles().length
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (node.type !== "line") if (isRoot && (rootBackgroundImage || (rootBackgroundColor || rootGradientApplied) && !rootHasOpacity)) renderBorderOnly(node, ctx);
|
|
214
242
|
else renderBackgroundAndBorder(node, ctx);
|
|
215
243
|
const def = getNodeDef(node.type);
|
|
216
244
|
switch (def.category) {
|
|
@@ -227,8 +255,33 @@ function renderPptx(pages, slidePx, buildContext, master) {
|
|
|
227
255
|
}
|
|
228
256
|
}
|
|
229
257
|
renderNode(data, true);
|
|
258
|
+
const baseShapeOrder = [...authoring.currentTargetShapeHandles()];
|
|
259
|
+
const authoredArrows = [];
|
|
260
|
+
for (const arrow of deferredArrows) {
|
|
261
|
+
const beforeCount = authoring.currentTargetShapeHandles().length;
|
|
262
|
+
renderArrowNodeFromRegistry(arrow.node, ctx);
|
|
263
|
+
const after = authoring.currentTargetShapeHandles();
|
|
264
|
+
if (after.length === beforeCount + 1) authoredArrows.push({
|
|
265
|
+
insertionIndex: arrow.insertionIndex,
|
|
266
|
+
handle: after.at(-1)
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (authoredArrows.length > 0) {
|
|
270
|
+
const orderedHandles = [];
|
|
271
|
+
for (let index = 0; index <= baseShapeOrder.length; index += 1) {
|
|
272
|
+
for (const arrow of authoredArrows) if (arrow.insertionIndex === index) orderedHandles.push(arrow.handle);
|
|
273
|
+
const baseHandle = baseShapeOrder[index];
|
|
274
|
+
if (baseHandle) orderedHandles.push(baseHandle);
|
|
275
|
+
}
|
|
276
|
+
authoring.reorderCurrentTargetShapes(orderedHandles);
|
|
277
|
+
}
|
|
230
278
|
}
|
|
231
|
-
return createWritablePptx(() =>
|
|
279
|
+
return createWritablePptx(() => authoring.source);
|
|
280
|
+
}
|
|
281
|
+
function renderArrowNodeFromRegistry(node, ctx) {
|
|
282
|
+
const def = getNodeDef("arrow");
|
|
283
|
+
if (!def.render) throw new Error("No render function registered for Arrow");
|
|
284
|
+
def.render(node, ctx);
|
|
232
285
|
}
|
|
233
286
|
//#endregion
|
|
234
287
|
export { renderPptx };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderPptx.js","names":[],"sources":["../../src/renderPptx/renderPptx.ts"],"sourcesContent":["import {\n addEmptySlideFromLayout,\n addSlideNumber,\n asEmu,\n asPt,\n createPptx,\n type AddTextBoxRunPropertiesInput,\n type CreatePptxBackground,\n} from \"@pptx-glimpse/document\";\nimport type { PositionedNode, SlideMasterOptions } from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { RenderContext, NodeBounds } from \"./types.ts\";\nimport { pxToEmu, pxToPt } from \"./units.ts\";\nimport { getImageData } from \"../shared/measureImage.ts\";\nimport { resolveBoxSpacing } from \"../shared/boxSpacing.ts\";\nimport {\n renderBackgroundAndBorder,\n renderBorderOnly,\n} from \"./utils/backgroundBorder.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\nimport { toColorInput } from \"./pptxAuthoring.ts\";\nimport { createGlimpseRunProperties } from \"./utils/glimpseTextBox.ts\";\nimport {\n createShapeBoundsInput,\n noneShapeFill,\n shapeOutline,\n solidShapeFill,\n} from \"./utils/glimpseShape.ts\";\nimport { imageBytesFromSource } from \"./utils/glimpsePicture.ts\";\nimport { createWritablePptx } from \"./writablePptx.ts\";\n\ntype SlidePx = { w: number; h: number };\n\nfunction buildIdPositionMap(\n node: PositionedNode,\n diagnostics: import(\"../buildContext.ts\").BuildContext[\"diagnostics\"],\n): Map<string, NodeBounds> {\n const map = new Map<string, NodeBounds>();\n\n function traverse(n: PositionedNode) {\n if (n.id) {\n if (map.has(n.id)) {\n diagnostics.add(\n \"DUPLICATE_NODE_ID\",\n `Duplicate node id \"${n.id}\" — only the first occurrence will be used for Arrow references`,\n );\n } else {\n map.set(n.id, { x: n.x, y: n.y, w: n.w, h: n.h });\n }\n }\n if (n.type === \"vstack\" || n.type === \"hstack\" || n.type === \"layer\") {\n for (const child of n.children) {\n traverse(child);\n }\n }\n }\n\n traverse(node);\n return map;\n}\n\n/**\n * zIndex でソートして描画順を制御する(安定ソート)\n * zIndex が小さいノードが先に描画される(PowerPoint は追加順に重ねるため)\n */\nfunction sortByZIndex<T extends { zIndex?: number }>(children: T[]): T[] {\n // すべての子要素に zIndex が未設定の場合はそのまま返す\n if (children.every((c) => c.zIndex === undefined)) return children;\n return [...children].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));\n}\n\nfunction masterBackground(\n master: SlideMasterOptions | undefined,\n buildContext: BuildContext,\n): CreatePptxBackground | undefined {\n const background = master?.background;\n if (!background) return undefined;\n if (\"color\" in background) {\n return { kind: \"solid\", color: toColorInput(background.color)! };\n }\n if (\"data\" in background) {\n return { kind: \"image\", bytes: imageBytesFromSource(\"\", background.data) };\n }\n const src = \"path\" in background ? background.path : background.image;\n return {\n kind: \"image\",\n bytes: imageBytesFromSource(\n src,\n getImageData(src, buildContext.imageDataCache),\n ),\n };\n}\n\nfunction masterBounds(obj: { x: number; y: number; w: number; h: number }) {\n return createShapeBoundsInput(obj);\n}\n\nfunction masterTextRunProperties(\n obj: Extract<\n NonNullable<SlideMasterOptions[\"objects\"]>[number],\n { type: \"text\" }\n >,\n): AddTextBoxRunPropertiesInput {\n const properties = createGlimpseRunProperties({\n fontSize: obj.fontSize,\n fontFace: obj.fontFamily,\n color: obj.color,\n bold: obj.bold,\n italic: obj.italic,\n underline: obj.underline,\n strike: obj.strike,\n highlight: obj.highlight,\n });\n return {\n ...properties,\n fontFace: obj.fontFamily === undefined ? undefined : properties.fontFace,\n fontSize: obj.fontSize === undefined ? undefined : properties.fontSize,\n };\n}\n\nfunction addMasterContent(\n buildContext: BuildContext,\n master: SlideMasterOptions,\n): void {\n const target = buildContext.pptxAuthoring.source.slideMasters[0]?.handle;\n if (!target)\n throw new Error(\"createPptx did not create a slide master handle\");\n buildContext.pptxAuthoring.selectSlide(target);\n for (const obj of master.objects ?? []) {\n switch (obj.type) {\n case \"text\":\n buildContext.pptxAuthoring.registerTextBox({\n ...masterBounds(obj),\n body: {\n anchor: \"middle\",\n marginLeft: asEmu(91440),\n marginRight: asEmu(91440),\n marginTop: asEmu(91440),\n marginBottom: asEmu(91440),\n },\n paragraphs: [\n {\n properties: {\n align: obj.textAlign,\n marginLeft: asEmu(0),\n indent: asEmu(0),\n bullet: { type: \"none\" },\n },\n runs: [\n {\n text: obj.text,\n properties: masterTextRunProperties(obj),\n },\n ],\n },\n ],\n });\n break;\n case \"image\":\n buildContext.pptxAuthoring.registerPicture({\n ...masterBounds(obj),\n bytes: imageBytesFromSource(\n obj.src,\n getImageData(obj.src, buildContext.imageDataCache),\n ),\n });\n break;\n case \"rect\":\n buildContext.pptxAuthoring.registerShape(\n {\n ...masterBounds(obj),\n geometry: { kind: \"preset\", preset: \"rect\" },\n fill: obj.fill\n ? solidShapeFill(obj.fill.color ?? \"FFFFFF\")\n : noneShapeFill(),\n outline: obj.border ? shapeOutline(obj.border) : undefined,\n },\n {\n fillColor: obj.fill?.color,\n fillOpacity:\n obj.fill?.transparency === undefined\n ? undefined\n : 1 - obj.fill.transparency / 100,\n },\n );\n break;\n case \"line\":\n buildContext.pptxAuthoring.registerShape({\n ...masterBounds(obj),\n geometry: { kind: \"preset\", preset: \"line\" },\n fill: noneShapeFill(),\n outline: shapeOutline(obj.line ?? { color: \"000000\", width: 1 }),\n });\n break;\n }\n }\n if (master.slideNumber) {\n const value = master.slideNumber;\n buildContext.pptxAuthoring.replaceSource(\n addSlideNumber(buildContext.pptxAuthoring.source, target, {\n offsetX: asEmu(Math.round(pxToEmu(value.x))),\n offsetY: asEmu(Math.round(pxToEmu(value.y))),\n width:\n value.w === undefined\n ? asEmu(800000)\n : asEmu(Math.round(pxToEmu(value.w))),\n height:\n value.h === undefined\n ? asEmu(300000)\n : asEmu(Math.round(pxToEmu(value.h))),\n properties: {\n fontFace: value.fontFamily,\n fontSize: value.fontSize ? asPt(pxToPt(value.fontSize)) : undefined,\n color: toColorInput(value.color),\n },\n }),\n );\n }\n}\n\n/**\n * PositionedNode ツリーを glimpse の PPTX source model に変換する\n * @param pages PositionedNode ツリーの配列(各要素が1ページ)\n * @param slidePx スライド全体のサイズ(px)\n * @param master スライドマスターオプション(省略可能)\n * @returns PPTX の write / writeFile / stream 互換 facade\n */\nexport function renderPptx(\n pages: PositionedNode[],\n slidePx: SlidePx,\n buildContext: BuildContext,\n master?: SlideMasterOptions,\n) {\n const margin =\n master?.margin === undefined ? undefined : resolveBoxSpacing(master.margin);\n let source = createPptx({\n slideSize: {\n width: asEmu(Math.round(pxToEmu(slidePx.w))),\n height: asEmu(Math.round(pxToEmu(slidePx.h))),\n },\n slideMaster: {\n name: master?.title ?? \"POM_MASTER\",\n background: masterBackground(master, buildContext),\n },\n slideLayout: {\n name: \"POM_LAYOUT\",\n margin: margin\n ? {\n top: asEmu(Math.round(pxToEmu(margin.top))),\n right: asEmu(Math.round(pxToEmu(margin.right))),\n bottom: asEmu(Math.round(pxToEmu(margin.bottom))),\n left: asEmu(Math.round(pxToEmu(margin.left))),\n }\n : undefined,\n },\n });\n buildContext.pptxAuthoring.initialize(source, margin !== undefined);\n if (master) addMasterContent(buildContext, master);\n\n for (const [pageIndex, data] of pages.entries()) {\n if (pageIndex > 0) {\n const layoutPartPath =\n buildContext.pptxAuthoring.source.slideLayouts[0]?.partPath;\n if (!layoutPartPath)\n throw new Error(\"createPptx did not create a slide layout\");\n source = addEmptySlideFromLayout(buildContext.pptxAuthoring.source, {\n layoutPartPath,\n });\n buildContext.pptxAuthoring.replaceSource(source);\n }\n const slideHandle =\n buildContext.pptxAuthoring.source.slides[pageIndex]?.handle;\n if (!slideHandle)\n throw new Error(`slide handle was not found: ${pageIndex + 1}`);\n buildContext.pptxAuthoring.selectSlide(slideHandle);\n const idPositionMap = buildIdPositionMap(data, buildContext.diagnostics);\n const ctx: RenderContext = { buildContext, idPositionMap };\n\n // ルートノードの backgroundColor はスライドの background プロパティとして適用\n // これにより、マスタースライドのオブジェクトを覆い隠さない\n // line/arrow ノードは backgroundColor を持たないためスキップ\n // ただし opacity が指定されている場合は slide.background では透過を表現できないため、\n // renderBackgroundAndBorder で描画する\n const isLinelike = data.type === \"line\" || data.type === \"arrow\";\n const rootBackgroundColor = !isLinelike ? data.backgroundColor : undefined;\n const rootBackgroundGradient = !isLinelike\n ? data.backgroundGradient\n : undefined;\n const rootHasOpacity =\n !isLinelike && \"opacity\" in data && data.opacity !== undefined;\n const rootGradientApplied =\n rootBackgroundGradient && !rootHasOpacity\n ? buildContext.pptxAuthoring.setSlideBackgroundGradient(\n rootBackgroundGradient,\n )\n : false;\n if (\n !rootGradientApplied &&\n rootBackgroundColor &&\n !rootBackgroundGradient &&\n !rootHasOpacity\n ) {\n buildContext.pptxAuthoring.setSlideBackgroundSolid(rootBackgroundColor);\n }\n\n // ルートノードの backgroundImage はスライドの background プロパティとして適用\n // backgroundColor と backgroundImage の両方がある場合、backgroundImage が優先\n const rootBackgroundImage = !isLinelike ? data.backgroundImage : undefined;\n if (rootBackgroundImage) {\n const cachedData = getImageData(\n rootBackgroundImage.src,\n buildContext.imageDataCache,\n );\n buildContext.pptxAuthoring.setSlideBackgroundImage(\n imageBytesFromSource(rootBackgroundImage.src, cachedData),\n );\n }\n\n /**\n * node をスライドにレンダリングする\n * @param isRoot ルートノードかどうか(ルートノードの background は slide.background で処理済み)\n */\n function renderNode(node: PositionedNode, isRoot = false) {\n // line/arrow ノードは backgroundColor/border を持たないため、background/border の描画をスキップ\n if (node.type !== \"line\" && node.type !== \"arrow\") {\n // ルートノードの backgroundColor/backgroundImage は既に slide.background に適用済みなのでスキップ\n // ただし opacity がある場合は slide.background では透過を表現できないため通常描画\n if (\n isRoot &&\n (rootBackgroundImage ||\n ((rootBackgroundColor || rootGradientApplied) && !rootHasOpacity))\n ) {\n // border のみ描画(backgroundColor/backgroundImage はスキップ)\n renderBorderOnly(node, ctx);\n } else {\n renderBackgroundAndBorder(node, ctx);\n }\n }\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"leaf\":\n if (!def.render) {\n throw new Error(\n `No render function registered for leaf node: ${node.type}`,\n );\n }\n def.render(node, ctx);\n break;\n\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n PositionedNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n // zIndex でソートして描画順を制御(値が小さいものが先に描画される)\n for (const child of sortByZIndex(containerNode.children)) {\n renderNode(child);\n }\n break;\n }\n }\n }\n\n renderNode(data, true); // ルートノードとして処理\n }\n\n return createWritablePptx(() => buildContext.pptxAuthoring.source);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"renderPptx.js","names":[],"sources":["../../src/renderPptx/renderPptx.ts"],"sourcesContent":["import {\n asEmu,\n asPt,\n createPptx,\n type AddTextBoxRunPropertiesInput,\n type CreatePptxBackground,\n} from \"@pptx-glimpse/document\";\nimport type { PositionedNode, SlideMasterOptions } from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { RenderContext, NodeBounds } from \"./types.ts\";\nimport { pxToEmu, pxToPt } from \"./units.ts\";\nimport { getImageData } from \"../shared/measureImage.ts\";\nimport { resolveBoxSpacing } from \"../shared/boxSpacing.ts\";\nimport {\n renderBackgroundAndBorder,\n renderBorderOnly,\n} from \"./utils/backgroundBorder.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\nimport {\n enrichPictureInput,\n enrichShapeInput,\n toColorInput,\n toSlideBackgroundGradient,\n} from \"./glimpseAdapter.ts\";\nimport { PptxAuthoringContext } from \"./authoringContext.ts\";\nimport { createGlimpseRunProperties } from \"./utils/glimpseTextBox.ts\";\nimport {\n createShapeBoundsInput,\n noneShapeFill,\n shapeOutline,\n solidShapeFill,\n} from \"./utils/glimpseShape.ts\";\nimport { imageBytesFromSource } from \"./utils/glimpsePicture.ts\";\nimport { createWritablePptx } from \"./writablePptx.ts\";\n\ntype SlidePx = { w: number; h: number };\n\nfunction buildIdMaps(\n node: PositionedNode,\n diagnostics: import(\"../buildContext.ts\").BuildContext[\"diagnostics\"],\n): {\n positions: Map<string, NodeBounds>;\n nodes: Map<string, PositionedNode>;\n} {\n const positions = new Map<string, NodeBounds>();\n const nodes = new Map<string, PositionedNode>();\n\n function traverse(n: PositionedNode) {\n if (n.id) {\n if (positions.has(n.id)) {\n diagnostics.add(\n \"DUPLICATE_NODE_ID\",\n `Duplicate node id \"${n.id}\" — only the first occurrence will be used for Arrow references`,\n );\n } else {\n positions.set(n.id, { x: n.x, y: n.y, w: n.w, h: n.h });\n nodes.set(n.id, n);\n }\n }\n if (n.type === \"vstack\" || n.type === \"hstack\" || n.type === \"layer\") {\n for (const child of n.children) {\n traverse(child);\n }\n }\n }\n\n traverse(node);\n return { positions, nodes };\n}\n\n/**\n * zIndex でソートして描画順を制御する(安定ソート)\n * zIndex が小さいノードが先に描画される(PowerPoint は追加順に重ねるため)\n */\nfunction sortByZIndex<T extends { zIndex?: number }>(children: T[]): T[] {\n // すべての子要素に zIndex が未設定の場合はそのまま返す\n if (children.every((c) => c.zIndex === undefined)) return children;\n return [...children].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));\n}\n\nfunction masterBackground(\n master: SlideMasterOptions | undefined,\n buildContext: BuildContext,\n): CreatePptxBackground | undefined {\n const background = master?.background;\n if (!background) return undefined;\n if (\"color\" in background) {\n return { kind: \"solid\", color: toColorInput(background.color)! };\n }\n if (\"data\" in background) {\n return { kind: \"image\", bytes: imageBytesFromSource(\"\", background.data) };\n }\n const src = \"path\" in background ? background.path : background.image;\n return {\n kind: \"image\",\n bytes: imageBytesFromSource(\n src,\n getImageData(src, buildContext.imageDataCache),\n ),\n };\n}\n\nfunction masterBounds(obj: { x: number; y: number; w: number; h: number }) {\n return createShapeBoundsInput(obj);\n}\n\nfunction masterTextRunProperties(\n obj: Extract<\n NonNullable<SlideMasterOptions[\"objects\"]>[number],\n { type: \"text\" }\n >,\n): AddTextBoxRunPropertiesInput {\n const properties = createGlimpseRunProperties({\n fontSize: obj.fontSize,\n fontFace: obj.fontFamily,\n color: obj.color,\n bold: obj.bold,\n italic: obj.italic,\n underline: obj.underline,\n strike: obj.strike,\n highlight: obj.highlight,\n });\n return {\n ...properties,\n fontFace: obj.fontFamily === undefined ? undefined : properties.fontFace,\n fontSize: obj.fontSize === undefined ? undefined : properties.fontSize,\n };\n}\n\nfunction addMasterContent(\n buildContext: BuildContext,\n authoring: PptxAuthoringContext,\n master: SlideMasterOptions,\n): void {\n const target = authoring.source.slideMasters[0]?.handle;\n if (!target)\n throw new Error(\"createPptx did not create a slide master handle\");\n authoring.selectTarget(target);\n for (const obj of master.objects ?? []) {\n switch (obj.type) {\n case \"text\":\n authoring.addTextBox({\n ...masterBounds(obj),\n body: {\n anchor: \"middle\",\n marginLeft: asEmu(91440),\n marginRight: asEmu(91440),\n marginTop: asEmu(91440),\n marginBottom: asEmu(91440),\n },\n paragraphs: [\n {\n properties: {\n align: obj.textAlign,\n marginLeft: asEmu(0),\n indent: asEmu(0),\n bullet: { type: \"none\" },\n },\n runs: [\n {\n text: obj.text,\n properties: masterTextRunProperties(obj),\n },\n ],\n },\n ],\n });\n break;\n case \"image\":\n authoring.addPicture(\n enrichPictureInput(\n {\n ...masterBounds(obj),\n bytes: imageBytesFromSource(\n obj.src,\n getImageData(obj.src, buildContext.imageDataCache),\n ),\n },\n undefined,\n ),\n );\n break;\n case \"rect\":\n authoring.addShape(\n enrichShapeInput(\n {\n ...masterBounds(obj),\n geometry: { kind: \"preset\", preset: \"rect\" },\n fill: obj.fill\n ? solidShapeFill(obj.fill.color ?? \"FFFFFF\")\n : noneShapeFill(),\n outline: obj.border ? shapeOutline(obj.border) : undefined,\n },\n {\n fillColor: obj.fill?.color,\n fillOpacity:\n obj.fill?.transparency === undefined\n ? undefined\n : 1 - obj.fill.transparency / 100,\n },\n ),\n );\n break;\n case \"line\":\n authoring.addShape({\n ...masterBounds(obj),\n geometry: { kind: \"preset\", preset: \"line\" },\n fill: noneShapeFill(),\n outline: shapeOutline(obj.line ?? { color: \"000000\", width: 1 }),\n });\n break;\n }\n }\n if (master.slideNumber) {\n const value = master.slideNumber;\n authoring.addSlideNumber({\n offsetX: asEmu(Math.round(pxToEmu(value.x))),\n offsetY: asEmu(Math.round(pxToEmu(value.y))),\n width:\n value.w === undefined\n ? asEmu(800000)\n : asEmu(Math.round(pxToEmu(value.w))),\n height:\n value.h === undefined\n ? asEmu(300000)\n : asEmu(Math.round(pxToEmu(value.h))),\n properties: {\n fontFace: value.fontFamily,\n fontSize: value.fontSize ? asPt(pxToPt(value.fontSize)) : undefined,\n color: toColorInput(value.color),\n },\n });\n }\n}\n\n/**\n * PositionedNode ツリーを glimpse の PPTX source model に変換する\n * @param pages PositionedNode ツリーの配列(各要素が1ページ)\n * @param slidePx スライド全体のサイズ(px)\n * @param master スライドマスターオプション(省略可能)\n * @returns PPTX の write / writeFile / stream 互換 facade\n */\nexport function renderPptx(\n pages: PositionedNode[],\n slidePx: SlidePx,\n buildContext: BuildContext,\n master?: SlideMasterOptions,\n) {\n const margin =\n master?.margin === undefined ? undefined : resolveBoxSpacing(master.margin);\n const source = createPptx({\n slideSize: {\n width: asEmu(Math.round(pxToEmu(slidePx.w))),\n height: asEmu(Math.round(pxToEmu(slidePx.h))),\n },\n slideMaster: {\n name: master?.title ?? \"POM_MASTER\",\n background: masterBackground(master, buildContext),\n },\n slideLayout: {\n name: \"POM_LAYOUT\",\n margin: margin\n ? {\n top: asEmu(Math.round(pxToEmu(margin.top))),\n right: asEmu(Math.round(pxToEmu(margin.right))),\n bottom: asEmu(Math.round(pxToEmu(margin.bottom))),\n left: asEmu(Math.round(pxToEmu(margin.left))),\n }\n : undefined,\n },\n });\n const authoring = new PptxAuthoringContext(source, margin !== undefined);\n if (master) addMasterContent(buildContext, authoring, master);\n\n for (const [pageIndex, data] of pages.entries()) {\n if (pageIndex > 0) {\n const layoutPartPath = authoring.source.slideLayouts[0]?.partPath;\n if (!layoutPartPath)\n throw new Error(\"createPptx did not create a slide layout\");\n const slideHandle = authoring.addEmptySlideFromLayout({\n layoutPartPath,\n });\n authoring.selectTarget(slideHandle);\n } else {\n const slideHandle = authoring.source.slides[pageIndex]?.handle;\n if (!slideHandle)\n throw new Error(`slide handle was not found: ${pageIndex + 1}`);\n authoring.selectTarget(slideHandle);\n }\n const idMaps = buildIdMaps(data, buildContext.diagnostics);\n const ctx: RenderContext = {\n buildContext,\n authoring,\n idPositionMap: idMaps.positions,\n idNodeMap: idMaps.nodes,\n connectorTargetMap: new Map(),\n };\n const deferredArrows: {\n node: Extract<PositionedNode, { type: \"arrow\" }>;\n insertionIndex: number;\n }[] = [];\n\n // ルートノードの backgroundColor はスライドの background プロパティとして適用\n // これにより、マスタースライドのオブジェクトを覆い隠さない\n // line/arrow ノードは backgroundColor を持たないためスキップ\n // ただし opacity が指定されている場合は slide.background では透過を表現できないため、\n // renderBackgroundAndBorder で描画する\n const isLinelike = data.type === \"line\" || data.type === \"arrow\";\n const rootBackgroundColor = !isLinelike ? data.backgroundColor : undefined;\n const rootBackgroundGradient = !isLinelike\n ? data.backgroundGradient\n : undefined;\n const rootHasOpacity =\n !isLinelike && \"opacity\" in data && data.opacity !== undefined;\n const rootGradient =\n rootBackgroundGradient && !rootHasOpacity\n ? toSlideBackgroundGradient(rootBackgroundGradient)\n : undefined;\n const rootGradientApplied = rootGradient !== undefined;\n if (rootGradient) authoring.setSlideBackground(rootGradient);\n if (\n !rootGradientApplied &&\n rootBackgroundColor &&\n !rootBackgroundGradient &&\n !rootHasOpacity\n ) {\n authoring.setSlideBackground({\n kind: \"solid\",\n color: toColorInput(rootBackgroundColor)!,\n });\n }\n\n // ルートノードの backgroundImage はスライドの background プロパティとして適用\n // backgroundColor と backgroundImage の両方がある場合、backgroundImage が優先\n const rootBackgroundImage = !isLinelike ? data.backgroundImage : undefined;\n if (rootBackgroundImage) {\n const cachedData = getImageData(\n rootBackgroundImage.src,\n buildContext.imageDataCache,\n );\n authoring.setSlideBackground({\n kind: \"image\",\n bytes: imageBytesFromSource(rootBackgroundImage.src, cachedData),\n });\n }\n\n /**\n * node をスライドにレンダリングする\n * @param isRoot ルートノードかどうか(ルートノードの background は slide.background で処理済み)\n */\n function renderNode(node: PositionedNode, isRoot = false) {\n // Native connector は接続先の authored shape handle が必要なため、\n // 通常ノードをすべて追加した後に authoring し、最後にこの位置へ戻す。\n if (node.type === \"arrow\") {\n deferredArrows.push({\n node,\n insertionIndex: authoring.currentTargetShapeHandles().length,\n });\n return;\n }\n\n // Arrow は上で defer 済み。Line は backgroundColor/border を持たない。\n if (node.type !== \"line\") {\n // ルートノードの backgroundColor/backgroundImage は既に slide.background に適用済みなのでスキップ\n // ただし opacity がある場合は slide.background では透過を表現できないため通常描画\n if (\n isRoot &&\n (rootBackgroundImage ||\n ((rootBackgroundColor || rootGradientApplied) && !rootHasOpacity))\n ) {\n // border のみ描画(backgroundColor/backgroundImage はスキップ)\n renderBorderOnly(node, ctx);\n } else {\n renderBackgroundAndBorder(node, ctx);\n }\n }\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"leaf\":\n if (!def.render) {\n throw new Error(\n `No render function registered for leaf node: ${node.type}`,\n );\n }\n def.render(node, ctx);\n break;\n\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n PositionedNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n // zIndex でソートして描画順を制御(値が小さいものが先に描画される)\n for (const child of sortByZIndex(containerNode.children)) {\n renderNode(child);\n }\n break;\n }\n }\n }\n\n renderNode(data, true); // ルートノードとして処理\n const baseShapeOrder = [...authoring.currentTargetShapeHandles()];\n const authoredArrows: {\n insertionIndex: number;\n handle: (typeof baseShapeOrder)[number];\n }[] = [];\n for (const arrow of deferredArrows) {\n const beforeCount = authoring.currentTargetShapeHandles().length;\n renderArrowNodeFromRegistry(arrow.node, ctx);\n const after = authoring.currentTargetShapeHandles();\n if (after.length === beforeCount + 1) {\n authoredArrows.push({\n insertionIndex: arrow.insertionIndex,\n handle: after.at(-1)!,\n });\n }\n }\n if (authoredArrows.length > 0) {\n const orderedHandles: (typeof baseShapeOrder)[number][] = [];\n for (let index = 0; index <= baseShapeOrder.length; index += 1) {\n for (const arrow of authoredArrows) {\n if (arrow.insertionIndex === index) orderedHandles.push(arrow.handle);\n }\n const baseHandle = baseShapeOrder[index];\n if (baseHandle) orderedHandles.push(baseHandle);\n }\n authoring.reorderCurrentTargetShapes(orderedHandles);\n }\n }\n\n return createWritablePptx(() => authoring.source);\n}\n\nfunction renderArrowNodeFromRegistry(\n node: Extract<PositionedNode, { type: \"arrow\" }>,\n ctx: RenderContext,\n): void {\n const def = getNodeDef(\"arrow\");\n if (!def.render) throw new Error(\"No render function registered for Arrow\");\n def.render(node, ctx);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/renderPptx/utils/connectorSites.ts
|
|
2
|
+
const cardinalConnectorShapeTypes = /* @__PURE__ */ new Set([
|
|
3
|
+
"rect",
|
|
4
|
+
"roundRect",
|
|
5
|
+
"ellipse"
|
|
6
|
+
]);
|
|
7
|
+
function supportsCardinalConnectorSites(shapeType) {
|
|
8
|
+
return cardinalConnectorShapeTypes.has(shapeType);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* DrawingML の rect / roundRect / ellipse / text box に共通する4接続点。
|
|
12
|
+
* index は geometry 固有のローカル座標系に属するため、画面上の座標だけを
|
|
13
|
+
* shape の中心まわりに回転し、index 自体は維持する。
|
|
14
|
+
*/
|
|
15
|
+
function cardinalConnectorSites(bounds, rotate = 0) {
|
|
16
|
+
const centerX = bounds.x + bounds.w / 2;
|
|
17
|
+
const centerY = bounds.y + bounds.h / 2;
|
|
18
|
+
const sites = [
|
|
19
|
+
{
|
|
20
|
+
index: 0,
|
|
21
|
+
x: centerX,
|
|
22
|
+
y: bounds.y
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
index: 1,
|
|
26
|
+
x: bounds.x,
|
|
27
|
+
y: centerY
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
index: 2,
|
|
31
|
+
x: centerX,
|
|
32
|
+
y: bounds.y + bounds.h
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
index: 3,
|
|
36
|
+
x: bounds.x + bounds.w,
|
|
37
|
+
y: centerY
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
const normalized = (rotate % 360 + 360) % 360;
|
|
41
|
+
if (normalized === 0) return sites;
|
|
42
|
+
const radians = normalized * Math.PI / 180;
|
|
43
|
+
const cos = Math.cos(radians);
|
|
44
|
+
const sin = Math.sin(radians);
|
|
45
|
+
return sites.map((site) => {
|
|
46
|
+
const dx = site.x - centerX;
|
|
47
|
+
const dy = site.y - centerY;
|
|
48
|
+
return {
|
|
49
|
+
index: site.index,
|
|
50
|
+
x: centerX + dx * cos - dy * sin,
|
|
51
|
+
y: centerY + dx * sin + dy * cos
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { cardinalConnectorSites, supportsCardinalConnectorSites };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=connectorSites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectorSites.js","names":[],"sources":["../../../src/renderPptx/utils/connectorSites.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { NodeBounds } from \"../types.ts\";\n\nexport type ConnectorSite = {\n index: 0 | 1 | 2 | 3;\n x: number;\n y: number;\n};\n\ntype ShapeType = Extract<PositionedNode, { type: \"shape\" }>[\"shapeType\"];\n\nconst cardinalConnectorShapeTypes: ReadonlySet<ShapeType> = new Set([\n \"rect\",\n \"roundRect\",\n \"ellipse\",\n]);\n\nexport function supportsCardinalConnectorSites(shapeType: ShapeType): boolean {\n return cardinalConnectorShapeTypes.has(shapeType);\n}\n\n/**\n * DrawingML の rect / roundRect / ellipse / text box に共通する4接続点。\n * index は geometry 固有のローカル座標系に属するため、画面上の座標だけを\n * shape の中心まわりに回転し、index 自体は維持する。\n */\nexport function cardinalConnectorSites(\n bounds: NodeBounds,\n rotate = 0,\n): readonly ConnectorSite[] {\n const centerX = bounds.x + bounds.w / 2;\n const centerY = bounds.y + bounds.h / 2;\n const sites: readonly ConnectorSite[] = [\n { index: 0, x: centerX, y: bounds.y },\n { index: 1, x: bounds.x, y: centerY },\n { index: 2, x: centerX, y: bounds.y + bounds.h },\n { index: 3, x: bounds.x + bounds.w, y: centerY },\n ];\n const normalized = ((rotate % 360) + 360) % 360;\n if (normalized === 0) return sites;\n\n const radians = (normalized * Math.PI) / 180;\n const cos = Math.cos(radians);\n const sin = Math.sin(radians);\n return sites.map((site) => {\n const dx = site.x - centerX;\n const dy = site.y - centerY;\n return {\n index: site.index,\n x: centerX + dx * cos - dy * sin,\n y: centerY + dx * sin + dy * cos,\n };\n });\n}\n"],"mappings":";AAWA,MAAM,8CAAsD,IAAI,IAAI;CAClE;CACA;CACA;AACF,CAAC;AAED,SAAgB,+BAA+B,WAA+B;CAC5E,OAAO,4BAA4B,IAAI,SAAS;AAClD;;;;;;AAOA,SAAgB,uBACd,QACA,SAAS,GACiB;CAC1B,MAAM,UAAU,OAAO,IAAI,OAAO,IAAI;CACtC,MAAM,UAAU,OAAO,IAAI,OAAO,IAAI;CACtC,MAAM,QAAkC;EACtC;GAAE,OAAO;GAAG,GAAG;GAAS,GAAG,OAAO;EAAE;EACpC;GAAE,OAAO;GAAG,GAAG,OAAO;GAAG,GAAG;EAAQ;EACpC;GAAE,OAAO;GAAG,GAAG;GAAS,GAAG,OAAO,IAAI,OAAO;EAAE;EAC/C;GAAE,OAAO;GAAG,GAAG,OAAO,IAAI,OAAO;GAAG,GAAG;EAAQ;CACjD;CACA,MAAM,cAAe,SAAS,MAAO,OAAO;CAC5C,IAAI,eAAe,GAAG,OAAO;CAE7B,MAAM,UAAW,aAAa,KAAK,KAAM;CACzC,MAAM,MAAM,KAAK,IAAI,OAAO;CAC5B,MAAM,MAAM,KAAK,IAAI,OAAO;CAC5B,OAAO,MAAM,KAAK,SAAS;EACzB,MAAM,KAAK,KAAK,IAAI;EACpB,MAAM,KAAK,KAAK,IAAI;EACpB,OAAO;GACL,OAAO,KAAK;GACZ,GAAG,UAAU,KAAK,MAAM,KAAK;GAC7B,GAAG,UAAU,KAAK,MAAM,KAAK;EAC/B;CACF,CAAC;AACH"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pxToEmu } from "../units.js";
|
|
2
|
+
import { enrichPictureInput } from "../glimpseAdapter.js";
|
|
2
3
|
import * as fs from "fs";
|
|
3
4
|
import { asEmu, asOoxmlAngle, asOoxmlPercent } from "@pptx-glimpse/document";
|
|
4
5
|
//#region src/renderPptx/utils/glimpsePicture.ts
|
|
@@ -100,10 +101,7 @@ function addGlimpsePicture(ctx, content, bytes, options) {
|
|
|
100
101
|
rotation: createPictureRotationInput(options?.rotate),
|
|
101
102
|
crop: sizingResult.crop
|
|
102
103
|
};
|
|
103
|
-
ctx.
|
|
104
|
-
name: options?.name,
|
|
105
|
-
shadow: options?.shadow
|
|
106
|
-
});
|
|
104
|
+
ctx.authoring.addPicture(enrichPictureInput(input, options?.shadow), options?.name);
|
|
107
105
|
}
|
|
108
106
|
//#endregion
|
|
109
107
|
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 } 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.
|
|
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\";\nimport { enrichPictureInput } from \"../glimpseAdapter.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.authoring.addPicture(\n enrichPictureInput(input, options?.shadow),\n options?.name,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseGradient } from "../../shared/gradient.js";
|
|
2
2
|
import { pxToEmu } from "../units.js";
|
|
3
|
-
import { toColorInput } from "../
|
|
3
|
+
import { enrichShapeInput, toColorInput } from "../glimpseAdapter.js";
|
|
4
4
|
import { asEmu, asOoxmlAngle } from "@pptx-glimpse/document";
|
|
5
5
|
//#region src/renderPptx/utils/glimpseShape.ts
|
|
6
6
|
function positiveEmu(valuePx) {
|
|
@@ -59,11 +59,11 @@ function arrowEndpoint(type) {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
function addGlimpseShape(ctx, input, bounds, options) {
|
|
62
|
-
ctx.
|
|
62
|
+
return ctx.authoring.addShape(enrichShapeInput(input, {
|
|
63
63
|
...options,
|
|
64
64
|
zeroWidth: bounds.w === 0,
|
|
65
65
|
zeroHeight: bounds.h === 0
|
|
66
|
-
});
|
|
66
|
+
}), options?.name);
|
|
67
67
|
}
|
|
68
68
|
//#endregion
|
|
69
69
|
export { addGlimpseShape, arrowEndpoint, backgroundShapeFill, createShapeBoundsInput, createShapeRotationInput, noShapeOutline, noneShapeFill, shapeOutline, solidShapeFill };
|
|
@@ -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 {
|
|
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 type SourceHandle,\n} from \"@pptx-glimpse/document\";\nimport type { BorderStyle, ShadowStyle, TextGlow } from \"../../types.ts\";\nimport { parseGradient } from \"../../shared/gradient.ts\";\nimport {\n enrichShapeInput,\n toColorInput,\n type CustomGeometryXmlInput,\n} from \"../glimpseAdapter.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): SourceHandle {\n return ctx.authoring.addShape(\n enrichShapeInput(input, {\n ...options,\n zeroWidth: bounds.w === 0,\n zeroHeight: bounds.h === 0,\n }),\n options?.name,\n );\n}\n"],"mappings":";;;;;AAuCA,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,SACc;CACd,OAAO,IAAI,UAAU,SACnB,iBAAiB,OAAO;EACtB,GAAG;EACH,WAAW,OAAO,MAAM;EACxB,YAAY,OAAO,MAAM;CAC3B,CAAC,GACD,SAAS,IACX;AACF"}
|
|
@@ -1,34 +1,7 @@
|
|
|
1
1
|
import { pxToEmu, pxToPt } from "../units.js";
|
|
2
|
-
import {
|
|
3
|
-
import { asEmu, asHundredthPt, asOoxmlAngle, asOoxmlPercent
|
|
2
|
+
import { createGlimpseRunProperties } from "../glimpseAdapter.js";
|
|
3
|
+
import { asEmu, asHundredthPt, asOoxmlAngle, asOoxmlPercent } from "@pptx-glimpse/document";
|
|
4
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
5
|
function createGlimpseParagraph(text, style, options = {}) {
|
|
33
6
|
const fontSizePx = style.fontSize ?? 24;
|
|
34
7
|
return {
|
|
@@ -96,7 +69,7 @@ function addGlimpseTextBox(ctx, bounds, options = {}) {
|
|
|
96
69
|
align: options.align,
|
|
97
70
|
lineHeight: options.lineHeight
|
|
98
71
|
}), options);
|
|
99
|
-
ctx.
|
|
72
|
+
ctx.authoring.addTextBox(textBoxInput(bounds, options, paragraphs));
|
|
100
73
|
}
|
|
101
74
|
function isSupportedAutoNumScheme(value) {
|
|
102
75
|
return value === "arabicPeriod" || value === "arabicParenR" || value === "romanUcPeriod" || value === "romanLcPeriod" || value === "alphaUcPeriod" || value === "alphaLcPeriod" || value === "alphaLcParenR" || value === "alphaUcParenR" || value === "arabicPlain";
|
|
@@ -1 +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
|
|
1
|
+
{"version":3,"file":"glimpseTextBox.js","names":[],"sources":["../../../src/renderPptx/utils/glimpseTextBox.ts"],"sourcesContent":["import {\n asEmu,\n asHundredthPt,\n asOoxmlAngle,\n asOoxmlPercent,\n type AddTextBoxInput,\n type AddTextBoxParagraphInput,\n type SourceAutoNumScheme,\n} from \"@pptx-glimpse/document\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToEmu, pxToPt } from \"../units.ts\";\nimport {\n createGlimpseRunProperties,\n type GlimpseTextRunStyle,\n} from \"../glimpseAdapter.ts\";\n\ntype TextBoundsPx = { x: number; y: number; w: number; h: number };\n\ntype TextBoxVerticalAlign = \"top\" | \"middle\" | \"bottom\";\n\nexport type { GlimpseTextRunStyle } from \"../glimpseAdapter.ts\";\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\nexport { createGlimpseRunProperties } from \"../glimpseAdapter.ts\";\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 } = {},\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.authoring.addTextBox(textBoxInput(bounds, options, paragraphs));\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":";;;;AA2CA,SAAS,uBACP,MACA,OACA,UAA+D,CAAC,GACtC;CAC1B,MAAM,aAAa,MAAM,YAAY;CACrC,OAAO;EACL,YAAY;GACV,OAAO,QAAQ;GACf,aACE,QAAQ,eAAe,KAAA,IACnB,cACE,KAAK,MAAM,OAAO,aAAa,QAAQ,UAAU,IAAI,GAAG,CAC1D,IACA,KAAA;EACR;EACA,MAAM,CAAC;GAAE;GAAM,YAAY,2BAA2B,KAAK;EAAE,CAAC;CAChE;AACF;AAEA,SAAgB,wBACd,MACA,OACA,UAA+D,CAAC,GACpC;CAC5B,OAAO,KACJ,QAAQ,UAAU,IAAI,CAAC,CACvB,MAAM,IAAI,CAAC,CACX,KAAK,SAAS,uBAAuB,MAAM,OAAO,OAAO,CAAC;AAC/D;AAEA,SAAS,aACP,QACA,SACA,YACiB;CACjB,MAAM,SACJ,QAAQ,WAAW,KAAA,IACf,MAAM,KAAK,MAAM,QAAQ,QAAQ,MAAM,CAAC,CAAC,IACzC,KAAA;CACN,OAAO;EACL,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,CAAC;EACvD,QAAQ,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,CAAC;EACxD,UACE,QAAQ,WAAW,KAAA,IACf,aAAa,KAAK,MAAM,QAAQ,SAAS,GAAK,CAAC,IAC/C,KAAA;EACN,MAAM;GACJ,YAAY;GACZ,aAAa;GACb,WAAW;GACX,cAAc;GACd,QAAQ,QAAQ;GAChB,SAAS,QAAQ,UAAU,UAAU,KAAA;EACvC;EACA;CACF;AACF;AAEA,SAAS,mBACP,YACA,SACqC;CACrC,IAAI,CAAC,QAAQ,QAAQ,OAAO;CAC5B,MAAM,SACJ,QAAQ,OAAO,SAAS,WACpB;EACE,MAAM;EACN,WAAW;EACX,MAAM,eAAe,GAAM;CAC7B,IACA;EACE,MAAM;EACN,QAAQ,yBAAyB,QAAQ,OAAO,MAAM,IAClD,QAAQ,OAAO,SACf;EACJ,SAAS,QAAQ,OAAO,WAAW;EACnC,UAAU;EACV,MAAM,eAAe,GAAM;CAC7B;CACN,OAAO,WAAW,KAAK,eAAe;EACpC,GAAG;EACH,YAAY;GACV,GAAG,UAAU;GACb,YAAY,MAAM,MAAM;GACxB,QAAQ,MAAM,OAAO;GACrB,aAAa;IACX,MAAM;IACN,OAAO,eAAe,KAAK,OAAO,QAAQ,cAAc,OAAO,GAAM,CAAC;GACxE;GACA;EACF;CACF,EAAE;AACJ;AAEA,SAAgB,kBACd,KACA,QACA,UAEI,CAAC,GACC;CACN,MAAM,aAAa,mBACjB,QAAQ,cACN,wBAAwB,QAAQ,QAAQ,IAAI,SAAS;EACnD,OAAO,QAAQ;EACf,YAAY,QAAQ;CACtB,CAAC,GACH,OACF;CACA,IAAI,UAAU,WAAW,aAAa,QAAQ,SAAS,UAAU,CAAC;AACpE;AAEA,SAAS,yBACP,OAC8B;CAC9B,OACE,UAAU,kBACV,UAAU,kBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU;AAEd"}
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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 で表す。arrow endpoint の\n * 型変換だけは native connector の 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"}
|