@hirokisakabe/pom 9.1.2 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buildContext.js +2 -2
- package/dist/buildContext.js.map +1 -1
- package/dist/buildPptx.d.ts +2 -1
- package/dist/buildPptx.d.ts.map +1 -1
- package/dist/buildPptx.js +4 -3
- package/dist/buildPptx.js.map +1 -1
- package/dist/calcYogaLayout/calcYogaLayout.js +1 -1
- package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/registry/definitions/icon.js +1 -1
- package/dist/registry/definitions/list.js.map +1 -1
- package/dist/renderPptx/nodes/chart.js +172 -47
- package/dist/renderPptx/nodes/chart.js.map +1 -1
- package/dist/renderPptx/nodes/flow.js +4 -1
- package/dist/renderPptx/nodes/flow.js.map +1 -1
- package/dist/renderPptx/nodes/icon.js +5 -2
- package/dist/renderPptx/nodes/icon.js.map +1 -1
- package/dist/renderPptx/nodes/list.js +7 -9
- package/dist/renderPptx/nodes/list.js.map +1 -1
- package/dist/renderPptx/nodes/matrix.js +4 -1
- package/dist/renderPptx/nodes/matrix.js.map +1 -1
- package/dist/renderPptx/nodes/processArrow.js +8 -2
- package/dist/renderPptx/nodes/processArrow.js.map +1 -1
- package/dist/renderPptx/nodes/pyramid.js +4 -1
- package/dist/renderPptx/nodes/pyramid.js.map +1 -1
- package/dist/renderPptx/nodes/shape.js +5 -2
- package/dist/renderPptx/nodes/shape.js.map +1 -1
- package/dist/renderPptx/nodes/table.js +89 -59
- package/dist/renderPptx/nodes/table.js.map +1 -1
- package/dist/renderPptx/nodes/text.js +1 -18
- package/dist/renderPptx/nodes/text.js.map +1 -1
- package/dist/renderPptx/nodes/timeline.js +9 -3
- package/dist/renderPptx/nodes/timeline.js.map +1 -1
- package/dist/renderPptx/nodes/tree.js +4 -1
- package/dist/renderPptx/nodes/tree.js.map +1 -1
- package/dist/renderPptx/pptxAuthoring.js +381 -0
- package/dist/renderPptx/pptxAuthoring.js.map +1 -0
- package/dist/renderPptx/renderPptx.js +157 -126
- package/dist/renderPptx/renderPptx.js.map +1 -1
- package/dist/renderPptx/textOptions.js +7 -8
- package/dist/renderPptx/textOptions.js.map +1 -1
- package/dist/renderPptx/units.js +3 -3
- package/dist/renderPptx/units.js.map +1 -1
- package/dist/renderPptx/utils/backgroundBorder.js +30 -44
- package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
- package/dist/renderPptx/utils/contentArea.js +1 -1
- package/dist/renderPptx/utils/contentArea.js.map +1 -1
- package/dist/renderPptx/utils/glimpsePicture.js +3 -22
- package/dist/renderPptx/utils/glimpsePicture.js.map +1 -1
- package/dist/renderPptx/utils/glimpseShape.js +6 -25
- package/dist/renderPptx/utils/glimpseShape.js.map +1 -1
- package/dist/renderPptx/utils/glimpseTextBox.js +41 -60
- package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -1
- package/dist/renderPptx/utils/straightLine.js +5 -2
- package/dist/renderPptx/utils/straightLine.js.map +1 -1
- package/dist/renderPptx/utils/visualStyle.js +3 -33
- package/dist/renderPptx/utils/visualStyle.js.map +1 -1
- package/dist/renderPptx/writablePptx.d.ts +23 -0
- package/dist/renderPptx/writablePptx.d.ts.map +1 -0
- package/dist/renderPptx/writablePptx.js +74 -0
- package/dist/renderPptx/writablePptx.js.map +1 -0
- package/dist/shared/measureImage.js.map +1 -1
- package/package.json +4 -5
- package/dist/renderPptx/glimpseTextBoxes.js +0 -646
- package/dist/renderPptx/glimpseTextBoxes.js.map +0 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { getImageData } from "../shared/measureImage.js";
|
|
2
2
|
import { resolveBoxSpacing } from "../shared/boxSpacing.js";
|
|
3
3
|
import { getNodeDef } from "../registry/nodeRegistry.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { pxToEmu, pxToPt } from "./units.js";
|
|
5
|
+
import { toColorInput } from "./pptxAuthoring.js";
|
|
6
|
+
import { createGlimpseRunProperties } from "./utils/glimpseTextBox.js";
|
|
7
|
+
import { imageBytesFromSource } from "./utils/glimpsePicture.js";
|
|
8
|
+
import { createShapeBoundsInput, noneShapeFill, shapeOutline, solidShapeFill } from "./utils/glimpseShape.js";
|
|
6
9
|
import "../registry/index.js";
|
|
7
10
|
import { renderBackgroundAndBorder, renderBorderOnly } from "./utils/backgroundBorder.js";
|
|
11
|
+
import { createWritablePptx } from "./writablePptx.js";
|
|
12
|
+
import { addEmptySlideFromLayout, addSlideNumber, asEmu, asPt, createPptx } from "@pptx-glimpse/document";
|
|
8
13
|
//#region src/renderPptx/renderPptx.ts
|
|
9
|
-
async function loadPptxGenJS() {
|
|
10
|
-
const mod = await import("pptxgenjs");
|
|
11
|
-
return mod.default?.default ?? mod.default ?? mod;
|
|
12
|
-
}
|
|
13
|
-
const DEFAULT_MASTER_NAME = "POM_MASTER";
|
|
14
14
|
function buildIdPositionMap(node, diagnostics) {
|
|
15
15
|
const map = /* @__PURE__ */ new Map();
|
|
16
16
|
function traverse(n) {
|
|
@@ -34,130 +34,163 @@ function sortByZIndex(children) {
|
|
|
34
34
|
if (children.every((c) => c.zIndex === void 0)) return children;
|
|
35
35
|
return [...children].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
italic: obj.italic,
|
|
54
|
-
underline: convertUnderline(obj.underline),
|
|
55
|
-
strike: convertStrike(obj.strike),
|
|
56
|
-
highlight: obj.highlight,
|
|
57
|
-
align: obj.textAlign
|
|
58
|
-
}
|
|
59
|
-
} };
|
|
60
|
-
case "image": {
|
|
61
|
-
const imageProps = {
|
|
62
|
-
x: pxToIn(obj.x),
|
|
63
|
-
y: pxToIn(obj.y),
|
|
64
|
-
w: pxToIn(obj.w),
|
|
65
|
-
h: pxToIn(obj.h)
|
|
66
|
-
};
|
|
67
|
-
if (obj.src.startsWith("data:")) imageProps.data = obj.src;
|
|
68
|
-
else imageProps.path = obj.src;
|
|
69
|
-
return { image: imageProps };
|
|
70
|
-
}
|
|
71
|
-
case "rect": return { rect: {
|
|
72
|
-
x: pxToIn(obj.x),
|
|
73
|
-
y: pxToIn(obj.y),
|
|
74
|
-
w: pxToIn(obj.w),
|
|
75
|
-
h: pxToIn(obj.h),
|
|
76
|
-
fill: obj.fill ? {
|
|
77
|
-
color: obj.fill.color,
|
|
78
|
-
transparency: obj.fill.transparency
|
|
79
|
-
} : void 0,
|
|
80
|
-
line: obj.border ? {
|
|
81
|
-
color: obj.border.color,
|
|
82
|
-
width: obj.border.width,
|
|
83
|
-
dashType: obj.border.dashType
|
|
84
|
-
} : void 0
|
|
85
|
-
} };
|
|
86
|
-
case "line": return { line: {
|
|
87
|
-
x: pxToIn(obj.x),
|
|
88
|
-
y: pxToIn(obj.y),
|
|
89
|
-
w: pxToIn(obj.w),
|
|
90
|
-
h: pxToIn(obj.h),
|
|
91
|
-
line: obj.line ? {
|
|
92
|
-
color: obj.line.color,
|
|
93
|
-
width: obj.line.width,
|
|
94
|
-
dashType: obj.line.dashType
|
|
95
|
-
} : {
|
|
96
|
-
color: "000000",
|
|
97
|
-
width: 1
|
|
98
|
-
}
|
|
99
|
-
} };
|
|
100
|
-
}
|
|
37
|
+
function masterBackground(master, buildContext) {
|
|
38
|
+
const background = master?.background;
|
|
39
|
+
if (!background) return void 0;
|
|
40
|
+
if ("color" in background) return {
|
|
41
|
+
kind: "solid",
|
|
42
|
+
color: toColorInput(background.color)
|
|
43
|
+
};
|
|
44
|
+
if ("data" in background) return {
|
|
45
|
+
kind: "image",
|
|
46
|
+
bytes: imageBytesFromSource("", background.data)
|
|
47
|
+
};
|
|
48
|
+
const src = "path" in background ? background.path : background.image;
|
|
49
|
+
return {
|
|
50
|
+
kind: "image",
|
|
51
|
+
bytes: imageBytesFromSource(src, getImageData(src, buildContext.imageDataCache))
|
|
52
|
+
};
|
|
101
53
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
function
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
54
|
+
function masterBounds(obj) {
|
|
55
|
+
return createShapeBoundsInput(obj);
|
|
56
|
+
}
|
|
57
|
+
function masterTextRunProperties(obj) {
|
|
58
|
+
const properties = createGlimpseRunProperties({
|
|
59
|
+
fontSize: obj.fontSize,
|
|
60
|
+
fontFace: obj.fontFamily,
|
|
61
|
+
color: obj.color,
|
|
62
|
+
bold: obj.bold,
|
|
63
|
+
italic: obj.italic,
|
|
64
|
+
underline: obj.underline,
|
|
65
|
+
strike: obj.strike,
|
|
66
|
+
highlight: obj.highlight
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
...properties,
|
|
70
|
+
fontFace: obj.fontFamily === void 0 ? void 0 : properties.fontFace,
|
|
71
|
+
fontSize: obj.fontSize === void 0 ? void 0 : properties.fontSize
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function addMasterContent(buildContext, master) {
|
|
75
|
+
const target = buildContext.pptxAuthoring.source.slideMasters[0]?.handle;
|
|
76
|
+
if (!target) throw new Error("createPptx did not create a slide master handle");
|
|
77
|
+
buildContext.pptxAuthoring.selectSlide(target);
|
|
78
|
+
for (const obj of master.objects ?? []) switch (obj.type) {
|
|
79
|
+
case "text":
|
|
80
|
+
buildContext.pptxAuthoring.registerTextBox({
|
|
81
|
+
...masterBounds(obj),
|
|
82
|
+
body: {
|
|
83
|
+
anchor: "middle",
|
|
84
|
+
marginLeft: asEmu(91440),
|
|
85
|
+
marginRight: asEmu(91440),
|
|
86
|
+
marginTop: asEmu(91440),
|
|
87
|
+
marginBottom: asEmu(91440)
|
|
88
|
+
},
|
|
89
|
+
paragraphs: [{
|
|
90
|
+
properties: {
|
|
91
|
+
align: obj.textAlign,
|
|
92
|
+
marginLeft: asEmu(0),
|
|
93
|
+
indent: asEmu(0),
|
|
94
|
+
bullet: { type: "none" }
|
|
95
|
+
},
|
|
96
|
+
runs: [{
|
|
97
|
+
text: obj.text,
|
|
98
|
+
properties: masterTextRunProperties(obj)
|
|
99
|
+
}]
|
|
100
|
+
}]
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
case "image":
|
|
104
|
+
buildContext.pptxAuthoring.registerPicture({
|
|
105
|
+
...masterBounds(obj),
|
|
106
|
+
bytes: imageBytesFromSource(obj.src, getImageData(obj.src, buildContext.imageDataCache))
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
case "rect":
|
|
110
|
+
buildContext.pptxAuthoring.registerShape({
|
|
111
|
+
...masterBounds(obj),
|
|
112
|
+
geometry: {
|
|
113
|
+
kind: "preset",
|
|
114
|
+
preset: "rect"
|
|
115
|
+
},
|
|
116
|
+
fill: obj.fill ? solidShapeFill(obj.fill.color ?? "FFFFFF") : noneShapeFill(),
|
|
117
|
+
outline: obj.border ? shapeOutline(obj.border) : void 0
|
|
118
|
+
}, {
|
|
119
|
+
fillColor: obj.fill?.color,
|
|
120
|
+
fillOpacity: obj.fill?.transparency === void 0 ? void 0 : 1 - obj.fill.transparency / 100
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
case "line":
|
|
124
|
+
buildContext.pptxAuthoring.registerShape({
|
|
125
|
+
...masterBounds(obj),
|
|
126
|
+
geometry: {
|
|
127
|
+
kind: "preset",
|
|
128
|
+
preset: "line"
|
|
129
|
+
},
|
|
130
|
+
fill: noneShapeFill(),
|
|
131
|
+
outline: shapeOutline(obj.line ?? {
|
|
132
|
+
color: "000000",
|
|
133
|
+
width: 1
|
|
134
|
+
})
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
113
137
|
}
|
|
114
|
-
if (master.
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
if (master.slideNumber) {
|
|
139
|
+
const value = master.slideNumber;
|
|
140
|
+
buildContext.pptxAuthoring.replaceSource(addSlideNumber(buildContext.pptxAuthoring.source, target, {
|
|
141
|
+
offsetX: asEmu(Math.round(pxToEmu(value.x))),
|
|
142
|
+
offsetY: asEmu(Math.round(pxToEmu(value.y))),
|
|
143
|
+
width: value.w === void 0 ? asEmu(8e5) : asEmu(Math.round(pxToEmu(value.w))),
|
|
144
|
+
height: value.h === void 0 ? asEmu(3e5) : asEmu(Math.round(pxToEmu(value.h))),
|
|
145
|
+
properties: {
|
|
146
|
+
fontFace: value.fontFamily,
|
|
147
|
+
fontSize: value.fontSize ? asPt(pxToPt(value.fontSize)) : void 0,
|
|
148
|
+
color: toColorInput(value.color)
|
|
149
|
+
}
|
|
150
|
+
}));
|
|
122
151
|
}
|
|
123
|
-
if (master.objects && master.objects.length > 0) masterProps.objects = master.objects.map((obj) => convertMasterObject(obj));
|
|
124
|
-
if (master.slideNumber) masterProps.slideNumber = {
|
|
125
|
-
x: pxToIn(master.slideNumber.x),
|
|
126
|
-
y: pxToIn(master.slideNumber.y),
|
|
127
|
-
w: master.slideNumber.w ? pxToIn(master.slideNumber.w) : void 0,
|
|
128
|
-
h: master.slideNumber.h ? pxToIn(master.slideNumber.h) : void 0,
|
|
129
|
-
fontSize: master.slideNumber.fontSize ? pxToPt(master.slideNumber.fontSize) : void 0,
|
|
130
|
-
fontFace: master.slideNumber.fontFamily,
|
|
131
|
-
color: master.slideNumber.color
|
|
132
|
-
};
|
|
133
|
-
pptx.defineSlideMaster(masterProps);
|
|
134
|
-
return masterName;
|
|
135
152
|
}
|
|
136
153
|
/**
|
|
137
|
-
* PositionedNode ツリーを
|
|
154
|
+
* PositionedNode ツリーを glimpse の PPTX source model に変換する
|
|
138
155
|
* @param pages PositionedNode ツリーの配列(各要素が1ページ)
|
|
139
156
|
* @param slidePx スライド全体のサイズ(px)
|
|
140
157
|
* @param master スライドマスターオプション(省略可能)
|
|
141
|
-
* @returns
|
|
158
|
+
* @returns PPTX の write / writeFile / stream 互換 facade
|
|
142
159
|
*/
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
function renderPptx(pages, slidePx, buildContext, master) {
|
|
161
|
+
const margin = master?.margin === void 0 ? void 0 : resolveBoxSpacing(master.margin);
|
|
162
|
+
let source = createPptx({
|
|
163
|
+
slideSize: {
|
|
164
|
+
width: asEmu(Math.round(pxToEmu(slidePx.w))),
|
|
165
|
+
height: asEmu(Math.round(pxToEmu(slidePx.h)))
|
|
166
|
+
},
|
|
167
|
+
slideMaster: {
|
|
168
|
+
name: master?.title ?? "POM_MASTER",
|
|
169
|
+
background: masterBackground(master, buildContext)
|
|
170
|
+
},
|
|
171
|
+
slideLayout: {
|
|
172
|
+
name: "POM_LAYOUT",
|
|
173
|
+
margin: margin ? {
|
|
174
|
+
top: asEmu(Math.round(pxToEmu(margin.top))),
|
|
175
|
+
right: asEmu(Math.round(pxToEmu(margin.right))),
|
|
176
|
+
bottom: asEmu(Math.round(pxToEmu(margin.bottom))),
|
|
177
|
+
left: asEmu(Math.round(pxToEmu(margin.left)))
|
|
178
|
+
} : void 0
|
|
179
|
+
}
|
|
153
180
|
});
|
|
154
|
-
|
|
155
|
-
|
|
181
|
+
buildContext.pptxAuthoring.initialize(source, margin !== void 0);
|
|
182
|
+
if (master) addMasterContent(buildContext, master);
|
|
156
183
|
for (const [pageIndex, data] of pages.entries()) {
|
|
157
|
-
|
|
184
|
+
if (pageIndex > 0) {
|
|
185
|
+
const layoutPartPath = buildContext.pptxAuthoring.source.slideLayouts[0]?.partPath;
|
|
186
|
+
if (!layoutPartPath) throw new Error("createPptx did not create a slide layout");
|
|
187
|
+
source = addEmptySlideFromLayout(buildContext.pptxAuthoring.source, { layoutPartPath });
|
|
188
|
+
buildContext.pptxAuthoring.replaceSource(source);
|
|
189
|
+
}
|
|
190
|
+
const slideHandle = buildContext.pptxAuthoring.source.slides[pageIndex]?.handle;
|
|
191
|
+
if (!slideHandle) throw new Error(`slide handle was not found: ${pageIndex + 1}`);
|
|
192
|
+
buildContext.pptxAuthoring.selectSlide(slideHandle);
|
|
158
193
|
const ctx = {
|
|
159
|
-
slide,
|
|
160
|
-
pptx,
|
|
161
194
|
buildContext,
|
|
162
195
|
idPositionMap: buildIdPositionMap(data, buildContext.diagnostics)
|
|
163
196
|
};
|
|
@@ -165,21 +198,19 @@ async function renderPptx(pages, slidePx, buildContext, master) {
|
|
|
165
198
|
const rootBackgroundColor = !isLinelike ? data.backgroundColor : void 0;
|
|
166
199
|
const rootBackgroundGradient = !isLinelike ? data.backgroundGradient : void 0;
|
|
167
200
|
const rootHasOpacity = !isLinelike && "opacity" in data && data.opacity !== void 0;
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
else if (rootBackgroundColor && !rootBackgroundGradient && !rootHasOpacity) slide.background = { color: rootBackgroundColor };
|
|
201
|
+
const rootGradientApplied = rootBackgroundGradient && !rootHasOpacity ? buildContext.pptxAuthoring.setSlideBackgroundGradient(rootBackgroundGradient) : false;
|
|
202
|
+
if (!rootGradientApplied && rootBackgroundColor && !rootBackgroundGradient && !rootHasOpacity) buildContext.pptxAuthoring.setSlideBackgroundSolid(rootBackgroundColor);
|
|
171
203
|
const rootBackgroundImage = !isLinelike ? data.backgroundImage : void 0;
|
|
172
204
|
if (rootBackgroundImage) {
|
|
173
205
|
const cachedData = getImageData(rootBackgroundImage.src, buildContext.imageDataCache);
|
|
174
|
-
|
|
175
|
-
else slide.background = { path: rootBackgroundImage.src };
|
|
206
|
+
buildContext.pptxAuthoring.setSlideBackgroundImage(imageBytesFromSource(rootBackgroundImage.src, cachedData));
|
|
176
207
|
}
|
|
177
208
|
/**
|
|
178
209
|
* node をスライドにレンダリングする
|
|
179
210
|
* @param isRoot ルートノードかどうか(ルートノードの background は slide.background で処理済み)
|
|
180
211
|
*/
|
|
181
212
|
function renderNode(node, isRoot = false) {
|
|
182
|
-
if (node.type !== "line" && node.type !== "arrow") if (isRoot && (rootBackgroundImage || (rootBackgroundColor ||
|
|
213
|
+
if (node.type !== "line" && node.type !== "arrow") if (isRoot && (rootBackgroundImage || (rootBackgroundColor || rootGradientApplied) && !rootHasOpacity)) renderBorderOnly(node, ctx);
|
|
183
214
|
else renderBackgroundAndBorder(node, ctx);
|
|
184
215
|
const def = getNodeDef(node.type);
|
|
185
216
|
switch (def.category) {
|
|
@@ -197,7 +228,7 @@ async function renderPptx(pages, slidePx, buildContext, master) {
|
|
|
197
228
|
}
|
|
198
229
|
renderNode(data, true);
|
|
199
230
|
}
|
|
200
|
-
return
|
|
231
|
+
return createWritablePptx(() => buildContext.pptxAuthoring.source);
|
|
201
232
|
}
|
|
202
233
|
//#endregion
|
|
203
234
|
export { renderPptx };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderPptx.js","names":[],"sources":["../../src/renderPptx/renderPptx.ts"],"sourcesContent":["// pptxgenjs の型定義\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\n\n// pptxgenjs は CJS パッケージのため動的 import で読み込む\nasync function loadPptxGenJS(): Promise<new () => PptxGenJSInstance> {\n const pptxModule = await import(\"pptxgenjs\");\n // CJS default export の解決: module.default.default (ESM wrapper) または module.default\n /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n const mod = pptxModule as any;\n return mod.default?.default ?? mod.default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\ntype SlideMasterProps = Parameters<PptxGenJSInstance[\"defineSlideMaster\"]>[0];\ntype ImageProps = {\n x: number;\n y: number;\n w: number;\n h: number;\n path?: string;\n data?: string;\n};\nimport type {\n PositionedNode,\n SlideMasterOptions,\n MasterObject,\n} from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { RenderContext, NodeBounds } from \"./types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { convertUnderline, convertStrike } from \"./textOptions.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\";\n\ntype SlidePx = { w: number; h: number };\n\nconst DEFAULT_MASTER_NAME = \"POM_MASTER\";\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\n/**\n * MasterObject を pptxgenjs の objects 形式に変換する\n */\nfunction convertMasterObject(\n obj: MasterObject,\n): SlideMasterProps[\"objects\"] extends (infer T)[] | undefined ? T : never {\n switch (obj.type) {\n case \"text\":\n return {\n text: {\n text: obj.text,\n options: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n fontSize: obj.fontSize ? pxToPt(obj.fontSize) : undefined,\n fontFace: obj.fontFamily,\n color: obj.color,\n bold: obj.bold,\n italic: obj.italic,\n underline: convertUnderline(obj.underline),\n strike: convertStrike(obj.strike),\n highlight: obj.highlight,\n align: obj.textAlign,\n },\n },\n };\n case \"image\": {\n const imageProps: ImageProps = {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n };\n // src が data URI かパスかを判定\n if (obj.src.startsWith(\"data:\")) {\n imageProps.data = obj.src;\n } else {\n imageProps.path = obj.src;\n }\n return { image: imageProps };\n }\n case \"rect\":\n return {\n rect: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n fill: obj.fill\n ? { color: obj.fill.color, transparency: obj.fill.transparency }\n : undefined,\n line: obj.border\n ? {\n color: obj.border.color,\n width: obj.border.width,\n dashType: obj.border.dashType,\n }\n : undefined,\n },\n };\n case \"line\":\n return {\n line: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n line: obj.line\n ? {\n color: obj.line.color,\n width: obj.line.width,\n dashType: obj.line.dashType,\n }\n : { color: \"000000\", width: 1 },\n },\n };\n }\n}\n\n/**\n * SlideMasterOptions から pptxgenjs の defineSlideMaster を呼び出す\n */\nfunction defineSlideMasterFromOptions(\n pptx: PptxGenJSInstance,\n master: SlideMasterOptions,\n): string {\n const masterName = master.title || DEFAULT_MASTER_NAME;\n\n const masterProps: SlideMasterProps = {\n title: masterName,\n };\n\n // background の変換\n if (master.background) {\n if (\"color\" in master.background) {\n masterProps.background = { color: master.background.color };\n } else if (\"path\" in master.background) {\n masterProps.background = { path: master.background.path };\n } else if (\"data\" in master.background) {\n masterProps.background = { data: master.background.data };\n } else if (\"image\" in master.background) {\n masterProps.background = { path: master.background.image };\n }\n }\n\n // margin の変換 (px -> inches)\n if (master.margin !== undefined) {\n const margin = resolveBoxSpacing(master.margin);\n masterProps.margin = [\n pxToIn(margin.top),\n pxToIn(margin.right),\n pxToIn(margin.bottom),\n pxToIn(margin.left),\n ];\n }\n\n // objects の変換\n if (master.objects && master.objects.length > 0) {\n masterProps.objects = master.objects.map((obj) => convertMasterObject(obj));\n }\n\n // slideNumber の変換\n if (master.slideNumber) {\n masterProps.slideNumber = {\n x: pxToIn(master.slideNumber.x),\n y: pxToIn(master.slideNumber.y),\n w: master.slideNumber.w ? pxToIn(master.slideNumber.w) : undefined,\n h: master.slideNumber.h ? pxToIn(master.slideNumber.h) : undefined,\n fontSize: master.slideNumber.fontSize\n ? pxToPt(master.slideNumber.fontSize)\n : undefined,\n fontFace: master.slideNumber.fontFamily,\n color: master.slideNumber.color,\n };\n }\n\n pptx.defineSlideMaster(masterProps);\n return masterName;\n}\n\n/**\n * PositionedNode ツリーを PptxGenJS スライドに変換する\n * @param pages PositionedNode ツリーの配列(各要素が1ページ)\n * @param slidePx スライド全体のサイズ(px)\n * @param master スライドマスターオプション(省略可能)\n * @returns PptxGenJS インスタンス\n */\nexport async function renderPptx(\n pages: PositionedNode[],\n slidePx: SlidePx,\n buildContext: BuildContext,\n master?: SlideMasterOptions,\n) {\n const slideIn = { w: pxToIn(slidePx.w), h: pxToIn(slidePx.h) }; // layout(=px) → PptxGenJS(=inch) への最終変換\n\n const PptxGenJS = await loadPptxGenJS();\n const pptx = new PptxGenJS();\n\n pptx.defineLayout({ name: \"custom\", width: slideIn.w, height: slideIn.h });\n pptx.layout = \"custom\";\n\n // マスターが指定されている場合、defineSlideMaster を呼び出す\n const masterName = master\n ? defineSlideMasterFromOptions(pptx, master)\n : undefined;\n\n for (const [pageIndex, data] of pages.entries()) {\n // マスターが指定されている場合は masterName を使用\n const slide = masterName ? pptx.addSlide({ masterName }) : pptx.addSlide();\n const idPositionMap = buildIdPositionMap(data, buildContext.diagnostics);\n const ctx: RenderContext = { slide, pptx, 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 rootGradientMarker =\n rootBackgroundGradient && !rootHasOpacity\n ? buildContext.glimpseTextBoxes.registerSlideBackgroundGradient(\n rootBackgroundGradient,\n pageIndex + 1,\n )\n : undefined;\n if (rootGradientMarker) {\n slide.background = { color: rootGradientMarker };\n } else if (\n rootBackgroundColor &&\n !rootBackgroundGradient &&\n !rootHasOpacity\n ) {\n slide.background = { color: 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 if (cachedData) {\n slide.background = { data: cachedData };\n } else {\n slide.background = { path: rootBackgroundImage.src };\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 || rootGradientMarker) && !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 pptx;\n}\n"],"mappings":"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"}
|
|
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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"}
|
|
@@ -3,7 +3,7 @@ import { pxToIn, pxToPt } from "./units.js";
|
|
|
3
3
|
import { getContentAreaIn } from "./utils/contentArea.js";
|
|
4
4
|
//#region src/renderPptx/textOptions.ts
|
|
5
5
|
/**
|
|
6
|
-
* underline
|
|
6
|
+
* underline プロパティを描画用の形式に変換する
|
|
7
7
|
*/
|
|
8
8
|
function convertUnderline(underline) {
|
|
9
9
|
if (underline === void 0) return void 0;
|
|
@@ -15,7 +15,7 @@ function convertUnderline(underline) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* strike
|
|
18
|
+
* strike プロパティを描画用の形式に変換する
|
|
19
19
|
*/
|
|
20
20
|
function convertStrike(strike) {
|
|
21
21
|
if (strike) return "sngStrike";
|
|
@@ -37,10 +37,9 @@ function resolveSubSup(run, parent) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* glow
|
|
41
|
-
* size はユーザー入力 px
|
|
42
|
-
*
|
|
43
|
-
* 渡すとデフォルトが消える。ここで pom 側のデフォルトを確定させる。
|
|
40
|
+
* glow プロパティを描画用の形式に変換する。
|
|
41
|
+
* size はユーザー入力 px、描画用の glow.size は pt。
|
|
42
|
+
* ここで既存の出力互換性を保つデフォルトを確定させる。
|
|
44
43
|
*/
|
|
45
44
|
function convertGlow(glow) {
|
|
46
45
|
if (glow === void 0) return void 0;
|
|
@@ -51,8 +50,8 @@ function convertGlow(glow) {
|
|
|
51
50
|
};
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
|
-
* outline
|
|
55
|
-
* size はユーザー入力 px
|
|
53
|
+
* outline プロパティを描画用の形式に変換する
|
|
54
|
+
* size はユーザー入力 px、描画用の outline.size は pt
|
|
56
55
|
*/
|
|
57
56
|
function convertOutline(outline) {
|
|
58
57
|
if (outline === void 0) return void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textOptions.js","names":[],"sources":["../../src/renderPptx/textOptions.ts"],"sourcesContent":["import type {\n PositionedNode,\n TextGlow,\n TextOutline,\n Underline,\n UnderlineStyle,\n} from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { getContentAreaIn } from \"./utils/contentArea.ts\";\nimport { measureFontVerticalMetricsRatio } from \"../calcYogaLayout/fontLoader.ts\";\n\ntype TextNode = Extract<PositionedNode, { type: \"text\" }>;\n\n/**\n * underline
|
|
1
|
+
{"version":3,"file":"textOptions.js","names":[],"sources":["../../src/renderPptx/textOptions.ts"],"sourcesContent":["import type {\n PositionedNode,\n TextGlow,\n TextOutline,\n Underline,\n UnderlineStyle,\n} from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { getContentAreaIn } from \"./utils/contentArea.ts\";\nimport { measureFontVerticalMetricsRatio } from \"../calcYogaLayout/fontLoader.ts\";\n\ntype TextNode = Extract<PositionedNode, { type: \"text\" }>;\n\n/**\n * underline プロパティを描画用の形式に変換する\n */\nexport function convertUnderline(\n underline: Underline | undefined,\n): { style?: UnderlineStyle; color?: string } | undefined {\n if (underline === undefined) return undefined;\n if (underline === false) return undefined;\n if (underline === true) return { style: \"sng\" };\n return {\n style: underline.style,\n color: underline.color,\n };\n}\n\n/**\n * strike プロパティを描画用の形式に変換する\n */\nexport function convertStrike(\n strike: boolean | undefined,\n): \"sngStrike\" | undefined {\n if (strike) return \"sngStrike\";\n return undefined;\n}\n\n/**\n * run と親 (node / cell / li) の subscript / superscript を相互排他に解決する。\n * OOXML の baseline 値で表現される性質上、両方 true は意味を持たないため、\n * run 側で sub/sup のいずれかが指定された場合は親の指定をまるごと無効化する\n * (片方だけ指定して反対側を親から漏らさない)。\n */\nexport function resolveSubSup(\n run: { subscript?: boolean; superscript?: boolean },\n parent: { subscript?: boolean; superscript?: boolean },\n): { subscript: boolean | undefined; superscript: boolean | undefined } {\n if (run.subscript !== undefined || run.superscript !== undefined) {\n return { subscript: run.subscript, superscript: run.superscript };\n }\n return { subscript: parent.subscript, superscript: parent.superscript };\n}\n\n/**\n * glow プロパティを描画用の形式に変換する。\n * size はユーザー入力 px、描画用の glow.size は pt。\n * ここで既存の出力互換性を保つデフォルトを確定させる。\n */\nexport function convertGlow(\n glow: TextGlow | undefined,\n): { size: number; opacity: number; color: string } | undefined {\n if (glow === undefined) return undefined;\n return {\n size: pxToPt(glow.size ?? 8),\n opacity: glow.opacity ?? 0.75,\n color: glow.color ?? \"FFFFFF\",\n };\n}\n\n/**\n * outline プロパティを描画用の形式に変換する\n * size はユーザー入力 px、描画用の outline.size は pt\n */\nexport function convertOutline(\n outline: TextOutline | undefined,\n): { size: number; color: string } | undefined {\n if (outline === undefined) return undefined;\n return {\n size: pxToPt(outline.size ?? 1),\n color: outline.color ?? \"FFFFFF\",\n };\n}\n\n/**\n * 行内でグリフ ink が上下中央に来るようにするための描画 y 補正量 (px)\n *\n * レンダラ (LibreOffice / PowerPoint) は固定行送り (spcPts) のとき\n * baseline を「行下端 − winDescent × fontSize」に置くため、グリフ ink\n * (typoAscender + typoDescender) は行内で下寄りになり、同じ gap でも\n * テキスト上側の視覚余白が広く下側が狭く見える (#846)。\n * baseline の実位置と「ink を行内中央に置いたときの baseline 位置」の\n * 差分を返し、呼び出し側でテキストフレームをその分だけ上へずらす。\n */\nexport function calcGlyphCenteringShiftPx(\n fontSizePx: number,\n lineHeight: number,\n fontWeight: \"normal\" | \"bold\",\n): number {\n const lineHeightPx = fontSizePx * lineHeight;\n const metrics = measureFontVerticalMetricsRatio(fontWeight);\n const baselineFromTopPx = lineHeightPx - metrics.winDescent * fontSizePx;\n const inkHeightPx =\n (metrics.typoAscender + metrics.typoDescender) * fontSizePx;\n const centeredBaselineFromTopPx =\n (lineHeightPx - inkHeightPx) / 2 + metrics.typoAscender * fontSizePx;\n return baselineFromTopPx - centeredBaselineFromTopPx;\n}\n\nexport function createTextOptions(node: TextNode) {\n const fontSizePx = node.fontSize ?? 24;\n const fontFamily = node.fontFamily ?? \"Noto Sans JP\";\n const lineHeight = node.lineHeight ?? 1.3;\n\n const area = getContentAreaIn(node);\n const glyphShiftPx = calcGlyphCenteringShiftPx(\n fontSizePx,\n lineHeight,\n node.bold ? \"bold\" : \"normal\",\n );\n\n return {\n ...area,\n y: area.y - pxToIn(glyphShiftPx),\n fontSize: pxToPt(fontSizePx),\n fontFace: fontFamily,\n align: node.textAlign ?? \"left\",\n valign: \"top\" as const,\n margin: 0,\n // 行送りを固定値 (spcPts) で指定する。倍率指定 (spcPct) はレンダラがフォント\n // メトリクスに対する倍率として解釈するため、計測高さ (行数 × fontSize ×\n // lineHeight) と実描画の行高さが一致せず、グリフがボックスからはみ出して\n // 上下余白が非対称になる (#846)\n lineSpacing: pxToPt(fontSizePx * lineHeight),\n rotate: node.rotate,\n color: node.color,\n bold: node.bold,\n italic: node.italic,\n underline: convertUnderline(node.underline),\n strike: convertStrike(node.strike),\n subscript: node.subscript,\n superscript: node.superscript,\n highlight: node.highlight,\n glow: convertGlow(node.glow),\n outline: convertOutline(node.outline),\n // letterSpacing はユーザー入力 px、描画用の charSpacing は pt\n charSpacing:\n node.letterSpacing !== undefined ? pxToPt(node.letterSpacing) : undefined,\n };\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,iBACd,WACwD;CACxD,IAAI,cAAc,KAAA,GAAW,OAAO,KAAA;CACpC,IAAI,cAAc,OAAO,OAAO,KAAA;CAChC,IAAI,cAAc,MAAM,OAAO,EAAE,OAAO,MAAM;CAC9C,OAAO;EACL,OAAO,UAAU;EACjB,OAAO,UAAU;CACnB;AACF;;;;AAKA,SAAgB,cACd,QACyB;CACzB,IAAI,QAAQ,OAAO;AAErB;;;;;;;AAQA,SAAgB,cACd,KACA,QACsE;CACtE,IAAI,IAAI,cAAc,KAAA,KAAa,IAAI,gBAAgB,KAAA,GACrD,OAAO;EAAE,WAAW,IAAI;EAAW,aAAa,IAAI;CAAY;CAElE,OAAO;EAAE,WAAW,OAAO;EAAW,aAAa,OAAO;CAAY;AACxE;;;;;;AAOA,SAAgB,YACd,MAC8D;CAC9D,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;CAC/B,OAAO;EACL,MAAM,OAAO,KAAK,QAAQ,CAAC;EAC3B,SAAS,KAAK,WAAW;EACzB,OAAO,KAAK,SAAS;CACvB;AACF;;;;;AAMA,SAAgB,eACd,SAC6C;CAC7C,IAAI,YAAY,KAAA,GAAW,OAAO,KAAA;CAClC,OAAO;EACL,MAAM,OAAO,QAAQ,QAAQ,CAAC;EAC9B,OAAO,QAAQ,SAAS;CAC1B;AACF;;;;;;;;;;;AAYA,SAAgB,0BACd,YACA,YACA,YACQ;CACR,MAAM,eAAe,aAAa;CAClC,MAAM,UAAU,gCAAgC,UAAU;CAM1D,OAL0B,eAAe,QAAQ,aAAa,eAI3D,gBAFA,QAAQ,eAAe,QAAQ,iBAAiB,cAElB,IAAI,QAAQ,eAAe;AAE9D;AAEA,SAAgB,kBAAkB,MAAgB;CAChD,MAAM,aAAa,KAAK,YAAY;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,aAAa,KAAK,cAAc;CAEtC,MAAM,OAAO,iBAAiB,IAAI;CAClC,MAAM,eAAe,0BACnB,YACA,YACA,KAAK,OAAO,SAAS,QACvB;CAEA,OAAO;EACL,GAAG;EACH,GAAG,KAAK,IAAI,OAAO,YAAY;EAC/B,UAAU,OAAO,UAAU;EAC3B,UAAU;EACV,OAAO,KAAK,aAAa;EACzB,QAAQ;EACR,QAAQ;EAKR,aAAa,OAAO,aAAa,UAAU;EAC3C,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,WAAW,iBAAiB,KAAK,SAAS;EAC1C,QAAQ,cAAc,KAAK,MAAM;EACjC,WAAW,KAAK;EAChB,aAAa,KAAK;EAClB,WAAW,KAAK;EAChB,MAAM,YAAY,KAAK,IAAI;EAC3B,SAAS,eAAe,KAAK,OAAO;EAEpC,aACE,KAAK,kBAAkB,KAAA,IAAY,OAAO,KAAK,aAAa,IAAI,KAAA;CACpE;AACF"}
|
package/dist/renderPptx/units.js
CHANGED
|
@@ -4,12 +4,12 @@ const pxToPt = (px) => px * 72 / 96;
|
|
|
4
4
|
/**
|
|
5
5
|
* px を DrawingML の EMU (English Metric Unit) に変換する。
|
|
6
6
|
* 1 inch = 914400 EMU、96 DPI 基準で 1 px = 9525 EMU。
|
|
7
|
-
*
|
|
7
|
+
* DrawingML の位置・サイズ指定で使う。
|
|
8
8
|
*/
|
|
9
9
|
const pxToEmu = (px) => px * EMU_PER_IN / 96;
|
|
10
10
|
/**
|
|
11
|
-
* px
|
|
12
|
-
* まとめて変換する。
|
|
11
|
+
* px 単位の矩形を互換用の位置オプション (inch 単位の x/y/w/h) に
|
|
12
|
+
* まとめて変換する。
|
|
13
13
|
*/
|
|
14
14
|
const rectPxToIn = (rect) => ({
|
|
15
15
|
x: pxToIn(rect.x),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"units.js","names":[],"sources":["../../src/renderPptx/units.ts"],"sourcesContent":["export const PX_PER_IN = 96;\nexport const EMU_PER_IN = 914400;\n\nexport const pxToIn = (px: number) => px / PX_PER_IN;\n\nexport const pxToPt = (px: number) => (px * 72) / PX_PER_IN;\n\n/**\n * px を DrawingML の EMU (English Metric Unit) に変換する。\n * 1 inch = 914400 EMU、96 DPI 基準で 1 px = 9525 EMU。\n *
|
|
1
|
+
{"version":3,"file":"units.js","names":[],"sources":["../../src/renderPptx/units.ts"],"sourcesContent":["export const PX_PER_IN = 96;\nexport const EMU_PER_IN = 914400;\n\nexport const pxToIn = (px: number) => px / PX_PER_IN;\n\nexport const pxToPt = (px: number) => (px * 72) / PX_PER_IN;\n\n/**\n * px を DrawingML の EMU (English Metric Unit) に変換する。\n * 1 inch = 914400 EMU、96 DPI 基準で 1 px = 9525 EMU。\n * DrawingML の位置・サイズ指定で使う。\n */\nexport const pxToEmu = (px: number) => (px * EMU_PER_IN) / PX_PER_IN;\n\n/**\n * px 単位の矩形を互換用の位置オプション (inch 単位の x/y/w/h) に\n * まとめて変換する。\n */\nexport const rectPxToIn = (rect: {\n x: number;\n y: number;\n w: number;\n h: number;\n}) => ({\n x: pxToIn(rect.x),\n y: pxToIn(rect.y),\n w: pxToIn(rect.w),\n h: pxToIn(rect.h),\n});\n"],"mappings":"AACA,MAAa,aAAa;AAE1B,MAAa,UAAU,OAAe,KAAA;AAEtC,MAAa,UAAU,OAAgB,KAAK,KAAA;;;;;;AAO5C,MAAa,WAAW,OAAgB,KAAK,aAAA;;;;;AAM7C,MAAa,cAAc,UAKpB;CACL,GAAG,OAAO,KAAK,CAAC;CAChB,GAAG,OAAO,KAAK,CAAC;CAChB,GAAG,OAAO,KAAK,CAAC;CAChB,GAAG,OAAO,KAAK,CAAC;AAClB"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { getImageData } from "../../shared/measureImage.js";
|
|
2
|
-
import { rectPxToIn } from "../units.js";
|
|
3
2
|
import { addGlimpsePicture, imageBytesFromSource } from "./glimpsePicture.js";
|
|
4
3
|
import { addGlimpseShape, backgroundShapeFill, createShapeBoundsInput, noShapeOutline, noneShapeFill, shapeOutline } from "./glimpseShape.js";
|
|
5
|
-
import { BORDER_SIDES,
|
|
4
|
+
import { BORDER_SIDES, hasVisibleBorder, resolvePerSideBorders, resolveRectRadius } from "./visualStyle.js";
|
|
6
5
|
//#region src/renderPptx/utils/backgroundBorder.ts
|
|
7
6
|
/**
|
|
8
7
|
* ノードの背景色・背景画像・ボーダー・影を描画する
|
|
@@ -19,28 +18,16 @@ function renderBackgroundAndBorder(node, ctx) {
|
|
|
19
18
|
const hasShadow = Boolean(shadow);
|
|
20
19
|
if (!hasBackground && !hasBackgroundImage && !hasUniformBorder && !perSideBorders && !hasShadow) return;
|
|
21
20
|
const shapeType = borderRadius ? "roundRect" : "rect";
|
|
22
|
-
const legacyShapeType = borderRadius ? ctx.pptx.ShapeType.roundRect : ctx.pptx.ShapeType.rect;
|
|
23
21
|
const rectRadius = resolveRectRadius(borderRadius, node.w, node.h);
|
|
24
22
|
if (!hasBackgroundImage) {
|
|
25
23
|
if (hasBackground || hasUniformBorder || hasShadow) {
|
|
26
|
-
if (hasShadow && !backgroundGradient) {
|
|
27
|
-
ctx.slide.addShape(legacyShapeType, {
|
|
28
|
-
...rectPxToIn(node),
|
|
29
|
-
fill: hasBackground ? {
|
|
30
|
-
color: backgroundColor,
|
|
31
|
-
transparency: node.opacity !== void 0 ? (1 - node.opacity) * 100 : void 0
|
|
32
|
-
} : { type: "none" },
|
|
33
|
-
line: hasUniformBorder ? convertBorderLine(border, "000000") : { type: "none" },
|
|
34
|
-
rectRadius,
|
|
35
|
-
shadow: convertShadow(shadow)
|
|
36
|
-
});
|
|
37
|
-
renderPerSideBorderLines(node, perSideBorders, ctx);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
24
|
const fill = hasBackground ? backgroundShapeFill(backgroundColor, backgroundGradient) : noneShapeFill();
|
|
41
25
|
const line = hasUniformBorder ? shapeOutline(border, "000000") : noShapeOutline();
|
|
42
26
|
addGlimpseShape(ctx, {
|
|
43
|
-
|
|
27
|
+
geometry: {
|
|
28
|
+
kind: "preset",
|
|
29
|
+
preset: shapeType
|
|
30
|
+
},
|
|
44
31
|
...createShapeBoundsInput(node),
|
|
45
32
|
fill,
|
|
46
33
|
outline: line
|
|
@@ -57,7 +44,10 @@ function renderBackgroundAndBorder(node, ctx) {
|
|
|
57
44
|
return;
|
|
58
45
|
}
|
|
59
46
|
if (hasBackground) addGlimpseShape(ctx, {
|
|
60
|
-
|
|
47
|
+
geometry: {
|
|
48
|
+
kind: "preset",
|
|
49
|
+
preset: shapeType
|
|
50
|
+
},
|
|
61
51
|
...createShapeBoundsInput(node),
|
|
62
52
|
fill: backgroundShapeFill(backgroundColor, backgroundGradient),
|
|
63
53
|
outline: noShapeOutline()
|
|
@@ -77,29 +67,19 @@ function renderBackgroundAndBorder(node, ctx) {
|
|
|
77
67
|
h: node.h
|
|
78
68
|
} });
|
|
79
69
|
}
|
|
80
|
-
if (hasUniformBorder || hasShadow) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
preset: shapeType,
|
|
94
|
-
...createShapeBoundsInput(node),
|
|
95
|
-
fill: noneShapeFill(),
|
|
96
|
-
outline: hasUniformBorder ? shapeOutline(border, "000000") : noShapeOutline()
|
|
97
|
-
}, node, {
|
|
98
|
-
shadow,
|
|
99
|
-
rectRadius,
|
|
100
|
-
dashType: border?.dashType
|
|
101
|
-
});
|
|
102
|
-
}
|
|
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,
|
|
80
|
+
rectRadius,
|
|
81
|
+
dashType: border?.dashType
|
|
82
|
+
});
|
|
103
83
|
renderPerSideBorderLines(node, perSideBorders, ctx);
|
|
104
84
|
}
|
|
105
85
|
/**
|
|
@@ -129,7 +109,10 @@ function renderBorderOnly(node, ctx) {
|
|
|
129
109
|
}
|
|
130
110
|
if (!hasVisibleBorder(border)) return;
|
|
131
111
|
addGlimpseShape(ctx, {
|
|
132
|
-
|
|
112
|
+
geometry: {
|
|
113
|
+
kind: "preset",
|
|
114
|
+
preset: borderRadius ? "roundRect" : "rect"
|
|
115
|
+
},
|
|
133
116
|
...createShapeBoundsInput(node),
|
|
134
117
|
fill: noneShapeFill(),
|
|
135
118
|
outline: shapeOutline(border, "000000")
|
|
@@ -175,7 +158,10 @@ function renderPerSideBorderLines(node, perSideBorders, ctx) {
|
|
|
175
158
|
const style = perSideBorders[side];
|
|
176
159
|
if (!style) continue;
|
|
177
160
|
addGlimpseShape(ctx, {
|
|
178
|
-
|
|
161
|
+
geometry: {
|
|
162
|
+
kind: "preset",
|
|
163
|
+
preset: "line"
|
|
164
|
+
},
|
|
179
165
|
...createShapeBoundsInput(edges[side]),
|
|
180
166
|
fill: noneShapeFill(),
|
|
181
167
|
outline: shapeOutline(style, "000000")
|