pptx-react-viewer 1.1.4 → 1.1.5

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.
Files changed (28) hide show
  1. package/dist/index.js +512 -96
  2. package/dist/index.mjs +512 -97
  3. package/dist/viewer/index.js +530 -102
  4. package/dist/viewer/index.mjs +530 -103
  5. package/node_modules/emf-converter/dist/index.d.mts +2 -2
  6. package/node_modules/emf-converter/dist/index.d.ts +2 -2
  7. package/node_modules/emf-converter/dist/index.js +91 -33
  8. package/node_modules/emf-converter/dist/index.mjs +91 -33
  9. package/node_modules/emf-converter/package.json +1 -1
  10. package/node_modules/mtx-decompressor/dist/index.js +39 -9
  11. package/node_modules/mtx-decompressor/dist/index.mjs +39 -9
  12. package/node_modules/mtx-decompressor/package.json +1 -1
  13. package/node_modules/pptx-viewer-core/dist/cli/index.js +0 -0
  14. package/node_modules/pptx-viewer-core/dist/cli/index.mjs +0 -0
  15. package/node_modules/pptx-viewer-core/dist/converter/index.js +0 -0
  16. package/node_modules/pptx-viewer-core/dist/converter/index.mjs +0 -0
  17. package/node_modules/pptx-viewer-core/dist/index.d.mts +95 -11
  18. package/node_modules/pptx-viewer-core/dist/index.d.ts +95 -11
  19. package/node_modules/pptx-viewer-core/dist/index.js +795 -257
  20. package/node_modules/pptx-viewer-core/dist/index.mjs +791 -258
  21. package/node_modules/pptx-viewer-core/dist/{signature-inspection-status-BcJSdOvb.d.mts → signature-inspection-status-BCUpfCQh.d.mts} +13 -2
  22. package/node_modules/pptx-viewer-core/dist/{signature-inspection-status-BcJSdOvb.d.ts → signature-inspection-status-BCUpfCQh.d.ts} +13 -2
  23. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.mts +2 -2
  24. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.ts +2 -2
  25. package/node_modules/pptx-viewer-core/dist/signature-node/index.js +17 -3
  26. package/node_modules/pptx-viewer-core/dist/signature-node/index.mjs +16 -4
  27. package/node_modules/pptx-viewer-core/package.json +1 -1
  28. package/package.json +6 -4
@@ -48,7 +48,7 @@ interface EmfConvertOptions {
48
48
  * a numeric DPI scale (for backward compatibility). Default DPI scale is 2.
49
49
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
50
50
  */
51
- declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number): Promise<string | null>;
51
+ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
52
52
  /**
53
53
  * Converts a WMF (Windows Metafile) binary buffer to a PNG data-URL string.
54
54
  *
@@ -70,7 +70,7 @@ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, max
70
70
  * a numeric DPI scale. Default DPI scale is 2.
71
71
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
72
72
  */
73
- declare function convertWmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number): Promise<string | null>;
73
+ declare function convertWmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
74
74
 
75
75
  /**
76
76
  * Canvas creation, styling, string reading, stock objects, and export helpers.
@@ -48,7 +48,7 @@ interface EmfConvertOptions {
48
48
  * a numeric DPI scale (for backward compatibility). Default DPI scale is 2.
49
49
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
50
50
  */
51
- declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number): Promise<string | null>;
51
+ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
52
52
  /**
53
53
  * Converts a WMF (Windows Metafile) binary buffer to a PNG data-URL string.
54
54
  *
@@ -70,7 +70,7 @@ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, max
70
70
  * a numeric DPI scale. Default DPI scale is 2.
71
71
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
72
72
  */
73
- declare function convertWmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number): Promise<string | null>;
73
+ declare function convertWmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
74
74
 
75
75
  /**
76
76
  * Canvas creation, styling, string reading, stock objects, and export helpers.
@@ -74,6 +74,8 @@ function createTempCanvas(width, height) {
74
74
  if (width <= 0 || height <= 0) {
75
75
  return null;
76
76
  }
77
+ width = Math.max(1, Math.min(Math.floor(width), 8192));
78
+ height = Math.max(1, Math.min(Math.floor(height), 8192));
77
79
  if (typeof OffscreenCanvas !== "undefined") {
78
80
  const canvas = new OffscreenCanvas(width, height);
79
81
  const ctx = canvas.getContext("2d");
@@ -134,18 +136,34 @@ function applyFont(ctx, state) {
134
136
  ctx.font = `${italic}${weight}${size}px ${state.fontFamily}`;
135
137
  }
136
138
  function readUtf16LE(view, offset, charCount) {
137
- const chars = [];
138
- for (let i = 0; i < charCount; i++) {
139
- if (offset + i * 2 + 1 >= view.byteLength) {
140
- break;
141
- }
142
- const code = view.getUint16(offset + i * 2, true);
143
- if (code === 0) {
144
- break;
139
+ if (charCount <= 0) {
140
+ return "";
141
+ }
142
+ const maxBytes = view.byteLength - offset;
143
+ if (maxBytes <= 0) {
144
+ return "";
145
+ }
146
+ const usableChars = Math.min(charCount, Math.floor(maxBytes / 2));
147
+ if (usableChars <= 0) {
148
+ return "";
149
+ }
150
+ let decoded;
151
+ try {
152
+ const bytes = new Uint8Array(view.buffer, view.byteOffset + offset, usableChars * 2);
153
+ decoded = new TextDecoder("utf-16le").decode(bytes);
154
+ } catch {
155
+ const chars = [];
156
+ for (let i = 0; i < usableChars; i++) {
157
+ const code = view.getUint16(offset + i * 2, true);
158
+ if (code === 0) {
159
+ return chars.join("");
160
+ }
161
+ chars.push(String.fromCharCode(code));
145
162
  }
146
- chars.push(String.fromCharCode(code));
163
+ return chars.join("");
147
164
  }
148
- return chars.join("");
165
+ const nul = decoded.indexOf(String.fromCharCode(0));
166
+ return nul === -1 ? decoded : decoded.slice(0, nul);
149
167
  }
150
168
  function getStockObject(index) {
151
169
  switch (index) {
@@ -2135,7 +2153,12 @@ function applyClipCombineMode(rCtx, combineMode, opName, clipFn) {
2135
2153
  return true;
2136
2154
  }
2137
2155
  }
2138
- function traceRegionNodePath(ctx, node) {
2156
+ var MAX_REGION_TRACE_DEPTH = 64;
2157
+ function traceRegionNodePath(ctx, node, depth = 0) {
2158
+ if (depth > MAX_REGION_TRACE_DEPTH) {
2159
+ ctx.rect(0, 0, 0, 0);
2160
+ return;
2161
+ }
2139
2162
  switch (node.type) {
2140
2163
  case "rect":
2141
2164
  ctx.rect(node.x, node.y, node.width, node.height);
@@ -2150,7 +2173,7 @@ function traceRegionNodePath(ctx, node) {
2150
2173
  ctx.rect(0, 0, 0, 0);
2151
2174
  break;
2152
2175
  case "combine":
2153
- traceRegionNodePath(ctx, node.left);
2176
+ traceRegionNodePath(ctx, node.left, depth + 1);
2154
2177
  if (node.combineMode !== 0) {
2155
2178
  emfWarn(
2156
2179
  `traceRegionNodePath: combine mode ${node.combineMode} not fully supported, using left subtree only`
@@ -2161,7 +2184,7 @@ function traceRegionNodePath(ctx, node) {
2161
2184
  } catch {
2162
2185
  }
2163
2186
  ctx.beginPath();
2164
- traceRegionNodePath(ctx, node.right);
2187
+ traceRegionNodePath(ctx, node.right, depth + 1);
2165
2188
  }
2166
2189
  break;
2167
2190
  }
@@ -2959,19 +2982,23 @@ function handleEmfPlusObjectRecord(rCtx, recFlags, dataOff, recDataSize) {
2959
2982
  }
2960
2983
  }
2961
2984
  }
2962
- function parseRegionNode(view, off, endOff) {
2985
+ var MAX_REGION_NODE_DEPTH = 64;
2986
+ function parseRegionNode(view, off, endOff, depth = 0) {
2963
2987
  if (off + 4 > endOff) {
2964
2988
  return null;
2965
2989
  }
2990
+ if (depth > MAX_REGION_NODE_DEPTH) {
2991
+ return null;
2992
+ }
2966
2993
  const nodeType = view.getUint32(off, true);
2967
2994
  let cursor = off + 4;
2968
2995
  if (nodeType <= 4) {
2969
- const leftResult = parseRegionNode(view, cursor, endOff);
2996
+ const leftResult = parseRegionNode(view, cursor, endOff, depth + 1);
2970
2997
  if (!leftResult) {
2971
2998
  return null;
2972
2999
  }
2973
3000
  cursor += leftResult.bytesRead;
2974
- const rightResult = parseRegionNode(view, cursor, endOff);
3001
+ const rightResult = parseRegionNode(view, cursor, endOff, depth + 1);
2975
3002
  if (!rightResult) {
2976
3003
  return null;
2977
3004
  }
@@ -3361,18 +3388,22 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
3361
3388
  if (recDataSize >= 4) {
3362
3389
  const totalSize = view.getUint32(dataOff, true);
3363
3390
  const objectType = recFlags >> 8 & 127;
3364
- rCtx.continuationTotalSize = totalSize;
3365
- rCtx.continuationObjectId = objectId;
3366
- rCtx.continuationObjectType = objectType;
3367
- rCtx.continuationBuffer = new Uint8Array(totalSize);
3368
- const chunkSize = recDataSize - 4;
3369
- const chunk = new Uint8Array(
3370
- view.buffer,
3371
- view.byteOffset + dataOff + 4,
3372
- Math.min(chunkSize, totalSize)
3373
- );
3374
- rCtx.continuationBuffer.set(chunk, 0);
3375
- rCtx.continuationOffset = chunk.length;
3391
+ const MAX_CONTINUATION_BYTES = 64 * 1024 * 1024;
3392
+ const remainingEmfPlusBytes = view.byteLength - dataOff;
3393
+ if (!Number.isFinite(totalSize) || totalSize <= 0 || totalSize > MAX_CONTINUATION_BYTES || totalSize > remainingEmfPlusBytes || recDataSize - 4 < 0) ; else {
3394
+ rCtx.continuationTotalSize = totalSize;
3395
+ rCtx.continuationObjectId = objectId;
3396
+ rCtx.continuationObjectType = objectType;
3397
+ rCtx.continuationBuffer = new Uint8Array(totalSize);
3398
+ const chunkSize = recDataSize - 4;
3399
+ const chunk = new Uint8Array(
3400
+ view.buffer,
3401
+ view.byteOffset + dataOff + 4,
3402
+ Math.min(chunkSize, totalSize)
3403
+ );
3404
+ rCtx.continuationBuffer.set(chunk, 0);
3405
+ rCtx.continuationOffset = chunk.length;
3406
+ }
3376
3407
  }
3377
3408
  } else {
3378
3409
  const remaining = rCtx.continuationTotalSize - rCtx.continuationOffset;
@@ -4017,8 +4048,11 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
4017
4048
  }
4018
4049
 
4019
4050
  // src/emf-converter.ts
4020
- async function processDeferredImages(ctx, deferredImages) {
4021
- emfLog(`processDeferredImages: processing ${deferredImages.length} deferred images...`);
4051
+ var MAX_METAFILE_RECURSION = 3;
4052
+ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4053
+ emfLog(
4054
+ `processDeferredImages: processing ${deferredImages.length} deferred images (recursionDepth=${recursionDepth})...`
4055
+ );
4022
4056
  for (let idx = 0; idx < deferredImages.length; idx++) {
4023
4057
  const img = deferredImages[idx];
4024
4058
  emfLog(
@@ -4037,8 +4071,26 @@ async function processDeferredImages(ctx, deferredImages) {
4037
4071
  img.transform[5]
4038
4072
  );
4039
4073
  if (img.isMetafile) {
4074
+ if (recursionDepth >= MAX_METAFILE_RECURSION) {
4075
+ emfWarn(
4076
+ ` Deferred image [${idx}]: skipping embedded metafile \u2014 recursion depth ${recursionDepth} >= ${MAX_METAFILE_RECURSION}`
4077
+ );
4078
+ continue;
4079
+ }
4040
4080
  emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
4041
- const metafileDataUrl = await convertEmfToDataUrl(plainBuffer) ?? await convertWmfToDataUrl(plainBuffer);
4081
+ const metafileDataUrl = await convertEmfToDataUrl(
4082
+ plainBuffer,
4083
+ void 0,
4084
+ void 0,
4085
+ void 0,
4086
+ recursionDepth + 1
4087
+ ) ?? await convertWmfToDataUrl(
4088
+ plainBuffer,
4089
+ void 0,
4090
+ void 0,
4091
+ void 0,
4092
+ recursionDepth + 1
4093
+ );
4042
4094
  if (metafileDataUrl) {
4043
4095
  emfLog(
4044
4096
  ` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
@@ -4083,7 +4135,10 @@ async function processDeferredImages(ctx, deferredImages) {
4083
4135
  }
4084
4136
  ctx.setTransform(1, 0, 0, 1, 0, 0);
4085
4137
  }
4086
- async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale) {
4138
+ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4139
+ if (recursionDepth > MAX_METAFILE_RECURSION) {
4140
+ return null;
4141
+ }
4087
4142
  const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4088
4143
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4089
4144
  const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
@@ -4152,7 +4207,10 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4152
4207
  return null;
4153
4208
  }
4154
4209
  }
4155
- async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale) {
4210
+ async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4211
+ if (recursionDepth > MAX_METAFILE_RECURSION) {
4212
+ return null;
4213
+ }
4156
4214
  const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4157
4215
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4158
4216
  const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
@@ -72,6 +72,8 @@ function createTempCanvas(width, height) {
72
72
  if (width <= 0 || height <= 0) {
73
73
  return null;
74
74
  }
75
+ width = Math.max(1, Math.min(Math.floor(width), 8192));
76
+ height = Math.max(1, Math.min(Math.floor(height), 8192));
75
77
  if (typeof OffscreenCanvas !== "undefined") {
76
78
  const canvas = new OffscreenCanvas(width, height);
77
79
  const ctx = canvas.getContext("2d");
@@ -132,18 +134,34 @@ function applyFont(ctx, state) {
132
134
  ctx.font = `${italic}${weight}${size}px ${state.fontFamily}`;
133
135
  }
134
136
  function readUtf16LE(view, offset, charCount) {
135
- const chars = [];
136
- for (let i = 0; i < charCount; i++) {
137
- if (offset + i * 2 + 1 >= view.byteLength) {
138
- break;
139
- }
140
- const code = view.getUint16(offset + i * 2, true);
141
- if (code === 0) {
142
- break;
137
+ if (charCount <= 0) {
138
+ return "";
139
+ }
140
+ const maxBytes = view.byteLength - offset;
141
+ if (maxBytes <= 0) {
142
+ return "";
143
+ }
144
+ const usableChars = Math.min(charCount, Math.floor(maxBytes / 2));
145
+ if (usableChars <= 0) {
146
+ return "";
147
+ }
148
+ let decoded;
149
+ try {
150
+ const bytes = new Uint8Array(view.buffer, view.byteOffset + offset, usableChars * 2);
151
+ decoded = new TextDecoder("utf-16le").decode(bytes);
152
+ } catch {
153
+ const chars = [];
154
+ for (let i = 0; i < usableChars; i++) {
155
+ const code = view.getUint16(offset + i * 2, true);
156
+ if (code === 0) {
157
+ return chars.join("");
158
+ }
159
+ chars.push(String.fromCharCode(code));
143
160
  }
144
- chars.push(String.fromCharCode(code));
161
+ return chars.join("");
145
162
  }
146
- return chars.join("");
163
+ const nul = decoded.indexOf(String.fromCharCode(0));
164
+ return nul === -1 ? decoded : decoded.slice(0, nul);
147
165
  }
148
166
  function getStockObject(index) {
149
167
  switch (index) {
@@ -2133,7 +2151,12 @@ function applyClipCombineMode(rCtx, combineMode, opName, clipFn) {
2133
2151
  return true;
2134
2152
  }
2135
2153
  }
2136
- function traceRegionNodePath(ctx, node) {
2154
+ var MAX_REGION_TRACE_DEPTH = 64;
2155
+ function traceRegionNodePath(ctx, node, depth = 0) {
2156
+ if (depth > MAX_REGION_TRACE_DEPTH) {
2157
+ ctx.rect(0, 0, 0, 0);
2158
+ return;
2159
+ }
2137
2160
  switch (node.type) {
2138
2161
  case "rect":
2139
2162
  ctx.rect(node.x, node.y, node.width, node.height);
@@ -2148,7 +2171,7 @@ function traceRegionNodePath(ctx, node) {
2148
2171
  ctx.rect(0, 0, 0, 0);
2149
2172
  break;
2150
2173
  case "combine":
2151
- traceRegionNodePath(ctx, node.left);
2174
+ traceRegionNodePath(ctx, node.left, depth + 1);
2152
2175
  if (node.combineMode !== 0) {
2153
2176
  emfWarn(
2154
2177
  `traceRegionNodePath: combine mode ${node.combineMode} not fully supported, using left subtree only`
@@ -2159,7 +2182,7 @@ function traceRegionNodePath(ctx, node) {
2159
2182
  } catch {
2160
2183
  }
2161
2184
  ctx.beginPath();
2162
- traceRegionNodePath(ctx, node.right);
2185
+ traceRegionNodePath(ctx, node.right, depth + 1);
2163
2186
  }
2164
2187
  break;
2165
2188
  }
@@ -2957,19 +2980,23 @@ function handleEmfPlusObjectRecord(rCtx, recFlags, dataOff, recDataSize) {
2957
2980
  }
2958
2981
  }
2959
2982
  }
2960
- function parseRegionNode(view, off, endOff) {
2983
+ var MAX_REGION_NODE_DEPTH = 64;
2984
+ function parseRegionNode(view, off, endOff, depth = 0) {
2961
2985
  if (off + 4 > endOff) {
2962
2986
  return null;
2963
2987
  }
2988
+ if (depth > MAX_REGION_NODE_DEPTH) {
2989
+ return null;
2990
+ }
2964
2991
  const nodeType = view.getUint32(off, true);
2965
2992
  let cursor = off + 4;
2966
2993
  if (nodeType <= 4) {
2967
- const leftResult = parseRegionNode(view, cursor, endOff);
2994
+ const leftResult = parseRegionNode(view, cursor, endOff, depth + 1);
2968
2995
  if (!leftResult) {
2969
2996
  return null;
2970
2997
  }
2971
2998
  cursor += leftResult.bytesRead;
2972
- const rightResult = parseRegionNode(view, cursor, endOff);
2999
+ const rightResult = parseRegionNode(view, cursor, endOff, depth + 1);
2973
3000
  if (!rightResult) {
2974
3001
  return null;
2975
3002
  }
@@ -3359,18 +3386,22 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
3359
3386
  if (recDataSize >= 4) {
3360
3387
  const totalSize = view.getUint32(dataOff, true);
3361
3388
  const objectType = recFlags >> 8 & 127;
3362
- rCtx.continuationTotalSize = totalSize;
3363
- rCtx.continuationObjectId = objectId;
3364
- rCtx.continuationObjectType = objectType;
3365
- rCtx.continuationBuffer = new Uint8Array(totalSize);
3366
- const chunkSize = recDataSize - 4;
3367
- const chunk = new Uint8Array(
3368
- view.buffer,
3369
- view.byteOffset + dataOff + 4,
3370
- Math.min(chunkSize, totalSize)
3371
- );
3372
- rCtx.continuationBuffer.set(chunk, 0);
3373
- rCtx.continuationOffset = chunk.length;
3389
+ const MAX_CONTINUATION_BYTES = 64 * 1024 * 1024;
3390
+ const remainingEmfPlusBytes = view.byteLength - dataOff;
3391
+ if (!Number.isFinite(totalSize) || totalSize <= 0 || totalSize > MAX_CONTINUATION_BYTES || totalSize > remainingEmfPlusBytes || recDataSize - 4 < 0) ; else {
3392
+ rCtx.continuationTotalSize = totalSize;
3393
+ rCtx.continuationObjectId = objectId;
3394
+ rCtx.continuationObjectType = objectType;
3395
+ rCtx.continuationBuffer = new Uint8Array(totalSize);
3396
+ const chunkSize = recDataSize - 4;
3397
+ const chunk = new Uint8Array(
3398
+ view.buffer,
3399
+ view.byteOffset + dataOff + 4,
3400
+ Math.min(chunkSize, totalSize)
3401
+ );
3402
+ rCtx.continuationBuffer.set(chunk, 0);
3403
+ rCtx.continuationOffset = chunk.length;
3404
+ }
3374
3405
  }
3375
3406
  } else {
3376
3407
  const remaining = rCtx.continuationTotalSize - rCtx.continuationOffset;
@@ -4015,8 +4046,11 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
4015
4046
  }
4016
4047
 
4017
4048
  // src/emf-converter.ts
4018
- async function processDeferredImages(ctx, deferredImages) {
4019
- emfLog(`processDeferredImages: processing ${deferredImages.length} deferred images...`);
4049
+ var MAX_METAFILE_RECURSION = 3;
4050
+ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4051
+ emfLog(
4052
+ `processDeferredImages: processing ${deferredImages.length} deferred images (recursionDepth=${recursionDepth})...`
4053
+ );
4020
4054
  for (let idx = 0; idx < deferredImages.length; idx++) {
4021
4055
  const img = deferredImages[idx];
4022
4056
  emfLog(
@@ -4035,8 +4069,26 @@ async function processDeferredImages(ctx, deferredImages) {
4035
4069
  img.transform[5]
4036
4070
  );
4037
4071
  if (img.isMetafile) {
4072
+ if (recursionDepth >= MAX_METAFILE_RECURSION) {
4073
+ emfWarn(
4074
+ ` Deferred image [${idx}]: skipping embedded metafile \u2014 recursion depth ${recursionDepth} >= ${MAX_METAFILE_RECURSION}`
4075
+ );
4076
+ continue;
4077
+ }
4038
4078
  emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
4039
- const metafileDataUrl = await convertEmfToDataUrl(plainBuffer) ?? await convertWmfToDataUrl(plainBuffer);
4079
+ const metafileDataUrl = await convertEmfToDataUrl(
4080
+ plainBuffer,
4081
+ void 0,
4082
+ void 0,
4083
+ void 0,
4084
+ recursionDepth + 1
4085
+ ) ?? await convertWmfToDataUrl(
4086
+ plainBuffer,
4087
+ void 0,
4088
+ void 0,
4089
+ void 0,
4090
+ recursionDepth + 1
4091
+ );
4040
4092
  if (metafileDataUrl) {
4041
4093
  emfLog(
4042
4094
  ` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
@@ -4081,7 +4133,10 @@ async function processDeferredImages(ctx, deferredImages) {
4081
4133
  }
4082
4134
  ctx.setTransform(1, 0, 0, 1, 0, 0);
4083
4135
  }
4084
- async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale) {
4136
+ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4137
+ if (recursionDepth > MAX_METAFILE_RECURSION) {
4138
+ return null;
4139
+ }
4085
4140
  const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4086
4141
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4087
4142
  const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
@@ -4150,7 +4205,10 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4150
4205
  return null;
4151
4206
  }
4152
4207
  }
4153
- async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale) {
4208
+ async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4209
+ if (recursionDepth > MAX_METAFILE_RECURSION) {
4210
+ return null;
4211
+ }
4154
4212
  const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4155
4213
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4156
4214
  const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emf-converter",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Convert EMF/WMF metafile binaries to PNG data URLs using Canvas",
5
5
  "keywords": [
6
6
  "canvas",
@@ -593,14 +593,8 @@ function decodeSimpleGlyph(numContours, streams, out, calcBBox, minX, minY, maxX
593
593
  const flag = flagBytes[i];
594
594
  onCurve[i] = flag & 128 ? 0 : 1;
595
595
  const enc = TRIPLET_ENCODINGS[flag & 127];
596
- const extraBytes = enc.byteCount - 1;
597
- const subBuf = new Uint8Array(extraBytes);
598
- for (let b = 0; b < extraBytes; b++) {
599
- subBuf[b] = sGlyph.readU8();
600
- }
601
- const sub = new Stream(subBuf, extraBytes);
602
- let dx = sub.readNBits(enc.xBits) + enc.deltaX;
603
- let dy = sub.readNBits(enc.yBits) + enc.deltaY;
596
+ let dx = sGlyph.readNBits(enc.xBits) + enc.deltaX;
597
+ let dy = sGlyph.readNBits(enc.yBits) + enc.deltaY;
604
598
  if (enc.xSign !== 0) {
605
599
  dx *= enc.xSign;
606
600
  }
@@ -1158,6 +1152,8 @@ var MAX_2BYTE_DIST = 512;
1158
1152
  var PRELOAD_SIZE = 2 * 32 * 96 + 4 * 256;
1159
1153
  var LEN_MIN = 2;
1160
1154
  var DIST_MIN = 1;
1155
+ var MAX_OUT_LEN = 4 * 1024 * 1024;
1156
+ var MAX_OUT = 16 * 1024 * 1024;
1161
1157
  var RLE_INITIAL = 0;
1162
1158
  var RLE_NORMAL = 1;
1163
1159
  var RLE_SEEN_ESCAPE = 2;
@@ -1167,6 +1163,9 @@ function setDistRange(length) {
1167
1163
  let distMax = DIST_MIN + ((1 << DIST_WIDTH * numDistRanges) - 1);
1168
1164
  while (distMax < length) {
1169
1165
  numDistRanges++;
1166
+ if (numDistRanges > 8) {
1167
+ throw new Error("LZCOMP setDistRange: numDistRanges exceeds bound (8)");
1168
+ }
1170
1169
  distMax = DIST_MIN + ((1 << DIST_WIDTH * numDistRanges) - 1);
1171
1170
  }
1172
1171
  const DUP2 = 256 + (1 << LEN_WIDTH) * numDistRanges;
@@ -1197,7 +1196,11 @@ function decodeLength(lenEcoder, symbol, numDistRangesOut) {
1197
1196
  let firstTime = true;
1198
1197
  let value = 0;
1199
1198
  let done;
1199
+ let iters = 0;
1200
1200
  do {
1201
+ if (++iters > 16) {
1202
+ throw new Error("LZCOMP decodeLength: iteration cap exceeded");
1203
+ }
1201
1204
  let bits;
1202
1205
  if (firstTime) {
1203
1206
  bits = symbol - 256;
@@ -1236,6 +1239,9 @@ function lzcompDecompress(data, size, version) {
1236
1239
  const distEcoder = new AHuff(bio, 1 << DIST_WIDTH);
1237
1240
  const lenEcoder = new AHuff(bio, 1 << LEN_WIDTH);
1238
1241
  const outLen = bio.readValue(24);
1242
+ if (outLen > MAX_OUT_LEN) {
1243
+ throw new Error(`LZCOMP outLen ${outLen} exceeds maximum (${MAX_OUT_LEN})`);
1244
+ }
1239
1245
  const { DUP2, DUP4, DUP6, NUM_SYMS } = setDistRange(outLen);
1240
1246
  const symEcoder = new AHuff(bio, NUM_SYMS);
1241
1247
  const windowSize = PRELOAD_SIZE + outLen;
@@ -1252,6 +1258,9 @@ function lzcompDecompress(data, size, version) {
1252
1258
  if (!usingRunLength) {
1253
1259
  if (outIdx >= outBufSize) {
1254
1260
  outBufSize += outBufSize >>> 1;
1261
+ if (outBufSize > MAX_OUT) {
1262
+ throw new Error("LZCOMP output exceeds maximum size budget");
1263
+ }
1255
1264
  const tmp = new Uint8Array(outBufSize);
1256
1265
  tmp.set(outBuf);
1257
1266
  outBuf = tmp;
@@ -1270,6 +1279,9 @@ function lzcompDecompress(data, size, version) {
1270
1279
  } else {
1271
1280
  if (outIdx >= outBufSize) {
1272
1281
  outBufSize += outBufSize >>> 1;
1282
+ if (outBufSize > MAX_OUT) {
1283
+ throw new Error("LZCOMP output exceeds maximum size budget");
1284
+ }
1273
1285
  const tmp = new Uint8Array(outBufSize);
1274
1286
  tmp.set(outBuf);
1275
1287
  outBuf = tmp;
@@ -1282,6 +1294,9 @@ function lzcompDecompress(data, size, version) {
1282
1294
  if (rleCount === 0) {
1283
1295
  if (outIdx >= outBufSize) {
1284
1296
  outBufSize += outBufSize >>> 1;
1297
+ if (outBufSize > MAX_OUT) {
1298
+ throw new Error("LZCOMP output exceeds maximum size budget");
1299
+ }
1285
1300
  const tmp = new Uint8Array(outBufSize);
1286
1301
  tmp.set(outBuf);
1287
1302
  outBuf = tmp;
@@ -1295,6 +1310,9 @@ function lzcompDecompress(data, size, version) {
1295
1310
  case RLE_NEED_BYTE: {
1296
1311
  if (outIdx + rleCount > outBufSize) {
1297
1312
  outBufSize = outIdx + rleCount + (outBufSize >>> 1);
1313
+ if (outBufSize > MAX_OUT) {
1314
+ throw new Error("LZCOMP output exceeds maximum size budget");
1315
+ }
1298
1316
  const tmp = new Uint8Array(outBufSize);
1299
1317
  tmp.set(outBuf);
1300
1318
  outBuf = tmp;
@@ -1458,11 +1476,23 @@ function dumpContainer(ctr) {
1458
1476
  // src/mtx-decompress.ts
1459
1477
  var ENCRYPTION_KEY = 80;
1460
1478
  function unpackMtx(data, size) {
1479
+ if (size < 10 || data.length < 10) {
1480
+ throw new Error("MTX data too small: header requires at least 10 bytes");
1481
+ }
1461
1482
  const versionMagic = data[0];
1462
1483
  const offset2 = data[4] << 16 | data[5] << 8 | data[6];
1463
1484
  const offset3 = data[7] << 16 | data[8] << 8 | data[9];
1485
+ if (offset2 < 10 || offset3 < offset2 || offset3 > size) {
1486
+ throw new Error(
1487
+ `MTX header offsets out of bounds: offset2=${offset2}, offset3=${offset3}, size=${size}`
1488
+ );
1489
+ }
1464
1490
  const offsets = [10, offset2, offset3];
1465
- const blockSizes = [offset2 - 10, offset3 - offset2, size - offset3];
1491
+ const blockSizes = [
1492
+ Math.max(0, offset2 - 10),
1493
+ Math.max(0, offset3 - offset2),
1494
+ Math.max(0, size - offset3)
1495
+ ];
1466
1496
  const streams = [];
1467
1497
  const decompressedSizes = [];
1468
1498
  for (let i = 0; i < 3; i++) {