pptx-glimpse 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/index.cjs +351 -96
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +351 -96
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -88,6 +88,19 @@ const results = await convertPptxToPng(pptx, {
|
|
|
88
88
|
});
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
#### Text output mode (`textOutput`)
|
|
92
|
+
|
|
93
|
+
By default, text is converted to `<path>` outlines, which renders consistently in any environment but bypasses the browser's native text rasterization (hinting, text-specific anti-aliasing), so glyph edges can look jagged when SVGs are displayed inline in a browser.
|
|
94
|
+
|
|
95
|
+
With `textOutput: "text"`, `convertPptxToSvg` emits native `<text>` elements along with `@font-face` rules that embed subsetted fonts as data URIs. This produces smoother text rendering in browsers, smaller SVGs for CJK-heavy slides, and selectable/copyable text. Characters not covered by the resolved font fall back to the viewer's fonts via the `font-family` chain.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// SVG only — convertPptxToPng always uses path output
|
|
99
|
+
const svgResults = await convertPptxToSvg(pptx, { textOutput: "text" });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Note: embedded fonts and `<text>` may not render as expected when the SVG is referenced via `<img src="...svg">` or sanitized. `convertPptxToPng` always uses path output regardless of this option.
|
|
103
|
+
|
|
91
104
|
### Advanced Usage
|
|
92
105
|
|
|
93
106
|
<details>
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,192 @@ __export(index_exports, {
|
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
|
+
// src/converter.ts
|
|
39
|
+
var import_node_fs2 = require("fs");
|
|
40
|
+
|
|
41
|
+
// src/font/font-embedder.ts
|
|
42
|
+
var import_node_buffer = require("buffer");
|
|
43
|
+
|
|
44
|
+
// src/warning-logger.ts
|
|
45
|
+
var PREFIX = "[pptx-glimpse]";
|
|
46
|
+
var currentLevel = "off";
|
|
47
|
+
var entries = [];
|
|
48
|
+
var featureCounts = /* @__PURE__ */ new Map();
|
|
49
|
+
function initWarningLogger(level) {
|
|
50
|
+
currentLevel = level;
|
|
51
|
+
entries = [];
|
|
52
|
+
featureCounts.clear();
|
|
53
|
+
}
|
|
54
|
+
function warn(feature, message, context) {
|
|
55
|
+
if (currentLevel === "off") return;
|
|
56
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
57
|
+
const existing = featureCounts.get(feature);
|
|
58
|
+
if (existing) {
|
|
59
|
+
existing.count++;
|
|
60
|
+
} else {
|
|
61
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
62
|
+
}
|
|
63
|
+
if (currentLevel === "debug") {
|
|
64
|
+
const ctx = context ? ` (${context})` : "";
|
|
65
|
+
console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function debug(feature, message, context) {
|
|
69
|
+
if (currentLevel !== "debug") return;
|
|
70
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
71
|
+
const existing = featureCounts.get(feature);
|
|
72
|
+
if (existing) {
|
|
73
|
+
existing.count++;
|
|
74
|
+
} else {
|
|
75
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
76
|
+
}
|
|
77
|
+
const ctx = context ? ` (${context})` : "";
|
|
78
|
+
console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
|
|
79
|
+
}
|
|
80
|
+
function getWarningSummary() {
|
|
81
|
+
const features = [];
|
|
82
|
+
for (const [feature, { message, count }] of featureCounts) {
|
|
83
|
+
features.push({ feature, message, count });
|
|
84
|
+
}
|
|
85
|
+
return { totalCount: entries.length, features };
|
|
86
|
+
}
|
|
87
|
+
function flushWarnings() {
|
|
88
|
+
const summary = getWarningSummary();
|
|
89
|
+
if (currentLevel !== "off" && summary.features.length > 0) {
|
|
90
|
+
console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
|
|
91
|
+
for (const { feature, count } of summary.features) {
|
|
92
|
+
console.warn(` - ${feature}: ${count} occurrence(s)`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
entries = [];
|
|
96
|
+
featureCounts.clear();
|
|
97
|
+
return summary;
|
|
98
|
+
}
|
|
99
|
+
function getWarningEntries() {
|
|
100
|
+
return entries;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/font/font-subsetter.ts
|
|
104
|
+
async function tryLoadOpentypeCtors() {
|
|
105
|
+
try {
|
|
106
|
+
const specifier = "opentype.js";
|
|
107
|
+
const mod = await import(
|
|
108
|
+
/* @vite-ignore */
|
|
109
|
+
specifier
|
|
110
|
+
);
|
|
111
|
+
return { Font: mod.Font, Glyph: mod.Glyph };
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function glyphName(glyph, firstUnicode) {
|
|
117
|
+
if (glyph.name) return glyph.name;
|
|
118
|
+
return `uni${firstUnicode.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
119
|
+
}
|
|
120
|
+
async function subsetFont(font, chars, familyName) {
|
|
121
|
+
const opentype = await tryLoadOpentypeCtors();
|
|
122
|
+
if (!opentype) return null;
|
|
123
|
+
const source = font;
|
|
124
|
+
if (typeof source.charToGlyph !== "function" || !source.glyphs) return null;
|
|
125
|
+
const glyphMap = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const char of chars) {
|
|
127
|
+
const codePoint = char.codePointAt(0);
|
|
128
|
+
if (codePoint === void 0) continue;
|
|
129
|
+
let glyph;
|
|
130
|
+
try {
|
|
131
|
+
glyph = source.charToGlyph(char);
|
|
132
|
+
} catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (!glyph || !glyph.index) continue;
|
|
136
|
+
const entry = glyphMap.get(glyph.index);
|
|
137
|
+
if (entry) {
|
|
138
|
+
entry.unicodes.add(codePoint);
|
|
139
|
+
} else {
|
|
140
|
+
glyphMap.set(glyph.index, { glyph, unicodes: /* @__PURE__ */ new Set([codePoint]) });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (glyphMap.size === 0) return null;
|
|
144
|
+
try {
|
|
145
|
+
const notdefSource = source.glyphs.get(0);
|
|
146
|
+
const glyphs = [
|
|
147
|
+
new opentype.Glyph({
|
|
148
|
+
name: ".notdef",
|
|
149
|
+
advanceWidth: notdefSource?.advanceWidth ?? source.unitsPerEm / 2,
|
|
150
|
+
path: notdefSource?.path
|
|
151
|
+
})
|
|
152
|
+
];
|
|
153
|
+
for (const { glyph, unicodes } of glyphMap.values()) {
|
|
154
|
+
const unicodeList = [...unicodes].sort((a, b) => a - b);
|
|
155
|
+
glyphs.push(
|
|
156
|
+
new opentype.Glyph({
|
|
157
|
+
name: glyphName(glyph, unicodeList[0]),
|
|
158
|
+
unicode: unicodeList[0],
|
|
159
|
+
unicodes: unicodeList,
|
|
160
|
+
advanceWidth: glyph.advanceWidth ?? 0,
|
|
161
|
+
path: glyph.path
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const subset = new opentype.Font({
|
|
166
|
+
familyName: familyName || "EmbeddedFont",
|
|
167
|
+
styleName: "Regular",
|
|
168
|
+
unitsPerEm: source.unitsPerEm,
|
|
169
|
+
ascender: source.ascender,
|
|
170
|
+
// opentype.js は descender に負値を要求する
|
|
171
|
+
descender: source.descender < 0 ? source.descender : -1,
|
|
172
|
+
glyphs
|
|
173
|
+
});
|
|
174
|
+
return new Uint8Array(subset.toArrayBuffer());
|
|
175
|
+
} catch (e) {
|
|
176
|
+
warn(
|
|
177
|
+
"font.subsetFailed",
|
|
178
|
+
`Failed to subset font "${familyName}": ${e instanceof Error ? e.message : String(e)}`
|
|
179
|
+
);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/font/script-font-context.ts
|
|
185
|
+
var jpanMajorFont = null;
|
|
186
|
+
var jpanMinorFont = null;
|
|
187
|
+
function setScriptFonts(majorJpan, minorJpan) {
|
|
188
|
+
jpanMajorFont = majorJpan;
|
|
189
|
+
jpanMinorFont = minorJpan;
|
|
190
|
+
}
|
|
191
|
+
function resetScriptFonts() {
|
|
192
|
+
jpanMajorFont = null;
|
|
193
|
+
jpanMinorFont = null;
|
|
194
|
+
}
|
|
195
|
+
function getJpanFallbackFont() {
|
|
196
|
+
return jpanMajorFont ?? jpanMinorFont;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/font/font-embedder.ts
|
|
200
|
+
function escapeCssFamilyName(name) {
|
|
201
|
+
return name.replace(/[\\"]/g, (c) => `\\${c}`).replace(/[<>&]/g, (c) => `\\${c.codePointAt(0).toString(16)} `);
|
|
202
|
+
}
|
|
203
|
+
async function buildFontFaceStyle(usages, fontResolver) {
|
|
204
|
+
const faces = [];
|
|
205
|
+
const jpanFallback = getJpanFallbackFont();
|
|
206
|
+
for (const [familyName, usage] of usages) {
|
|
207
|
+
const font = fontResolver.resolveFont(
|
|
208
|
+
usage.fonts[0],
|
|
209
|
+
usage.fonts[1] ?? null,
|
|
210
|
+
usage.fonts[2] ?? jpanFallback
|
|
211
|
+
);
|
|
212
|
+
if (!font) continue;
|
|
213
|
+
const buffer = await subsetFont(font, usage.chars, familyName);
|
|
214
|
+
if (!buffer) continue;
|
|
215
|
+
const base64 = import_node_buffer.Buffer.from(buffer).toString("base64");
|
|
216
|
+
faces.push(
|
|
217
|
+
`@font-face{font-family:"${escapeCssFamilyName(familyName)}";src:url(data:font/otf;base64,${base64}) format("opentype");}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (faces.length === 0) return "";
|
|
221
|
+
return `<style type="text/css">${faces.join("")}</style>`;
|
|
222
|
+
}
|
|
223
|
+
|
|
38
224
|
// src/font/font-mapping.ts
|
|
39
225
|
var DEFAULT_FONT_MAPPING = {
|
|
40
226
|
// ラテン文字フォント
|
|
@@ -101,6 +287,40 @@ function getCurrentMappedFont(fontFamily) {
|
|
|
101
287
|
return getMappedFont(fontFamily, currentMapping);
|
|
102
288
|
}
|
|
103
289
|
|
|
290
|
+
// src/font/font-usage-collector.ts
|
|
291
|
+
var FontUsageCollector = class {
|
|
292
|
+
/** key: tspan の font-family リスト先頭のフォント名 (= @font-face で宣言する名前) */
|
|
293
|
+
usages = /* @__PURE__ */ new Map();
|
|
294
|
+
record(fonts, text) {
|
|
295
|
+
const primary = fonts.find((f) => f !== null && f !== void 0);
|
|
296
|
+
if (!primary || text.length === 0) return;
|
|
297
|
+
let usage = this.usages.get(primary);
|
|
298
|
+
if (!usage) {
|
|
299
|
+
usage = { fonts: [...fonts], chars: /* @__PURE__ */ new Set() };
|
|
300
|
+
this.usages.set(primary, usage);
|
|
301
|
+
}
|
|
302
|
+
for (const char of text) {
|
|
303
|
+
usage.chars.add(char);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
getUsages() {
|
|
307
|
+
return this.usages;
|
|
308
|
+
}
|
|
309
|
+
reset() {
|
|
310
|
+
this.usages.clear();
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
var currentCollector = null;
|
|
314
|
+
function setFontUsageCollector(collector) {
|
|
315
|
+
currentCollector = collector;
|
|
316
|
+
}
|
|
317
|
+
function getFontUsageCollector() {
|
|
318
|
+
return currentCollector;
|
|
319
|
+
}
|
|
320
|
+
function resetFontUsageCollector() {
|
|
321
|
+
currentCollector = null;
|
|
322
|
+
}
|
|
323
|
+
|
|
104
324
|
// src/font/opentype-helpers.ts
|
|
105
325
|
var import_promises = require("fs/promises");
|
|
106
326
|
|
|
@@ -1044,65 +1264,6 @@ function measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
|
|
|
1044
1264
|
return totalWidth;
|
|
1045
1265
|
}
|
|
1046
1266
|
|
|
1047
|
-
// src/warning-logger.ts
|
|
1048
|
-
var PREFIX = "[pptx-glimpse]";
|
|
1049
|
-
var currentLevel = "off";
|
|
1050
|
-
var entries = [];
|
|
1051
|
-
var featureCounts = /* @__PURE__ */ new Map();
|
|
1052
|
-
function initWarningLogger(level) {
|
|
1053
|
-
currentLevel = level;
|
|
1054
|
-
entries = [];
|
|
1055
|
-
featureCounts.clear();
|
|
1056
|
-
}
|
|
1057
|
-
function warn(feature, message, context) {
|
|
1058
|
-
if (currentLevel === "off") return;
|
|
1059
|
-
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
1060
|
-
const existing = featureCounts.get(feature);
|
|
1061
|
-
if (existing) {
|
|
1062
|
-
existing.count++;
|
|
1063
|
-
} else {
|
|
1064
|
-
featureCounts.set(feature, { message, count: 1 });
|
|
1065
|
-
}
|
|
1066
|
-
if (currentLevel === "debug") {
|
|
1067
|
-
const ctx = context ? ` (${context})` : "";
|
|
1068
|
-
console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
function debug(feature, message, context) {
|
|
1072
|
-
if (currentLevel !== "debug") return;
|
|
1073
|
-
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
1074
|
-
const existing = featureCounts.get(feature);
|
|
1075
|
-
if (existing) {
|
|
1076
|
-
existing.count++;
|
|
1077
|
-
} else {
|
|
1078
|
-
featureCounts.set(feature, { message, count: 1 });
|
|
1079
|
-
}
|
|
1080
|
-
const ctx = context ? ` (${context})` : "";
|
|
1081
|
-
console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
|
|
1082
|
-
}
|
|
1083
|
-
function getWarningSummary() {
|
|
1084
|
-
const features = [];
|
|
1085
|
-
for (const [feature, { message, count }] of featureCounts) {
|
|
1086
|
-
features.push({ feature, message, count });
|
|
1087
|
-
}
|
|
1088
|
-
return { totalCount: entries.length, features };
|
|
1089
|
-
}
|
|
1090
|
-
function flushWarnings() {
|
|
1091
|
-
const summary = getWarningSummary();
|
|
1092
|
-
if (currentLevel !== "off" && summary.features.length > 0) {
|
|
1093
|
-
console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
|
|
1094
|
-
for (const { feature, count } of summary.features) {
|
|
1095
|
-
console.warn(` - ${feature}: ${count} occurrence(s)`);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
entries = [];
|
|
1099
|
-
featureCounts.clear();
|
|
1100
|
-
return summary;
|
|
1101
|
-
}
|
|
1102
|
-
function getWarningEntries() {
|
|
1103
|
-
return entries;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
1267
|
// src/font/cjk-font-fallback.ts
|
|
1107
1268
|
var import_node_os = require("os");
|
|
1108
1269
|
var MACOS_FALLBACKS = {
|
|
@@ -1639,21 +1800,6 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping, sk
|
|
|
1639
1800
|
return setup;
|
|
1640
1801
|
}
|
|
1641
1802
|
|
|
1642
|
-
// src/font/script-font-context.ts
|
|
1643
|
-
var jpanMajorFont = null;
|
|
1644
|
-
var jpanMinorFont = null;
|
|
1645
|
-
function setScriptFonts(majorJpan, minorJpan) {
|
|
1646
|
-
jpanMajorFont = majorJpan;
|
|
1647
|
-
jpanMinorFont = minorJpan;
|
|
1648
|
-
}
|
|
1649
|
-
function resetScriptFonts() {
|
|
1650
|
-
jpanMajorFont = null;
|
|
1651
|
-
jpanMinorFont = null;
|
|
1652
|
-
}
|
|
1653
|
-
function getJpanFallbackFont() {
|
|
1654
|
-
return jpanMajorFont ?? jpanMinorFont;
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
1803
|
// src/font/text-measurer.ts
|
|
1658
1804
|
var DefaultTextMeasurer = class {
|
|
1659
1805
|
measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
|
|
@@ -1801,6 +1947,10 @@ async function svgToPng(svgString, options) {
|
|
|
1801
1947
|
} else if (options?.height) {
|
|
1802
1948
|
resvgOptions.fitTo = { mode: "height", value: options.height };
|
|
1803
1949
|
}
|
|
1950
|
+
const fontBuffers = options?.fontBuffers;
|
|
1951
|
+
if (fontBuffers && fontBuffers.length > 0) {
|
|
1952
|
+
resvgOptions.font = { fontBuffers };
|
|
1953
|
+
}
|
|
1804
1954
|
const resvg = new import_resvg_wasm.Resvg(svgString, resvgOptions);
|
|
1805
1955
|
const rendered = resvg.render();
|
|
1806
1956
|
return {
|
|
@@ -2760,7 +2910,7 @@ function parseChartTitle(titleNode) {
|
|
|
2760
2910
|
function parseLegend(legendNode) {
|
|
2761
2911
|
if (!legendNode) return null;
|
|
2762
2912
|
const legendPos = legendNode.legendPos;
|
|
2763
|
-
const pos = legendPos?.["@_val"] ?? "
|
|
2913
|
+
const pos = legendPos?.["@_val"] ?? "r";
|
|
2764
2914
|
return { position: pos };
|
|
2765
2915
|
}
|
|
2766
2916
|
|
|
@@ -5417,6 +5567,7 @@ var DEFAULT_SERIES_COLORS = [
|
|
|
5417
5567
|
{ hex: "#5B9BD5", alpha: 1 },
|
|
5418
5568
|
{ hex: "#70AD47", alpha: 1 }
|
|
5419
5569
|
];
|
|
5570
|
+
var LEGEND_SIDE_WIDTH = 100;
|
|
5420
5571
|
function renderChart(element) {
|
|
5421
5572
|
const { transform, chart } = element;
|
|
5422
5573
|
const w = emuToPixels(transform.extentWidth);
|
|
@@ -5435,6 +5586,8 @@ function renderChart(element) {
|
|
|
5435
5586
|
if (chart.legend) {
|
|
5436
5587
|
if (chart.legend.position === "b") margin.bottom = 50;
|
|
5437
5588
|
else if (chart.legend.position === "t") margin.top += 20;
|
|
5589
|
+
else if (chart.legend.position === "r") margin.right = LEGEND_SIDE_WIDTH;
|
|
5590
|
+
else if (chart.legend.position === "l") margin.left += LEGEND_SIDE_WIDTH;
|
|
5438
5591
|
}
|
|
5439
5592
|
const plotX = margin.left;
|
|
5440
5593
|
const plotY = margin.top;
|
|
@@ -6189,18 +6342,34 @@ function renderLegend(chart, chartW, chartH, position) {
|
|
|
6189
6342
|
color: s.color
|
|
6190
6343
|
}));
|
|
6191
6344
|
if (entries2.length === 0) return "";
|
|
6192
|
-
const
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6345
|
+
const ENTRY_HEIGHT = 20;
|
|
6346
|
+
if (position === "r" || position === "l") {
|
|
6347
|
+
const totalH = entries2.length * ENTRY_HEIGHT;
|
|
6348
|
+
const startY = Math.max((chartH - totalH) / 2, 5);
|
|
6349
|
+
const legendX = position === "r" ? chartW - LEGEND_SIDE_WIDTH + 5 : 5;
|
|
6350
|
+
for (let i = 0; i < entries2.length; i++) {
|
|
6351
|
+
const ey = startY + i * ENTRY_HEIGHT;
|
|
6352
|
+
parts.push(
|
|
6353
|
+
`<rect x="${round(legendX)}" y="${round(ey)}" width="12" height="12" ${fillAttr(entries2[i].color)}/>`
|
|
6354
|
+
);
|
|
6355
|
+
parts.push(
|
|
6356
|
+
`<text x="${round(legendX + 16)}" y="${round(ey + 10)}" font-size="10" fill="#595959">${escapeXml(entries2[i].label)}</text>`
|
|
6357
|
+
);
|
|
6358
|
+
}
|
|
6359
|
+
} else {
|
|
6360
|
+
const entryWidth = 80;
|
|
6361
|
+
const totalWidth = entries2.length * entryWidth;
|
|
6362
|
+
const startX = Math.max((chartW - totalWidth) / 2, 5);
|
|
6363
|
+
const legendY = position === "t" ? 25 : chartH - 15;
|
|
6364
|
+
for (let i = 0; i < entries2.length; i++) {
|
|
6365
|
+
const ex = startX + i * entryWidth;
|
|
6366
|
+
parts.push(
|
|
6367
|
+
`<rect x="${round(ex)}" y="${round(legendY - 6)}" width="12" height="12" ${fillAttr(entries2[i].color)}/>`
|
|
6368
|
+
);
|
|
6369
|
+
parts.push(
|
|
6370
|
+
`<text x="${round(ex + 16)}" y="${round(legendY + 4)}" font-size="10" fill="#595959">${escapeXml(entries2[i].label)}</text>`
|
|
6371
|
+
);
|
|
6372
|
+
}
|
|
6204
6373
|
}
|
|
6205
6374
|
return parts.join("");
|
|
6206
6375
|
}
|
|
@@ -8036,7 +8205,19 @@ function renderTextBody(textBody, transform) {
|
|
|
8036
8205
|
getLineSpacing(para, lnSpcReduction),
|
|
8037
8206
|
paragraphGapPx
|
|
8038
8207
|
);
|
|
8039
|
-
const
|
|
8208
|
+
const firstSeg = line.segments[0];
|
|
8209
|
+
const bulletFontChain = buildBulletFontChain(
|
|
8210
|
+
para.properties,
|
|
8211
|
+
firstSeg?.properties.fontFamily,
|
|
8212
|
+
firstSeg?.properties.fontFamilyEa
|
|
8213
|
+
);
|
|
8214
|
+
const bulletStyles = buildBulletStyleAttrs(
|
|
8215
|
+
para.properties,
|
|
8216
|
+
lineFontSize,
|
|
8217
|
+
fontScale,
|
|
8218
|
+
bulletFontChain
|
|
8219
|
+
);
|
|
8220
|
+
getFontUsageCollector()?.record(bulletFontChain, bulletText);
|
|
8040
8221
|
tspans.push(
|
|
8041
8222
|
`<tspan x="${bulletX}" dy="${dy}" text-anchor="start" ${bulletStyles}>${escapeXml2(bulletText)}</tspan>`
|
|
8042
8223
|
);
|
|
@@ -8081,7 +8262,18 @@ function renderTextBody(textBody, transform) {
|
|
|
8081
8262
|
getLineSpacing(para, lnSpcReduction),
|
|
8082
8263
|
paragraphGapPx
|
|
8083
8264
|
);
|
|
8084
|
-
const
|
|
8265
|
+
const bulletFontChain = buildBulletFontChain(
|
|
8266
|
+
para.properties,
|
|
8267
|
+
firstRun?.properties.fontFamily,
|
|
8268
|
+
firstRun?.properties.fontFamilyEa
|
|
8269
|
+
);
|
|
8270
|
+
const bulletStyles = buildBulletStyleAttrs(
|
|
8271
|
+
para.properties,
|
|
8272
|
+
fontSize,
|
|
8273
|
+
fontScale,
|
|
8274
|
+
bulletFontChain
|
|
8275
|
+
);
|
|
8276
|
+
getFontUsageCollector()?.record(bulletFontChain, bulletText);
|
|
8085
8277
|
tspans.push(
|
|
8086
8278
|
`<tspan x="${bulletX}" dy="${dy}" text-anchor="start" ${bulletStyles}>${escapeXml2(bulletText)}</tspan>`
|
|
8087
8279
|
);
|
|
@@ -8216,14 +8408,18 @@ function toAlpha(num) {
|
|
|
8216
8408
|
}
|
|
8217
8409
|
return result;
|
|
8218
8410
|
}
|
|
8219
|
-
function
|
|
8411
|
+
function buildBulletFontChain(props, runFontFamily, runFontFamilyEa) {
|
|
8412
|
+
return [props.bulletFont, runFontFamily ?? null, runFontFamilyEa ?? null];
|
|
8413
|
+
}
|
|
8414
|
+
function buildBulletStyleAttrs(props, textFontSizePt, fontScale, bulletFontChain) {
|
|
8220
8415
|
const styles = [];
|
|
8221
8416
|
if (props.bulletSizePct !== null) {
|
|
8222
8417
|
const size = textFontSizePt * (props.bulletSizePct / 1e5);
|
|
8223
8418
|
styles.push(`font-size="${size}pt"`);
|
|
8224
8419
|
}
|
|
8225
|
-
|
|
8226
|
-
|
|
8420
|
+
const fontFamilyValue = buildFontFamilyValue(bulletFontChain);
|
|
8421
|
+
if (fontFamilyValue) {
|
|
8422
|
+
styles.push(`font-family="${fontFamilyValue}"`);
|
|
8227
8423
|
}
|
|
8228
8424
|
if (props.bulletColor) {
|
|
8229
8425
|
styles.push(`fill="${props.bulletColor.hex}"`);
|
|
@@ -8407,6 +8603,7 @@ function renderSegment(text, props, fontScale, prefix) {
|
|
|
8407
8603
|
let tspanContent;
|
|
8408
8604
|
if (!needsScriptSplit(props)) {
|
|
8409
8605
|
const styles = buildStyleAttrs(props, fontScale);
|
|
8606
|
+
getFontUsageCollector()?.record([props.fontFamily, props.fontFamilyEa], text);
|
|
8410
8607
|
tspanContent = `<tspan ${prefix}${styles}>${escapeXml2(text)}</tspan>`;
|
|
8411
8608
|
} else {
|
|
8412
8609
|
const parts = splitByScript(text);
|
|
@@ -8415,6 +8612,7 @@ function renderSegment(text, props, fontScale, prefix) {
|
|
|
8415
8612
|
const part = parts[i];
|
|
8416
8613
|
const fonts = part.isEa ? [props.fontFamilyEa, getJpanFallbackFont(), props.fontFamily] : [props.fontFamily, props.fontFamilyEa];
|
|
8417
8614
|
const styles = buildStyleAttrs(props, fontScale, fonts);
|
|
8615
|
+
getFontUsageCollector()?.record(fonts, part.text);
|
|
8418
8616
|
if (i === 0) {
|
|
8419
8617
|
result.push(`<tspan ${prefix}${styles}>${escapeXml2(part.text)}</tspan>`);
|
|
8420
8618
|
} else {
|
|
@@ -9195,6 +9393,7 @@ function escapeXmlAttr(str) {
|
|
|
9195
9393
|
|
|
9196
9394
|
// src/converter.ts
|
|
9197
9395
|
async function convertPptxToSvg(input, options) {
|
|
9396
|
+
const textOutput = options?.textOutput ?? "path";
|
|
9198
9397
|
const setup = await createOpentypeSetupFromSystem(
|
|
9199
9398
|
options?.fontDirs,
|
|
9200
9399
|
options?.fontMapping,
|
|
@@ -9202,7 +9401,13 @@ async function convertPptxToSvg(input, options) {
|
|
|
9202
9401
|
);
|
|
9203
9402
|
if (setup) {
|
|
9204
9403
|
setTextMeasurer(setup.measurer);
|
|
9205
|
-
|
|
9404
|
+
if (textOutput !== "text") {
|
|
9405
|
+
setTextPathFontResolver(setup.fontResolver);
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
const fontUsageCollector = textOutput === "text" ? new FontUsageCollector() : null;
|
|
9409
|
+
if (fontUsageCollector) {
|
|
9410
|
+
setFontUsageCollector(fontUsageCollector);
|
|
9206
9411
|
}
|
|
9207
9412
|
setFontMapping(createFontMapping(options?.fontMapping));
|
|
9208
9413
|
enableXmlCache();
|
|
@@ -9221,7 +9426,14 @@ async function convertPptxToSvg(input, options) {
|
|
|
9221
9426
|
const { slide, layoutElements, layoutShowMasterSp, masterElements } = parsed;
|
|
9222
9427
|
const effectiveMasterElements = slide.showMasterSp && layoutShowMasterSp ? masterElements : [];
|
|
9223
9428
|
slide.elements = mergeElements(effectiveMasterElements, layoutElements, slide.elements);
|
|
9224
|
-
|
|
9429
|
+
fontUsageCollector?.reset();
|
|
9430
|
+
let svg = renderSlideToSvg(slide, data.presInfo.slideSize);
|
|
9431
|
+
if (fontUsageCollector && setup) {
|
|
9432
|
+
const style = await buildFontFaceStyle(fontUsageCollector.getUsages(), setup.fontResolver);
|
|
9433
|
+
if (style) {
|
|
9434
|
+
svg = injectIntoSvgDefs(svg, style);
|
|
9435
|
+
}
|
|
9436
|
+
}
|
|
9225
9437
|
results.push({ slideNumber, svg });
|
|
9226
9438
|
}
|
|
9227
9439
|
flushWarnings();
|
|
@@ -9230,17 +9442,60 @@ async function convertPptxToSvg(input, options) {
|
|
|
9230
9442
|
clearXmlCache();
|
|
9231
9443
|
resetTextMeasurer();
|
|
9232
9444
|
resetTextPathFontResolver();
|
|
9445
|
+
resetFontUsageCollector();
|
|
9233
9446
|
resetFontMapping();
|
|
9234
9447
|
resetScriptFonts();
|
|
9235
9448
|
}
|
|
9236
9449
|
}
|
|
9450
|
+
function injectIntoSvgDefs(svg, content) {
|
|
9451
|
+
const openTagEnd = svg.indexOf(">");
|
|
9452
|
+
if (openTagEnd === -1) return svg;
|
|
9453
|
+
return `${svg.slice(0, openTagEnd + 1)}<defs>${content}</defs>${svg.slice(openTagEnd + 1)}`;
|
|
9454
|
+
}
|
|
9455
|
+
var cachedFontBuffers = null;
|
|
9456
|
+
var cachedFontBuffersKey = null;
|
|
9457
|
+
var MAX_TOTAL_FONT_BUFFER_BYTES = 100 * 1024 * 1024;
|
|
9458
|
+
function loadFontBuffers(fontDirs, skipSystemFonts) {
|
|
9459
|
+
const key = `${(fontDirs ?? []).join("\0")}
|
|
9460
|
+
${skipSystemFonts ?? false}`;
|
|
9461
|
+
if (cachedFontBuffers !== null && cachedFontBuffersKey === key) {
|
|
9462
|
+
return cachedFontBuffers;
|
|
9463
|
+
}
|
|
9464
|
+
const allPaths = collectFontFilePaths(fontDirs, skipSystemFonts);
|
|
9465
|
+
const ttfOtfPaths = allPaths.filter((p) => {
|
|
9466
|
+
const lower = p.toLowerCase();
|
|
9467
|
+
return lower.endsWith(".ttf") || lower.endsWith(".otf");
|
|
9468
|
+
});
|
|
9469
|
+
const pathsWithSize = [];
|
|
9470
|
+
for (const p of ttfOtfPaths) {
|
|
9471
|
+
try {
|
|
9472
|
+
pathsWithSize.push({ path: p, size: (0, import_node_fs2.statSync)(p).size });
|
|
9473
|
+
} catch {
|
|
9474
|
+
}
|
|
9475
|
+
}
|
|
9476
|
+
pathsWithSize.sort((a, b) => a.size - b.size);
|
|
9477
|
+
const buffers = [];
|
|
9478
|
+
let totalSize = 0;
|
|
9479
|
+
for (const { path, size } of pathsWithSize) {
|
|
9480
|
+
if (totalSize + size > MAX_TOTAL_FONT_BUFFER_BYTES) break;
|
|
9481
|
+
try {
|
|
9482
|
+
buffers.push(new Uint8Array((0, import_node_fs2.readFileSync)(path)));
|
|
9483
|
+
totalSize += size;
|
|
9484
|
+
} catch {
|
|
9485
|
+
}
|
|
9486
|
+
}
|
|
9487
|
+
cachedFontBuffers = buffers;
|
|
9488
|
+
cachedFontBuffersKey = key;
|
|
9489
|
+
return buffers;
|
|
9490
|
+
}
|
|
9237
9491
|
async function convertPptxToPng(input, options) {
|
|
9238
|
-
const svgResults = await convertPptxToSvg(input, options);
|
|
9492
|
+
const svgResults = await convertPptxToSvg(input, { ...options, textOutput: "path" });
|
|
9239
9493
|
const width = options?.width ?? DEFAULT_OUTPUT_WIDTH;
|
|
9240
9494
|
const height = options?.height;
|
|
9495
|
+
const fontBuffers = loadFontBuffers(options?.fontDirs, options?.skipSystemFonts);
|
|
9241
9496
|
const results = [];
|
|
9242
9497
|
for (const { slideNumber, svg } of svgResults) {
|
|
9243
|
-
const pngResult = await svgToPng(svg, { width, height });
|
|
9498
|
+
const pngResult = await svgToPng(svg, { width, height, fontBuffers });
|
|
9244
9499
|
results.push({
|
|
9245
9500
|
slideNumber,
|
|
9246
9501
|
png: pngResult.png,
|
package/dist/index.d.cts
CHANGED
|
@@ -48,6 +48,15 @@ interface ConvertOptions {
|
|
|
48
48
|
fontMapping?: FontMapping;
|
|
49
49
|
/** true のとき OS のシステムフォントをスキャンせず fontDirs のみを使用する */
|
|
50
50
|
skipSystemFonts?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* SVG でのテキスト出力方式。デフォルト: "path"
|
|
53
|
+
* - "path": グリフをアウトライン化した <path> として出力する。フォント環境に依存しない
|
|
54
|
+
* - "text": ネイティブ <text> 要素 + サブセット化フォントの @font-face (data URI) 埋め込みで出力する。
|
|
55
|
+
* ブラウザでのインライン表示時にネイティブテキスト描画 (ヒンティング等) が効き、テキスト選択も可能になる。
|
|
56
|
+
* <img src="...svg"> 参照やサニタイズ環境では期待どおり描画されないことがある。
|
|
57
|
+
* convertPptxToPng では無視され、常に "path" で変換される (resvg は @font-face を解釈しないため)
|
|
58
|
+
*/
|
|
59
|
+
textOutput?: "path" | "text";
|
|
51
60
|
}
|
|
52
61
|
interface SlideSvg {
|
|
53
62
|
slideNumber: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,15 @@ interface ConvertOptions {
|
|
|
48
48
|
fontMapping?: FontMapping;
|
|
49
49
|
/** true のとき OS のシステムフォントをスキャンせず fontDirs のみを使用する */
|
|
50
50
|
skipSystemFonts?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* SVG でのテキスト出力方式。デフォルト: "path"
|
|
53
|
+
* - "path": グリフをアウトライン化した <path> として出力する。フォント環境に依存しない
|
|
54
|
+
* - "text": ネイティブ <text> 要素 + サブセット化フォントの @font-face (data URI) 埋め込みで出力する。
|
|
55
|
+
* ブラウザでのインライン表示時にネイティブテキスト描画 (ヒンティング等) が効き、テキスト選択も可能になる。
|
|
56
|
+
* <img src="...svg"> 参照やサニタイズ環境では期待どおり描画されないことがある。
|
|
57
|
+
* convertPptxToPng では無視され、常に "path" で変換される (resvg は @font-face を解釈しないため)
|
|
58
|
+
*/
|
|
59
|
+
textOutput?: "path" | "text";
|
|
51
60
|
}
|
|
52
61
|
interface SlideSvg {
|
|
53
62
|
slideNumber: number;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,189 @@
|
|
|
1
|
+
// src/converter.ts
|
|
2
|
+
import { readFileSync, statSync } from "fs";
|
|
3
|
+
|
|
4
|
+
// src/font/font-embedder.ts
|
|
5
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
6
|
+
|
|
7
|
+
// src/warning-logger.ts
|
|
8
|
+
var PREFIX = "[pptx-glimpse]";
|
|
9
|
+
var currentLevel = "off";
|
|
10
|
+
var entries = [];
|
|
11
|
+
var featureCounts = /* @__PURE__ */ new Map();
|
|
12
|
+
function initWarningLogger(level) {
|
|
13
|
+
currentLevel = level;
|
|
14
|
+
entries = [];
|
|
15
|
+
featureCounts.clear();
|
|
16
|
+
}
|
|
17
|
+
function warn(feature, message, context) {
|
|
18
|
+
if (currentLevel === "off") return;
|
|
19
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
20
|
+
const existing = featureCounts.get(feature);
|
|
21
|
+
if (existing) {
|
|
22
|
+
existing.count++;
|
|
23
|
+
} else {
|
|
24
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
25
|
+
}
|
|
26
|
+
if (currentLevel === "debug") {
|
|
27
|
+
const ctx = context ? ` (${context})` : "";
|
|
28
|
+
console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function debug(feature, message, context) {
|
|
32
|
+
if (currentLevel !== "debug") return;
|
|
33
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
34
|
+
const existing = featureCounts.get(feature);
|
|
35
|
+
if (existing) {
|
|
36
|
+
existing.count++;
|
|
37
|
+
} else {
|
|
38
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
39
|
+
}
|
|
40
|
+
const ctx = context ? ` (${context})` : "";
|
|
41
|
+
console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
|
|
42
|
+
}
|
|
43
|
+
function getWarningSummary() {
|
|
44
|
+
const features = [];
|
|
45
|
+
for (const [feature, { message, count }] of featureCounts) {
|
|
46
|
+
features.push({ feature, message, count });
|
|
47
|
+
}
|
|
48
|
+
return { totalCount: entries.length, features };
|
|
49
|
+
}
|
|
50
|
+
function flushWarnings() {
|
|
51
|
+
const summary = getWarningSummary();
|
|
52
|
+
if (currentLevel !== "off" && summary.features.length > 0) {
|
|
53
|
+
console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
|
|
54
|
+
for (const { feature, count } of summary.features) {
|
|
55
|
+
console.warn(` - ${feature}: ${count} occurrence(s)`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
entries = [];
|
|
59
|
+
featureCounts.clear();
|
|
60
|
+
return summary;
|
|
61
|
+
}
|
|
62
|
+
function getWarningEntries() {
|
|
63
|
+
return entries;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/font/font-subsetter.ts
|
|
67
|
+
async function tryLoadOpentypeCtors() {
|
|
68
|
+
try {
|
|
69
|
+
const specifier = "opentype.js";
|
|
70
|
+
const mod = await import(
|
|
71
|
+
/* @vite-ignore */
|
|
72
|
+
specifier
|
|
73
|
+
);
|
|
74
|
+
return { Font: mod.Font, Glyph: mod.Glyph };
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function glyphName(glyph, firstUnicode) {
|
|
80
|
+
if (glyph.name) return glyph.name;
|
|
81
|
+
return `uni${firstUnicode.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
82
|
+
}
|
|
83
|
+
async function subsetFont(font, chars, familyName) {
|
|
84
|
+
const opentype = await tryLoadOpentypeCtors();
|
|
85
|
+
if (!opentype) return null;
|
|
86
|
+
const source = font;
|
|
87
|
+
if (typeof source.charToGlyph !== "function" || !source.glyphs) return null;
|
|
88
|
+
const glyphMap = /* @__PURE__ */ new Map();
|
|
89
|
+
for (const char of chars) {
|
|
90
|
+
const codePoint = char.codePointAt(0);
|
|
91
|
+
if (codePoint === void 0) continue;
|
|
92
|
+
let glyph;
|
|
93
|
+
try {
|
|
94
|
+
glyph = source.charToGlyph(char);
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!glyph || !glyph.index) continue;
|
|
99
|
+
const entry = glyphMap.get(glyph.index);
|
|
100
|
+
if (entry) {
|
|
101
|
+
entry.unicodes.add(codePoint);
|
|
102
|
+
} else {
|
|
103
|
+
glyphMap.set(glyph.index, { glyph, unicodes: /* @__PURE__ */ new Set([codePoint]) });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (glyphMap.size === 0) return null;
|
|
107
|
+
try {
|
|
108
|
+
const notdefSource = source.glyphs.get(0);
|
|
109
|
+
const glyphs = [
|
|
110
|
+
new opentype.Glyph({
|
|
111
|
+
name: ".notdef",
|
|
112
|
+
advanceWidth: notdefSource?.advanceWidth ?? source.unitsPerEm / 2,
|
|
113
|
+
path: notdefSource?.path
|
|
114
|
+
})
|
|
115
|
+
];
|
|
116
|
+
for (const { glyph, unicodes } of glyphMap.values()) {
|
|
117
|
+
const unicodeList = [...unicodes].sort((a, b) => a - b);
|
|
118
|
+
glyphs.push(
|
|
119
|
+
new opentype.Glyph({
|
|
120
|
+
name: glyphName(glyph, unicodeList[0]),
|
|
121
|
+
unicode: unicodeList[0],
|
|
122
|
+
unicodes: unicodeList,
|
|
123
|
+
advanceWidth: glyph.advanceWidth ?? 0,
|
|
124
|
+
path: glyph.path
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const subset = new opentype.Font({
|
|
129
|
+
familyName: familyName || "EmbeddedFont",
|
|
130
|
+
styleName: "Regular",
|
|
131
|
+
unitsPerEm: source.unitsPerEm,
|
|
132
|
+
ascender: source.ascender,
|
|
133
|
+
// opentype.js は descender に負値を要求する
|
|
134
|
+
descender: source.descender < 0 ? source.descender : -1,
|
|
135
|
+
glyphs
|
|
136
|
+
});
|
|
137
|
+
return new Uint8Array(subset.toArrayBuffer());
|
|
138
|
+
} catch (e) {
|
|
139
|
+
warn(
|
|
140
|
+
"font.subsetFailed",
|
|
141
|
+
`Failed to subset font "${familyName}": ${e instanceof Error ? e.message : String(e)}`
|
|
142
|
+
);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/font/script-font-context.ts
|
|
148
|
+
var jpanMajorFont = null;
|
|
149
|
+
var jpanMinorFont = null;
|
|
150
|
+
function setScriptFonts(majorJpan, minorJpan) {
|
|
151
|
+
jpanMajorFont = majorJpan;
|
|
152
|
+
jpanMinorFont = minorJpan;
|
|
153
|
+
}
|
|
154
|
+
function resetScriptFonts() {
|
|
155
|
+
jpanMajorFont = null;
|
|
156
|
+
jpanMinorFont = null;
|
|
157
|
+
}
|
|
158
|
+
function getJpanFallbackFont() {
|
|
159
|
+
return jpanMajorFont ?? jpanMinorFont;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/font/font-embedder.ts
|
|
163
|
+
function escapeCssFamilyName(name) {
|
|
164
|
+
return name.replace(/[\\"]/g, (c) => `\\${c}`).replace(/[<>&]/g, (c) => `\\${c.codePointAt(0).toString(16)} `);
|
|
165
|
+
}
|
|
166
|
+
async function buildFontFaceStyle(usages, fontResolver) {
|
|
167
|
+
const faces = [];
|
|
168
|
+
const jpanFallback = getJpanFallbackFont();
|
|
169
|
+
for (const [familyName, usage] of usages) {
|
|
170
|
+
const font = fontResolver.resolveFont(
|
|
171
|
+
usage.fonts[0],
|
|
172
|
+
usage.fonts[1] ?? null,
|
|
173
|
+
usage.fonts[2] ?? jpanFallback
|
|
174
|
+
);
|
|
175
|
+
if (!font) continue;
|
|
176
|
+
const buffer = await subsetFont(font, usage.chars, familyName);
|
|
177
|
+
if (!buffer) continue;
|
|
178
|
+
const base64 = Buffer2.from(buffer).toString("base64");
|
|
179
|
+
faces.push(
|
|
180
|
+
`@font-face{font-family:"${escapeCssFamilyName(familyName)}";src:url(data:font/otf;base64,${base64}) format("opentype");}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (faces.length === 0) return "";
|
|
184
|
+
return `<style type="text/css">${faces.join("")}</style>`;
|
|
185
|
+
}
|
|
186
|
+
|
|
1
187
|
// src/font/font-mapping.ts
|
|
2
188
|
var DEFAULT_FONT_MAPPING = {
|
|
3
189
|
// ラテン文字フォント
|
|
@@ -64,6 +250,40 @@ function getCurrentMappedFont(fontFamily) {
|
|
|
64
250
|
return getMappedFont(fontFamily, currentMapping);
|
|
65
251
|
}
|
|
66
252
|
|
|
253
|
+
// src/font/font-usage-collector.ts
|
|
254
|
+
var FontUsageCollector = class {
|
|
255
|
+
/** key: tspan の font-family リスト先頭のフォント名 (= @font-face で宣言する名前) */
|
|
256
|
+
usages = /* @__PURE__ */ new Map();
|
|
257
|
+
record(fonts, text) {
|
|
258
|
+
const primary = fonts.find((f) => f !== null && f !== void 0);
|
|
259
|
+
if (!primary || text.length === 0) return;
|
|
260
|
+
let usage = this.usages.get(primary);
|
|
261
|
+
if (!usage) {
|
|
262
|
+
usage = { fonts: [...fonts], chars: /* @__PURE__ */ new Set() };
|
|
263
|
+
this.usages.set(primary, usage);
|
|
264
|
+
}
|
|
265
|
+
for (const char of text) {
|
|
266
|
+
usage.chars.add(char);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
getUsages() {
|
|
270
|
+
return this.usages;
|
|
271
|
+
}
|
|
272
|
+
reset() {
|
|
273
|
+
this.usages.clear();
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var currentCollector = null;
|
|
277
|
+
function setFontUsageCollector(collector) {
|
|
278
|
+
currentCollector = collector;
|
|
279
|
+
}
|
|
280
|
+
function getFontUsageCollector() {
|
|
281
|
+
return currentCollector;
|
|
282
|
+
}
|
|
283
|
+
function resetFontUsageCollector() {
|
|
284
|
+
currentCollector = null;
|
|
285
|
+
}
|
|
286
|
+
|
|
67
287
|
// src/font/opentype-helpers.ts
|
|
68
288
|
import { readFile } from "fs/promises";
|
|
69
289
|
|
|
@@ -1007,65 +1227,6 @@ function measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
|
|
|
1007
1227
|
return totalWidth;
|
|
1008
1228
|
}
|
|
1009
1229
|
|
|
1010
|
-
// src/warning-logger.ts
|
|
1011
|
-
var PREFIX = "[pptx-glimpse]";
|
|
1012
|
-
var currentLevel = "off";
|
|
1013
|
-
var entries = [];
|
|
1014
|
-
var featureCounts = /* @__PURE__ */ new Map();
|
|
1015
|
-
function initWarningLogger(level) {
|
|
1016
|
-
currentLevel = level;
|
|
1017
|
-
entries = [];
|
|
1018
|
-
featureCounts.clear();
|
|
1019
|
-
}
|
|
1020
|
-
function warn(feature, message, context) {
|
|
1021
|
-
if (currentLevel === "off") return;
|
|
1022
|
-
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
1023
|
-
const existing = featureCounts.get(feature);
|
|
1024
|
-
if (existing) {
|
|
1025
|
-
existing.count++;
|
|
1026
|
-
} else {
|
|
1027
|
-
featureCounts.set(feature, { message, count: 1 });
|
|
1028
|
-
}
|
|
1029
|
-
if (currentLevel === "debug") {
|
|
1030
|
-
const ctx = context ? ` (${context})` : "";
|
|
1031
|
-
console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
function debug(feature, message, context) {
|
|
1035
|
-
if (currentLevel !== "debug") return;
|
|
1036
|
-
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
1037
|
-
const existing = featureCounts.get(feature);
|
|
1038
|
-
if (existing) {
|
|
1039
|
-
existing.count++;
|
|
1040
|
-
} else {
|
|
1041
|
-
featureCounts.set(feature, { message, count: 1 });
|
|
1042
|
-
}
|
|
1043
|
-
const ctx = context ? ` (${context})` : "";
|
|
1044
|
-
console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
|
|
1045
|
-
}
|
|
1046
|
-
function getWarningSummary() {
|
|
1047
|
-
const features = [];
|
|
1048
|
-
for (const [feature, { message, count }] of featureCounts) {
|
|
1049
|
-
features.push({ feature, message, count });
|
|
1050
|
-
}
|
|
1051
|
-
return { totalCount: entries.length, features };
|
|
1052
|
-
}
|
|
1053
|
-
function flushWarnings() {
|
|
1054
|
-
const summary = getWarningSummary();
|
|
1055
|
-
if (currentLevel !== "off" && summary.features.length > 0) {
|
|
1056
|
-
console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
|
|
1057
|
-
for (const { feature, count } of summary.features) {
|
|
1058
|
-
console.warn(` - ${feature}: ${count} occurrence(s)`);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
entries = [];
|
|
1062
|
-
featureCounts.clear();
|
|
1063
|
-
return summary;
|
|
1064
|
-
}
|
|
1065
|
-
function getWarningEntries() {
|
|
1066
|
-
return entries;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
1230
|
// src/font/cjk-font-fallback.ts
|
|
1070
1231
|
import { platform } from "os";
|
|
1071
1232
|
var MACOS_FALLBACKS = {
|
|
@@ -1602,21 +1763,6 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping, sk
|
|
|
1602
1763
|
return setup;
|
|
1603
1764
|
}
|
|
1604
1765
|
|
|
1605
|
-
// src/font/script-font-context.ts
|
|
1606
|
-
var jpanMajorFont = null;
|
|
1607
|
-
var jpanMinorFont = null;
|
|
1608
|
-
function setScriptFonts(majorJpan, minorJpan) {
|
|
1609
|
-
jpanMajorFont = majorJpan;
|
|
1610
|
-
jpanMinorFont = minorJpan;
|
|
1611
|
-
}
|
|
1612
|
-
function resetScriptFonts() {
|
|
1613
|
-
jpanMajorFont = null;
|
|
1614
|
-
jpanMinorFont = null;
|
|
1615
|
-
}
|
|
1616
|
-
function getJpanFallbackFont() {
|
|
1617
|
-
return jpanMajorFont ?? jpanMinorFont;
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
1766
|
// src/font/text-measurer.ts
|
|
1621
1767
|
var DefaultTextMeasurer = class {
|
|
1622
1768
|
measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
|
|
@@ -1763,6 +1909,10 @@ async function svgToPng(svgString, options) {
|
|
|
1763
1909
|
} else if (options?.height) {
|
|
1764
1910
|
resvgOptions.fitTo = { mode: "height", value: options.height };
|
|
1765
1911
|
}
|
|
1912
|
+
const fontBuffers = options?.fontBuffers;
|
|
1913
|
+
if (fontBuffers && fontBuffers.length > 0) {
|
|
1914
|
+
resvgOptions.font = { fontBuffers };
|
|
1915
|
+
}
|
|
1766
1916
|
const resvg = new Resvg(svgString, resvgOptions);
|
|
1767
1917
|
const rendered = resvg.render();
|
|
1768
1918
|
return {
|
|
@@ -2722,7 +2872,7 @@ function parseChartTitle(titleNode) {
|
|
|
2722
2872
|
function parseLegend(legendNode) {
|
|
2723
2873
|
if (!legendNode) return null;
|
|
2724
2874
|
const legendPos = legendNode.legendPos;
|
|
2725
|
-
const pos = legendPos?.["@_val"] ?? "
|
|
2875
|
+
const pos = legendPos?.["@_val"] ?? "r";
|
|
2726
2876
|
return { position: pos };
|
|
2727
2877
|
}
|
|
2728
2878
|
|
|
@@ -5379,6 +5529,7 @@ var DEFAULT_SERIES_COLORS = [
|
|
|
5379
5529
|
{ hex: "#5B9BD5", alpha: 1 },
|
|
5380
5530
|
{ hex: "#70AD47", alpha: 1 }
|
|
5381
5531
|
];
|
|
5532
|
+
var LEGEND_SIDE_WIDTH = 100;
|
|
5382
5533
|
function renderChart(element) {
|
|
5383
5534
|
const { transform, chart } = element;
|
|
5384
5535
|
const w = emuToPixels(transform.extentWidth);
|
|
@@ -5397,6 +5548,8 @@ function renderChart(element) {
|
|
|
5397
5548
|
if (chart.legend) {
|
|
5398
5549
|
if (chart.legend.position === "b") margin.bottom = 50;
|
|
5399
5550
|
else if (chart.legend.position === "t") margin.top += 20;
|
|
5551
|
+
else if (chart.legend.position === "r") margin.right = LEGEND_SIDE_WIDTH;
|
|
5552
|
+
else if (chart.legend.position === "l") margin.left += LEGEND_SIDE_WIDTH;
|
|
5400
5553
|
}
|
|
5401
5554
|
const plotX = margin.left;
|
|
5402
5555
|
const plotY = margin.top;
|
|
@@ -6151,18 +6304,34 @@ function renderLegend(chart, chartW, chartH, position) {
|
|
|
6151
6304
|
color: s.color
|
|
6152
6305
|
}));
|
|
6153
6306
|
if (entries2.length === 0) return "";
|
|
6154
|
-
const
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6307
|
+
const ENTRY_HEIGHT = 20;
|
|
6308
|
+
if (position === "r" || position === "l") {
|
|
6309
|
+
const totalH = entries2.length * ENTRY_HEIGHT;
|
|
6310
|
+
const startY = Math.max((chartH - totalH) / 2, 5);
|
|
6311
|
+
const legendX = position === "r" ? chartW - LEGEND_SIDE_WIDTH + 5 : 5;
|
|
6312
|
+
for (let i = 0; i < entries2.length; i++) {
|
|
6313
|
+
const ey = startY + i * ENTRY_HEIGHT;
|
|
6314
|
+
parts.push(
|
|
6315
|
+
`<rect x="${round(legendX)}" y="${round(ey)}" width="12" height="12" ${fillAttr(entries2[i].color)}/>`
|
|
6316
|
+
);
|
|
6317
|
+
parts.push(
|
|
6318
|
+
`<text x="${round(legendX + 16)}" y="${round(ey + 10)}" font-size="10" fill="#595959">${escapeXml(entries2[i].label)}</text>`
|
|
6319
|
+
);
|
|
6320
|
+
}
|
|
6321
|
+
} else {
|
|
6322
|
+
const entryWidth = 80;
|
|
6323
|
+
const totalWidth = entries2.length * entryWidth;
|
|
6324
|
+
const startX = Math.max((chartW - totalWidth) / 2, 5);
|
|
6325
|
+
const legendY = position === "t" ? 25 : chartH - 15;
|
|
6326
|
+
for (let i = 0; i < entries2.length; i++) {
|
|
6327
|
+
const ex = startX + i * entryWidth;
|
|
6328
|
+
parts.push(
|
|
6329
|
+
`<rect x="${round(ex)}" y="${round(legendY - 6)}" width="12" height="12" ${fillAttr(entries2[i].color)}/>`
|
|
6330
|
+
);
|
|
6331
|
+
parts.push(
|
|
6332
|
+
`<text x="${round(ex + 16)}" y="${round(legendY + 4)}" font-size="10" fill="#595959">${escapeXml(entries2[i].label)}</text>`
|
|
6333
|
+
);
|
|
6334
|
+
}
|
|
6166
6335
|
}
|
|
6167
6336
|
return parts.join("");
|
|
6168
6337
|
}
|
|
@@ -7998,7 +8167,19 @@ function renderTextBody(textBody, transform) {
|
|
|
7998
8167
|
getLineSpacing(para, lnSpcReduction),
|
|
7999
8168
|
paragraphGapPx
|
|
8000
8169
|
);
|
|
8001
|
-
const
|
|
8170
|
+
const firstSeg = line.segments[0];
|
|
8171
|
+
const bulletFontChain = buildBulletFontChain(
|
|
8172
|
+
para.properties,
|
|
8173
|
+
firstSeg?.properties.fontFamily,
|
|
8174
|
+
firstSeg?.properties.fontFamilyEa
|
|
8175
|
+
);
|
|
8176
|
+
const bulletStyles = buildBulletStyleAttrs(
|
|
8177
|
+
para.properties,
|
|
8178
|
+
lineFontSize,
|
|
8179
|
+
fontScale,
|
|
8180
|
+
bulletFontChain
|
|
8181
|
+
);
|
|
8182
|
+
getFontUsageCollector()?.record(bulletFontChain, bulletText);
|
|
8002
8183
|
tspans.push(
|
|
8003
8184
|
`<tspan x="${bulletX}" dy="${dy}" text-anchor="start" ${bulletStyles}>${escapeXml2(bulletText)}</tspan>`
|
|
8004
8185
|
);
|
|
@@ -8043,7 +8224,18 @@ function renderTextBody(textBody, transform) {
|
|
|
8043
8224
|
getLineSpacing(para, lnSpcReduction),
|
|
8044
8225
|
paragraphGapPx
|
|
8045
8226
|
);
|
|
8046
|
-
const
|
|
8227
|
+
const bulletFontChain = buildBulletFontChain(
|
|
8228
|
+
para.properties,
|
|
8229
|
+
firstRun?.properties.fontFamily,
|
|
8230
|
+
firstRun?.properties.fontFamilyEa
|
|
8231
|
+
);
|
|
8232
|
+
const bulletStyles = buildBulletStyleAttrs(
|
|
8233
|
+
para.properties,
|
|
8234
|
+
fontSize,
|
|
8235
|
+
fontScale,
|
|
8236
|
+
bulletFontChain
|
|
8237
|
+
);
|
|
8238
|
+
getFontUsageCollector()?.record(bulletFontChain, bulletText);
|
|
8047
8239
|
tspans.push(
|
|
8048
8240
|
`<tspan x="${bulletX}" dy="${dy}" text-anchor="start" ${bulletStyles}>${escapeXml2(bulletText)}</tspan>`
|
|
8049
8241
|
);
|
|
@@ -8178,14 +8370,18 @@ function toAlpha(num) {
|
|
|
8178
8370
|
}
|
|
8179
8371
|
return result;
|
|
8180
8372
|
}
|
|
8181
|
-
function
|
|
8373
|
+
function buildBulletFontChain(props, runFontFamily, runFontFamilyEa) {
|
|
8374
|
+
return [props.bulletFont, runFontFamily ?? null, runFontFamilyEa ?? null];
|
|
8375
|
+
}
|
|
8376
|
+
function buildBulletStyleAttrs(props, textFontSizePt, fontScale, bulletFontChain) {
|
|
8182
8377
|
const styles = [];
|
|
8183
8378
|
if (props.bulletSizePct !== null) {
|
|
8184
8379
|
const size = textFontSizePt * (props.bulletSizePct / 1e5);
|
|
8185
8380
|
styles.push(`font-size="${size}pt"`);
|
|
8186
8381
|
}
|
|
8187
|
-
|
|
8188
|
-
|
|
8382
|
+
const fontFamilyValue = buildFontFamilyValue(bulletFontChain);
|
|
8383
|
+
if (fontFamilyValue) {
|
|
8384
|
+
styles.push(`font-family="${fontFamilyValue}"`);
|
|
8189
8385
|
}
|
|
8190
8386
|
if (props.bulletColor) {
|
|
8191
8387
|
styles.push(`fill="${props.bulletColor.hex}"`);
|
|
@@ -8369,6 +8565,7 @@ function renderSegment(text, props, fontScale, prefix) {
|
|
|
8369
8565
|
let tspanContent;
|
|
8370
8566
|
if (!needsScriptSplit(props)) {
|
|
8371
8567
|
const styles = buildStyleAttrs(props, fontScale);
|
|
8568
|
+
getFontUsageCollector()?.record([props.fontFamily, props.fontFamilyEa], text);
|
|
8372
8569
|
tspanContent = `<tspan ${prefix}${styles}>${escapeXml2(text)}</tspan>`;
|
|
8373
8570
|
} else {
|
|
8374
8571
|
const parts = splitByScript(text);
|
|
@@ -8377,6 +8574,7 @@ function renderSegment(text, props, fontScale, prefix) {
|
|
|
8377
8574
|
const part = parts[i];
|
|
8378
8575
|
const fonts = part.isEa ? [props.fontFamilyEa, getJpanFallbackFont(), props.fontFamily] : [props.fontFamily, props.fontFamilyEa];
|
|
8379
8576
|
const styles = buildStyleAttrs(props, fontScale, fonts);
|
|
8577
|
+
getFontUsageCollector()?.record(fonts, part.text);
|
|
8380
8578
|
if (i === 0) {
|
|
8381
8579
|
result.push(`<tspan ${prefix}${styles}>${escapeXml2(part.text)}</tspan>`);
|
|
8382
8580
|
} else {
|
|
@@ -9157,6 +9355,7 @@ function escapeXmlAttr(str) {
|
|
|
9157
9355
|
|
|
9158
9356
|
// src/converter.ts
|
|
9159
9357
|
async function convertPptxToSvg(input, options) {
|
|
9358
|
+
const textOutput = options?.textOutput ?? "path";
|
|
9160
9359
|
const setup = await createOpentypeSetupFromSystem(
|
|
9161
9360
|
options?.fontDirs,
|
|
9162
9361
|
options?.fontMapping,
|
|
@@ -9164,7 +9363,13 @@ async function convertPptxToSvg(input, options) {
|
|
|
9164
9363
|
);
|
|
9165
9364
|
if (setup) {
|
|
9166
9365
|
setTextMeasurer(setup.measurer);
|
|
9167
|
-
|
|
9366
|
+
if (textOutput !== "text") {
|
|
9367
|
+
setTextPathFontResolver(setup.fontResolver);
|
|
9368
|
+
}
|
|
9369
|
+
}
|
|
9370
|
+
const fontUsageCollector = textOutput === "text" ? new FontUsageCollector() : null;
|
|
9371
|
+
if (fontUsageCollector) {
|
|
9372
|
+
setFontUsageCollector(fontUsageCollector);
|
|
9168
9373
|
}
|
|
9169
9374
|
setFontMapping(createFontMapping(options?.fontMapping));
|
|
9170
9375
|
enableXmlCache();
|
|
@@ -9183,7 +9388,14 @@ async function convertPptxToSvg(input, options) {
|
|
|
9183
9388
|
const { slide, layoutElements, layoutShowMasterSp, masterElements } = parsed;
|
|
9184
9389
|
const effectiveMasterElements = slide.showMasterSp && layoutShowMasterSp ? masterElements : [];
|
|
9185
9390
|
slide.elements = mergeElements(effectiveMasterElements, layoutElements, slide.elements);
|
|
9186
|
-
|
|
9391
|
+
fontUsageCollector?.reset();
|
|
9392
|
+
let svg = renderSlideToSvg(slide, data.presInfo.slideSize);
|
|
9393
|
+
if (fontUsageCollector && setup) {
|
|
9394
|
+
const style = await buildFontFaceStyle(fontUsageCollector.getUsages(), setup.fontResolver);
|
|
9395
|
+
if (style) {
|
|
9396
|
+
svg = injectIntoSvgDefs(svg, style);
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9187
9399
|
results.push({ slideNumber, svg });
|
|
9188
9400
|
}
|
|
9189
9401
|
flushWarnings();
|
|
@@ -9192,17 +9404,60 @@ async function convertPptxToSvg(input, options) {
|
|
|
9192
9404
|
clearXmlCache();
|
|
9193
9405
|
resetTextMeasurer();
|
|
9194
9406
|
resetTextPathFontResolver();
|
|
9407
|
+
resetFontUsageCollector();
|
|
9195
9408
|
resetFontMapping();
|
|
9196
9409
|
resetScriptFonts();
|
|
9197
9410
|
}
|
|
9198
9411
|
}
|
|
9412
|
+
function injectIntoSvgDefs(svg, content) {
|
|
9413
|
+
const openTagEnd = svg.indexOf(">");
|
|
9414
|
+
if (openTagEnd === -1) return svg;
|
|
9415
|
+
return `${svg.slice(0, openTagEnd + 1)}<defs>${content}</defs>${svg.slice(openTagEnd + 1)}`;
|
|
9416
|
+
}
|
|
9417
|
+
var cachedFontBuffers = null;
|
|
9418
|
+
var cachedFontBuffersKey = null;
|
|
9419
|
+
var MAX_TOTAL_FONT_BUFFER_BYTES = 100 * 1024 * 1024;
|
|
9420
|
+
function loadFontBuffers(fontDirs, skipSystemFonts) {
|
|
9421
|
+
const key = `${(fontDirs ?? []).join("\0")}
|
|
9422
|
+
${skipSystemFonts ?? false}`;
|
|
9423
|
+
if (cachedFontBuffers !== null && cachedFontBuffersKey === key) {
|
|
9424
|
+
return cachedFontBuffers;
|
|
9425
|
+
}
|
|
9426
|
+
const allPaths = collectFontFilePaths(fontDirs, skipSystemFonts);
|
|
9427
|
+
const ttfOtfPaths = allPaths.filter((p) => {
|
|
9428
|
+
const lower = p.toLowerCase();
|
|
9429
|
+
return lower.endsWith(".ttf") || lower.endsWith(".otf");
|
|
9430
|
+
});
|
|
9431
|
+
const pathsWithSize = [];
|
|
9432
|
+
for (const p of ttfOtfPaths) {
|
|
9433
|
+
try {
|
|
9434
|
+
pathsWithSize.push({ path: p, size: statSync(p).size });
|
|
9435
|
+
} catch {
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
pathsWithSize.sort((a, b) => a.size - b.size);
|
|
9439
|
+
const buffers = [];
|
|
9440
|
+
let totalSize = 0;
|
|
9441
|
+
for (const { path, size } of pathsWithSize) {
|
|
9442
|
+
if (totalSize + size > MAX_TOTAL_FONT_BUFFER_BYTES) break;
|
|
9443
|
+
try {
|
|
9444
|
+
buffers.push(new Uint8Array(readFileSync(path)));
|
|
9445
|
+
totalSize += size;
|
|
9446
|
+
} catch {
|
|
9447
|
+
}
|
|
9448
|
+
}
|
|
9449
|
+
cachedFontBuffers = buffers;
|
|
9450
|
+
cachedFontBuffersKey = key;
|
|
9451
|
+
return buffers;
|
|
9452
|
+
}
|
|
9199
9453
|
async function convertPptxToPng(input, options) {
|
|
9200
|
-
const svgResults = await convertPptxToSvg(input, options);
|
|
9454
|
+
const svgResults = await convertPptxToSvg(input, { ...options, textOutput: "path" });
|
|
9201
9455
|
const width = options?.width ?? DEFAULT_OUTPUT_WIDTH;
|
|
9202
9456
|
const height = options?.height;
|
|
9457
|
+
const fontBuffers = loadFontBuffers(options?.fontDirs, options?.skipSystemFonts);
|
|
9203
9458
|
const results = [];
|
|
9204
9459
|
for (const { slideNumber, svg } of svgResults) {
|
|
9205
|
-
const pngResult = await svgToPng(svg, { width, height });
|
|
9460
|
+
const pngResult = await svgToPng(svg, { width, height, fontBuffers });
|
|
9206
9461
|
results.push({
|
|
9207
9462
|
slideNumber,
|
|
9208
9463
|
png: pngResult.png,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pptx-glimpse",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A lightweight JavaScript library for rendering PowerPoint (.pptx) files as SVG or PNG in Node.js. No LibreOffice required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@changesets/cli": "^2.29.8",
|
|
47
47
|
"@types/node": "^25.2.2",
|
|
48
48
|
"@types/ws": "^8.18.1",
|
|
49
|
-
"@vitest/coverage-v8": "
|
|
49
|
+
"@vitest/coverage-v8": "4.1.0",
|
|
50
50
|
"eslint": "^9.0.0",
|
|
51
51
|
"eslint-config-prettier": "^10.0.0",
|
|
52
52
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"tsx": "^4.21.0",
|
|
60
60
|
"typescript": "^5.9.3",
|
|
61
61
|
"typescript-eslint": "^8.55.0",
|
|
62
|
-
"vitest": "^
|
|
62
|
+
"vitest": "^4.1.0",
|
|
63
63
|
"ws": "^8.19.0"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|