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 CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/pptx-glimpse)](https://www.npmjs.com/package/pptx-glimpse)
4
4
 
5
- A Node.js library to render PPTX as SVG / PNG.
5
+ A lightweight JavaScript library for rendering PowerPoint (.pptx) files as SVG or PNG in Node.js. No LibreOffice required.
6
6
 
7
- [Demo](https://pptx-glimpse.vercel.app/) | [npm](https://www.npmjs.com/package/pptx-glimpse)
7
+ **[Try the Demo](https://pptx-glimpse.vercel.app/)** | [npm](https://www.npmjs.com/package/pptx-glimpse)
8
+
9
+ ![pptx-glimpse demo](https://raw.githubusercontent.com/hirokisakabe/pptx-glimpse/main/docs/demo.gif)
8
10
 
9
11
  ## Motivation
10
12
 
package/dist/index.cjs CHANGED
@@ -35,6 +35,8 @@ __export(index_exports, {
35
35
  convertPptxToPng: () => convertPptxToPng,
36
36
  convertPptxToSvg: () => convertPptxToSvg,
37
37
  createFontMapping: () => createFontMapping,
38
+ createOpentypeSetupFromBuffers: () => createOpentypeSetupFromBuffers,
39
+ createOpentypeTextMeasurerFromBuffers: () => createOpentypeTextMeasurerFromBuffers,
38
40
  getMappedFont: () => getMappedFont,
39
41
  getWarningEntries: () => getWarningEntries,
40
42
  getWarningSummary: () => getWarningSummary
@@ -3813,6 +3815,65 @@ function renderPlaceholder(mimeType, w, h, transformAttr) {
3813
3815
  ].join("");
3814
3816
  }
3815
3817
 
3818
+ // src/warning-logger.ts
3819
+ var PREFIX = "[pptx-glimpse]";
3820
+ var currentLevel = "off";
3821
+ var entries = [];
3822
+ var featureCounts = /* @__PURE__ */ new Map();
3823
+ function initWarningLogger(level) {
3824
+ currentLevel = level;
3825
+ entries = [];
3826
+ featureCounts.clear();
3827
+ }
3828
+ function warn(feature, message, context) {
3829
+ if (currentLevel === "off") return;
3830
+ entries.push({ feature, message, ...context !== void 0 && { context } });
3831
+ const existing = featureCounts.get(feature);
3832
+ if (existing) {
3833
+ existing.count++;
3834
+ } else {
3835
+ featureCounts.set(feature, { message, count: 1 });
3836
+ }
3837
+ if (currentLevel === "debug") {
3838
+ const ctx = context ? ` (${context})` : "";
3839
+ console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
3840
+ }
3841
+ }
3842
+ function debug(feature, message, context) {
3843
+ if (currentLevel !== "debug") return;
3844
+ entries.push({ feature, message, ...context !== void 0 && { context } });
3845
+ const existing = featureCounts.get(feature);
3846
+ if (existing) {
3847
+ existing.count++;
3848
+ } else {
3849
+ featureCounts.set(feature, { message, count: 1 });
3850
+ }
3851
+ const ctx = context ? ` (${context})` : "";
3852
+ console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
3853
+ }
3854
+ function getWarningSummary() {
3855
+ const features = [];
3856
+ for (const [feature, { message, count }] of featureCounts) {
3857
+ features.push({ feature, message, count });
3858
+ }
3859
+ return { totalCount: entries.length, features };
3860
+ }
3861
+ function flushWarnings() {
3862
+ const summary = getWarningSummary();
3863
+ if (currentLevel !== "off" && summary.features.length > 0) {
3864
+ console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
3865
+ for (const { feature, count } of summary.features) {
3866
+ console.warn(` - ${feature}: ${count} occurrence(s)`);
3867
+ }
3868
+ }
3869
+ entries = [];
3870
+ featureCounts.clear();
3871
+ return summary;
3872
+ }
3873
+ function getWarningEntries() {
3874
+ return entries;
3875
+ }
3876
+
3816
3877
  // src/renderer/chart-renderer.ts
3817
3878
  var DEFAULT_SERIES_COLORS = [
3818
3879
  { hex: "#4472C4", alpha: 1 },
@@ -3894,11 +3955,20 @@ function renderChartTitle(title, chartWidth) {
3894
3955
  function renderBarChart(chart, x, y, w, h) {
3895
3956
  const parts = [];
3896
3957
  const { series, categories } = chart;
3897
- if (series.length === 0) return "";
3958
+ if (series.length === 0) {
3959
+ debug("chart.bar", "series is empty");
3960
+ return "";
3961
+ }
3898
3962
  const maxVal = getMaxValue(series);
3899
- if (maxVal === 0) return "";
3963
+ if (maxVal === 0) {
3964
+ debug("chart.bar", "max value is 0");
3965
+ return "";
3966
+ }
3900
3967
  const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
3901
- if (catCount === 0) return "";
3968
+ if (catCount === 0) {
3969
+ debug("chart.bar", "category count is 0");
3970
+ return "";
3971
+ }
3902
3972
  const isHorizontal = chart.barDirection === "bar";
3903
3973
  parts.push(
3904
3974
  `<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
@@ -3958,11 +4028,20 @@ function renderBarChart(chart, x, y, w, h) {
3958
4028
  function renderLineChart(chart, x, y, w, h) {
3959
4029
  const parts = [];
3960
4030
  const { series, categories } = chart;
3961
- if (series.length === 0) return "";
4031
+ if (series.length === 0) {
4032
+ debug("chart.line", "series is empty");
4033
+ return "";
4034
+ }
3962
4035
  const maxVal = getMaxValue(series);
3963
- if (maxVal === 0) return "";
4036
+ if (maxVal === 0) {
4037
+ debug("chart.line", "max value is 0");
4038
+ return "";
4039
+ }
3964
4040
  const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
3965
- if (catCount === 0) return "";
4041
+ if (catCount === 0) {
4042
+ debug("chart.line", "category count is 0");
4043
+ return "";
4044
+ }
3966
4045
  parts.push(
3967
4046
  `<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
3968
4047
  );
@@ -3998,11 +4077,20 @@ function renderLineChart(chart, x, y, w, h) {
3998
4077
  function renderAreaChart(chart, x, y, w, h) {
3999
4078
  const parts = [];
4000
4079
  const { series, categories } = chart;
4001
- if (series.length === 0) return "";
4080
+ if (series.length === 0) {
4081
+ debug("chart.area", "series is empty");
4082
+ return "";
4083
+ }
4002
4084
  const maxVal = getMaxValue(series);
4003
- if (maxVal === 0) return "";
4085
+ if (maxVal === 0) {
4086
+ debug("chart.area", "max value is 0");
4087
+ return "";
4088
+ }
4004
4089
  const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
4005
- if (catCount === 0) return "";
4090
+ if (catCount === 0) {
4091
+ debug("chart.area", "category count is 0");
4092
+ return "";
4093
+ }
4006
4094
  parts.push(
4007
4095
  `<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4008
4096
  );
@@ -4039,9 +4127,15 @@ function renderAreaChart(chart, x, y, w, h) {
4039
4127
  function renderPieChart(chart, x, y, w, h) {
4040
4128
  const parts = [];
4041
4129
  const series = chart.series[0];
4042
- if (!series || series.values.length === 0) return "";
4130
+ if (!series || series.values.length === 0) {
4131
+ debug("chart.pie", "series is empty or has no values");
4132
+ return "";
4133
+ }
4043
4134
  const total = series.values.reduce((sum, v) => sum + v, 0);
4044
- if (total === 0) return "";
4135
+ if (total === 0) {
4136
+ debug("chart.pie", "total value is 0");
4137
+ return "";
4138
+ }
4045
4139
  const cx = x + w / 2;
4046
4140
  const cy = y + h / 2;
4047
4141
  const r = Math.min(w, h) / 2 * 0.85;
@@ -4071,9 +4165,15 @@ function renderPieChart(chart, x, y, w, h) {
4071
4165
  function renderDoughnutChart(chart, x, y, w, h) {
4072
4166
  const parts = [];
4073
4167
  const series = chart.series[0];
4074
- if (!series || series.values.length === 0) return "";
4168
+ if (!series || series.values.length === 0) {
4169
+ debug("chart.doughnut", "series is empty or has no values");
4170
+ return "";
4171
+ }
4075
4172
  const total = series.values.reduce((sum, v) => sum + v, 0);
4076
- if (total === 0) return "";
4173
+ if (total === 0) {
4174
+ debug("chart.doughnut", "total value is 0");
4175
+ return "";
4176
+ }
4077
4177
  const cx = x + w / 2;
4078
4178
  const cy = y + h / 2;
4079
4179
  const outerR = Math.min(w, h) / 2 * 0.85;
@@ -4112,7 +4212,10 @@ function renderDoughnutChart(chart, x, y, w, h) {
4112
4212
  function renderScatterChart(chart, x, y, w, h) {
4113
4213
  const parts = [];
4114
4214
  const { series } = chart;
4115
- if (series.length === 0) return "";
4215
+ if (series.length === 0) {
4216
+ debug("chart.scatter", "series is empty");
4217
+ return "";
4218
+ }
4116
4219
  let maxX = 0;
4117
4220
  let maxY = 0;
4118
4221
  for (const s of series) {
@@ -4144,7 +4247,10 @@ function renderScatterChart(chart, x, y, w, h) {
4144
4247
  function renderBubbleChart(chart, x, y, w, h) {
4145
4248
  const parts = [];
4146
4249
  const { series } = chart;
4147
- if (series.length === 0) return "";
4250
+ if (series.length === 0) {
4251
+ debug("chart.bubble", "series is empty");
4252
+ return "";
4253
+ }
4148
4254
  let maxX = 0;
4149
4255
  let maxY = 0;
4150
4256
  let maxBubble = 0;
@@ -4186,11 +4292,20 @@ function renderBubbleChart(chart, x, y, w, h) {
4186
4292
  function renderRadarChart(chart, x, y, w, h) {
4187
4293
  const parts = [];
4188
4294
  const { series, categories } = chart;
4189
- if (series.length === 0) return "";
4295
+ if (series.length === 0) {
4296
+ debug("chart.radar", "series is empty");
4297
+ return "";
4298
+ }
4190
4299
  const maxVal = getMaxValue(series);
4191
- if (maxVal === 0) return "";
4300
+ if (maxVal === 0) {
4301
+ debug("chart.radar", "max value is 0");
4302
+ return "";
4303
+ }
4192
4304
  const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
4193
- if (catCount === 0) return "";
4305
+ if (catCount === 0) {
4306
+ debug("chart.radar", "category count is 0");
4307
+ return "";
4308
+ }
4194
4309
  const cx = x + w / 2;
4195
4310
  const cy = y + h / 2;
4196
4311
  const radius = Math.min(w, h) / 2 * 0.85;
@@ -4254,12 +4369,18 @@ function renderRadarChart(chart, x, y, w, h) {
4254
4369
  function renderStockChart(chart, x, y, w, h) {
4255
4370
  const parts = [];
4256
4371
  const { series, categories } = chart;
4257
- if (series.length < 3) return "";
4372
+ if (series.length < 3) {
4373
+ debug("chart.stock", `insufficient series count: ${series.length} (need at least 3)`);
4374
+ return "";
4375
+ }
4258
4376
  const highSeries = series[0];
4259
4377
  const lowSeries = series[1];
4260
4378
  const closeSeries = series[2];
4261
4379
  const catCount = categories.length || highSeries.values.length;
4262
- if (catCount === 0) return "";
4380
+ if (catCount === 0) {
4381
+ debug("chart.stock", "category count is 0");
4382
+ return "";
4383
+ }
4263
4384
  let maxVal = 0;
4264
4385
  let minVal = Infinity;
4265
4386
  for (const s of [highSeries, lowSeries, closeSeries]) {
@@ -4268,7 +4389,10 @@ function renderStockChart(chart, x, y, w, h) {
4268
4389
  minVal = Math.min(minVal, v);
4269
4390
  }
4270
4391
  }
4271
- if (maxVal === minVal) return "";
4392
+ if (maxVal === minVal) {
4393
+ debug("chart.stock", "max equals min value");
4394
+ return "";
4395
+ }
4272
4396
  parts.push(
4273
4397
  `<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4274
4398
  );
@@ -4304,10 +4428,16 @@ function renderStockChart(chart, x, y, w, h) {
4304
4428
  function renderSurfaceChart(chart, x, y, w, h) {
4305
4429
  const parts = [];
4306
4430
  const { series, categories } = chart;
4307
- if (series.length === 0) return "";
4431
+ if (series.length === 0) {
4432
+ debug("chart.surface", "series is empty");
4433
+ return "";
4434
+ }
4308
4435
  const rows = series.length;
4309
4436
  const cols = categories.length || Math.max(...series.map((s) => s.values.length));
4310
- if (cols === 0) return "";
4437
+ if (cols === 0) {
4438
+ debug("chart.surface", "column count is 0");
4439
+ return "";
4440
+ }
4311
4441
  let minVal = Infinity;
4312
4442
  let maxVal = -Infinity;
4313
4443
  for (const s of series) {
@@ -4380,9 +4510,15 @@ function heatmapColor(t) {
4380
4510
  function renderOfPieChart(chart, x, y, w, h) {
4381
4511
  const parts = [];
4382
4512
  const series = chart.series[0];
4383
- if (!series || series.values.length === 0) return "";
4513
+ if (!series || series.values.length === 0) {
4514
+ debug("chart.ofPie", "series is empty or has no values");
4515
+ return "";
4516
+ }
4384
4517
  const total = series.values.reduce((sum, v) => sum + v, 0);
4385
- if (total === 0) return "";
4518
+ if (total === 0) {
4519
+ debug("chart.ofPie", "total value is 0");
4520
+ return "";
4521
+ }
4386
4522
  const splitPos = chart.splitPos ?? 2;
4387
4523
  const secondPieSize = chart.secondPieSize ?? 75;
4388
4524
  const isBarOfPie = chart.ofPieType === "bar";
@@ -4747,65 +4883,6 @@ async function svgToPng(svgString, options) {
4747
4883
  return { png: result.data, width: result.info.width, height: result.info.height };
4748
4884
  }
4749
4885
 
4750
- // src/warning-logger.ts
4751
- var PREFIX = "[pptx-glimpse]";
4752
- var currentLevel = "off";
4753
- var entries = [];
4754
- var featureCounts = /* @__PURE__ */ new Map();
4755
- function initWarningLogger(level) {
4756
- currentLevel = level;
4757
- entries = [];
4758
- featureCounts.clear();
4759
- }
4760
- function warn(feature, message, context) {
4761
- if (currentLevel === "off") return;
4762
- entries.push({ feature, message, ...context !== void 0 && { context } });
4763
- const existing = featureCounts.get(feature);
4764
- if (existing) {
4765
- existing.count++;
4766
- } else {
4767
- featureCounts.set(feature, { message, count: 1 });
4768
- }
4769
- if (currentLevel === "debug") {
4770
- const ctx = context ? ` (${context})` : "";
4771
- console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
4772
- }
4773
- }
4774
- function debug(feature, message, context) {
4775
- if (currentLevel !== "debug") return;
4776
- entries.push({ feature, message, ...context !== void 0 && { context } });
4777
- const existing = featureCounts.get(feature);
4778
- if (existing) {
4779
- existing.count++;
4780
- } else {
4781
- featureCounts.set(feature, { message, count: 1 });
4782
- }
4783
- const ctx = context ? ` (${context})` : "";
4784
- console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
4785
- }
4786
- function getWarningSummary() {
4787
- const features = [];
4788
- for (const [feature, { message, count }] of featureCounts) {
4789
- features.push({ feature, message, count });
4790
- }
4791
- return { totalCount: entries.length, features };
4792
- }
4793
- function flushWarnings() {
4794
- const summary = getWarningSummary();
4795
- if (currentLevel !== "off" && summary.features.length > 0) {
4796
- console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
4797
- for (const { feature, count } of summary.features) {
4798
- console.warn(` - ${feature}: ${count} occurrence(s)`);
4799
- }
4800
- }
4801
- entries = [];
4802
- featureCounts.clear();
4803
- return summary;
4804
- }
4805
- function getWarningEntries() {
4806
- return entries;
4807
- }
4808
-
4809
4886
  // src/parser/pptx-reader.ts
4810
4887
  var import_fflate = require("fflate");
4811
4888
  function readPptx(input) {
@@ -6183,10 +6260,16 @@ function parseCustomGeometry(custGeom) {
6183
6260
  for (const path of paths) {
6184
6261
  const w = Number(path["@_w"] ?? 0);
6185
6262
  const h = Number(path["@_h"] ?? 0);
6186
- if (w === 0 && h === 0) continue;
6263
+ if (w === 0 && h === 0) {
6264
+ debug("custGeom.path", "path skipped: width and height are both 0");
6265
+ continue;
6266
+ }
6187
6267
  const vars = evaluateGuides(avGd, gdGd, w, h);
6188
6268
  const commands = buildPathCommands(path, vars);
6189
- if (!commands) continue;
6269
+ if (!commands) {
6270
+ debug("custGeom.path", "path skipped: failed to build path commands");
6271
+ continue;
6272
+ }
6190
6273
  result.push({ width: w, height: h, commands });
6191
6274
  }
6192
6275
  return result.length > 0 ? result : null;
@@ -6759,7 +6842,10 @@ function parseImage(pic, rels, slidePath, archive, colorResolver) {
6759
6842
  if (!rel) return null;
6760
6843
  const mediaPath = resolveRelationshipTarget(slidePath, rel.target);
6761
6844
  const mediaData = archive.media.get(mediaPath);
6762
- if (!mediaData) return null;
6845
+ if (!mediaData) {
6846
+ debug("picture.media", `media file not found: ${mediaPath}`);
6847
+ return null;
6848
+ }
6763
6849
  const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "png";
6764
6850
  const mimeMap = {
6765
6851
  png: "image/png",
@@ -6958,9 +7044,15 @@ function parseSmartArt(graphicData, transform, rels, slidePath, archive, colorRe
6958
7044
  }
6959
7045
  }
6960
7046
  }
6961
- if (!drawingPath) return null;
7047
+ if (!drawingPath) {
7048
+ debug("smartArt.drawing", "diagramDrawing relationship not found");
7049
+ return null;
7050
+ }
6962
7051
  const drawingXml = archive.files.get(drawingPath);
6963
- if (!drawingXml) return null;
7052
+ if (!drawingXml) {
7053
+ debug("smartArt.drawing", `drawing XML not found in archive: ${drawingPath}`);
7054
+ return null;
7055
+ }
6964
7056
  const parsed = parseXml(drawingXml);
6965
7057
  const drawing = parsed.drawing;
6966
7058
  const spTree = drawing?.spTree;
@@ -8020,6 +8112,108 @@ function collectFontFilePaths(additionalDirs) {
8020
8112
  return result;
8021
8113
  }
8022
8114
 
8115
+ // src/font/ttc-parser.ts
8116
+ var TTC_TAG = 1953784678;
8117
+ function isTtcBuffer(data) {
8118
+ const view = toDataView(data);
8119
+ if (view.byteLength < 4) return false;
8120
+ return view.getUint32(0) === TTC_TAG;
8121
+ }
8122
+ function extractTtcFonts(data) {
8123
+ const view = toDataView(data);
8124
+ const bytes = toUint8Array(data);
8125
+ if (view.byteLength < 12) return [];
8126
+ if (view.getUint32(0) !== TTC_TAG) return [];
8127
+ const numFonts = view.getUint32(8);
8128
+ if (numFonts === 0) return [];
8129
+ const headerEnd = 12 + numFonts * 4;
8130
+ if (view.byteLength < headerEnd) return [];
8131
+ const results = [];
8132
+ for (let i = 0; i < numFonts; i++) {
8133
+ try {
8134
+ const fontOffset = view.getUint32(12 + i * 4);
8135
+ const extracted = extractSingleFont(view, bytes, fontOffset);
8136
+ if (extracted) results.push(extracted);
8137
+ } catch {
8138
+ }
8139
+ }
8140
+ return results;
8141
+ }
8142
+ function extractSingleFont(view, bytes, fontOffset) {
8143
+ if (fontOffset + 12 > view.byteLength) return null;
8144
+ const sfVersion = view.getUint32(fontOffset);
8145
+ const numTables = view.getUint16(fontOffset + 4);
8146
+ if (numTables === 0) return null;
8147
+ const tableRecordsStart = fontOffset + 12;
8148
+ const tableRecordsEnd = tableRecordsStart + numTables * 16;
8149
+ if (tableRecordsEnd > view.byteLength) return null;
8150
+ const tables = [];
8151
+ for (let i = 0; i < numTables; i++) {
8152
+ const recOffset = tableRecordsStart + i * 16;
8153
+ const tableOffset = view.getUint32(recOffset + 8);
8154
+ const tableLength = view.getUint32(recOffset + 12);
8155
+ if (tableOffset > view.byteLength || tableLength > view.byteLength - tableOffset) {
8156
+ return null;
8157
+ }
8158
+ tables.push({
8159
+ tag: view.getUint32(recOffset),
8160
+ checkSum: view.getUint32(recOffset + 4),
8161
+ offset: tableOffset,
8162
+ length: tableLength
8163
+ });
8164
+ }
8165
+ const headerSize = 12 + numTables * 16;
8166
+ let dataSize = 0;
8167
+ for (const table of tables) {
8168
+ dataSize += alignTo4(table.length);
8169
+ }
8170
+ const totalSize = headerSize + dataSize;
8171
+ const output = new ArrayBuffer(totalSize);
8172
+ const outView = new DataView(output);
8173
+ const outBytes = new Uint8Array(output);
8174
+ outView.setUint32(0, sfVersion);
8175
+ outView.setUint16(4, numTables);
8176
+ const { searchRange, entrySelector, rangeShift } = calcOffsetTableFields(numTables);
8177
+ outView.setUint16(6, searchRange);
8178
+ outView.setUint16(8, entrySelector);
8179
+ outView.setUint16(10, rangeShift);
8180
+ let currentDataOffset = headerSize;
8181
+ for (let i = 0; i < numTables; i++) {
8182
+ const table = tables[i];
8183
+ const recOffset = 12 + i * 16;
8184
+ outView.setUint32(recOffset, table.tag);
8185
+ outView.setUint32(recOffset + 4, table.checkSum);
8186
+ outView.setUint32(recOffset + 8, currentDataOffset);
8187
+ outView.setUint32(recOffset + 12, table.length);
8188
+ outBytes.set(bytes.subarray(table.offset, table.offset + table.length), currentDataOffset);
8189
+ currentDataOffset += alignTo4(table.length);
8190
+ }
8191
+ return output;
8192
+ }
8193
+ function alignTo4(n) {
8194
+ return n + 3 & ~3;
8195
+ }
8196
+ function calcOffsetTableFields(numTables) {
8197
+ let searchRange = 16;
8198
+ let entrySelector = 0;
8199
+ while (searchRange * 2 <= numTables * 16) {
8200
+ searchRange *= 2;
8201
+ entrySelector++;
8202
+ }
8203
+ const rangeShift = numTables * 16 - searchRange;
8204
+ return { searchRange, entrySelector, rangeShift };
8205
+ }
8206
+ function toDataView(data) {
8207
+ if (data instanceof Uint8Array) {
8208
+ return new DataView(data.buffer, data.byteOffset, data.byteLength);
8209
+ }
8210
+ return new DataView(data);
8211
+ }
8212
+ function toUint8Array(data) {
8213
+ if (data instanceof Uint8Array) return data;
8214
+ return new Uint8Array(data);
8215
+ }
8216
+
8023
8217
  // src/font/opentype-helpers.ts
8024
8218
  async function tryLoadOpentype() {
8025
8219
  try {
@@ -8046,6 +8240,63 @@ function toArrayBuffer(data) {
8046
8240
  if (data instanceof ArrayBuffer) return data;
8047
8241
  return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
8048
8242
  }
8243
+ function parseFontBuffer(arrayBuffer, opentype) {
8244
+ if (isTtcBuffer(arrayBuffer)) {
8245
+ const results = [];
8246
+ for (const buf of extractTtcFonts(arrayBuffer)) {
8247
+ try {
8248
+ results.push(opentype.parse(buf));
8249
+ } catch {
8250
+ }
8251
+ }
8252
+ return results;
8253
+ }
8254
+ return [opentype.parse(arrayBuffer)];
8255
+ }
8256
+ async function createOpentypeTextMeasurerFromBuffers(fontBuffers, fontMapping) {
8257
+ const setup = await createOpentypeSetupFromBuffers(fontBuffers, fontMapping);
8258
+ return setup?.measurer ?? null;
8259
+ }
8260
+ async function createOpentypeSetupFromBuffers(fontBuffers, fontMapping) {
8261
+ if (fontBuffers.length === 0) return null;
8262
+ const opentype = await tryLoadOpentype();
8263
+ if (!opentype) return null;
8264
+ const mapping = createFontMapping(fontMapping);
8265
+ const reverseMap = buildReverseMapping(mapping);
8266
+ const measurerFonts = /* @__PURE__ */ new Map();
8267
+ const resolverFonts = /* @__PURE__ */ new Map();
8268
+ let firstMeasurerFont = null;
8269
+ let firstResolverFont = null;
8270
+ for (const buffer of fontBuffers) {
8271
+ try {
8272
+ const arrayBuffer = toArrayBuffer(buffer.data);
8273
+ const isTtc = isTtcBuffer(arrayBuffer);
8274
+ const fonts = parseFontBuffer(arrayBuffer, opentype);
8275
+ for (const font of fonts) {
8276
+ if (!firstMeasurerFont) firstMeasurerFont = font;
8277
+ if (!firstResolverFont) firstResolverFont = font;
8278
+ if (isTtc) {
8279
+ const fontFamily = font.names.fontFamily;
8280
+ if (fontFamily) {
8281
+ for (const name of Object.values(fontFamily)) {
8282
+ registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
8283
+ }
8284
+ }
8285
+ } else if (buffer.name) {
8286
+ registerFont(buffer.name, font, reverseMap, measurerFonts, resolverFonts);
8287
+ }
8288
+ }
8289
+ } catch {
8290
+ }
8291
+ }
8292
+ if (measurerFonts.size === 0 && !firstMeasurerFont) return null;
8293
+ const measurer = new OpentypeTextMeasurer(measurerFonts, firstMeasurerFont ?? void 0);
8294
+ const fontResolver = new DefaultTextPathFontResolver(
8295
+ resolverFonts,
8296
+ firstResolverFont ?? void 0
8297
+ );
8298
+ return { measurer, fontResolver };
8299
+ }
8049
8300
  function registerFont(name, font, reverseMap, measurerFonts, resolverFonts) {
8050
8301
  const fullFont = font;
8051
8302
  if (!measurerFonts.has(name)) {
@@ -8077,13 +8328,15 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
8077
8328
  try {
8078
8329
  const data = await (0, import_promises.readFile)(filePath);
8079
8330
  const arrayBuffer = toArrayBuffer(data);
8080
- const font = opentype.parse(arrayBuffer);
8081
- if (!firstMeasurerFont) firstMeasurerFont = font;
8082
- if (!firstResolverFont) firstResolverFont = font;
8083
- const fontFamily = font.names.fontFamily;
8084
- if (fontFamily) {
8085
- for (const name of Object.values(fontFamily)) {
8086
- registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
8331
+ const fonts = parseFontBuffer(arrayBuffer, opentype);
8332
+ for (const font of fonts) {
8333
+ if (!firstMeasurerFont) firstMeasurerFont = font;
8334
+ if (!firstResolverFont) firstResolverFont = font;
8335
+ const fontFamily = font.names.fontFamily;
8336
+ if (fontFamily) {
8337
+ for (const name of Object.values(fontFamily)) {
8338
+ registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
8339
+ }
8087
8340
  }
8088
8341
  }
8089
8342
  } catch {
@@ -8229,6 +8482,8 @@ function collectFontsFromTextBody(textBody, fonts) {
8229
8482
  convertPptxToPng,
8230
8483
  convertPptxToSvg,
8231
8484
  createFontMapping,
8485
+ createOpentypeSetupFromBuffers,
8486
+ createOpentypeTextMeasurerFromBuffers,
8232
8487
  getMappedFont,
8233
8488
  getWarningEntries,
8234
8489
  getWarningSummary