pptx-glimpse 0.2.1 → 0.3.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 +4 -2
- package/dist/index.cjs +351 -96
- package/dist/index.d.cts +98 -1
- package/dist/index.d.ts +98 -1
- package/dist/index.js +349 -96
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -3770,6 +3770,65 @@ function renderPlaceholder(mimeType, w, h, transformAttr) {
|
|
|
3770
3770
|
].join("");
|
|
3771
3771
|
}
|
|
3772
3772
|
|
|
3773
|
+
// src/warning-logger.ts
|
|
3774
|
+
var PREFIX = "[pptx-glimpse]";
|
|
3775
|
+
var currentLevel = "off";
|
|
3776
|
+
var entries = [];
|
|
3777
|
+
var featureCounts = /* @__PURE__ */ new Map();
|
|
3778
|
+
function initWarningLogger(level) {
|
|
3779
|
+
currentLevel = level;
|
|
3780
|
+
entries = [];
|
|
3781
|
+
featureCounts.clear();
|
|
3782
|
+
}
|
|
3783
|
+
function warn(feature, message, context) {
|
|
3784
|
+
if (currentLevel === "off") return;
|
|
3785
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
3786
|
+
const existing = featureCounts.get(feature);
|
|
3787
|
+
if (existing) {
|
|
3788
|
+
existing.count++;
|
|
3789
|
+
} else {
|
|
3790
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
3791
|
+
}
|
|
3792
|
+
if (currentLevel === "debug") {
|
|
3793
|
+
const ctx = context ? ` (${context})` : "";
|
|
3794
|
+
console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
function debug(feature, message, context) {
|
|
3798
|
+
if (currentLevel !== "debug") return;
|
|
3799
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
3800
|
+
const existing = featureCounts.get(feature);
|
|
3801
|
+
if (existing) {
|
|
3802
|
+
existing.count++;
|
|
3803
|
+
} else {
|
|
3804
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
3805
|
+
}
|
|
3806
|
+
const ctx = context ? ` (${context})` : "";
|
|
3807
|
+
console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
|
|
3808
|
+
}
|
|
3809
|
+
function getWarningSummary() {
|
|
3810
|
+
const features = [];
|
|
3811
|
+
for (const [feature, { message, count }] of featureCounts) {
|
|
3812
|
+
features.push({ feature, message, count });
|
|
3813
|
+
}
|
|
3814
|
+
return { totalCount: entries.length, features };
|
|
3815
|
+
}
|
|
3816
|
+
function flushWarnings() {
|
|
3817
|
+
const summary = getWarningSummary();
|
|
3818
|
+
if (currentLevel !== "off" && summary.features.length > 0) {
|
|
3819
|
+
console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
|
|
3820
|
+
for (const { feature, count } of summary.features) {
|
|
3821
|
+
console.warn(` - ${feature}: ${count} occurrence(s)`);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
entries = [];
|
|
3825
|
+
featureCounts.clear();
|
|
3826
|
+
return summary;
|
|
3827
|
+
}
|
|
3828
|
+
function getWarningEntries() {
|
|
3829
|
+
return entries;
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3773
3832
|
// src/renderer/chart-renderer.ts
|
|
3774
3833
|
var DEFAULT_SERIES_COLORS = [
|
|
3775
3834
|
{ hex: "#4472C4", alpha: 1 },
|
|
@@ -3851,11 +3910,20 @@ function renderChartTitle(title, chartWidth) {
|
|
|
3851
3910
|
function renderBarChart(chart, x, y, w, h) {
|
|
3852
3911
|
const parts = [];
|
|
3853
3912
|
const { series, categories } = chart;
|
|
3854
|
-
if (series.length === 0)
|
|
3913
|
+
if (series.length === 0) {
|
|
3914
|
+
debug("chart.bar", "series is empty");
|
|
3915
|
+
return "";
|
|
3916
|
+
}
|
|
3855
3917
|
const maxVal = getMaxValue(series);
|
|
3856
|
-
if (maxVal === 0)
|
|
3918
|
+
if (maxVal === 0) {
|
|
3919
|
+
debug("chart.bar", "max value is 0");
|
|
3920
|
+
return "";
|
|
3921
|
+
}
|
|
3857
3922
|
const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
3858
|
-
if (catCount === 0)
|
|
3923
|
+
if (catCount === 0) {
|
|
3924
|
+
debug("chart.bar", "category count is 0");
|
|
3925
|
+
return "";
|
|
3926
|
+
}
|
|
3859
3927
|
const isHorizontal = chart.barDirection === "bar";
|
|
3860
3928
|
parts.push(
|
|
3861
3929
|
`<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
@@ -3915,11 +3983,20 @@ function renderBarChart(chart, x, y, w, h) {
|
|
|
3915
3983
|
function renderLineChart(chart, x, y, w, h) {
|
|
3916
3984
|
const parts = [];
|
|
3917
3985
|
const { series, categories } = chart;
|
|
3918
|
-
if (series.length === 0)
|
|
3986
|
+
if (series.length === 0) {
|
|
3987
|
+
debug("chart.line", "series is empty");
|
|
3988
|
+
return "";
|
|
3989
|
+
}
|
|
3919
3990
|
const maxVal = getMaxValue(series);
|
|
3920
|
-
if (maxVal === 0)
|
|
3991
|
+
if (maxVal === 0) {
|
|
3992
|
+
debug("chart.line", "max value is 0");
|
|
3993
|
+
return "";
|
|
3994
|
+
}
|
|
3921
3995
|
const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
3922
|
-
if (catCount === 0)
|
|
3996
|
+
if (catCount === 0) {
|
|
3997
|
+
debug("chart.line", "category count is 0");
|
|
3998
|
+
return "";
|
|
3999
|
+
}
|
|
3923
4000
|
parts.push(
|
|
3924
4001
|
`<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
3925
4002
|
);
|
|
@@ -3955,11 +4032,20 @@ function renderLineChart(chart, x, y, w, h) {
|
|
|
3955
4032
|
function renderAreaChart(chart, x, y, w, h) {
|
|
3956
4033
|
const parts = [];
|
|
3957
4034
|
const { series, categories } = chart;
|
|
3958
|
-
if (series.length === 0)
|
|
4035
|
+
if (series.length === 0) {
|
|
4036
|
+
debug("chart.area", "series is empty");
|
|
4037
|
+
return "";
|
|
4038
|
+
}
|
|
3959
4039
|
const maxVal = getMaxValue(series);
|
|
3960
|
-
if (maxVal === 0)
|
|
4040
|
+
if (maxVal === 0) {
|
|
4041
|
+
debug("chart.area", "max value is 0");
|
|
4042
|
+
return "";
|
|
4043
|
+
}
|
|
3961
4044
|
const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
3962
|
-
if (catCount === 0)
|
|
4045
|
+
if (catCount === 0) {
|
|
4046
|
+
debug("chart.area", "category count is 0");
|
|
4047
|
+
return "";
|
|
4048
|
+
}
|
|
3963
4049
|
parts.push(
|
|
3964
4050
|
`<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
3965
4051
|
);
|
|
@@ -3996,9 +4082,15 @@ function renderAreaChart(chart, x, y, w, h) {
|
|
|
3996
4082
|
function renderPieChart(chart, x, y, w, h) {
|
|
3997
4083
|
const parts = [];
|
|
3998
4084
|
const series = chart.series[0];
|
|
3999
|
-
if (!series || series.values.length === 0)
|
|
4085
|
+
if (!series || series.values.length === 0) {
|
|
4086
|
+
debug("chart.pie", "series is empty or has no values");
|
|
4087
|
+
return "";
|
|
4088
|
+
}
|
|
4000
4089
|
const total = series.values.reduce((sum, v) => sum + v, 0);
|
|
4001
|
-
if (total === 0)
|
|
4090
|
+
if (total === 0) {
|
|
4091
|
+
debug("chart.pie", "total value is 0");
|
|
4092
|
+
return "";
|
|
4093
|
+
}
|
|
4002
4094
|
const cx = x + w / 2;
|
|
4003
4095
|
const cy = y + h / 2;
|
|
4004
4096
|
const r = Math.min(w, h) / 2 * 0.85;
|
|
@@ -4028,9 +4120,15 @@ function renderPieChart(chart, x, y, w, h) {
|
|
|
4028
4120
|
function renderDoughnutChart(chart, x, y, w, h) {
|
|
4029
4121
|
const parts = [];
|
|
4030
4122
|
const series = chart.series[0];
|
|
4031
|
-
if (!series || series.values.length === 0)
|
|
4123
|
+
if (!series || series.values.length === 0) {
|
|
4124
|
+
debug("chart.doughnut", "series is empty or has no values");
|
|
4125
|
+
return "";
|
|
4126
|
+
}
|
|
4032
4127
|
const total = series.values.reduce((sum, v) => sum + v, 0);
|
|
4033
|
-
if (total === 0)
|
|
4128
|
+
if (total === 0) {
|
|
4129
|
+
debug("chart.doughnut", "total value is 0");
|
|
4130
|
+
return "";
|
|
4131
|
+
}
|
|
4034
4132
|
const cx = x + w / 2;
|
|
4035
4133
|
const cy = y + h / 2;
|
|
4036
4134
|
const outerR = Math.min(w, h) / 2 * 0.85;
|
|
@@ -4069,7 +4167,10 @@ function renderDoughnutChart(chart, x, y, w, h) {
|
|
|
4069
4167
|
function renderScatterChart(chart, x, y, w, h) {
|
|
4070
4168
|
const parts = [];
|
|
4071
4169
|
const { series } = chart;
|
|
4072
|
-
if (series.length === 0)
|
|
4170
|
+
if (series.length === 0) {
|
|
4171
|
+
debug("chart.scatter", "series is empty");
|
|
4172
|
+
return "";
|
|
4173
|
+
}
|
|
4073
4174
|
let maxX = 0;
|
|
4074
4175
|
let maxY = 0;
|
|
4075
4176
|
for (const s of series) {
|
|
@@ -4101,7 +4202,10 @@ function renderScatterChart(chart, x, y, w, h) {
|
|
|
4101
4202
|
function renderBubbleChart(chart, x, y, w, h) {
|
|
4102
4203
|
const parts = [];
|
|
4103
4204
|
const { series } = chart;
|
|
4104
|
-
if (series.length === 0)
|
|
4205
|
+
if (series.length === 0) {
|
|
4206
|
+
debug("chart.bubble", "series is empty");
|
|
4207
|
+
return "";
|
|
4208
|
+
}
|
|
4105
4209
|
let maxX = 0;
|
|
4106
4210
|
let maxY = 0;
|
|
4107
4211
|
let maxBubble = 0;
|
|
@@ -4143,11 +4247,20 @@ function renderBubbleChart(chart, x, y, w, h) {
|
|
|
4143
4247
|
function renderRadarChart(chart, x, y, w, h) {
|
|
4144
4248
|
const parts = [];
|
|
4145
4249
|
const { series, categories } = chart;
|
|
4146
|
-
if (series.length === 0)
|
|
4250
|
+
if (series.length === 0) {
|
|
4251
|
+
debug("chart.radar", "series is empty");
|
|
4252
|
+
return "";
|
|
4253
|
+
}
|
|
4147
4254
|
const maxVal = getMaxValue(series);
|
|
4148
|
-
if (maxVal === 0)
|
|
4255
|
+
if (maxVal === 0) {
|
|
4256
|
+
debug("chart.radar", "max value is 0");
|
|
4257
|
+
return "";
|
|
4258
|
+
}
|
|
4149
4259
|
const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
4150
|
-
if (catCount === 0)
|
|
4260
|
+
if (catCount === 0) {
|
|
4261
|
+
debug("chart.radar", "category count is 0");
|
|
4262
|
+
return "";
|
|
4263
|
+
}
|
|
4151
4264
|
const cx = x + w / 2;
|
|
4152
4265
|
const cy = y + h / 2;
|
|
4153
4266
|
const radius = Math.min(w, h) / 2 * 0.85;
|
|
@@ -4211,12 +4324,18 @@ function renderRadarChart(chart, x, y, w, h) {
|
|
|
4211
4324
|
function renderStockChart(chart, x, y, w, h) {
|
|
4212
4325
|
const parts = [];
|
|
4213
4326
|
const { series, categories } = chart;
|
|
4214
|
-
if (series.length < 3)
|
|
4327
|
+
if (series.length < 3) {
|
|
4328
|
+
debug("chart.stock", `insufficient series count: ${series.length} (need at least 3)`);
|
|
4329
|
+
return "";
|
|
4330
|
+
}
|
|
4215
4331
|
const highSeries = series[0];
|
|
4216
4332
|
const lowSeries = series[1];
|
|
4217
4333
|
const closeSeries = series[2];
|
|
4218
4334
|
const catCount = categories.length || highSeries.values.length;
|
|
4219
|
-
if (catCount === 0)
|
|
4335
|
+
if (catCount === 0) {
|
|
4336
|
+
debug("chart.stock", "category count is 0");
|
|
4337
|
+
return "";
|
|
4338
|
+
}
|
|
4220
4339
|
let maxVal = 0;
|
|
4221
4340
|
let minVal = Infinity;
|
|
4222
4341
|
for (const s of [highSeries, lowSeries, closeSeries]) {
|
|
@@ -4225,7 +4344,10 @@ function renderStockChart(chart, x, y, w, h) {
|
|
|
4225
4344
|
minVal = Math.min(minVal, v);
|
|
4226
4345
|
}
|
|
4227
4346
|
}
|
|
4228
|
-
if (maxVal === minVal)
|
|
4347
|
+
if (maxVal === minVal) {
|
|
4348
|
+
debug("chart.stock", "max equals min value");
|
|
4349
|
+
return "";
|
|
4350
|
+
}
|
|
4229
4351
|
parts.push(
|
|
4230
4352
|
`<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
4231
4353
|
);
|
|
@@ -4261,10 +4383,16 @@ function renderStockChart(chart, x, y, w, h) {
|
|
|
4261
4383
|
function renderSurfaceChart(chart, x, y, w, h) {
|
|
4262
4384
|
const parts = [];
|
|
4263
4385
|
const { series, categories } = chart;
|
|
4264
|
-
if (series.length === 0)
|
|
4386
|
+
if (series.length === 0) {
|
|
4387
|
+
debug("chart.surface", "series is empty");
|
|
4388
|
+
return "";
|
|
4389
|
+
}
|
|
4265
4390
|
const rows = series.length;
|
|
4266
4391
|
const cols = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
4267
|
-
if (cols === 0)
|
|
4392
|
+
if (cols === 0) {
|
|
4393
|
+
debug("chart.surface", "column count is 0");
|
|
4394
|
+
return "";
|
|
4395
|
+
}
|
|
4268
4396
|
let minVal = Infinity;
|
|
4269
4397
|
let maxVal = -Infinity;
|
|
4270
4398
|
for (const s of series) {
|
|
@@ -4337,9 +4465,15 @@ function heatmapColor(t) {
|
|
|
4337
4465
|
function renderOfPieChart(chart, x, y, w, h) {
|
|
4338
4466
|
const parts = [];
|
|
4339
4467
|
const series = chart.series[0];
|
|
4340
|
-
if (!series || series.values.length === 0)
|
|
4468
|
+
if (!series || series.values.length === 0) {
|
|
4469
|
+
debug("chart.ofPie", "series is empty or has no values");
|
|
4470
|
+
return "";
|
|
4471
|
+
}
|
|
4341
4472
|
const total = series.values.reduce((sum, v) => sum + v, 0);
|
|
4342
|
-
if (total === 0)
|
|
4473
|
+
if (total === 0) {
|
|
4474
|
+
debug("chart.ofPie", "total value is 0");
|
|
4475
|
+
return "";
|
|
4476
|
+
}
|
|
4343
4477
|
const splitPos = chart.splitPos ?? 2;
|
|
4344
4478
|
const secondPieSize = chart.secondPieSize ?? 75;
|
|
4345
4479
|
const isBarOfPie = chart.ofPieType === "bar";
|
|
@@ -4704,65 +4838,6 @@ async function svgToPng(svgString, options) {
|
|
|
4704
4838
|
return { png: result.data, width: result.info.width, height: result.info.height };
|
|
4705
4839
|
}
|
|
4706
4840
|
|
|
4707
|
-
// src/warning-logger.ts
|
|
4708
|
-
var PREFIX = "[pptx-glimpse]";
|
|
4709
|
-
var currentLevel = "off";
|
|
4710
|
-
var entries = [];
|
|
4711
|
-
var featureCounts = /* @__PURE__ */ new Map();
|
|
4712
|
-
function initWarningLogger(level) {
|
|
4713
|
-
currentLevel = level;
|
|
4714
|
-
entries = [];
|
|
4715
|
-
featureCounts.clear();
|
|
4716
|
-
}
|
|
4717
|
-
function warn(feature, message, context) {
|
|
4718
|
-
if (currentLevel === "off") return;
|
|
4719
|
-
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
4720
|
-
const existing = featureCounts.get(feature);
|
|
4721
|
-
if (existing) {
|
|
4722
|
-
existing.count++;
|
|
4723
|
-
} else {
|
|
4724
|
-
featureCounts.set(feature, { message, count: 1 });
|
|
4725
|
-
}
|
|
4726
|
-
if (currentLevel === "debug") {
|
|
4727
|
-
const ctx = context ? ` (${context})` : "";
|
|
4728
|
-
console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
|
|
4729
|
-
}
|
|
4730
|
-
}
|
|
4731
|
-
function debug(feature, message, context) {
|
|
4732
|
-
if (currentLevel !== "debug") return;
|
|
4733
|
-
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
4734
|
-
const existing = featureCounts.get(feature);
|
|
4735
|
-
if (existing) {
|
|
4736
|
-
existing.count++;
|
|
4737
|
-
} else {
|
|
4738
|
-
featureCounts.set(feature, { message, count: 1 });
|
|
4739
|
-
}
|
|
4740
|
-
const ctx = context ? ` (${context})` : "";
|
|
4741
|
-
console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
|
|
4742
|
-
}
|
|
4743
|
-
function getWarningSummary() {
|
|
4744
|
-
const features = [];
|
|
4745
|
-
for (const [feature, { message, count }] of featureCounts) {
|
|
4746
|
-
features.push({ feature, message, count });
|
|
4747
|
-
}
|
|
4748
|
-
return { totalCount: entries.length, features };
|
|
4749
|
-
}
|
|
4750
|
-
function flushWarnings() {
|
|
4751
|
-
const summary = getWarningSummary();
|
|
4752
|
-
if (currentLevel !== "off" && summary.features.length > 0) {
|
|
4753
|
-
console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
|
|
4754
|
-
for (const { feature, count } of summary.features) {
|
|
4755
|
-
console.warn(` - ${feature}: ${count} occurrence(s)`);
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
entries = [];
|
|
4759
|
-
featureCounts.clear();
|
|
4760
|
-
return summary;
|
|
4761
|
-
}
|
|
4762
|
-
function getWarningEntries() {
|
|
4763
|
-
return entries;
|
|
4764
|
-
}
|
|
4765
|
-
|
|
4766
4841
|
// src/parser/pptx-reader.ts
|
|
4767
4842
|
import { unzipSync, strFromU8 } from "fflate";
|
|
4768
4843
|
function readPptx(input) {
|
|
@@ -6140,10 +6215,16 @@ function parseCustomGeometry(custGeom) {
|
|
|
6140
6215
|
for (const path of paths) {
|
|
6141
6216
|
const w = Number(path["@_w"] ?? 0);
|
|
6142
6217
|
const h = Number(path["@_h"] ?? 0);
|
|
6143
|
-
if (w === 0 && h === 0)
|
|
6218
|
+
if (w === 0 && h === 0) {
|
|
6219
|
+
debug("custGeom.path", "path skipped: width and height are both 0");
|
|
6220
|
+
continue;
|
|
6221
|
+
}
|
|
6144
6222
|
const vars = evaluateGuides(avGd, gdGd, w, h);
|
|
6145
6223
|
const commands = buildPathCommands(path, vars);
|
|
6146
|
-
if (!commands)
|
|
6224
|
+
if (!commands) {
|
|
6225
|
+
debug("custGeom.path", "path skipped: failed to build path commands");
|
|
6226
|
+
continue;
|
|
6227
|
+
}
|
|
6147
6228
|
result.push({ width: w, height: h, commands });
|
|
6148
6229
|
}
|
|
6149
6230
|
return result.length > 0 ? result : null;
|
|
@@ -6716,7 +6797,10 @@ function parseImage(pic, rels, slidePath, archive, colorResolver) {
|
|
|
6716
6797
|
if (!rel) return null;
|
|
6717
6798
|
const mediaPath = resolveRelationshipTarget(slidePath, rel.target);
|
|
6718
6799
|
const mediaData = archive.media.get(mediaPath);
|
|
6719
|
-
if (!mediaData)
|
|
6800
|
+
if (!mediaData) {
|
|
6801
|
+
debug("picture.media", `media file not found: ${mediaPath}`);
|
|
6802
|
+
return null;
|
|
6803
|
+
}
|
|
6720
6804
|
const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "png";
|
|
6721
6805
|
const mimeMap = {
|
|
6722
6806
|
png: "image/png",
|
|
@@ -6915,9 +6999,15 @@ function parseSmartArt(graphicData, transform, rels, slidePath, archive, colorRe
|
|
|
6915
6999
|
}
|
|
6916
7000
|
}
|
|
6917
7001
|
}
|
|
6918
|
-
if (!drawingPath)
|
|
7002
|
+
if (!drawingPath) {
|
|
7003
|
+
debug("smartArt.drawing", "diagramDrawing relationship not found");
|
|
7004
|
+
return null;
|
|
7005
|
+
}
|
|
6919
7006
|
const drawingXml = archive.files.get(drawingPath);
|
|
6920
|
-
if (!drawingXml)
|
|
7007
|
+
if (!drawingXml) {
|
|
7008
|
+
debug("smartArt.drawing", `drawing XML not found in archive: ${drawingPath}`);
|
|
7009
|
+
return null;
|
|
7010
|
+
}
|
|
6921
7011
|
const parsed = parseXml(drawingXml);
|
|
6922
7012
|
const drawing = parsed.drawing;
|
|
6923
7013
|
const spTree = drawing?.spTree;
|
|
@@ -7977,6 +8067,108 @@ function collectFontFilePaths(additionalDirs) {
|
|
|
7977
8067
|
return result;
|
|
7978
8068
|
}
|
|
7979
8069
|
|
|
8070
|
+
// src/font/ttc-parser.ts
|
|
8071
|
+
var TTC_TAG = 1953784678;
|
|
8072
|
+
function isTtcBuffer(data) {
|
|
8073
|
+
const view = toDataView(data);
|
|
8074
|
+
if (view.byteLength < 4) return false;
|
|
8075
|
+
return view.getUint32(0) === TTC_TAG;
|
|
8076
|
+
}
|
|
8077
|
+
function extractTtcFonts(data) {
|
|
8078
|
+
const view = toDataView(data);
|
|
8079
|
+
const bytes = toUint8Array(data);
|
|
8080
|
+
if (view.byteLength < 12) return [];
|
|
8081
|
+
if (view.getUint32(0) !== TTC_TAG) return [];
|
|
8082
|
+
const numFonts = view.getUint32(8);
|
|
8083
|
+
if (numFonts === 0) return [];
|
|
8084
|
+
const headerEnd = 12 + numFonts * 4;
|
|
8085
|
+
if (view.byteLength < headerEnd) return [];
|
|
8086
|
+
const results = [];
|
|
8087
|
+
for (let i = 0; i < numFonts; i++) {
|
|
8088
|
+
try {
|
|
8089
|
+
const fontOffset = view.getUint32(12 + i * 4);
|
|
8090
|
+
const extracted = extractSingleFont(view, bytes, fontOffset);
|
|
8091
|
+
if (extracted) results.push(extracted);
|
|
8092
|
+
} catch {
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
return results;
|
|
8096
|
+
}
|
|
8097
|
+
function extractSingleFont(view, bytes, fontOffset) {
|
|
8098
|
+
if (fontOffset + 12 > view.byteLength) return null;
|
|
8099
|
+
const sfVersion = view.getUint32(fontOffset);
|
|
8100
|
+
const numTables = view.getUint16(fontOffset + 4);
|
|
8101
|
+
if (numTables === 0) return null;
|
|
8102
|
+
const tableRecordsStart = fontOffset + 12;
|
|
8103
|
+
const tableRecordsEnd = tableRecordsStart + numTables * 16;
|
|
8104
|
+
if (tableRecordsEnd > view.byteLength) return null;
|
|
8105
|
+
const tables = [];
|
|
8106
|
+
for (let i = 0; i < numTables; i++) {
|
|
8107
|
+
const recOffset = tableRecordsStart + i * 16;
|
|
8108
|
+
const tableOffset = view.getUint32(recOffset + 8);
|
|
8109
|
+
const tableLength = view.getUint32(recOffset + 12);
|
|
8110
|
+
if (tableOffset > view.byteLength || tableLength > view.byteLength - tableOffset) {
|
|
8111
|
+
return null;
|
|
8112
|
+
}
|
|
8113
|
+
tables.push({
|
|
8114
|
+
tag: view.getUint32(recOffset),
|
|
8115
|
+
checkSum: view.getUint32(recOffset + 4),
|
|
8116
|
+
offset: tableOffset,
|
|
8117
|
+
length: tableLength
|
|
8118
|
+
});
|
|
8119
|
+
}
|
|
8120
|
+
const headerSize = 12 + numTables * 16;
|
|
8121
|
+
let dataSize = 0;
|
|
8122
|
+
for (const table of tables) {
|
|
8123
|
+
dataSize += alignTo4(table.length);
|
|
8124
|
+
}
|
|
8125
|
+
const totalSize = headerSize + dataSize;
|
|
8126
|
+
const output = new ArrayBuffer(totalSize);
|
|
8127
|
+
const outView = new DataView(output);
|
|
8128
|
+
const outBytes = new Uint8Array(output);
|
|
8129
|
+
outView.setUint32(0, sfVersion);
|
|
8130
|
+
outView.setUint16(4, numTables);
|
|
8131
|
+
const { searchRange, entrySelector, rangeShift } = calcOffsetTableFields(numTables);
|
|
8132
|
+
outView.setUint16(6, searchRange);
|
|
8133
|
+
outView.setUint16(8, entrySelector);
|
|
8134
|
+
outView.setUint16(10, rangeShift);
|
|
8135
|
+
let currentDataOffset = headerSize;
|
|
8136
|
+
for (let i = 0; i < numTables; i++) {
|
|
8137
|
+
const table = tables[i];
|
|
8138
|
+
const recOffset = 12 + i * 16;
|
|
8139
|
+
outView.setUint32(recOffset, table.tag);
|
|
8140
|
+
outView.setUint32(recOffset + 4, table.checkSum);
|
|
8141
|
+
outView.setUint32(recOffset + 8, currentDataOffset);
|
|
8142
|
+
outView.setUint32(recOffset + 12, table.length);
|
|
8143
|
+
outBytes.set(bytes.subarray(table.offset, table.offset + table.length), currentDataOffset);
|
|
8144
|
+
currentDataOffset += alignTo4(table.length);
|
|
8145
|
+
}
|
|
8146
|
+
return output;
|
|
8147
|
+
}
|
|
8148
|
+
function alignTo4(n) {
|
|
8149
|
+
return n + 3 & ~3;
|
|
8150
|
+
}
|
|
8151
|
+
function calcOffsetTableFields(numTables) {
|
|
8152
|
+
let searchRange = 16;
|
|
8153
|
+
let entrySelector = 0;
|
|
8154
|
+
while (searchRange * 2 <= numTables * 16) {
|
|
8155
|
+
searchRange *= 2;
|
|
8156
|
+
entrySelector++;
|
|
8157
|
+
}
|
|
8158
|
+
const rangeShift = numTables * 16 - searchRange;
|
|
8159
|
+
return { searchRange, entrySelector, rangeShift };
|
|
8160
|
+
}
|
|
8161
|
+
function toDataView(data) {
|
|
8162
|
+
if (data instanceof Uint8Array) {
|
|
8163
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
8164
|
+
}
|
|
8165
|
+
return new DataView(data);
|
|
8166
|
+
}
|
|
8167
|
+
function toUint8Array(data) {
|
|
8168
|
+
if (data instanceof Uint8Array) return data;
|
|
8169
|
+
return new Uint8Array(data);
|
|
8170
|
+
}
|
|
8171
|
+
|
|
7980
8172
|
// src/font/opentype-helpers.ts
|
|
7981
8173
|
async function tryLoadOpentype() {
|
|
7982
8174
|
try {
|
|
@@ -8003,6 +8195,63 @@ function toArrayBuffer(data) {
|
|
|
8003
8195
|
if (data instanceof ArrayBuffer) return data;
|
|
8004
8196
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
8005
8197
|
}
|
|
8198
|
+
function parseFontBuffer(arrayBuffer, opentype) {
|
|
8199
|
+
if (isTtcBuffer(arrayBuffer)) {
|
|
8200
|
+
const results = [];
|
|
8201
|
+
for (const buf of extractTtcFonts(arrayBuffer)) {
|
|
8202
|
+
try {
|
|
8203
|
+
results.push(opentype.parse(buf));
|
|
8204
|
+
} catch {
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
return results;
|
|
8208
|
+
}
|
|
8209
|
+
return [opentype.parse(arrayBuffer)];
|
|
8210
|
+
}
|
|
8211
|
+
async function createOpentypeTextMeasurerFromBuffers(fontBuffers, fontMapping) {
|
|
8212
|
+
const setup = await createOpentypeSetupFromBuffers(fontBuffers, fontMapping);
|
|
8213
|
+
return setup?.measurer ?? null;
|
|
8214
|
+
}
|
|
8215
|
+
async function createOpentypeSetupFromBuffers(fontBuffers, fontMapping) {
|
|
8216
|
+
if (fontBuffers.length === 0) return null;
|
|
8217
|
+
const opentype = await tryLoadOpentype();
|
|
8218
|
+
if (!opentype) return null;
|
|
8219
|
+
const mapping = createFontMapping(fontMapping);
|
|
8220
|
+
const reverseMap = buildReverseMapping(mapping);
|
|
8221
|
+
const measurerFonts = /* @__PURE__ */ new Map();
|
|
8222
|
+
const resolverFonts = /* @__PURE__ */ new Map();
|
|
8223
|
+
let firstMeasurerFont = null;
|
|
8224
|
+
let firstResolverFont = null;
|
|
8225
|
+
for (const buffer of fontBuffers) {
|
|
8226
|
+
try {
|
|
8227
|
+
const arrayBuffer = toArrayBuffer(buffer.data);
|
|
8228
|
+
const isTtc = isTtcBuffer(arrayBuffer);
|
|
8229
|
+
const fonts = parseFontBuffer(arrayBuffer, opentype);
|
|
8230
|
+
for (const font of fonts) {
|
|
8231
|
+
if (!firstMeasurerFont) firstMeasurerFont = font;
|
|
8232
|
+
if (!firstResolverFont) firstResolverFont = font;
|
|
8233
|
+
if (isTtc) {
|
|
8234
|
+
const fontFamily = font.names.fontFamily;
|
|
8235
|
+
if (fontFamily) {
|
|
8236
|
+
for (const name of Object.values(fontFamily)) {
|
|
8237
|
+
registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
} else if (buffer.name) {
|
|
8241
|
+
registerFont(buffer.name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8242
|
+
}
|
|
8243
|
+
}
|
|
8244
|
+
} catch {
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
if (measurerFonts.size === 0 && !firstMeasurerFont) return null;
|
|
8248
|
+
const measurer = new OpentypeTextMeasurer(measurerFonts, firstMeasurerFont ?? void 0);
|
|
8249
|
+
const fontResolver = new DefaultTextPathFontResolver(
|
|
8250
|
+
resolverFonts,
|
|
8251
|
+
firstResolverFont ?? void 0
|
|
8252
|
+
);
|
|
8253
|
+
return { measurer, fontResolver };
|
|
8254
|
+
}
|
|
8006
8255
|
function registerFont(name, font, reverseMap, measurerFonts, resolverFonts) {
|
|
8007
8256
|
const fullFont = font;
|
|
8008
8257
|
if (!measurerFonts.has(name)) {
|
|
@@ -8034,13 +8283,15 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
|
|
|
8034
8283
|
try {
|
|
8035
8284
|
const data = await readFile(filePath);
|
|
8036
8285
|
const arrayBuffer = toArrayBuffer(data);
|
|
8037
|
-
const
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8286
|
+
const fonts = parseFontBuffer(arrayBuffer, opentype);
|
|
8287
|
+
for (const font of fonts) {
|
|
8288
|
+
if (!firstMeasurerFont) firstMeasurerFont = font;
|
|
8289
|
+
if (!firstResolverFont) firstResolverFont = font;
|
|
8290
|
+
const fontFamily = font.names.fontFamily;
|
|
8291
|
+
if (fontFamily) {
|
|
8292
|
+
for (const name of Object.values(fontFamily)) {
|
|
8293
|
+
registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8294
|
+
}
|
|
8044
8295
|
}
|
|
8045
8296
|
}
|
|
8046
8297
|
} catch {
|
|
@@ -8185,6 +8436,8 @@ export {
|
|
|
8185
8436
|
convertPptxToPng,
|
|
8186
8437
|
convertPptxToSvg,
|
|
8187
8438
|
createFontMapping,
|
|
8439
|
+
createOpentypeSetupFromBuffers,
|
|
8440
|
+
createOpentypeTextMeasurerFromBuffers,
|
|
8188
8441
|
getMappedFont,
|
|
8189
8442
|
getWarningEntries,
|
|
8190
8443
|
getWarningSummary
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pptx-glimpse",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"test:coverage": "vitest run --coverage",
|
|
25
25
|
"test:watch": "vitest",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
|
+
"knip": "knip",
|
|
27
28
|
"dev": "tsx scripts/dev-server.ts",
|
|
28
29
|
"render": "tsx scripts/test-render.ts",
|
|
29
30
|
"inspect": "tsx scripts/inspect-pptx.ts",
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
"eslint": "^9.0.0",
|
|
76
77
|
"eslint-config-prettier": "^10.0.0",
|
|
77
78
|
"jszip": "^3.10.1",
|
|
79
|
+
"knip": "^5.85.0",
|
|
78
80
|
"pixelmatch": "^7.1.0",
|
|
79
81
|
"prettier": "^3.0.0",
|
|
80
82
|
"tsup": "^8.0.0",
|