@hirokisakabe/pom 9.1.1 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buildContext.js +2 -6
- 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 -9
- 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 +98 -90
- package/dist/renderPptx/nodes/flow.js.map +1 -1
- package/dist/renderPptx/nodes/icon.js +36 -24
- package/dist/renderPptx/nodes/icon.js.map +1 -1
- package/dist/renderPptx/nodes/image.js +5 -22
- package/dist/renderPptx/nodes/image.js.map +1 -1
- package/dist/renderPptx/nodes/list.js +94 -111
- package/dist/renderPptx/nodes/list.js.map +1 -1
- package/dist/renderPptx/nodes/matrix.js +95 -70
- package/dist/renderPptx/nodes/matrix.js.map +1 -1
- package/dist/renderPptx/nodes/processArrow.js +74 -35
- package/dist/renderPptx/nodes/processArrow.js.map +1 -1
- package/dist/renderPptx/nodes/pyramid.js +37 -16
- package/dist/renderPptx/nodes/pyramid.js.map +1 -1
- package/dist/renderPptx/nodes/shape.js +66 -37
- package/dist/renderPptx/nodes/shape.js.map +1 -1
- package/dist/renderPptx/nodes/svg.js +2 -5
- package/dist/renderPptx/nodes/svg.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 +114 -74
- package/dist/renderPptx/nodes/timeline.js.map +1 -1
- package/dist/renderPptx/nodes/tree.js +81 -79
- 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 +158 -128
- 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 +70 -48
- 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 +111 -0
- package/dist/renderPptx/utils/glimpsePicture.js.map +1 -0
- package/dist/renderPptx/utils/glimpseShape.js +71 -0
- package/dist/renderPptx/utils/glimpseShape.js.map +1 -0
- package/dist/renderPptx/utils/glimpseTextBox.js +107 -0
- package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -0
- package/dist/renderPptx/utils/straightLine.js +33 -15
- package/dist/renderPptx/utils/straightLine.js.map +1 -1
- package/dist/renderPptx/utils/visualStyle.js +3 -56
- 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 -347
- package/dist/renderPptx/glimpseTextBoxes.js.map +0 -1
- package/dist/renderPptx/glowEffects.js +0 -157
- package/dist/renderPptx/glowEffects.js.map +0 -1
- package/dist/renderPptx/gradientFills.js +0 -154
- package/dist/renderPptx/gradientFills.js.map +0 -1
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import { EMU_PER_IN, pxToEmu, pxToPt } from "./units.js";
|
|
2
|
-
import { createTextOptions, resolveSubSup } from "./textOptions.js";
|
|
3
|
-
import { parseLinearGradient } from "../shared/gradient.js";
|
|
4
|
-
import { addTextBox, asEmu, asHundredthPt, asOoxmlAngle, asOoxmlPercent, asPt, createPptx } from "@pptx-glimpse/document";
|
|
5
|
-
//#region src/renderPptx/glimpseTextBoxes.ts
|
|
6
|
-
/**
|
|
7
|
-
* Text primitive の @pptx-glimpse/document writer への段階的 swap。
|
|
8
|
-
*
|
|
9
|
-
* 混在期間の合成方式は「pptxgenjs 出力 zip をベースに、swap 済み Text
|
|
10
|
-
* primitive の shape XML だけを glimpse writer で生成して該当 marker shape と
|
|
11
|
-
* 差し替える」方式を採用する。pptxgenjs 側にはまだ Shape / Image / Table /
|
|
12
|
-
* Chart など未 swap primitive と slide master 生成が残っているため、既存 zip を
|
|
13
|
-
* ベースにすると [Content_Types].xml / rels / media parts の管理を現行実装へ
|
|
14
|
-
* 寄せられる。代替案として glimpse の package をベースに未 swap primitive を
|
|
15
|
-
* pptxgenjs から取り込む方式も検討したが、初回スライス時点では pptxgenjs 側の
|
|
16
|
-
* 非 text primitive と master 出力を XML part 単位で切り出す責務が増え、以降の
|
|
17
|
-
* primitive swap より先に package 合成の複雑さが大きくなるため採用しない。
|
|
18
|
-
*
|
|
19
|
-
* marker shape は描画順を保持するためだけに pptxgenjs へ追加し、write 時に
|
|
20
|
-
* glimpse の `<p:sp>` で丸ごと置換する。Text content 自体は pptxgenjs `addText`
|
|
21
|
-
* を経由しない。
|
|
22
|
-
*/
|
|
23
|
-
const MARKER_PREFIX = "pom-text:";
|
|
24
|
-
const HYPERLINK_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
25
|
-
async function loadJSZip() {
|
|
26
|
-
const mod = await import("jszip");
|
|
27
|
-
return mod.default ?? mod;
|
|
28
|
-
}
|
|
29
|
-
function cleanHex(color) {
|
|
30
|
-
return color?.replace(/^#/, "").toUpperCase();
|
|
31
|
-
}
|
|
32
|
-
function toColorInput(color) {
|
|
33
|
-
const hex = cleanHex(color);
|
|
34
|
-
return hex ? {
|
|
35
|
-
kind: "srgb",
|
|
36
|
-
hex
|
|
37
|
-
} : void 0;
|
|
38
|
-
}
|
|
39
|
-
function toUnderlineInput(underline) {
|
|
40
|
-
if (underline === void 0 || underline === false) return void 0;
|
|
41
|
-
if (underline === true) return true;
|
|
42
|
-
return {
|
|
43
|
-
style: underline.style,
|
|
44
|
-
color: toColorInput(underline.color)
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function toBaselineInput(subscript, superscript) {
|
|
48
|
-
if (subscript) return "subscript";
|
|
49
|
-
if (superscript) return "superscript";
|
|
50
|
-
}
|
|
51
|
-
function toGlowInput(glow) {
|
|
52
|
-
if (!glow) return void 0;
|
|
53
|
-
return {
|
|
54
|
-
radius: asEmu(Math.round(pxToEmu(glow.size ?? 8))),
|
|
55
|
-
color: toColorInput(glow.color ?? "FFFFFF")
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function toOutlineInput(outline) {
|
|
59
|
-
if (!outline) return void 0;
|
|
60
|
-
return {
|
|
61
|
-
width: asEmu(Math.round(pxToEmu(outline.size ?? 1))),
|
|
62
|
-
color: toColorInput(outline.color ?? "FFFFFF")
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function toCharSpacing(letterSpacingPx) {
|
|
66
|
-
if (letterSpacingPx === void 0) return void 0;
|
|
67
|
-
return Math.round(pxToPt(letterSpacingPx) * 100);
|
|
68
|
-
}
|
|
69
|
-
function toTextGradientInput(value) {
|
|
70
|
-
if (!value) return void 0;
|
|
71
|
-
const linear = parseLinearGradient(value);
|
|
72
|
-
if (!linear) return void 0;
|
|
73
|
-
const dmlAngle = ((linear.angle - 90) % 360 + 360) % 360;
|
|
74
|
-
return {
|
|
75
|
-
angle: asOoxmlAngle(Math.round(dmlAngle * 6e4)),
|
|
76
|
-
stops: linear.stops.map((stop) => ({
|
|
77
|
-
position: asOoxmlPercent(Math.round(stop.position * 1e3)),
|
|
78
|
-
color: toColorInput(stop.color)
|
|
79
|
-
}))
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function stripUndefined(value) {
|
|
83
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
84
|
-
}
|
|
85
|
-
function buildRunProperties(node, run, gradientFill) {
|
|
86
|
-
const fontSizePx = run?.fontSize ?? node.fontSize ?? 24;
|
|
87
|
-
const letterSpacingPx = run?.letterSpacing ?? node.letterSpacing;
|
|
88
|
-
const subSup = run ? resolveSubSup(run, node) : node;
|
|
89
|
-
const color = gradientFill ? void 0 : toColorInput(run?.color ?? node.color);
|
|
90
|
-
return stripUndefined({
|
|
91
|
-
fontFace: run?.fontFamily ?? node.fontFamily ?? "Noto Sans JP",
|
|
92
|
-
fontSize: asPt(pxToPt(fontSizePx)),
|
|
93
|
-
color,
|
|
94
|
-
gradientFill,
|
|
95
|
-
bold: run?.bold ?? node.bold,
|
|
96
|
-
italic: run?.italic ?? node.italic,
|
|
97
|
-
underline: toUnderlineInput(resolveUnderline(node, run)),
|
|
98
|
-
strike: run?.strike ?? node.strike,
|
|
99
|
-
baseline: toBaselineInput(subSup.subscript, subSup.superscript),
|
|
100
|
-
highlight: toColorInput(run?.highlight ?? node.highlight),
|
|
101
|
-
glow: toGlowInput(node.glow),
|
|
102
|
-
outline: toOutlineInput(node.outline),
|
|
103
|
-
charSpacing: toCharSpacing(letterSpacingPx)
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
function resolveUnderline(node, run) {
|
|
107
|
-
if (run?.underline !== void 0) return run.underline;
|
|
108
|
-
if (node.underline !== void 0) return node.underline;
|
|
109
|
-
return run?.href ? true : void 0;
|
|
110
|
-
}
|
|
111
|
-
function createParagraphProperties(node) {
|
|
112
|
-
const lineHeight = node.lineHeight ?? 1.3;
|
|
113
|
-
const fontSizePx = node.fontSize ?? 24;
|
|
114
|
-
return stripUndefined({
|
|
115
|
-
align: node.textAlign,
|
|
116
|
-
lineSpacing: asHundredthPt(Math.round(pxToPt(fontSizePx * lineHeight) * 100))
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
function buildParagraphs(node) {
|
|
120
|
-
const gradientFill = toTextGradientInput(node.textGradient);
|
|
121
|
-
const sourceRuns = node.runs && node.runs.length > 0 ? node.runs.map((run) => ({
|
|
122
|
-
text: run.text,
|
|
123
|
-
properties: buildRunProperties(node, run, gradientFill),
|
|
124
|
-
href: run.href
|
|
125
|
-
})) : [{
|
|
126
|
-
text: node.text ?? "",
|
|
127
|
-
properties: buildRunProperties(node, void 0, gradientFill)
|
|
128
|
-
}];
|
|
129
|
-
const paragraphRuns = [[]];
|
|
130
|
-
for (const run of sourceRuns) run.text.replace(/\r*\n/g, "\n").split("\n").forEach((line, index) => {
|
|
131
|
-
if (index > 0) paragraphRuns.push([]);
|
|
132
|
-
paragraphRuns[paragraphRuns.length - 1]?.push({
|
|
133
|
-
...run,
|
|
134
|
-
text: line
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
return {
|
|
138
|
-
paragraphs: paragraphRuns.map((runs) => ({
|
|
139
|
-
properties: createParagraphProperties(node),
|
|
140
|
-
runs
|
|
141
|
-
})),
|
|
142
|
-
hyperlinks: paragraphRuns.flatMap((runs) => runs.map((run) => run.text ? run.href : void 0))
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
function withGlowAlpha(xml, node) {
|
|
146
|
-
const glow = node.glow;
|
|
147
|
-
if (!glow) return xml;
|
|
148
|
-
const alpha = Math.round((glow.opacity ?? .75) * 1e5);
|
|
149
|
-
const color = cleanHex(glow.color ?? "FFFFFF");
|
|
150
|
-
const target = `<a:glow rad="${Math.round(pxToEmu(glow.size ?? 8))}"><a:srgbClr val="${color}"/></a:glow>`;
|
|
151
|
-
const replacement = `<a:glow rad="${Math.round(pxToEmu(glow.size ?? 8))}"><a:srgbClr val="${color}"><a:alpha val="${alpha}"/></a:srgbClr></a:glow>`;
|
|
152
|
-
return xml.replaceAll(target, replacement);
|
|
153
|
-
}
|
|
154
|
-
function withPptxGenParagraphDefaults(xml) {
|
|
155
|
-
let result = xml.replaceAll("baseline=\"-25000\"", "baseline=\"-40000\"");
|
|
156
|
-
result = result.replace(/<a:bodyPr\b([^>]*)\/>/g, (_match, attrs) => {
|
|
157
|
-
const attrText = attrs;
|
|
158
|
-
const withRtl = /(?:^|\s)rtlCol=/.test(attrText) ? attrText : `${attrText} rtlCol="0"`;
|
|
159
|
-
return `<a:bodyPr${/(?:^|\s)anchor=/.test(withRtl) ? withRtl : `${withRtl} anchor="t"`}/>`;
|
|
160
|
-
});
|
|
161
|
-
result = result.replace(/<a:pPr([^>]*)>([\s\S]*?)<\/a:pPr>/g, (match, attrs, body) => {
|
|
162
|
-
const attrText = attrs;
|
|
163
|
-
const nextAttrs = /(?:^|\s)indent=/.test(attrText) ? attrText : `${attrText} indent="0" marL="0"`;
|
|
164
|
-
const bodyText = body;
|
|
165
|
-
return `<a:pPr${nextAttrs}>${bodyText.includes("<a:buNone/>") ? bodyText : `${bodyText}<a:buNone/>`}</a:pPr>`;
|
|
166
|
-
});
|
|
167
|
-
result = result.replace(/<a:p>(?!<a:pPr)/g, "<a:p><a:pPr indent=\"0\" marL=\"0\"><a:buNone/></a:pPr>");
|
|
168
|
-
return result;
|
|
169
|
-
}
|
|
170
|
-
function createTextBoxXml(node, name) {
|
|
171
|
-
const source = createPptx();
|
|
172
|
-
const slideHandle = source.slides[0]?.handle;
|
|
173
|
-
if (!slideHandle) throw new Error("createPptx did not create an editable slide");
|
|
174
|
-
const textOptions = createTextOptions(node);
|
|
175
|
-
const { paragraphs, hyperlinks } = buildParagraphs(node);
|
|
176
|
-
const edit = addTextBox(source, slideHandle, {
|
|
177
|
-
offsetX: asEmu(Math.round(textOptions.x * EMU_PER_IN)),
|
|
178
|
-
offsetY: asEmu(Math.round(textOptions.y * EMU_PER_IN)),
|
|
179
|
-
width: asEmu(Math.round(textOptions.w * EMU_PER_IN)),
|
|
180
|
-
height: asEmu(Math.round(textOptions.h * EMU_PER_IN)),
|
|
181
|
-
rotation: node.rotate !== void 0 ? asOoxmlAngle(Math.round(node.rotate * 6e4)) : void 0,
|
|
182
|
-
name,
|
|
183
|
-
body: {
|
|
184
|
-
marginLeft: asEmu(0),
|
|
185
|
-
marginRight: asEmu(0),
|
|
186
|
-
marginTop: asEmu(0),
|
|
187
|
-
marginBottom: asEmu(0)
|
|
188
|
-
},
|
|
189
|
-
paragraphs
|
|
190
|
-
}).edits?.at(-1);
|
|
191
|
-
if (edit?.kind !== "addTextBox") throw new Error("addTextBox did not produce an addTextBox edit");
|
|
192
|
-
return {
|
|
193
|
-
xml: withPptxGenParagraphDefaults(withGlowAlpha(edit.xml, node)),
|
|
194
|
-
hyperlinks
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
var GlimpseTextBoxRegistry = class {
|
|
198
|
-
registered = [];
|
|
199
|
-
register(node) {
|
|
200
|
-
const index = this.registered.length;
|
|
201
|
-
const marker = `${MARKER_PREFIX}${index}`;
|
|
202
|
-
const name = `Text ${index + 1}`;
|
|
203
|
-
const { xml, hyperlinks } = createTextBoxXml(node, name);
|
|
204
|
-
this.registered.push({
|
|
205
|
-
marker,
|
|
206
|
-
name,
|
|
207
|
-
xml,
|
|
208
|
-
hyperlinks
|
|
209
|
-
});
|
|
210
|
-
return marker;
|
|
211
|
-
}
|
|
212
|
-
get isEmpty() {
|
|
213
|
-
return this.registered.length === 0;
|
|
214
|
-
}
|
|
215
|
-
get entries() {
|
|
216
|
-
return this.registered;
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
function escapeRegExp(value) {
|
|
220
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
221
|
-
}
|
|
222
|
-
function replaceShapeId(xml, id, name) {
|
|
223
|
-
return xml.replace(/<p:cNvPr id="[^"]+" name="[^"]*"/, `<p:cNvPr id="${id}" name="${name}"`);
|
|
224
|
-
}
|
|
225
|
-
function xmlAttr(value) {
|
|
226
|
-
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
227
|
-
}
|
|
228
|
-
function slideRelsPath(slidePath) {
|
|
229
|
-
return `ppt/slides/_rels/${slidePath.split("/").at(-1)}.rels`;
|
|
230
|
-
}
|
|
231
|
-
var SlideRelationshipEditor = class {
|
|
232
|
-
xml;
|
|
233
|
-
nextId;
|
|
234
|
-
changed = false;
|
|
235
|
-
constructor(xml) {
|
|
236
|
-
this.xml = xml;
|
|
237
|
-
const ids = Array.from(xml.matchAll(/\bId="rId(\d+)"/g), (match) => Number(match[1]));
|
|
238
|
-
this.nextId = Math.max(0, ...ids) + 1;
|
|
239
|
-
}
|
|
240
|
-
addHyperlink(href) {
|
|
241
|
-
const id = `rId${this.nextId++}`;
|
|
242
|
-
const rel = `<Relationship Id="${id}" Type="${HYPERLINK_REL_TYPE}" Target="${xmlAttr(href)}" TargetMode="External"/>`;
|
|
243
|
-
this.xml = this.xml.replace("</Relationships>", `${rel}</Relationships>`);
|
|
244
|
-
this.changed = true;
|
|
245
|
-
return id;
|
|
246
|
-
}
|
|
247
|
-
get result() {
|
|
248
|
-
return {
|
|
249
|
-
xml: this.xml,
|
|
250
|
-
changed: this.changed
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
function createRelationshipEditor(xml) {
|
|
255
|
-
return new SlideRelationshipEditor(xml ?? "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"></Relationships>");
|
|
256
|
-
}
|
|
257
|
-
function withHyperlinkRelationships(xml, hyperlinks, addRelationship) {
|
|
258
|
-
if (!hyperlinks.some(Boolean)) return xml;
|
|
259
|
-
let index = 0;
|
|
260
|
-
return xml.replace(/<a:rPr\b([^>]*)>([\s\S]*?)<\/a:rPr>/g, (match, attrs, body) => {
|
|
261
|
-
const href = hyperlinks[index++];
|
|
262
|
-
if (!href) return match;
|
|
263
|
-
return `<a:rPr${attrs}>${body}<a:hlinkClick r:id="${addRelationship(href)}"/></a:rPr>`;
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
function applyGlimpseTextBoxesToXml(xml, registry, addRelationship) {
|
|
267
|
-
let result = xml;
|
|
268
|
-
for (const entry of registry.entries) {
|
|
269
|
-
const re = new RegExp(`<p:sp><p:nvSpPr><p:cNvPr id="([^"]+)" name="${escapeRegExp(entry.marker)}"[\\s\\S]*?</p:sp>`, "g");
|
|
270
|
-
result = result.replace(re, (_match, id) => {
|
|
271
|
-
return withHyperlinkRelationships(replaceShapeId(entry.xml, id, entry.name), entry.hyperlinks, addRelationship);
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
return result;
|
|
275
|
-
}
|
|
276
|
-
async function applyGlimpseTextBoxes(data, registry) {
|
|
277
|
-
const zip = await (await loadJSZip()).loadAsync(data);
|
|
278
|
-
const slidePaths = Object.keys(zip.files).filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path));
|
|
279
|
-
for (const path of slidePaths) {
|
|
280
|
-
const file = zip.file(path);
|
|
281
|
-
if (!file) continue;
|
|
282
|
-
const original = await file.async("text");
|
|
283
|
-
const relsPath = slideRelsPath(path);
|
|
284
|
-
const relsFile = zip.file(relsPath);
|
|
285
|
-
const relationshipEditor = createRelationshipEditor(relsFile ? await relsFile.async("text") : void 0);
|
|
286
|
-
const replaced = applyGlimpseTextBoxesToXml(original, registry, (href) => relationshipEditor.addHyperlink(href));
|
|
287
|
-
if (replaced !== original) zip.file(path, replaced);
|
|
288
|
-
const relationships = relationshipEditor.result;
|
|
289
|
-
if (relationships.changed) zip.file(relsPath, relationships.xml);
|
|
290
|
-
}
|
|
291
|
-
return zip;
|
|
292
|
-
}
|
|
293
|
-
function patchPptxWriteForGlimpseTextBoxes(pptx, registry) {
|
|
294
|
-
if (registry.isEmpty) return;
|
|
295
|
-
const originalWrite = pptx.write.bind(pptx);
|
|
296
|
-
const patchedWrite = async (rawProps) => {
|
|
297
|
-
const props = typeof rawProps === "string" ? { outputType: rawProps } : rawProps;
|
|
298
|
-
const zip = await applyGlimpseTextBoxes(await originalWrite({ outputType: "uint8array" }), registry);
|
|
299
|
-
const outputType = props?.outputType;
|
|
300
|
-
if (outputType === "STREAM") return zip.generateAsync({
|
|
301
|
-
type: "nodebuffer",
|
|
302
|
-
compression: props?.compression ? "DEFLATE" : "STORE"
|
|
303
|
-
});
|
|
304
|
-
if (outputType) return zip.generateAsync({
|
|
305
|
-
type: outputType,
|
|
306
|
-
compression: props?.compression ? "DEFLATE" : "STORE"
|
|
307
|
-
});
|
|
308
|
-
return zip.generateAsync({
|
|
309
|
-
type: "blob",
|
|
310
|
-
compression: props?.compression ? "DEFLATE" : "STORE"
|
|
311
|
-
});
|
|
312
|
-
};
|
|
313
|
-
pptx.write = patchedWrite;
|
|
314
|
-
const patchedStream = async (props) => pptx.write({
|
|
315
|
-
outputType: "STREAM",
|
|
316
|
-
compression: props?.compression
|
|
317
|
-
});
|
|
318
|
-
pptx.stream = patchedStream;
|
|
319
|
-
const patchedWriteFile = async (rawProps) => {
|
|
320
|
-
const props = typeof rawProps === "string" ? { fileName: rawProps } : rawProps;
|
|
321
|
-
if (!(typeof process !== "undefined" && Boolean(process.versions?.node))) {
|
|
322
|
-
const browserWriter = pptx;
|
|
323
|
-
if (typeof browserWriter.writeFileToBrowser !== "function") throw new Error("pptx.writeFile browser download helper is unavailable; use pptx.write({ outputType: 'blob' }) instead");
|
|
324
|
-
const rawName = props?.fileName ?? "Presentation.pptx";
|
|
325
|
-
const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
|
|
326
|
-
const blob = await patchedWrite({
|
|
327
|
-
outputType: "blob",
|
|
328
|
-
compression: props?.compression
|
|
329
|
-
});
|
|
330
|
-
await browserWriter.writeFileToBrowser(fileName, blob);
|
|
331
|
-
return fileName;
|
|
332
|
-
}
|
|
333
|
-
const rawName = props?.fileName ?? "Presentation.pptx";
|
|
334
|
-
const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
|
|
335
|
-
const buffer = await patchedWrite({
|
|
336
|
-
outputType: "nodebuffer",
|
|
337
|
-
compression: props?.compression
|
|
338
|
-
});
|
|
339
|
-
await (await import("fs")).promises.writeFile(fileName, buffer);
|
|
340
|
-
return fileName;
|
|
341
|
-
};
|
|
342
|
-
pptx.writeFile = patchedWriteFile;
|
|
343
|
-
}
|
|
344
|
-
//#endregion
|
|
345
|
-
export { GlimpseTextBoxRegistry, patchPptxWriteForGlimpseTextBoxes };
|
|
346
|
-
|
|
347
|
-
//# sourceMappingURL=glimpseTextBoxes.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"glimpseTextBoxes.js","names":[],"sources":["../../src/renderPptx/glimpseTextBoxes.ts"],"sourcesContent":["/**\n * Text primitive の @pptx-glimpse/document writer への段階的 swap。\n *\n * 混在期間の合成方式は「pptxgenjs 出力 zip をベースに、swap 済み Text\n * primitive の shape XML だけを glimpse writer で生成して該当 marker shape と\n * 差し替える」方式を採用する。pptxgenjs 側にはまだ Shape / Image / Table /\n * Chart など未 swap primitive と slide master 生成が残っているため、既存 zip を\n * ベースにすると [Content_Types].xml / rels / media parts の管理を現行実装へ\n * 寄せられる。代替案として glimpse の package をベースに未 swap primitive を\n * pptxgenjs から取り込む方式も検討したが、初回スライス時点では pptxgenjs 側の\n * 非 text primitive と master 出力を XML part 単位で切り出す責務が増え、以降の\n * primitive swap より先に package 合成の複雑さが大きくなるため採用しない。\n *\n * marker shape は描画順を保持するためだけに pptxgenjs へ追加し、write 時に\n * glimpse の `<p:sp>` で丸ごと置換する。Text content 自体は pptxgenjs `addText`\n * を経由しない。\n */\nimport {\n addTextBox,\n asEmu,\n asHundredthPt,\n asOoxmlAngle,\n asOoxmlPercent,\n asPt,\n createPptx,\n type AddTextBoxGradientFillInput,\n type AddTextBoxInput,\n type AddTextBoxParagraphInput,\n type AddTextBoxRunPropertiesInput,\n type PptxSourceModelAddTextBoxEdit,\n} from \"@pptx-glimpse/document\";\nimport type {\n PositionedNode,\n TextGlow,\n TextOutline,\n Underline,\n} from \"../types.ts\";\nimport { parseLinearGradient } from \"../shared/gradient.ts\";\nimport { EMU_PER_IN, pxToEmu, pxToPt } from \"./units.ts\";\nimport { createTextOptions, resolveSubSup } from \"./textOptions.ts\";\n\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\ntype StreamProps = NonNullable<Parameters<PptxGenJSInstance[\"stream\"]>[0]>;\ntype WriteProps = NonNullable<Parameters<PptxGenJSInstance[\"write\"]>[0]>;\ntype WriteFileProps = NonNullable<\n Parameters<PptxGenJSInstance[\"writeFile\"]>[0]\n>;\ntype BrowserWritablePptx = PptxGenJSInstance & {\n writeFileToBrowser?: (fileName: string, blobContent: Blob) => Promise<string>;\n};\ntype TextPositionedNode = Extract<PositionedNode, { type: \"text\" }>;\n\nconst MARKER_PREFIX = \"pom-text:\";\nconst HYPERLINK_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink\";\n\ninterface GlimpseTextRun {\n text: string;\n properties: AddTextBoxRunPropertiesInput;\n href?: string;\n}\n\nasync function loadJSZip(): Promise<typeof import(\"jszip\")> {\n const mod = await import(\"jszip\");\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n return (mod as any).default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\n\nfunction cleanHex(color: string | undefined): string | undefined {\n return color?.replace(/^#/, \"\").toUpperCase();\n}\n\nfunction toColorInput(color: string | undefined) {\n const hex = cleanHex(color);\n return hex ? { kind: \"srgb\" as const, hex } : undefined;\n}\n\nfunction toUnderlineInput(underline: Underline | undefined) {\n if (underline === undefined || underline === false) return undefined;\n if (underline === true) return true;\n return {\n style: underline.style,\n color: toColorInput(underline.color),\n };\n}\n\nfunction toBaselineInput(\n subscript: boolean | undefined,\n superscript: boolean | undefined,\n) {\n if (subscript) return \"subscript\" as const;\n if (superscript) return \"superscript\" as const;\n return undefined;\n}\n\nfunction toGlowInput(glow: TextGlow | undefined) {\n if (!glow) return undefined;\n return {\n radius: asEmu(Math.round(pxToEmu(glow.size ?? 8))),\n color: toColorInput(glow.color ?? \"FFFFFF\")!,\n };\n}\n\nfunction toOutlineInput(outline: TextOutline | undefined) {\n if (!outline) return undefined;\n return {\n width: asEmu(Math.round(pxToEmu(outline.size ?? 1))),\n color: toColorInput(outline.color ?? \"FFFFFF\"),\n };\n}\n\nfunction toCharSpacing(letterSpacingPx: number | undefined) {\n if (letterSpacingPx === undefined) return undefined;\n return Math.round(pxToPt(letterSpacingPx) * 100);\n}\n\nfunction toTextGradientInput(\n value: string | undefined,\n): AddTextBoxGradientFillInput | undefined {\n if (!value) return undefined;\n const linear = parseLinearGradient(value);\n if (!linear) return undefined;\n const dmlAngle = (((linear.angle - 90) % 360) + 360) % 360;\n return {\n angle: asOoxmlAngle(Math.round(dmlAngle * 60000)),\n stops: linear.stops.map((stop) => ({\n position: asOoxmlPercent(Math.round(stop.position * 1000)),\n color: toColorInput(stop.color)!,\n })),\n };\n}\n\nfunction stripUndefined<T extends Record<string, unknown>>(value: T): T {\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined),\n ) as T;\n}\n\nfunction buildRunProperties(\n node: TextPositionedNode,\n run: NonNullable<TextPositionedNode[\"runs\"]>[number] | undefined,\n gradientFill: AddTextBoxGradientFillInput | undefined,\n): AddTextBoxRunPropertiesInput {\n const fontSizePx = run?.fontSize ?? node.fontSize ?? 24;\n const letterSpacingPx = run?.letterSpacing ?? node.letterSpacing;\n const subSup = run ? resolveSubSup(run, node) : node;\n const color = gradientFill\n ? undefined\n : toColorInput(run?.color ?? node.color);\n\n return stripUndefined({\n fontFace: run?.fontFamily ?? node.fontFamily ?? \"Noto Sans JP\",\n fontSize: asPt(pxToPt(fontSizePx)),\n color,\n gradientFill,\n bold: run?.bold ?? node.bold,\n italic: run?.italic ?? node.italic,\n underline: toUnderlineInput(resolveUnderline(node, run)),\n strike: run?.strike ?? node.strike,\n baseline: toBaselineInput(subSup.subscript, subSup.superscript),\n highlight: toColorInput(run?.highlight ?? node.highlight),\n glow: toGlowInput(node.glow),\n outline: toOutlineInput(node.outline),\n charSpacing: toCharSpacing(letterSpacingPx),\n });\n}\n\nfunction resolveUnderline(\n node: TextPositionedNode,\n run: NonNullable<TextPositionedNode[\"runs\"]>[number] | undefined,\n): Underline | undefined {\n if (run?.underline !== undefined) return run.underline;\n if (node.underline !== undefined) return node.underline;\n return run?.href ? true : undefined;\n}\n\nfunction createParagraphProperties(\n node: TextPositionedNode,\n): AddTextBoxParagraphInput[\"properties\"] {\n const lineHeight = node.lineHeight ?? 1.3;\n const fontSizePx = node.fontSize ?? 24;\n return stripUndefined({\n align: node.textAlign,\n lineSpacing: asHundredthPt(\n Math.round(pxToPt(fontSizePx * lineHeight) * 100),\n ),\n });\n}\n\nfunction buildParagraphs(node: TextPositionedNode): {\n paragraphs: readonly AddTextBoxParagraphInput[];\n hyperlinks: readonly (string | undefined)[];\n} {\n const gradientFill = toTextGradientInput(node.textGradient);\n const sourceRuns: GlimpseTextRun[] =\n node.runs && node.runs.length > 0\n ? node.runs.map((run) => ({\n text: run.text,\n properties: buildRunProperties(node, run, gradientFill),\n href: run.href,\n }))\n : [\n {\n text: node.text ?? \"\",\n properties: buildRunProperties(node, undefined, gradientFill),\n },\n ];\n const paragraphRuns: GlimpseTextRun[][] = [[]];\n for (const run of sourceRuns) {\n const lines = run.text.replace(/\\r*\\n/g, \"\\n\").split(\"\\n\");\n lines.forEach((line, index) => {\n if (index > 0) {\n paragraphRuns.push([]);\n }\n paragraphRuns[paragraphRuns.length - 1]?.push({\n ...run,\n text: line,\n });\n });\n }\n\n return {\n paragraphs: paragraphRuns.map((runs) => ({\n properties: createParagraphProperties(node),\n runs,\n })),\n hyperlinks: paragraphRuns.flatMap((runs) =>\n runs.map((run) => (run.text ? run.href : undefined)),\n ),\n };\n}\n\nfunction withGlowAlpha(xml: string, node: TextPositionedNode): string {\n const glow = node.glow;\n if (!glow) return xml;\n const alpha = Math.round((glow.opacity ?? 0.75) * 100000);\n const color = cleanHex(glow.color ?? \"FFFFFF\");\n const target = `<a:glow rad=\"${Math.round(pxToEmu(glow.size ?? 8))}\"><a:srgbClr val=\"${color}\"/></a:glow>`;\n const replacement = `<a:glow rad=\"${Math.round(pxToEmu(glow.size ?? 8))}\"><a:srgbClr val=\"${color}\"><a:alpha val=\"${alpha}\"/></a:srgbClr></a:glow>`;\n return xml.replaceAll(target, replacement);\n}\n\nfunction withPptxGenParagraphDefaults(xml: string): string {\n let result = xml.replaceAll('baseline=\"-25000\"', 'baseline=\"-40000\"');\n result = result.replace(/<a:bodyPr\\b([^>]*)\\/>/g, (_match, attrs) => {\n const attrText = attrs as string;\n const withRtl = /(?:^|\\s)rtlCol=/.test(attrText)\n ? attrText\n : `${attrText} rtlCol=\"0\"`;\n const withAnchor = /(?:^|\\s)anchor=/.test(withRtl)\n ? withRtl\n : `${withRtl} anchor=\"t\"`;\n return `<a:bodyPr${withAnchor}/>`;\n });\n result = result.replace(\n /<a:pPr([^>]*)>([\\s\\S]*?)<\\/a:pPr>/g,\n (match, attrs, body) => {\n const attrText = attrs as string;\n const nextAttrs = /(?:^|\\s)indent=/.test(attrText)\n ? attrText\n : `${attrText} indent=\"0\" marL=\"0\"`;\n const bodyText = body as string;\n const nextBody = bodyText.includes(\"<a:buNone/>\")\n ? bodyText\n : `${bodyText}<a:buNone/>`;\n return `<a:pPr${nextAttrs}>${nextBody}</a:pPr>`;\n },\n );\n result = result.replace(\n /<a:p>(?!<a:pPr)/g,\n '<a:p><a:pPr indent=\"0\" marL=\"0\"><a:buNone/></a:pPr>',\n );\n return result;\n}\n\nfunction createTextBoxXml(\n node: TextPositionedNode,\n name: string,\n): { xml: string; hyperlinks: readonly (string | undefined)[] } {\n const source = createPptx();\n const slideHandle = source.slides[0]?.handle;\n if (!slideHandle) {\n throw new Error(\"createPptx did not create an editable slide\");\n }\n\n const textOptions = createTextOptions(node);\n const { paragraphs, hyperlinks } = buildParagraphs(node);\n const input: AddTextBoxInput = {\n offsetX: asEmu(Math.round(textOptions.x * EMU_PER_IN)),\n offsetY: asEmu(Math.round(textOptions.y * EMU_PER_IN)),\n width: asEmu(Math.round(textOptions.w * EMU_PER_IN)),\n height: asEmu(Math.round(textOptions.h * EMU_PER_IN)),\n rotation:\n node.rotate !== undefined\n ? asOoxmlAngle(Math.round(node.rotate * 60000))\n : undefined,\n name,\n body: {\n marginLeft: asEmu(0),\n marginRight: asEmu(0),\n marginTop: asEmu(0),\n marginBottom: asEmu(0),\n },\n paragraphs,\n };\n const edited = addTextBox(source, slideHandle, input);\n const edit = edited.edits?.at(-1) as\n PptxSourceModelAddTextBoxEdit | undefined;\n if (edit?.kind !== \"addTextBox\") {\n throw new Error(\"addTextBox did not produce an addTextBox edit\");\n }\n return {\n xml: withPptxGenParagraphDefaults(withGlowAlpha(edit.xml, node)),\n hyperlinks,\n };\n}\n\ninterface RegisteredTextBox {\n marker: string;\n name: string;\n xml: string;\n hyperlinks: readonly (string | undefined)[];\n}\n\nexport class GlimpseTextBoxRegistry {\n private readonly registered: RegisteredTextBox[] = [];\n\n register(node: TextPositionedNode): string {\n const index = this.registered.length;\n const marker = `${MARKER_PREFIX}${index}`;\n const name = `Text ${index + 1}`;\n const { xml, hyperlinks } = createTextBoxXml(node, name);\n this.registered.push({ marker, name, xml, hyperlinks });\n return marker;\n }\n\n get isEmpty(): boolean {\n return this.registered.length === 0;\n }\n\n get entries(): readonly RegisteredTextBox[] {\n return this.registered;\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction replaceShapeId(xml: string, id: string, name: string): string {\n return xml.replace(\n /<p:cNvPr id=\"[^\"]+\" name=\"[^\"]*\"/,\n `<p:cNvPr id=\"${id}\" name=\"${name}\"`,\n );\n}\n\nfunction xmlAttr(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll('\"', \""\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n}\n\nfunction slideRelsPath(slidePath: string): string {\n const fileName = slidePath.split(\"/\").at(-1);\n return `ppt/slides/_rels/${fileName}.rels`;\n}\n\nclass SlideRelationshipEditor {\n private nextId: number;\n\n private changed = false;\n\n constructor(private xml: string) {\n const ids = Array.from(xml.matchAll(/\\bId=\"rId(\\d+)\"/g), (match) =>\n Number(match[1]),\n );\n this.nextId = Math.max(0, ...ids) + 1;\n }\n\n addHyperlink(href: string): string {\n const id = `rId${this.nextId++}`;\n const rel =\n `<Relationship Id=\"${id}\" Type=\"${HYPERLINK_REL_TYPE}\" ` +\n `Target=\"${xmlAttr(href)}\" TargetMode=\"External\"/>`;\n this.xml = this.xml.replace(\"</Relationships>\", `${rel}</Relationships>`);\n this.changed = true;\n return id;\n }\n\n get result(): { xml: string; changed: boolean } {\n return { xml: this.xml, changed: this.changed };\n }\n}\n\nfunction createRelationshipEditor(xml: string | undefined) {\n return new SlideRelationshipEditor(\n xml ??\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>' +\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"></Relationships>',\n );\n}\n\nfunction withHyperlinkRelationships(\n xml: string,\n hyperlinks: readonly (string | undefined)[],\n addRelationship: (href: string) => string,\n): string {\n if (!hyperlinks.some(Boolean)) return xml;\n let index = 0;\n return xml.replace(\n /<a:rPr\\b([^>]*)>([\\s\\S]*?)<\\/a:rPr>/g,\n (match, attrs, body) => {\n const href = hyperlinks[index++];\n if (!href) return match;\n const id = addRelationship(href);\n return `<a:rPr${attrs as string}>${body as string}<a:hlinkClick r:id=\"${id}\"/></a:rPr>`;\n },\n );\n}\n\nfunction applyGlimpseTextBoxesToXml(\n xml: string,\n registry: GlimpseTextBoxRegistry,\n addRelationship: (href: string) => string,\n): string {\n let result = xml;\n for (const entry of registry.entries) {\n const re = new RegExp(\n `<p:sp><p:nvSpPr><p:cNvPr id=\"([^\"]+)\" name=\"${escapeRegExp(\n entry.marker,\n )}\"[\\\\s\\\\S]*?</p:sp>`,\n \"g\",\n );\n result = result.replace(re, (_match, id: string) => {\n const xmlWithIds = replaceShapeId(entry.xml, id, entry.name);\n return withHyperlinkRelationships(\n xmlWithIds,\n entry.hyperlinks,\n addRelationship,\n );\n });\n }\n return result;\n}\n\nasync function applyGlimpseTextBoxes(\n data: Uint8Array | ArrayBuffer,\n registry: GlimpseTextBoxRegistry,\n): Promise<import(\"jszip\")> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(data);\n\n const slidePaths = Object.keys(zip.files).filter((path) =>\n /^ppt\\/slides\\/slide\\d+\\.xml$/.test(path),\n );\n for (const path of slidePaths) {\n const file = zip.file(path);\n if (!file) continue;\n const original = await file.async(\"text\");\n const relsPath = slideRelsPath(path);\n const relsFile = zip.file(relsPath);\n const relationshipEditor = createRelationshipEditor(\n relsFile ? await relsFile.async(\"text\") : undefined,\n );\n const replaced = applyGlimpseTextBoxesToXml(original, registry, (href) =>\n relationshipEditor.addHyperlink(href),\n );\n if (replaced !== original) {\n zip.file(path, replaced);\n }\n const relationships = relationshipEditor.result;\n if (relationships.changed) {\n zip.file(relsPath, relationships.xml);\n }\n }\n return zip;\n}\n\nexport function patchPptxWriteForGlimpseTextBoxes(\n pptx: PptxGenJSInstance,\n registry: GlimpseTextBoxRegistry,\n): void {\n if (registry.isEmpty) return;\n\n const originalWrite = pptx.write.bind(pptx);\n\n const patchedWrite = async (rawProps?: WriteProps | string) => {\n const props: WriteProps | undefined =\n typeof rawProps === \"string\"\n ? ({ outputType: rawProps } as WriteProps)\n : rawProps;\n const data = (await originalWrite({\n outputType: \"uint8array\",\n })) as Uint8Array;\n const zip = await applyGlimpseTextBoxes(data, registry);\n\n const outputType = props?.outputType;\n if (outputType === \"STREAM\") {\n return zip.generateAsync({\n type: \"nodebuffer\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n if (outputType) {\n return zip.generateAsync({\n type: outputType,\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n return zip.generateAsync({\n type: \"blob\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n };\n pptx.write = patchedWrite;\n\n const patchedStream = async (props?: StreamProps) =>\n pptx.write({\n outputType: \"STREAM\",\n compression: props?.compression,\n });\n pptx.stream = patchedStream;\n\n const patchedWriteFile = async (rawProps?: WriteFileProps | string) => {\n const props: WriteFileProps | undefined =\n typeof rawProps === \"string\" ? { fileName: rawProps } : rawProps;\n const isNode =\n typeof process !== \"undefined\" && Boolean(process.versions?.node);\n if (!isNode) {\n const browserWriter = pptx as BrowserWritablePptx;\n if (typeof browserWriter.writeFileToBrowser !== \"function\") {\n throw new Error(\n \"pptx.writeFile browser download helper is unavailable; use pptx.write({ outputType: 'blob' }) instead\",\n );\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const blob = (await patchedWrite({\n outputType: \"blob\",\n compression: props?.compression,\n })) as Blob;\n await browserWriter.writeFileToBrowser(fileName, blob);\n return fileName;\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const buffer = (await patchedWrite({\n outputType: \"nodebuffer\",\n compression: props?.compression,\n })) as Buffer;\n const fs = await import(\"fs\");\n await fs.promises.writeFile(fileName, buffer);\n return fileName;\n };\n pptx.writeFile = patchedWriteFile;\n}\n"],"mappings":"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"}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { pxToEmu } from "./units.js";
|
|
2
|
-
//#region src/renderPptx/glowEffects.ts
|
|
3
|
-
async function loadJSZip() {
|
|
4
|
-
const mod = await import("jszip");
|
|
5
|
-
return mod.default ?? mod;
|
|
6
|
-
}
|
|
7
|
-
const MARKER_PREFIX = "pom-glow:";
|
|
8
|
-
/**
|
|
9
|
-
* glow 仕様を一意なマーカー名にマップし、後処理で
|
|
10
|
-
* `<a:effectLst><a:glow>` 要素として挿入できるようにするレジストリ
|
|
11
|
-
*/
|
|
12
|
-
var GlowEffectRegistry = class {
|
|
13
|
-
markerBySpec = /* @__PURE__ */ new Map();
|
|
14
|
-
registered = [];
|
|
15
|
-
/**
|
|
16
|
-
* glow を登録し、`objectName` に渡すマーカー文字列を返す。
|
|
17
|
-
* 同じ仕様の glow は同じマーカーを返す。
|
|
18
|
-
*/
|
|
19
|
-
register(glow) {
|
|
20
|
-
const sizePx = glow.size ?? 8;
|
|
21
|
-
const opacity = glow.opacity ?? .75;
|
|
22
|
-
const color = (glow.color ?? "FFFFFF").replace(/^#/, "").toUpperCase();
|
|
23
|
-
const specKey = JSON.stringify({
|
|
24
|
-
sizePx,
|
|
25
|
-
opacity,
|
|
26
|
-
color
|
|
27
|
-
});
|
|
28
|
-
const existing = this.markerBySpec.get(specKey);
|
|
29
|
-
if (existing) return existing;
|
|
30
|
-
const index = this.registered.length;
|
|
31
|
-
const marker = `${MARKER_PREFIX}${index}`;
|
|
32
|
-
this.markerBySpec.set(specKey, marker);
|
|
33
|
-
this.registered.push({
|
|
34
|
-
marker,
|
|
35
|
-
sizePx,
|
|
36
|
-
opacity,
|
|
37
|
-
color
|
|
38
|
-
});
|
|
39
|
-
return marker;
|
|
40
|
-
}
|
|
41
|
-
get isEmpty() {
|
|
42
|
-
return this.registered.length === 0;
|
|
43
|
-
}
|
|
44
|
-
get entries() {
|
|
45
|
-
return this.registered;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* DrawingML の `<a:glow>` 要素を構築する (effectLst で囲まない、内側だけ)
|
|
50
|
-
*
|
|
51
|
-
* - rad (光彩半径): px → EMU
|
|
52
|
-
* - alpha (透明度): 0-1 → 1/1000 % (0-100000)
|
|
53
|
-
*/
|
|
54
|
-
function buildGlowXml(entry) {
|
|
55
|
-
const rad = Math.round(pxToEmu(entry.sizePx));
|
|
56
|
-
const alpha = Math.round(entry.opacity * 1e5);
|
|
57
|
-
return `<a:glow rad="${rad}"><a:srgbClr val="${entry.color}"><a:alpha val="${alpha}"/></a:srgbClr></a:glow>`;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* 1 つの slide XML を受け取り、登録された glow を該当 shape に適用した XML を返す
|
|
61
|
-
*
|
|
62
|
-
* `<p:cNvPr ... name="pom-glow:N">` を含む `<p:sp>` ブロックを正規表現で検出し、
|
|
63
|
-
* 以下のいずれかで `<a:glow>` を挿入する:
|
|
64
|
-
*
|
|
65
|
-
* 1. 既に `<a:effectLst>...</a:effectLst>` が存在する場合
|
|
66
|
-
* (= pptxgenjs が shape の `shadow` 等で同要素を生成済み):
|
|
67
|
-
* その `effectLst` 内側の末尾 (`</a:effectLst>` の直前) に `<a:glow>` を追加する。
|
|
68
|
-
* OOXML スキーマ上 `<p:spPr>` 内の `<a:effectLst>` は最大 1 個までで、二重に
|
|
69
|
-
* 並べると不正になるため。
|
|
70
|
-
* 2. 既存 `effectLst` が無い場合:
|
|
71
|
-
* `</p:spPr>` の直前に新規 `<a:effectLst><a:glow/></a:effectLst>` を挿入する。
|
|
72
|
-
*
|
|
73
|
-
* pptxgenjs は cNvPr を `<p:cNvPr.../>` (self-closing) と
|
|
74
|
-
* `<p:cNvPr...></p:cNvPr>` (open+close) のどちらの形式でも出力し得るため、
|
|
75
|
-
* cNvPr の閉じ形式に依存せず name 属性のあとから lazy に進める。
|
|
76
|
-
*/
|
|
77
|
-
function applyGlowToXml(xml, registry) {
|
|
78
|
-
let result = xml;
|
|
79
|
-
for (const entry of registry.entries) {
|
|
80
|
-
const glowXml = buildGlowXml(entry);
|
|
81
|
-
const re = new RegExp(`(<p:cNvPr[^>]*name="${entry.marker}"[\\s\\S]*?)(</p:spPr>)`, "g");
|
|
82
|
-
result = result.replace(re, (_match, prefix, suffix) => {
|
|
83
|
-
const block = prefix;
|
|
84
|
-
const closingIdx = block.lastIndexOf("</a:effectLst>");
|
|
85
|
-
if (closingIdx >= 0) {
|
|
86
|
-
const before = block.substring(0, closingIdx);
|
|
87
|
-
const after = block.substring(closingIdx);
|
|
88
|
-
return `${before}${glowXml}${after}${suffix}`;
|
|
89
|
-
}
|
|
90
|
-
return `${block}<a:effectLst>${glowXml}</a:effectLst>${suffix}`;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return result;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* 出力 zip 内のスライド XML に glow 効果を挿入する
|
|
97
|
-
*/
|
|
98
|
-
async function applyGlowEffects(data, registry) {
|
|
99
|
-
const zip = await (await loadJSZip()).loadAsync(data);
|
|
100
|
-
const slidePaths = Object.keys(zip.files).filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path));
|
|
101
|
-
for (const path of slidePaths) {
|
|
102
|
-
const file = zip.file(path);
|
|
103
|
-
if (!file) continue;
|
|
104
|
-
const original = await file.async("text");
|
|
105
|
-
const replaced = applyGlowToXml(original, registry);
|
|
106
|
-
if (replaced !== original) zip.file(path, replaced);
|
|
107
|
-
}
|
|
108
|
-
return zip;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* pptx インスタンスの write / writeFile をラップし、出力時に glow 後処理を適用する。
|
|
112
|
-
*
|
|
113
|
-
* 既に他の後処理 (gradientFills 等) によりラップされている場合に備え、
|
|
114
|
-
* 元の関数を保存してチェーン可能にしている。
|
|
115
|
-
*/
|
|
116
|
-
function patchPptxWriteForGlowEffects(pptx, registry) {
|
|
117
|
-
if (registry.isEmpty) return;
|
|
118
|
-
const originalWrite = pptx.write.bind(pptx);
|
|
119
|
-
const originalWriteFile = pptx.writeFile.bind(pptx);
|
|
120
|
-
const patchedWrite = async (rawProps) => {
|
|
121
|
-
const props = typeof rawProps === "string" ? { outputType: rawProps } : rawProps;
|
|
122
|
-
const zip = await applyGlowEffects(await originalWrite({ outputType: "uint8array" }), registry);
|
|
123
|
-
const outputType = props?.outputType;
|
|
124
|
-
if (outputType === "STREAM") return zip.generateAsync({
|
|
125
|
-
type: "nodebuffer",
|
|
126
|
-
compression: props?.compression ? "DEFLATE" : "STORE"
|
|
127
|
-
});
|
|
128
|
-
if (outputType) return zip.generateAsync({ type: outputType });
|
|
129
|
-
return zip.generateAsync({
|
|
130
|
-
type: "blob",
|
|
131
|
-
compression: props?.compression ? "DEFLATE" : "STORE"
|
|
132
|
-
});
|
|
133
|
-
};
|
|
134
|
-
pptx.write = patchedWrite;
|
|
135
|
-
const patchedStream = async (props) => pptx.write({
|
|
136
|
-
outputType: "STREAM",
|
|
137
|
-
compression: props?.compression
|
|
138
|
-
});
|
|
139
|
-
pptx.stream = patchedStream;
|
|
140
|
-
const patchedWriteFile = async (rawProps) => {
|
|
141
|
-
const props = typeof rawProps === "string" ? { fileName: rawProps } : rawProps;
|
|
142
|
-
if (!(typeof process !== "undefined" && Boolean(process.versions?.node))) return originalWriteFile(props);
|
|
143
|
-
const rawName = props?.fileName ?? "Presentation.pptx";
|
|
144
|
-
const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
|
|
145
|
-
const buffer = await patchedWrite({
|
|
146
|
-
outputType: "nodebuffer",
|
|
147
|
-
compression: props?.compression
|
|
148
|
-
});
|
|
149
|
-
await (await import("fs")).promises.writeFile(fileName, buffer);
|
|
150
|
-
return fileName;
|
|
151
|
-
};
|
|
152
|
-
pptx.writeFile = patchedWriteFile;
|
|
153
|
-
}
|
|
154
|
-
//#endregion
|
|
155
|
-
export { GlowEffectRegistry, applyGlowEffects, patchPptxWriteForGlowEffects };
|
|
156
|
-
|
|
157
|
-
//# sourceMappingURL=glowEffects.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"glowEffects.js","names":[],"sources":["../../src/renderPptx/glowEffects.ts"],"sourcesContent":["/**\n * Shape / Icon の glow (光彩) 効果の実現\n *\n * pptxgenjs の Shape options には glow 相当のネイティブサポートがないため\n * (TextPropsOptions の glow は文字グリフ用)、以下の方式で実現する:\n *\n * 1. レンダリング時: glow 指定のある shape に一意なマーカー名を `objectName`\n * として埋め込み、レジストリに登録する。マーカー名は `<p:cNvPr name=\"...\"/>`\n * として PPTX 出力 XML に書き込まれる。\n * 2. 出力時: pptx.write() / writeFile() をラップし、出力 zip 内のスライド XML\n * の該当 `<p:sp>` の `<p:spPr>` 末尾に DrawingML ネイティブの\n * `<a:effectLst><a:glow>...</a:glow></a:effectLst>` を挿入する。\n *\n * 文字列置換採用理由 / 注意点は gradientFills.ts と同様。\n */\nimport type { TextGlow } from \"../types.ts\";\nimport { pxToEmu } from \"./units.ts\";\n\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\ntype StreamProps = NonNullable<Parameters<PptxGenJSInstance[\"stream\"]>[0]>;\ntype WriteProps = NonNullable<Parameters<PptxGenJSInstance[\"write\"]>[0]>;\ntype WriteFileProps = NonNullable<\n Parameters<PptxGenJSInstance[\"writeFile\"]>[0]\n>;\n\nasync function loadJSZip(): Promise<typeof import(\"jszip\")> {\n const mod = await import(\"jszip\");\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n return (mod as any).default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\n\ninterface RegisteredGlow {\n /** objectName に埋め込むマーカー文字列 */\n marker: string;\n /** size (px) — XML 出力時に EMU に変換する */\n sizePx: number;\n /** opacity 0-1 */\n opacity: number;\n /** 6桁 HEX (# なし、大文字) */\n color: string;\n}\n\nconst MARKER_PREFIX = \"pom-glow:\";\n\n/**\n * glow 仕様を一意なマーカー名にマップし、後処理で\n * `<a:effectLst><a:glow>` 要素として挿入できるようにするレジストリ\n */\nexport class GlowEffectRegistry {\n private readonly markerBySpec = new Map<string, string>();\n private readonly registered: RegisteredGlow[] = [];\n\n /**\n * glow を登録し、`objectName` に渡すマーカー文字列を返す。\n * 同じ仕様の glow は同じマーカーを返す。\n */\n register(glow: TextGlow): string {\n const sizePx = glow.size ?? 8;\n const opacity = glow.opacity ?? 0.75;\n const color = (glow.color ?? \"FFFFFF\").replace(/^#/, \"\").toUpperCase();\n const specKey = JSON.stringify({ sizePx, opacity, color });\n const existing = this.markerBySpec.get(specKey);\n if (existing) return existing;\n\n const index = this.registered.length;\n const marker = `${MARKER_PREFIX}${index}`;\n this.markerBySpec.set(specKey, marker);\n this.registered.push({ marker, sizePx, opacity, color });\n return marker;\n }\n\n get isEmpty(): boolean {\n return this.registered.length === 0;\n }\n\n get entries(): readonly RegisteredGlow[] {\n return this.registered;\n }\n}\n\n/**\n * DrawingML の `<a:glow>` 要素を構築する (effectLst で囲まない、内側だけ)\n *\n * - rad (光彩半径): px → EMU\n * - alpha (透明度): 0-1 → 1/1000 % (0-100000)\n */\nfunction buildGlowXml(entry: RegisteredGlow): string {\n const rad = Math.round(pxToEmu(entry.sizePx));\n const alpha = Math.round(entry.opacity * 100000);\n return `<a:glow rad=\"${rad}\"><a:srgbClr val=\"${entry.color}\"><a:alpha val=\"${alpha}\"/></a:srgbClr></a:glow>`;\n}\n\n/**\n * 1 つの slide XML を受け取り、登録された glow を該当 shape に適用した XML を返す\n *\n * `<p:cNvPr ... name=\"pom-glow:N\">` を含む `<p:sp>` ブロックを正規表現で検出し、\n * 以下のいずれかで `<a:glow>` を挿入する:\n *\n * 1. 既に `<a:effectLst>...</a:effectLst>` が存在する場合\n * (= pptxgenjs が shape の `shadow` 等で同要素を生成済み):\n * その `effectLst` 内側の末尾 (`</a:effectLst>` の直前) に `<a:glow>` を追加する。\n * OOXML スキーマ上 `<p:spPr>` 内の `<a:effectLst>` は最大 1 個までで、二重に\n * 並べると不正になるため。\n * 2. 既存 `effectLst` が無い場合:\n * `</p:spPr>` の直前に新規 `<a:effectLst><a:glow/></a:effectLst>` を挿入する。\n *\n * pptxgenjs は cNvPr を `<p:cNvPr.../>` (self-closing) と\n * `<p:cNvPr...></p:cNvPr>` (open+close) のどちらの形式でも出力し得るため、\n * cNvPr の閉じ形式に依存せず name 属性のあとから lazy に進める。\n */\nfunction applyGlowToXml(xml: string, registry: GlowEffectRegistry): string {\n let result = xml;\n for (const entry of registry.entries) {\n const glowXml = buildGlowXml(entry);\n const re = new RegExp(\n `(<p:cNvPr[^>]*name=\"${entry.marker}\"[\\\\s\\\\S]*?)(</p:spPr>)`,\n \"g\",\n );\n result = result.replace(re, (_match, prefix, suffix) => {\n const block = prefix as string;\n // shape ブロック内に既存の effectLst があるかを `</a:effectLst>` の有無で判定する。\n // 別 shape の effectLst を誤って書き換えないよう、検索範囲は prefix\n // (= 該当 cNvPr 〜 直後の </p:spPr> の手前まで) に限定している。\n const closingIdx = block.lastIndexOf(\"</a:effectLst>\");\n if (closingIdx >= 0) {\n const before = block.substring(0, closingIdx);\n const after = block.substring(closingIdx);\n return `${before}${glowXml}${after}${suffix as string}`;\n }\n return `${block}<a:effectLst>${glowXml}</a:effectLst>${suffix as string}`;\n });\n }\n return result;\n}\n\n/**\n * 出力 zip 内のスライド XML に glow 効果を挿入する\n */\nexport async function applyGlowEffects(\n data: Uint8Array | ArrayBuffer,\n registry: GlowEffectRegistry,\n): Promise<import(\"jszip\")> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(data);\n\n const slidePaths = Object.keys(zip.files).filter((path) =>\n /^ppt\\/slides\\/slide\\d+\\.xml$/.test(path),\n );\n for (const path of slidePaths) {\n const file = zip.file(path);\n if (!file) continue;\n const original = await file.async(\"text\");\n const replaced = applyGlowToXml(original, registry);\n if (replaced !== original) {\n zip.file(path, replaced);\n }\n }\n return zip;\n}\n\n/**\n * pptx インスタンスの write / writeFile をラップし、出力時に glow 後処理を適用する。\n *\n * 既に他の後処理 (gradientFills 等) によりラップされている場合に備え、\n * 元の関数を保存してチェーン可能にしている。\n */\nexport function patchPptxWriteForGlowEffects(\n pptx: PptxGenJSInstance,\n registry: GlowEffectRegistry,\n): void {\n if (registry.isEmpty) return;\n\n const originalWrite = pptx.write.bind(pptx);\n const originalWriteFile = pptx.writeFile.bind(pptx);\n\n const patchedWrite = async (rawProps?: WriteProps | string) => {\n const props: WriteProps | undefined =\n typeof rawProps === \"string\"\n ? ({ outputType: rawProps } as WriteProps)\n : rawProps;\n const data = (await originalWrite({\n outputType: \"uint8array\",\n })) as Uint8Array;\n const zip = await applyGlowEffects(data, registry);\n\n const outputType = props?.outputType;\n if (outputType === \"STREAM\") {\n return zip.generateAsync({\n type: \"nodebuffer\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n if (outputType) {\n return zip.generateAsync({ type: outputType });\n }\n return zip.generateAsync({\n type: \"blob\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n };\n pptx.write = patchedWrite;\n\n const patchedStream = async (props?: StreamProps) =>\n pptx.write({\n outputType: \"STREAM\",\n compression: props?.compression,\n });\n pptx.stream = patchedStream;\n\n const patchedWriteFile = async (rawProps?: WriteFileProps | string) => {\n const props: WriteFileProps | undefined =\n typeof rawProps === \"string\" ? { fileName: rawProps } : rawProps;\n const isNode =\n typeof process !== \"undefined\" && Boolean(process.versions?.node);\n if (!isNode) {\n return originalWriteFile(props);\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const buffer = (await patchedWrite({\n outputType: \"nodebuffer\",\n compression: props?.compression,\n })) as Buffer;\n const fs = await import(\"fs\");\n await fs.promises.writeFile(fileName, buffer);\n return fileName;\n };\n pptx.writeFile = patchedWriteFile;\n}\n"],"mappings":"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"}
|