emf-converter 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,22 +44,20 @@ const pngDataUrl = await convertEmfToDataUrl(emfBuffer);
44
44
  const wmfPng = await convertWmfToDataUrl(wmfBuffer);
45
45
 
46
46
  // Optional: limit output dimensions (aspect ratio preserved)
47
- const scaled = await convertEmfToDataUrl(emfBuffer, 1024, 768);
47
+ const scaled = await convertEmfToDataUrl(emfBuffer, { maxWidth: 1024, maxHeight: 768 });
48
48
  ```
49
49
 
50
50
  Both functions return `Promise<string | null>` — `null` if the buffer is invalid or no Canvas API is available.
51
51
 
52
52
  ## API
53
53
 
54
- ### `convertEmfToDataUrl(buffer, maxWidth?, maxHeight?, options?)` · `convertWmfToDataUrl(buffer, maxWidth?, maxHeight?, options?)`
54
+ ### `convertEmfToDataUrl(buffer, options?)` · `convertWmfToDataUrl(buffer, options?)`
55
55
 
56
- | Parameter | Type | Description |
57
- | ----------- | ------------------------------------- | ---------------------------------------------------- |
58
- | `buffer` | `ArrayBuffer` | Raw EMF/WMF file bytes |
59
- | `maxWidth` | `number` (optional) | Maximum output width in pixels |
60
- | `maxHeight` | `number` (optional) | Maximum output height in pixels |
61
- | `options` | `EmfConvertOptions \| number` (opt.) | Options object, or a numeric `dpiScale` (legacy) |
62
- | **Returns** | `Promise<string \| null>` | PNG data URL or `null` on failure |
56
+ | Parameter | Type | Description |
57
+ | ----------- | ----------------------------- | ---------------------------------------------------- |
58
+ | `buffer` | `ArrayBuffer` | Raw EMF/WMF file bytes |
59
+ | `options` | `EmfConvertOptions` (optional)| Output size, DPI scale, record limits, font mapping |
60
+ | **Returns** | `Promise<string \| null>` | PNG data URL or `null` on failure |
63
61
 
64
62
  #### `EmfConvertOptions`
65
63
 
@@ -73,7 +71,7 @@ Both functions return `Promise<string | null>` — `null` if the buffer is inval
73
71
  | `fontFamilyMap` | `Record<string, string>` | — | Maps Windows face names (case-insensitive) to fonts available locally, e.g. `{ calibri: 'Carlito' }` |
74
72
 
75
73
  ```ts
76
- const png = await convertEmfToDataUrl(buffer, undefined, undefined, {
74
+ const png = await convertEmfToDataUrl(buffer, {
77
75
  dpiScale: 2,
78
76
  fontFamilyMap: { calibri: 'Carlito', 'ms shell dlg': 'Tahoma' },
79
77
  });
package/dist/index.d.mts CHANGED
@@ -58,14 +58,12 @@ interface EmfConvertOptions {
58
58
  * - The logical bounds are zero-sized or negative.
59
59
  * - No canvas API is available (e.g. headless test environment).
60
60
  *
61
- * @param buffer - The raw EMF file bytes.
62
- * @param maxWidth - Optional cap on the output canvas width (pixels).
63
- * @param maxHeight - Optional cap on the output canvas height (pixels).
64
- * @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
65
- * a numeric DPI scale (for backward compatibility). Default DPI scale is 2.
61
+ * @param buffer - The raw EMF file bytes.
62
+ * @param options - Optional {@link EmfConvertOptions} controlling output size,
63
+ * DPI scale, record limits, and font mapping.
66
64
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
67
65
  */
68
- declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
66
+ declare function convertEmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
69
67
  /**
70
68
  * Converts a WMF (Windows Metafile) binary buffer to a PNG data-URL string.
71
69
  *
@@ -80,14 +78,12 @@ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, max
80
78
  * - The header cannot be parsed or reports invalid dimensions.
81
79
  * - No canvas API is available.
82
80
  *
83
- * @param buffer - The raw WMF file bytes.
84
- * @param maxWidth - Optional cap on the output canvas width (pixels).
85
- * @param maxHeight - Optional cap on the output canvas height (pixels).
86
- * @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
87
- * a numeric DPI scale. Default DPI scale is 2.
81
+ * @param buffer - The raw WMF file bytes.
82
+ * @param options - Optional {@link EmfConvertOptions} controlling output size,
83
+ * DPI scale, record limits, and font mapping.
88
84
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
89
85
  */
90
- declare function convertWmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
86
+ declare function convertWmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
91
87
 
92
88
  /**
93
89
  * Canvas creation, styling, string reading, stock objects, and export helpers.
package/dist/index.d.ts CHANGED
@@ -58,14 +58,12 @@ interface EmfConvertOptions {
58
58
  * - The logical bounds are zero-sized or negative.
59
59
  * - No canvas API is available (e.g. headless test environment).
60
60
  *
61
- * @param buffer - The raw EMF file bytes.
62
- * @param maxWidth - Optional cap on the output canvas width (pixels).
63
- * @param maxHeight - Optional cap on the output canvas height (pixels).
64
- * @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
65
- * a numeric DPI scale (for backward compatibility). Default DPI scale is 2.
61
+ * @param buffer - The raw EMF file bytes.
62
+ * @param options - Optional {@link EmfConvertOptions} controlling output size,
63
+ * DPI scale, record limits, and font mapping.
66
64
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
67
65
  */
68
- declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
66
+ declare function convertEmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
69
67
  /**
70
68
  * Converts a WMF (Windows Metafile) binary buffer to a PNG data-URL string.
71
69
  *
@@ -80,14 +78,12 @@ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, max
80
78
  * - The header cannot be parsed or reports invalid dimensions.
81
79
  * - No canvas API is available.
82
80
  *
83
- * @param buffer - The raw WMF file bytes.
84
- * @param maxWidth - Optional cap on the output canvas width (pixels).
85
- * @param maxHeight - Optional cap on the output canvas height (pixels).
86
- * @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
87
- * a numeric DPI scale. Default DPI scale is 2.
81
+ * @param buffer - The raw WMF file bytes.
82
+ * @param options - Optional {@link EmfConvertOptions} controlling output size,
83
+ * DPI scale, record limits, and font mapping.
88
84
  * @returns A `data:image/png;base64,…` string, or `null` on failure.
89
85
  */
90
- declare function convertWmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, maxHeight?: number, optionsOrDpiScale?: EmfConvertOptions | number, recursionDepth?: number): Promise<string | null>;
86
+ declare function convertWmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
91
87
 
92
88
  /**
93
89
  * Canvas creation, styling, string reading, stock objects, and export helpers.
package/dist/index.js CHANGED
@@ -1402,14 +1402,9 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1402
1402
  if (text.length > 0) {
1403
1403
  applyFont(ctx, state);
1404
1404
  ctx.fillStyle = state.textColor;
1405
- let alignBaseline = "alphabetic";
1405
+ const vAlign = state.textAlign & 24;
1406
+ const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
1406
1407
  let alignHoriz = "left";
1407
- if (state.textAlign & 8) {
1408
- alignBaseline = "bottom";
1409
- }
1410
- if (state.textAlign & 24) {
1411
- alignBaseline = "alphabetic";
1412
- }
1413
1408
  if (state.textAlign & 6) {
1414
1409
  alignHoriz = "center";
1415
1410
  }
@@ -1437,17 +1432,29 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1437
1432
  }
1438
1433
  return true;
1439
1434
  }
1435
+ var ROP_PATCOPY = 15728673;
1436
+ var BS_NULL = 1;
1440
1437
  function handleBitBlt(rCtx, offset, dataOff, recSize) {
1441
- const { ctx, view } = rCtx;
1438
+ const { ctx, view, state } = rCtx;
1442
1439
  if (recSize >= 96) {
1443
1440
  const dstX = view.getInt32(dataOff + 16, true);
1444
1441
  const dstY = view.getInt32(dataOff + 20, true);
1445
1442
  const dstW = view.getInt32(dataOff + 24, true);
1446
1443
  const dstH = view.getInt32(dataOff + 28, true);
1444
+ const rop = view.getUint32(dataOff + 32, true);
1447
1445
  const offBmiSrc = view.getUint32(dataOff + 76, true);
1448
1446
  const cbBmiSrc = view.getUint32(dataOff + 80, true);
1449
1447
  const offBitsSrc = view.getUint32(dataOff + 84, true);
1450
1448
  const cbBitsSrc = view.getUint32(dataOff + 88, true);
1449
+ if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
1450
+ const prevFill = ctx.fillStyle;
1451
+ if (state.brushStyle !== BS_NULL) {
1452
+ ctx.fillStyle = state.brushColor;
1453
+ ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
1454
+ }
1455
+ ctx.fillStyle = prevFill;
1456
+ return true;
1457
+ }
1451
1458
  if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1452
1459
  const imageData = decodeDibToImageData(
1453
1460
  view,
@@ -4857,19 +4864,7 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4857
4864
  continue;
4858
4865
  }
4859
4866
  emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
4860
- const metafileDataUrl = await convertEmfToDataUrl(
4861
- plainBuffer,
4862
- void 0,
4863
- void 0,
4864
- void 0,
4865
- recursionDepth + 1
4866
- ) ?? await convertWmfToDataUrl(
4867
- plainBuffer,
4868
- void 0,
4869
- void 0,
4870
- void 0,
4871
- recursionDepth + 1
4872
- );
4867
+ const metafileDataUrl = await convertEmfToDataUrl(plainBuffer, void 0, recursionDepth + 1) ?? await convertWmfToDataUrl(plainBuffer, void 0, recursionDepth + 1);
4873
4868
  if (metafileDataUrl) {
4874
4869
  emfLog(
4875
4870
  ` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
@@ -4914,14 +4909,14 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4914
4909
  }
4915
4910
  ctx.setTransform(1, 0, 0, 1, 0, 0);
4916
4911
  }
4917
- async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4912
+ async function convertEmfToDataUrl(buffer, options, recursionDepth = 0) {
4918
4913
  if (recursionDepth > MAX_METAFILE_RECURSION) {
4919
4914
  return null;
4920
4915
  }
4921
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4916
+ const opts = options ?? {};
4922
4917
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4923
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4924
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
4918
+ const effectiveMaxWidth = opts.maxWidth;
4919
+ const effectiveMaxHeight = opts.maxHeight;
4925
4920
  const replayOptions = {
4926
4921
  maxRecords: opts.maxRecords,
4927
4922
  maxRecordsEmfPlus: opts.maxRecords,
@@ -4999,14 +4994,14 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4999
4994
  return null;
5000
4995
  }
5001
4996
  }
5002
- async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4997
+ async function convertWmfToDataUrl(buffer, options, recursionDepth = 0) {
5003
4998
  if (recursionDepth > MAX_METAFILE_RECURSION) {
5004
4999
  return null;
5005
5000
  }
5006
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
5001
+ const opts = options ?? {};
5007
5002
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
5008
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
5009
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
5003
+ const effectiveMaxWidth = opts.maxWidth;
5004
+ const effectiveMaxHeight = opts.maxHeight;
5010
5005
  const replayOptions = {
5011
5006
  maxRecords: opts.maxRecords,
5012
5007
  fontFamilyMap: opts.fontFamilyMap
package/dist/index.mjs CHANGED
@@ -1400,14 +1400,9 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1400
1400
  if (text.length > 0) {
1401
1401
  applyFont(ctx, state);
1402
1402
  ctx.fillStyle = state.textColor;
1403
- let alignBaseline = "alphabetic";
1403
+ const vAlign = state.textAlign & 24;
1404
+ const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
1404
1405
  let alignHoriz = "left";
1405
- if (state.textAlign & 8) {
1406
- alignBaseline = "bottom";
1407
- }
1408
- if (state.textAlign & 24) {
1409
- alignBaseline = "alphabetic";
1410
- }
1411
1406
  if (state.textAlign & 6) {
1412
1407
  alignHoriz = "center";
1413
1408
  }
@@ -1435,17 +1430,29 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1435
1430
  }
1436
1431
  return true;
1437
1432
  }
1433
+ var ROP_PATCOPY = 15728673;
1434
+ var BS_NULL = 1;
1438
1435
  function handleBitBlt(rCtx, offset, dataOff, recSize) {
1439
- const { ctx, view } = rCtx;
1436
+ const { ctx, view, state } = rCtx;
1440
1437
  if (recSize >= 96) {
1441
1438
  const dstX = view.getInt32(dataOff + 16, true);
1442
1439
  const dstY = view.getInt32(dataOff + 20, true);
1443
1440
  const dstW = view.getInt32(dataOff + 24, true);
1444
1441
  const dstH = view.getInt32(dataOff + 28, true);
1442
+ const rop = view.getUint32(dataOff + 32, true);
1445
1443
  const offBmiSrc = view.getUint32(dataOff + 76, true);
1446
1444
  const cbBmiSrc = view.getUint32(dataOff + 80, true);
1447
1445
  const offBitsSrc = view.getUint32(dataOff + 84, true);
1448
1446
  const cbBitsSrc = view.getUint32(dataOff + 88, true);
1447
+ if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
1448
+ const prevFill = ctx.fillStyle;
1449
+ if (state.brushStyle !== BS_NULL) {
1450
+ ctx.fillStyle = state.brushColor;
1451
+ ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
1452
+ }
1453
+ ctx.fillStyle = prevFill;
1454
+ return true;
1455
+ }
1449
1456
  if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1450
1457
  const imageData = decodeDibToImageData(
1451
1458
  view,
@@ -4855,19 +4862,7 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4855
4862
  continue;
4856
4863
  }
4857
4864
  emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
4858
- const metafileDataUrl = await convertEmfToDataUrl(
4859
- plainBuffer,
4860
- void 0,
4861
- void 0,
4862
- void 0,
4863
- recursionDepth + 1
4864
- ) ?? await convertWmfToDataUrl(
4865
- plainBuffer,
4866
- void 0,
4867
- void 0,
4868
- void 0,
4869
- recursionDepth + 1
4870
- );
4865
+ const metafileDataUrl = await convertEmfToDataUrl(plainBuffer, void 0, recursionDepth + 1) ?? await convertWmfToDataUrl(plainBuffer, void 0, recursionDepth + 1);
4871
4866
  if (metafileDataUrl) {
4872
4867
  emfLog(
4873
4868
  ` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
@@ -4912,14 +4907,14 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4912
4907
  }
4913
4908
  ctx.setTransform(1, 0, 0, 1, 0, 0);
4914
4909
  }
4915
- async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4910
+ async function convertEmfToDataUrl(buffer, options, recursionDepth = 0) {
4916
4911
  if (recursionDepth > MAX_METAFILE_RECURSION) {
4917
4912
  return null;
4918
4913
  }
4919
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4914
+ const opts = options ?? {};
4920
4915
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4921
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4922
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
4916
+ const effectiveMaxWidth = opts.maxWidth;
4917
+ const effectiveMaxHeight = opts.maxHeight;
4923
4918
  const replayOptions = {
4924
4919
  maxRecords: opts.maxRecords,
4925
4920
  maxRecordsEmfPlus: opts.maxRecords,
@@ -4997,14 +4992,14 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4997
4992
  return null;
4998
4993
  }
4999
4994
  }
5000
- async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4995
+ async function convertWmfToDataUrl(buffer, options, recursionDepth = 0) {
5001
4996
  if (recursionDepth > MAX_METAFILE_RECURSION) {
5002
4997
  return null;
5003
4998
  }
5004
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4999
+ const opts = options ?? {};
5005
5000
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
5006
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
5007
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
5001
+ const effectiveMaxWidth = opts.maxWidth;
5002
+ const effectiveMaxHeight = opts.maxHeight;
5008
5003
  const replayOptions = {
5009
5004
  maxRecords: opts.maxRecords,
5010
5005
  fontFamilyMap: opts.fontFamilyMap
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emf-converter",
3
- "version": "1.6.0",
3
+ "version": "2.0.0",
4
4
  "description": "Convert EMF/WMF metafile binaries to PNG data URLs using Canvas",
5
5
  "keywords": [
6
6
  "canvas",