emf-converter 1.4.1 โ 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/dist/index.js +10 -4
- package/dist/index.mjs +10 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# emf-converter
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/emf-converter)
|
|
4
|
-
[](https://github.com/ChristopherVR/emf-converter/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
5
6
|
|
|
6
|
-
A zero-dependency TypeScript library that converts **EMF** (Enhanced Metafile) and **WMF** (Windows Metafile) binary buffers into **PNG data URLs** by parsing their record streams and replaying drawing commands onto an HTML Canvas.
|
|
7
|
+
A zero-dependency TypeScript library that converts **EMF** (Enhanced Metafile) and **WMF** (Windows Metafile) binary buffers into **PNG data URLs** by parsing their record streams and replaying the drawing commands onto an HTML Canvas.
|
|
7
8
|
|
|
8
|
-
Windows Metafiles store a sequence of GDI drawing commands and are commonly embedded inside Office documents (
|
|
9
|
+
Windows Metafiles store a sequence of GDI drawing commands and are commonly embedded inside Office documents (Word, PowerPoint) and Windows clipboard data. This converter reads the raw binary, interprets each record, and replays the drawing operations onto a Canvas to produce a rasterised PNG. It handles three formats:
|
|
9
10
|
|
|
10
11
|
| Format | Description | Coordinate system |
|
|
11
12
|
| -------- | ------------------------------ | ----------------------- |
|
|
@@ -13,10 +14,16 @@ Windows Metafiles store a sequence of GDI drawing commands and are commonly embe
|
|
|
13
14
|
| **EMF** | Enhanced Metafile (32-bit GDI) | Bounds-based scaling |
|
|
14
15
|
| **EMF+** | GDI+ extension embedded in EMF | World transform matrix |
|
|
15
16
|
|
|
16
|
-
<samp>**[
|
|
17
|
+
<samp>**[โถ๏ธ Live demo](https://christophervr.github.io/emf-converter/)** ยท **[๐ฆ npm](https://www.npmjs.com/package/emf-converter)**</samp>
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
21
|
+
## Demo
|
|
22
|
+
|
|
23
|
+
Try it right in your browser โ drop in an `.emf` or `.wmf` file and see the rendered PNG, conversion time, and output size:
|
|
24
|
+
|
|
25
|
+
**https://christophervr.github.io/emf-converter/**
|
|
26
|
+
|
|
20
27
|
## Install
|
|
21
28
|
|
|
22
29
|
```bash
|
|
@@ -57,7 +64,7 @@ Both functions return `Promise<string | null>` โ `null` if the buffer is inval
|
|
|
57
64
|
|
|
58
65
|
A three-phase pipeline: **parse โ replay โ export**. The header parser extracts the drawing bounds, a Canvas is created and clamped to 4096ร4096, then records are scanned sequentially and dispatched to GDI, EMF+, or WMF handlers that drive the Canvas 2D context. Embedded bitmaps (DIB and GDI+ pixel formats) and recursively embedded metafiles are resolved asynchronously after the synchronous replay completes.
|
|
59
66
|
|
|
60
|
-
It supports 300+ EMF GDI record types, the EMF+ (GDI+) record set, and legacy WMF records, including state, transforms, objects, shapes, poly/path operations, text, bitmaps, and clipping.
|
|
67
|
+
It supports 300+ EMF GDI record types, the EMF+ (GDI+) record set, and legacy WMF records, including state, transforms, objects, shapes, poly/path operations, text, bitmaps, and clipping.
|
|
61
68
|
|
|
62
69
|
## Limitations
|
|
63
70
|
|
|
@@ -70,4 +77,4 @@ It supports 300+ EMF GDI record types, the EMF+ (GDI+) record set, and legacy WM
|
|
|
70
77
|
|
|
71
78
|
## License
|
|
72
79
|
|
|
73
|
-
[Apache-2.0](LICENSE)
|
|
80
|
+
[Apache-2.0](LICENSE) โ free for commercial and closed-source use, with an explicit patent grant.
|
package/dist/index.js
CHANGED
|
@@ -3893,7 +3893,13 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3893
3893
|
const windowExt = { cx: logicalW, cy: logicalH };
|
|
3894
3894
|
const coord = createWmfCoord(windowOrg, windowExt, canvasW, canvasH);
|
|
3895
3895
|
const objectTable = /* @__PURE__ */ new Map();
|
|
3896
|
-
|
|
3896
|
+
const allocObjectSlot = () => {
|
|
3897
|
+
let slot = 0;
|
|
3898
|
+
while (objectTable.has(slot)) {
|
|
3899
|
+
slot++;
|
|
3900
|
+
}
|
|
3901
|
+
return slot;
|
|
3902
|
+
};
|
|
3897
3903
|
const state = defaultState();
|
|
3898
3904
|
const stateStack = [];
|
|
3899
3905
|
const wCtx = { view, ctx, state, coord };
|
|
@@ -3969,7 +3975,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3969
3975
|
break;
|
|
3970
3976
|
case META_CREATEPENINDIRECT:
|
|
3971
3977
|
if (recSize >= 16) {
|
|
3972
|
-
const slot =
|
|
3978
|
+
const slot = allocObjectSlot();
|
|
3973
3979
|
objectTable.set(slot, {
|
|
3974
3980
|
kind: "pen",
|
|
3975
3981
|
style: view.getUint16(dataOff, true) & 255,
|
|
@@ -3980,7 +3986,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3980
3986
|
break;
|
|
3981
3987
|
case META_CREATEBRUSHINDIRECT:
|
|
3982
3988
|
if (recSize >= 14) {
|
|
3983
|
-
const slot =
|
|
3989
|
+
const slot = allocObjectSlot();
|
|
3984
3990
|
objectTable.set(slot, {
|
|
3985
3991
|
kind: "brush",
|
|
3986
3992
|
style: view.getUint16(dataOff, true),
|
|
@@ -3998,7 +4004,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3998
4004
|
}
|
|
3999
4005
|
family += String.fromCharCode(ch);
|
|
4000
4006
|
}
|
|
4001
|
-
const slot =
|
|
4007
|
+
const slot = allocObjectSlot();
|
|
4002
4008
|
objectTable.set(slot, {
|
|
4003
4009
|
kind: "font",
|
|
4004
4010
|
height: Math.abs(view.getInt16(dataOff, true)),
|
package/dist/index.mjs
CHANGED
|
@@ -3891,7 +3891,13 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3891
3891
|
const windowExt = { cx: logicalW, cy: logicalH };
|
|
3892
3892
|
const coord = createWmfCoord(windowOrg, windowExt, canvasW, canvasH);
|
|
3893
3893
|
const objectTable = /* @__PURE__ */ new Map();
|
|
3894
|
-
|
|
3894
|
+
const allocObjectSlot = () => {
|
|
3895
|
+
let slot = 0;
|
|
3896
|
+
while (objectTable.has(slot)) {
|
|
3897
|
+
slot++;
|
|
3898
|
+
}
|
|
3899
|
+
return slot;
|
|
3900
|
+
};
|
|
3895
3901
|
const state = defaultState();
|
|
3896
3902
|
const stateStack = [];
|
|
3897
3903
|
const wCtx = { view, ctx, state, coord };
|
|
@@ -3967,7 +3973,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3967
3973
|
break;
|
|
3968
3974
|
case META_CREATEPENINDIRECT:
|
|
3969
3975
|
if (recSize >= 16) {
|
|
3970
|
-
const slot =
|
|
3976
|
+
const slot = allocObjectSlot();
|
|
3971
3977
|
objectTable.set(slot, {
|
|
3972
3978
|
kind: "pen",
|
|
3973
3979
|
style: view.getUint16(dataOff, true) & 255,
|
|
@@ -3978,7 +3984,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3978
3984
|
break;
|
|
3979
3985
|
case META_CREATEBRUSHINDIRECT:
|
|
3980
3986
|
if (recSize >= 14) {
|
|
3981
|
-
const slot =
|
|
3987
|
+
const slot = allocObjectSlot();
|
|
3982
3988
|
objectTable.set(slot, {
|
|
3983
3989
|
kind: "brush",
|
|
3984
3990
|
style: view.getUint16(dataOff, true),
|
|
@@ -3996,7 +4002,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
|
|
|
3996
4002
|
}
|
|
3997
4003
|
family += String.fromCharCode(ch);
|
|
3998
4004
|
}
|
|
3999
|
-
const slot =
|
|
4005
|
+
const slot = allocObjectSlot();
|
|
4000
4006
|
objectTable.set(slot, {
|
|
4001
4007
|
kind: "font",
|
|
4002
4008
|
height: Math.abs(view.getInt16(dataOff, true)),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emf-converter",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "Convert EMF/WMF metafile binaries to PNG data URLs using Canvas",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"canvas",
|
|
@@ -47,6 +47,6 @@
|
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"tsup": "^8.5.1",
|
|
49
49
|
"typescript": "^6.0.3",
|
|
50
|
-
"vitest": "^4.1.
|
|
50
|
+
"vitest": "^4.1.9"
|
|
51
51
|
}
|
|
52
52
|
}
|