@mkabatek/pptx-viewer 1.5.4 → 1.5.11
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/dist/index.js +3 -1
- package/dist/index.mjs +3 -1
- package/dist/viewer/index.js +3 -1
- package/dist/viewer/index.mjs +3 -1
- package/package.json +2 -9
- package/node_modules/emf-converter/LICENSE +0 -21
- package/node_modules/emf-converter/README.md +0 -629
- package/node_modules/emf-converter/dist/index.d.mts +0 -86
- package/node_modules/emf-converter/dist/index.d.ts +0 -86
- package/node_modules/emf-converter/dist/index.js +0 -4257
- package/node_modules/emf-converter/dist/index.mjs +0 -4253
- package/node_modules/emf-converter/package.json +0 -53
- package/node_modules/mtx-decompressor/LICENSE +0 -373
- package/node_modules/mtx-decompressor/README.md +0 -271
- package/node_modules/mtx-decompressor/dist/index.d.mts +0 -84
- package/node_modules/mtx-decompressor/dist/index.d.ts +0 -84
- package/node_modules/mtx-decompressor/dist/index.js +0 -1532
- package/node_modules/mtx-decompressor/dist/index.mjs +0 -1528
- package/node_modules/mtx-decompressor/package.json +0 -44
- package/node_modules/pptx-viewer-core/LICENSE +0 -21
- package/node_modules/pptx-viewer-core/NOTICE +0 -16
- package/node_modules/pptx-viewer-core/README.md +0 -1294
- package/node_modules/pptx-viewer-core/dist/SvgExporter-BtZczTlB.d.ts +0 -557
- package/node_modules/pptx-viewer-core/dist/SvgExporter-D4mBWJHE.d.mts +0 -557
- package/node_modules/pptx-viewer-core/dist/cli/index.d.mts +0 -150
- package/node_modules/pptx-viewer-core/dist/cli/index.d.ts +0 -150
- package/node_modules/pptx-viewer-core/dist/cli/index.js +0 -0
- package/node_modules/pptx-viewer-core/dist/cli/index.mjs +0 -0
- package/node_modules/pptx-viewer-core/dist/converter/index.d.mts +0 -48
- package/node_modules/pptx-viewer-core/dist/converter/index.d.ts +0 -48
- package/node_modules/pptx-viewer-core/dist/converter/index.js +0 -0
- package/node_modules/pptx-viewer-core/dist/converter/index.mjs +0 -0
- package/node_modules/pptx-viewer-core/dist/index.d.mts +0 -12744
- package/node_modules/pptx-viewer-core/dist/index.d.ts +0 -12744
- package/node_modules/pptx-viewer-core/dist/index.js +0 -66894
- package/node_modules/pptx-viewer-core/dist/index.mjs +0 -66420
- package/node_modules/pptx-viewer-core/dist/presentation-nZxgWvXq.d.mts +0 -5645
- package/node_modules/pptx-viewer-core/dist/presentation-nZxgWvXq.d.ts +0 -5645
- package/node_modules/pptx-viewer-core/dist/signature-inspection-status-BCUpfCQh.d.mts +0 -220
- package/node_modules/pptx-viewer-core/dist/signature-inspection-status-BCUpfCQh.d.ts +0 -220
- package/node_modules/pptx-viewer-core/dist/signature-node/index.d.mts +0 -177
- package/node_modules/pptx-viewer-core/dist/signature-node/index.d.ts +0 -177
- package/node_modules/pptx-viewer-core/dist/signature-node/index.js +0 -1206
- package/node_modules/pptx-viewer-core/dist/signature-node/index.mjs +0 -1143
- package/node_modules/pptx-viewer-core/dist/text-operations-DCTGMltY.d.mts +0 -134
- package/node_modules/pptx-viewer-core/dist/text-operations-DYmhoi7U.d.ts +0 -134
- package/node_modules/pptx-viewer-core/package.json +0 -96
|
@@ -1,629 +0,0 @@
|
|
|
1
|
-
# emf-converter
|
|
2
|
-
|
|
3
|
-
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.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [emf-converter](#emf-converter)
|
|
8
|
-
- [Table of Contents](#table-of-contents)
|
|
9
|
-
- [Overview](#overview)
|
|
10
|
-
- [Quick Start](#quick-start)
|
|
11
|
-
- [API Reference](#api-reference)
|
|
12
|
-
- [`convertEmfToDataUrl(buffer, maxWidth?, maxHeight?)`](#convertemftodataurlbuffer-maxwidth-maxheight)
|
|
13
|
-
- [`convertWmfToDataUrl(buffer, maxWidth?, maxHeight?)`](#convertwmftodataurlbuffer-maxwidth-maxheight)
|
|
14
|
-
- [Architecture](#architecture)
|
|
15
|
-
- [High-Level Pipeline](#high-level-pipeline)
|
|
16
|
-
- [Module Map](#module-map)
|
|
17
|
-
- [EMF Record Replay Loop](#emf-record-replay-loop)
|
|
18
|
-
- [EMF+ Dual-Mode Processing](#emf-dual-mode-processing)
|
|
19
|
-
- [WMF Processing](#wmf-processing)
|
|
20
|
-
- [Deep Dive: How It Works](#deep-dive-how-it-works)
|
|
21
|
-
- [1. Header Parsing](#1-header-parsing)
|
|
22
|
-
- [2. Canvas Creation \& Scaling](#2-canvas-creation--scaling)
|
|
23
|
-
- [3. GDI Record Replay](#3-gdi-record-replay)
|
|
24
|
-
- [4. EMF+ Record Replay](#4-emf-record-replay)
|
|
25
|
-
- [5. Coordinate Systems](#5-coordinate-systems)
|
|
26
|
-
- [6. GDI Object Table](#6-gdi-object-table)
|
|
27
|
-
- [7. DIB (Bitmap) Decoding](#7-dib-bitmap-decoding)
|
|
28
|
-
- [8. Deferred Image Processing](#8-deferred-image-processing)
|
|
29
|
-
- [9. World Transforms](#9-world-transforms)
|
|
30
|
-
- [Supported Record Types](#supported-record-types)
|
|
31
|
-
- [EMF GDI Records](#emf-gdi-records)
|
|
32
|
-
- [EMF+ Records](#emf-records)
|
|
33
|
-
- [WMF Records](#wmf-records)
|
|
34
|
-
- [File Structure Reference](#file-structure-reference)
|
|
35
|
-
- [Limitations](#limitations)
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Overview
|
|
40
|
-
|
|
41
|
-
Windows Metafiles (EMF/WMF) are vector image formats that store a sequence of GDI (Graphics Device Interface) drawing commands. They are commonly embedded inside Office documents (PPTX, DOCX) and legacy Windows applications. This converter reads the raw binary data, interprets each record, and replays the drawing operations onto an HTML5 Canvas to produce a rasterised PNG.
|
|
42
|
-
|
|
43
|
-
The library handles three distinct formats:
|
|
44
|
-
|
|
45
|
-
| Format | Description | Record Size | Coordinate System |
|
|
46
|
-
| -------- | ------------------------------ | ------------------- | ----------------------- |
|
|
47
|
-
| **WMF** | Windows Metafile (16-bit) | 16-bit word-aligned | Window/viewport mapping |
|
|
48
|
-
| **EMF** | Enhanced Metafile (32-bit GDI) | 32-bit aligned | Bounds-based scaling |
|
|
49
|
-
| **EMF+** | GDI+ extension embedded in EMF | 32-bit aligned | World transform matrix |
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## Quick Start
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import { convertEmfToDataUrl, convertWmfToDataUrl } from "emf-converter";
|
|
57
|
-
|
|
58
|
-
// Convert an EMF buffer to a PNG data URL
|
|
59
|
-
const emfBuffer: ArrayBuffer = /* loaded from file or network */;
|
|
60
|
-
const pngDataUrl = await convertEmfToDataUrl(emfBuffer);
|
|
61
|
-
// => "data:image/png;base64,iVBORw0KGgo..."
|
|
62
|
-
|
|
63
|
-
// Convert a WMF buffer to a PNG data URL
|
|
64
|
-
const wmfBuffer: ArrayBuffer = /* loaded from file or network */;
|
|
65
|
-
const wmfPng = await convertWmfToDataUrl(wmfBuffer);
|
|
66
|
-
|
|
67
|
-
// Optional: limit output dimensions
|
|
68
|
-
const scaled = await convertEmfToDataUrl(emfBuffer, 1024, 768);
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Both functions return `Promise<string | null>` — they return `null` if the buffer is invalid or no canvas API is available.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## API Reference
|
|
76
|
-
|
|
77
|
-
### `convertEmfToDataUrl(buffer, maxWidth?, maxHeight?)`
|
|
78
|
-
|
|
79
|
-
Converts an EMF binary buffer to a PNG data URL.
|
|
80
|
-
|
|
81
|
-
| Parameter | Type | Description |
|
|
82
|
-
| ----------- | ------------------------- | --------------------------------- |
|
|
83
|
-
| `buffer` | `ArrayBuffer` | The raw EMF file bytes |
|
|
84
|
-
| `maxWidth` | `number` (optional) | Maximum output width in pixels |
|
|
85
|
-
| `maxHeight` | `number` (optional) | Maximum output height in pixels |
|
|
86
|
-
| **Returns** | `Promise<string \| null>` | PNG data URL or `null` on failure |
|
|
87
|
-
|
|
88
|
-
### `convertWmfToDataUrl(buffer, maxWidth?, maxHeight?)`
|
|
89
|
-
|
|
90
|
-
Converts a WMF binary buffer to a PNG data URL.
|
|
91
|
-
|
|
92
|
-
| Parameter | Type | Description |
|
|
93
|
-
| ----------- | ------------------------- | --------------------------------- |
|
|
94
|
-
| `buffer` | `ArrayBuffer` | The raw WMF file bytes |
|
|
95
|
-
| `maxWidth` | `number` (optional) | Maximum output width in pixels |
|
|
96
|
-
| `maxHeight` | `number` (optional) | Maximum output height in pixels |
|
|
97
|
-
| **Returns** | `Promise<string \| null>` | PNG data URL or `null` on failure |
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Architecture
|
|
102
|
-
|
|
103
|
-
### High-Level Pipeline
|
|
104
|
-
|
|
105
|
-
The converter follows a three-phase pipeline: **Parse → Replay → Export**.
|
|
106
|
-
|
|
107
|
-
```mermaid
|
|
108
|
-
flowchart LR
|
|
109
|
-
A[/"Binary Buffer<br/>(ArrayBuffer)"/] --> B{Format?}
|
|
110
|
-
B -->|EMF| C[Parse EMF Header]
|
|
111
|
-
B -->|WMF| D[Parse WMF Header]
|
|
112
|
-
C --> E[Create Canvas]
|
|
113
|
-
D --> E
|
|
114
|
-
E --> F{Replay Records}
|
|
115
|
-
F -->|GDI Records| G[GDI Record Handlers]
|
|
116
|
-
F -->|EMF+ Comments| H[EMF+ Record Handlers]
|
|
117
|
-
F -->|WMF Records| I[WMF Record Handlers]
|
|
118
|
-
G --> J[Canvas 2D Context]
|
|
119
|
-
H --> J
|
|
120
|
-
I --> J
|
|
121
|
-
J --> K[Process Deferred Images]
|
|
122
|
-
K --> L[Export to PNG Data URL]
|
|
123
|
-
L --> M[/"data:image/png;base64,..."/]
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Module Map
|
|
127
|
-
|
|
128
|
-
Every source file has a specific responsibility. Here's how they connect:
|
|
129
|
-
|
|
130
|
-
```mermaid
|
|
131
|
-
graph TB
|
|
132
|
-
subgraph "Public API"
|
|
133
|
-
A[emf-converter.ts]
|
|
134
|
-
B[index.ts]
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
subgraph "Header & Setup"
|
|
138
|
-
C[emf-header-parser.ts]
|
|
139
|
-
D[emf-canvas-helpers.ts]
|
|
140
|
-
E[emf-constants.ts]
|
|
141
|
-
F[emf-types.ts]
|
|
142
|
-
G[emf-logging.ts]
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
subgraph "EMF GDI Replay"
|
|
146
|
-
H[emf-record-replay.ts]
|
|
147
|
-
I[emf-gdi-state-handlers.ts]
|
|
148
|
-
J[emf-gdi-draw-handlers.ts]
|
|
149
|
-
K[emf-gdi-poly-path-handlers.ts]
|
|
150
|
-
L[emf-gdi-transform-handlers.ts]
|
|
151
|
-
M[emf-gdi-object-handlers.ts]
|
|
152
|
-
N[emf-gdi-draw-shapes.ts]
|
|
153
|
-
O[emf-gdi-draw-text-bitmap.ts]
|
|
154
|
-
P[emf-gdi-coord.ts]
|
|
155
|
-
Q[emf-gdi-polypolygon-helpers.ts]
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
subgraph "EMF+ GDI+ Replay"
|
|
159
|
-
R[emf-plus-replay.ts]
|
|
160
|
-
S[emf-plus-object-parser.ts]
|
|
161
|
-
T[emf-plus-draw-handlers.ts]
|
|
162
|
-
U[emf-plus-text-image-handlers.ts]
|
|
163
|
-
V[emf-plus-state-handlers.ts]
|
|
164
|
-
W[emf-plus-path.ts]
|
|
165
|
-
X[emf-plus-read-helpers.ts]
|
|
166
|
-
Y[emf-plus-object-complex.ts]
|
|
167
|
-
Z[emf-plus-bitmap-decoder.ts]
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
subgraph "WMF Replay"
|
|
171
|
-
AA[wmf-replay.ts]
|
|
172
|
-
AB[wmf-draw-handlers.ts]
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
subgraph "Bitmap Decoding"
|
|
176
|
-
AC[emf-dib-decoder.ts]
|
|
177
|
-
AD[emf-dib-rle-decoder.ts]
|
|
178
|
-
AE[emf-dib-uncompressed.ts]
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
subgraph "Color Helpers"
|
|
182
|
-
AF[emf-color-helpers.ts]
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
B --> A
|
|
186
|
-
A --> C
|
|
187
|
-
A --> D
|
|
188
|
-
A --> H
|
|
189
|
-
A --> AA
|
|
190
|
-
|
|
191
|
-
H --> I
|
|
192
|
-
H --> J
|
|
193
|
-
H --> K
|
|
194
|
-
H --> R
|
|
195
|
-
|
|
196
|
-
I --> L
|
|
197
|
-
I --> M
|
|
198
|
-
J --> N
|
|
199
|
-
J --> O
|
|
200
|
-
|
|
201
|
-
AA --> AB
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### EMF Record Replay Loop
|
|
205
|
-
|
|
206
|
-
The core of the EMF converter is a sequential record-scanning loop that dispatches each record to the appropriate handler:
|
|
207
|
-
|
|
208
|
-
```mermaid
|
|
209
|
-
flowchart TD
|
|
210
|
-
Start([Start Replay]) --> Read["Read record at offset:<br/>type = uint32, size = uint32"]
|
|
211
|
-
Read --> Check{Record Type?}
|
|
212
|
-
|
|
213
|
-
Check -->|EMR_COMMENT| Plus["Check for EMF+ signature<br/>(0x2B464D45 = 'EMF+')"]
|
|
214
|
-
Plus -->|EMF+ found| PlusReplay["replayEmfPlusRecords()<br/>Returns deferred images"]
|
|
215
|
-
Plus -->|Not EMF+| Skip1[Skip record]
|
|
216
|
-
|
|
217
|
-
Check -->|EMR_EOF| Done([End — return deferred images])
|
|
218
|
-
|
|
219
|
-
Check -->|State records| State["handleEmfGdiStateRecord()<br/>SaveDC, RestoreDC, SetTextColor,<br/>SetBkMode, transforms, objects"]
|
|
220
|
-
|
|
221
|
-
Check -->|Draw records| Draw["handleEmfGdiDrawRecord()<br/>Shapes → emf-gdi-draw-shapes<br/>Text/Bitmap → emf-gdi-draw-text-bitmap"]
|
|
222
|
-
|
|
223
|
-
Check -->|Poly/Path records| Poly["handleEmfGdiPolyPathRecord()<br/>Polygon, Polyline, Bezier,<br/>BeginPath, FillPath, StrokePath"]
|
|
224
|
-
|
|
225
|
-
Check -->|Ignored| Skip2["Skip: EMR_HEADER,<br/>EMR_SETBRUSHORGEX, etc."]
|
|
226
|
-
|
|
227
|
-
PlusReplay --> Next
|
|
228
|
-
Skip1 --> Next
|
|
229
|
-
State --> Next
|
|
230
|
-
Draw --> Next
|
|
231
|
-
Poly --> Next
|
|
232
|
-
Skip2 --> Next
|
|
233
|
-
|
|
234
|
-
Next["offset += recSize"] --> Guard{"offset + 8 ≤ bufferLen<br/>AND recordCount < 50,000?"}
|
|
235
|
-
Guard -->|Yes| Read
|
|
236
|
-
Guard -->|No| Done
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### EMF+ Dual-Mode Processing
|
|
240
|
-
|
|
241
|
-
EMF files can contain embedded EMF+ records inside `EMR_COMMENT` records. When detected, these are processed by a parallel GDI+ replay engine:
|
|
242
|
-
|
|
243
|
-
```mermaid
|
|
244
|
-
flowchart LR
|
|
245
|
-
subgraph "EMF Record Stream"
|
|
246
|
-
E1[EMR_HEADER]
|
|
247
|
-
E2[EMR_SELECTOBJECT]
|
|
248
|
-
E3["EMR_COMMENT<br/>(EMF+ signature)"]
|
|
249
|
-
E4[EMR_RECTANGLE]
|
|
250
|
-
E5["EMR_COMMENT<br/>(EMF+ signature)"]
|
|
251
|
-
E6[EMR_EOF]
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
subgraph "EMF+ Record Stream (inside comments)"
|
|
255
|
-
P1[EMFPLUS_HEADER]
|
|
256
|
-
P2[EMFPLUS_OBJECT ×N]
|
|
257
|
-
P3[EMFPLUS_FillRects]
|
|
258
|
-
P4[EMFPLUS_DrawPath]
|
|
259
|
-
P5[EMFPLUS_DrawString]
|
|
260
|
-
P6[EMFPLUS_DrawImage]
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
E3 --> P1
|
|
264
|
-
E3 --> P2
|
|
265
|
-
E3 --> P3
|
|
266
|
-
E5 --> P4
|
|
267
|
-
E5 --> P5
|
|
268
|
-
E5 --> P6
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
The EMF+ state (object table, world transform, save stack) **persists across multiple `EMR_COMMENT` records** within the same file, allowing complex drawings to span several comment blocks.
|
|
272
|
-
|
|
273
|
-
### WMF Processing
|
|
274
|
-
|
|
275
|
-
WMF uses a simpler 16-bit record format with word-aligned record sizes:
|
|
276
|
-
|
|
277
|
-
```mermaid
|
|
278
|
-
flowchart TD
|
|
279
|
-
Start([Parse WMF Header]) --> Magic{"Magic bytes?"}
|
|
280
|
-
Magic -->|0x9AC6CDD7<br/>Aldus Placeable| APM["Read bounds & DPI<br/>from APM header"]
|
|
281
|
-
Magic -->|Standard| Std["Use default bounds<br/>(800×600)"]
|
|
282
|
-
|
|
283
|
-
APM --> Main
|
|
284
|
-
Std --> Main
|
|
285
|
-
|
|
286
|
-
Main["Sequential Record Loop"] --> RecType{"Record Type?"}
|
|
287
|
-
RecType -->|Drawing| WDraw["handleWmfDrawRecord()<br/>MoveTo, LineTo, Rectangle,<br/>Ellipse, Polygon, Text, etc."]
|
|
288
|
-
RecType -->|State| WState["Inline handlers:<br/>SetWindowOrg, SetWindowExt,<br/>SaveDC, RestoreDC, CreatePen,<br/>CreateBrush, CreateFont,<br/>SelectObject, DeleteObject"]
|
|
289
|
-
RecType -->|META_EOF| WDone([Done])
|
|
290
|
-
|
|
291
|
-
WDraw --> Next2[offset += recSize]
|
|
292
|
-
WState --> Next2
|
|
293
|
-
Next2 --> Main
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
---
|
|
297
|
-
|
|
298
|
-
## Deep Dive: How It Works
|
|
299
|
-
|
|
300
|
-
### 1. Header Parsing
|
|
301
|
-
|
|
302
|
-
**EMF** files begin with an `EMR_HEADER` record (type `1`) containing:
|
|
303
|
-
|
|
304
|
-
- **Bounds rectangle** (8–20 bytes): the logical pixel extents of the drawing
|
|
305
|
-
- **Frame rectangle** (24–36 bytes): the physical dimensions in 0.01mm units
|
|
306
|
-
|
|
307
|
-
The parser in `emf-header-parser.ts` tries the bounds first; if they're degenerate (zero width/height), it falls back to the frame rectangle.
|
|
308
|
-
|
|
309
|
-
**WMF** files may have an optional **Aldus Placeable Metafile (APM)** header (magic `0x9AC6CDD7`) at byte 0, which provides bounds and DPI. The standard WMF header follows, starting with a file type (`1` = in-memory, `2` = on-disk).
|
|
310
|
-
|
|
311
|
-
### 2. Canvas Creation & Scaling
|
|
312
|
-
|
|
313
|
-
`emf-canvas-helpers.ts` → `createCanvas()` creates a rendering surface:
|
|
314
|
-
|
|
315
|
-
1. Compute logical dimensions from the metafile bounds
|
|
316
|
-
2. Apply `maxWidth`/`maxHeight` constraints if provided (maintaining aspect ratio)
|
|
317
|
-
3. Clamp to a maximum of **4096×4096** pixels to prevent memory issues
|
|
318
|
-
4. Prefer `OffscreenCanvas` (works in Web Workers); fall back to `HTMLCanvasElement`
|
|
319
|
-
|
|
320
|
-
### 3. GDI Record Replay
|
|
321
|
-
|
|
322
|
-
The GDI replay engine (`emf-record-replay.ts`) scans records sequentially. Each record has an 8-byte header:
|
|
323
|
-
|
|
324
|
-
```
|
|
325
|
-
┌──────────────┬──────────────┐
|
|
326
|
-
│ Record Type │ Record Size │
|
|
327
|
-
│ (uint32) │ (uint32) │
|
|
328
|
-
│ 4 bytes │ 4 bytes │
|
|
329
|
-
├──────────────┴──────────────┤
|
|
330
|
-
│ Record Data │
|
|
331
|
-
│ (recSize - 8 bytes) │
|
|
332
|
-
└─────────────────────────────┘
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
Records are dispatched to three handler modules:
|
|
336
|
-
|
|
337
|
-
| Module | Handles |
|
|
338
|
-
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
339
|
-
| `emf-gdi-state-handlers.ts` | SaveDC, RestoreDC, SetTextColor, SetBkColor, SetBkMode, SetPolyFillMode, SetTextAlign + delegates to transform and object handlers |
|
|
340
|
-
| `emf-gdi-draw-handlers.ts` | Delegates to shape handlers (MoveTo, LineTo, Rectangle, Ellipse, Arc family) and text/bitmap handlers (ExtTextOutW, BitBlt, StretchDIBits) |
|
|
341
|
-
| `emf-gdi-poly-path-handlers.ts` | Polygon, Polyline, PolyBezier (16-bit and 32-bit variants), PolyPolygon, BeginPath/EndPath/FillPath/StrokePath/CloseFigure |
|
|
342
|
-
|
|
343
|
-
### 4. EMF+ Record Replay
|
|
344
|
-
|
|
345
|
-
EMF+ records are embedded inside `EMR_COMMENT` records, identified by the signature `0x2B464D45` ("EMF+" in little-endian). Each EMF+ record has a 12-byte header:
|
|
346
|
-
|
|
347
|
-
```
|
|
348
|
-
┌──────────────┬──────────────┬──────────────┬──────────────┐
|
|
349
|
-
│ Record Type │ Record Flags│ Record Size │ Data Size │
|
|
350
|
-
│ (uint16) │ (uint16) │ (uint32) │ (uint32) │
|
|
351
|
-
│ 2 bytes │ 2 bytes │ 4 bytes │ 4 bytes │
|
|
352
|
-
├──────────────┴──────────────┴──────────────┴──────────────┤
|
|
353
|
-
│ Record Data │
|
|
354
|
-
│ (dataSize bytes) │
|
|
355
|
-
└───────────────────────────────────────────────────────────┘
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
The EMF+ replay engine (`emf-plus-replay.ts`) dispatches to:
|
|
359
|
-
|
|
360
|
-
| Module | Handles |
|
|
361
|
-
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
362
|
-
| `emf-plus-object-parser.ts` | Object definitions: Brush, Pen, Font, Path, Image, StringFormat, ImageAttributes |
|
|
363
|
-
| `emf-plus-draw-handlers.ts` | Shape operations: FillRects, DrawRects, FillEllipse, DrawEllipse, FillPie, DrawPie, DrawArc, DrawLines, FillPolygon |
|
|
364
|
-
| `emf-plus-text-image-handlers.ts` | FillPath, DrawPath, DrawString, DrawDriverString, DrawImage, DrawImagePoints |
|
|
365
|
-
| `emf-plus-state-handlers.ts` | Transform operations (Set/Reset/Multiply/Translate/Scale/Rotate WorldTransform), Save/Restore, Clipping, Rendering hints |
|
|
366
|
-
|
|
367
|
-
### 5. Coordinate Systems
|
|
368
|
-
|
|
369
|
-
The converter manages multiple coordinate mapping systems:
|
|
370
|
-
|
|
371
|
-
```mermaid
|
|
372
|
-
graph TD
|
|
373
|
-
subgraph "EMF GDI Coordinates"
|
|
374
|
-
A["Logical Coordinates<br/>(from metafile)"] -->|"Simple Scale"| B["Canvas Pixels<br/>x = (logX - bounds.left) × sx<br/>y = (logY - bounds.top) × sy"]
|
|
375
|
-
A -->|"Mapping Mode"| C["Window/Viewport Transform<br/>x = ((logX - winOrg.x) / winExt.cx) × vpExt.cx + vpOrg.x"]
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
subgraph "EMF+ GDI+ Coordinates"
|
|
379
|
-
D["GDI+ Logical Units"] -->|"World Transform"| E["Canvas via 6-element<br/>affine matrix [a,b,c,d,e,f]"]
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
subgraph "WMF Coordinates"
|
|
383
|
-
F["WMF Logical Units"] -->|"Window → Canvas"| G["mx(x) = ((x - winOrg.x) / winExt.cx) × canvasW"]
|
|
384
|
-
end
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
**GDI coordinates** (`emf-gdi-coord.ts`) use either simple bounds-based scaling or full window/viewport mapping mode, activated when the metafile sets `SetWindowExtEx`/`SetViewportExtEx`.
|
|
388
|
-
|
|
389
|
-
**EMF+ coordinates** use a 6-element affine transformation matrix `[a, b, c, d, e, f]` applied via `ctx.setTransform()`, supporting rotation, scaling, shearing, and translation.
|
|
390
|
-
|
|
391
|
-
**WMF coordinates** map through closure-based `mx()`/`my()`/`mw()`/`mh()` functions that convert from window space to canvas pixels.
|
|
392
|
-
|
|
393
|
-
### 6. GDI Object Table
|
|
394
|
-
|
|
395
|
-
Both GDI and GDI+ maintain their own object tables — essentially registries of reusable drawing resources:
|
|
396
|
-
|
|
397
|
-
```mermaid
|
|
398
|
-
graph LR
|
|
399
|
-
subgraph "GDI Object Table (Map<number, GdiObject>)"
|
|
400
|
-
P1["Slot 0: Pen<br/>style=0 (solid), width=2, color=#000"]
|
|
401
|
-
B1["Slot 1: Brush<br/>style=0 (solid), color=#ff0000"]
|
|
402
|
-
F1["Slot 2: Font<br/>height=12, weight=700, family=Arial"]
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
subgraph "EMF+ Object Table (Map<number, EmfPlusObject>)"
|
|
406
|
-
PB["Slot 0: plus-brush<br/>color=rgba(0,0,255,1)"]
|
|
407
|
-
PP["Slot 1: plus-pen<br/>color=#000, width=1.5"]
|
|
408
|
-
PH["Slot 3: plus-path<br/>points=[...], types=[...]"]
|
|
409
|
-
PI["Slot 5: plus-image<br/>data=ArrayBuffer, type=Bitmap"]
|
|
410
|
-
PF["Slot 6: plus-font<br/>emSize=14, family=Calibri"]
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
CR["CreatePen / CreateBrushIndirect /<br/>ExtCreateFontIndirectW"] --> P1
|
|
414
|
-
CR --> B1
|
|
415
|
-
CR --> F1
|
|
416
|
-
SO["SelectObject(slot)"] -.->|"Applies to state"| Canvas["Canvas Context"]
|
|
417
|
-
|
|
418
|
-
OBJ["EMFPLUS_OBJECT"] --> PB
|
|
419
|
-
OBJ --> PP
|
|
420
|
-
OBJ --> PH
|
|
421
|
-
OBJ --> PI
|
|
422
|
-
OBJ --> PF
|
|
423
|
-
DRAW["FillPath(pathId) /<br/>DrawString(fontId)"] -.->|"References by ID"| Canvas
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
**GDI objects** are created via `EMR_CREATEPEN`, `EMR_CREATEBRUSHINDIRECT`, `EMR_EXTCREATEFONTINDIRECTW`, etc., and selected into the drawing context with `EMR_SELECTOBJECT`. Stock objects (base index `0x80000000`) provide system defaults like `WHITE_BRUSH`, `BLACK_PEN`, etc.
|
|
427
|
-
|
|
428
|
-
**EMF+ objects** are defined via `EMFPLUS_OBJECT` records with a type/ID pair. Drawing commands reference objects by their slot ID in the lower 8 bits of `recFlags`.
|
|
429
|
-
|
|
430
|
-
### 7. DIB (Bitmap) Decoding
|
|
431
|
-
|
|
432
|
-
Metafiles can contain embedded bitmaps as Device-Independent Bitmaps (DIBs). The decoder pipeline handles:
|
|
433
|
-
|
|
434
|
-
```mermaid
|
|
435
|
-
flowchart TD
|
|
436
|
-
A["DIB Header<br/>(40+ bytes BITMAPINFOHEADER)"] --> B{Compression?}
|
|
437
|
-
B -->|BI_RGB| C["emf-dib-uncompressed.ts<br/>Decode raw pixel rows"]
|
|
438
|
-
B -->|BI_RLE8| D["emf-dib-rle-decoder.ts<br/>Run-length decode 8bpp"]
|
|
439
|
-
B -->|BI_RLE4| E["emf-dib-rle-decoder.ts<br/>Run-length decode 4bpp"]
|
|
440
|
-
B -->|BI_BITFIELDS| F["emf-dib-uncompressed.ts<br/>Custom R/G/B channel masks"]
|
|
441
|
-
|
|
442
|
-
C --> G{Bit Depth}
|
|
443
|
-
F --> G
|
|
444
|
-
G -->|1 bpp| H["Monochrome with colour table"]
|
|
445
|
-
G -->|4 bpp| I["16-colour indexed"]
|
|
446
|
-
G -->|8 bpp| J["256-colour indexed"]
|
|
447
|
-
G -->|16 bpp| K["5-5-5 or bitfield masks"]
|
|
448
|
-
G -->|24 bpp| L["B-G-R byte triplets"]
|
|
449
|
-
G -->|32 bpp| M["B-G-R-A quadruplets"]
|
|
450
|
-
|
|
451
|
-
H --> N["ImageData (RGBA pixels)"]
|
|
452
|
-
I --> N
|
|
453
|
-
J --> N
|
|
454
|
-
K --> N
|
|
455
|
-
L --> N
|
|
456
|
-
M --> N
|
|
457
|
-
|
|
458
|
-
D --> N
|
|
459
|
-
E --> N
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
EMF+ also has its own bitmap format (`emf-plus-bitmap-decoder.ts`) supporting GDI+ pixel formats:
|
|
463
|
-
|
|
464
|
-
- `PixelFormat24bppRGB`
|
|
465
|
-
- `PixelFormat32bppRGB`
|
|
466
|
-
- `PixelFormat32bppARGB`
|
|
467
|
-
- `PixelFormat32bppPARGB` (pre-multiplied alpha, un-multiplied during decode)
|
|
468
|
-
|
|
469
|
-
### 8. Deferred Image Processing
|
|
470
|
-
|
|
471
|
-
Image draws (both GDI `StretchDIBits`/`BitBlt` and EMF+ `DrawImage`/`DrawImagePoints`) that reference bitmaps or embedded metafiles are collected as **deferred images** during the synchronous replay phase. After all records are processed, these are resolved asynchronously:
|
|
472
|
-
|
|
473
|
-
```mermaid
|
|
474
|
-
sequenceDiagram
|
|
475
|
-
participant R as Record Replay (sync)
|
|
476
|
-
participant D as Deferred Queue
|
|
477
|
-
participant P as Post-Processor (async)
|
|
478
|
-
participant C as Canvas Context
|
|
479
|
-
|
|
480
|
-
R->>D: Push image draw {data, position, transform, isMetafile}
|
|
481
|
-
R->>D: Push image draw ...
|
|
482
|
-
R->>D: Push image draw ...
|
|
483
|
-
Note over R: EMR_EOF — replay complete
|
|
484
|
-
|
|
485
|
-
P->>D: Iterate deferred images
|
|
486
|
-
loop For each deferred image
|
|
487
|
-
alt Is embedded metafile
|
|
488
|
-
P->>P: Recursively call convertEmfToDataUrl() / convertWmfToDataUrl()
|
|
489
|
-
P->>C: drawImage(bitmap, dx, dy, dw, dh)
|
|
490
|
-
else Is raster bitmap
|
|
491
|
-
P->>P: createImageBitmap(blob)
|
|
492
|
-
P->>C: drawImage(bitmap, dx, dy, dw, dh)
|
|
493
|
-
end
|
|
494
|
-
end
|
|
495
|
-
Note over C: Canvas now has all images composited
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
This two-phase approach is necessary because `createImageBitmap()` is asynchronous, while the GDI record replay loop is synchronous for performance.
|
|
499
|
-
|
|
500
|
-
### 9. World Transforms
|
|
501
|
-
|
|
502
|
-
EMF+ supports a full 2D affine transformation matrix. The converter maintains and composes transforms using standard matrix multiplication:
|
|
503
|
-
|
|
504
|
-
```
|
|
505
|
-
┌ ┐ ┌ ┐ ┌ ┐
|
|
506
|
-
│ x_out │ │ a b 0 │ │ x │
|
|
507
|
-
│ y_out │ = │ c d 0 │ × │ y │
|
|
508
|
-
│ 1 │ │ e f 1 │ │ 1 │
|
|
509
|
-
└ ┘ └ ┘ └ ┘
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
Stored as a 6-element tuple: `[a, b, c, d, e, f]`
|
|
513
|
-
|
|
514
|
-
Supported transform operations:
|
|
515
|
-
| Operation | Effect |
|
|
516
|
-
|-----------|--------|
|
|
517
|
-
| `SetWorldTransform` | Replace the current matrix |
|
|
518
|
-
| `ResetWorldTransform` | Reset to identity `[1,0,0,1,0,0]` |
|
|
519
|
-
| `MultiplyWorldTransform` | Pre- or post-multiply with another matrix |
|
|
520
|
-
| `TranslateWorldTransform` | Apply translation `(dx, dy)` |
|
|
521
|
-
| `ScaleWorldTransform` | Apply scaling `(sx, sy)` |
|
|
522
|
-
| `RotateWorldTransform` | Apply rotation by angle (degrees) |
|
|
523
|
-
|
|
524
|
-
Save/Restore operations push/pop the world transform onto a stack, allowing nested coordinate spaces.
|
|
525
|
-
|
|
526
|
-
---
|
|
527
|
-
|
|
528
|
-
## Supported Record Types
|
|
529
|
-
|
|
530
|
-
### EMF GDI Records
|
|
531
|
-
|
|
532
|
-
| Category | Records |
|
|
533
|
-
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
534
|
-
| **Header/Control** | `EMR_HEADER`, `EMR_EOF`, `EMR_COMMENT` |
|
|
535
|
-
| **State** | `EMR_SAVEDC`, `EMR_RESTOREDC`, `EMR_SETTEXTCOLOR`, `EMR_SETBKCOLOR`, `EMR_SETBKMODE`, `EMR_SETPOLYFILLMODE`, `EMR_SETTEXTALIGN`, `EMR_SETROP2`, `EMR_SETSTRETCHBLTMODE`, `EMR_SETMITERLIMIT` |
|
|
536
|
-
| **Transforms** | `EMR_SETWINDOWEXTEX`, `EMR_SETWINDOWORGEX`, `EMR_SETVIEWPORTEXTEX`, `EMR_SETVIEWPORTORGEX`, `EMR_SETMAPMODE`, `EMR_SCALEVIEWPORTEXTEX`, `EMR_SCALEWINDOWEXTEX`, `EMR_SETWORLDTRANSFORM`, `EMR_MODIFYWORLDTRANSFORM` |
|
|
537
|
-
| **Objects** | `EMR_CREATEPEN`, `EMR_EXTCREATEPEN`, `EMR_CREATEBRUSHINDIRECT`, `EMR_EXTCREATEFONTINDIRECTW`, `EMR_SELECTOBJECT`, `EMR_DELETEOBJECT` |
|
|
538
|
-
| **Shapes** | `EMR_MOVETOEX`, `EMR_LINETO`, `EMR_RECTANGLE`, `EMR_ROUNDRECT`, `EMR_ELLIPSE`, `EMR_ARC`, `EMR_ARCTO`, `EMR_CHORD`, `EMR_PIE` |
|
|
539
|
-
| **Poly/Path** | `EMR_POLYGON`, `EMR_POLYLINE`, `EMR_POLYBEZIER`, `EMR_POLYBEZIERTO`, `EMR_POLYLINETO`, `EMR_POLYGON16`, `EMR_POLYLINE16`, `EMR_POLYBEZIER16`, `EMR_POLYBEZIERTO16`, `EMR_POLYLINETO16`, `EMR_POLYPOLYGON`, `EMR_POLYPOLYGON16` |
|
|
540
|
-
| **Path Ops** | `EMR_BEGINPATH`, `EMR_ENDPATH`, `EMR_CLOSEFIGURE`, `EMR_FILLPATH`, `EMR_STROKEANDFILLPATH`, `EMR_STROKEPATH`, `EMR_SELECTCLIPPATH` |
|
|
541
|
-
| **Text** | `EMR_EXTTEXTOUTW` |
|
|
542
|
-
| **Bitmap** | `EMR_BITBLT`, `EMR_STRETCHDIBITS` |
|
|
543
|
-
| **Clipping** | `EMR_INTERSECTCLIPRECT` |
|
|
544
|
-
|
|
545
|
-
### EMF+ Records
|
|
546
|
-
|
|
547
|
-
| Category | Records |
|
|
548
|
-
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
549
|
-
| **Control** | `Header`, `EndOfFile`, `GetDC` |
|
|
550
|
-
| **Objects** | `Object` (Brush, Pen, Path, Font, Image, StringFormat, ImageAttributes) |
|
|
551
|
-
| **Shapes** | `FillRects`, `DrawRects`, `FillEllipse`, `DrawEllipse`, `FillPie`, `DrawPie`, `DrawArc`, `DrawLines`, `FillPolygon` |
|
|
552
|
-
| **Path** | `FillPath`, `DrawPath` |
|
|
553
|
-
| **Text** | `DrawString`, `DrawDriverString` |
|
|
554
|
-
| **Images** | `DrawImage`, `DrawImagePoints` |
|
|
555
|
-
| **Transforms** | `SetWorldTransform`, `ResetWorldTransform`, `MultiplyWorldTransform`, `TranslateWorldTransform`, `ScaleWorldTransform`, `RotateWorldTransform`, `SetPageTransform` |
|
|
556
|
-
| **State** | `Save`, `Restore`, `BeginContainerNoParams`, `EndContainer` |
|
|
557
|
-
| **Clipping** | `ResetClip`, `SetClipRect`, `SetClipPath`, `SetClipRegion` |
|
|
558
|
-
| **Hints** | `SetAntiAliasMode`, `SetTextRenderingHint`, `SetInterpolationMode`, `SetPixelOffsetMode`, `SetCompositingQuality` |
|
|
559
|
-
|
|
560
|
-
### WMF Records
|
|
561
|
-
|
|
562
|
-
| Category | Records |
|
|
563
|
-
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
564
|
-
| **Control** | `META_EOF` |
|
|
565
|
-
| **State** | `META_SAVEDC`, `META_RESTOREDC`, `META_SETWINDOWORG`, `META_SETWINDOWEXT`, `META_SETTEXTCOLOR`, `META_SETBKCOLOR`, `META_SETBKMODE`, `META_SETPOLYFILLMODE`, `META_SETTEXTALIGN`, `META_SETROP2` |
|
|
566
|
-
| **Objects** | `META_CREATEPENINDIRECT`, `META_CREATEBRUSHINDIRECT`, `META_CREATEFONTINDIRECT`, `META_SELECTOBJECT`, `META_DELETEOBJECT` |
|
|
567
|
-
| **Shapes** | `META_MOVETO`, `META_LINETO`, `META_RECTANGLE`, `META_ROUNDRECT`, `META_ELLIPSE`, `META_ARC`, `META_PIE`, `META_CHORD` |
|
|
568
|
-
| **Poly** | `META_POLYGON`, `META_POLYLINE`, `META_POLYPOLYGON` |
|
|
569
|
-
| **Text** | `META_TEXTOUT`, `META_EXTTEXTOUT` |
|
|
570
|
-
|
|
571
|
-
---
|
|
572
|
-
|
|
573
|
-
## File Structure Reference
|
|
574
|
-
|
|
575
|
-
```
|
|
576
|
-
src/
|
|
577
|
-
├── index.ts # Barrel re-export of public API
|
|
578
|
-
├── emf-converter.ts # Public API: convertEmfToDataUrl, convertWmfToDataUrl
|
|
579
|
-
├── emf-types.ts # All TypeScript type definitions & state factories
|
|
580
|
-
├── emf-constants.ts # Numeric constants for EMF/EMF+/WMF record types
|
|
581
|
-
├── emf-logging.ts # Debug logging (toggle via DEBUG_EMF flag)
|
|
582
|
-
├── emf-color-helpers.ts # COLORREF → hex, ARGB → rgba() conversions
|
|
583
|
-
├── emf-canvas-helpers.ts # Canvas creation, styling, stock objects, UTF-16 reading
|
|
584
|
-
├── emf-header-parser.ts # EMF & WMF binary header parsers
|
|
585
|
-
│
|
|
586
|
-
├── emf-record-replay.ts # Main EMF GDI record loop & dispatcher
|
|
587
|
-
├── emf-gdi-state-handlers.ts # GDI state: save/restore, color/mode settings
|
|
588
|
-
├── emf-gdi-transform-handlers.ts # GDI coordinate system & world transform records
|
|
589
|
-
├── emf-gdi-object-handlers.ts # GDI object creation, selection, deletion
|
|
590
|
-
├── emf-gdi-draw-handlers.ts # GDI draw dispatcher (shapes + text/bitmap)
|
|
591
|
-
├── emf-gdi-draw-shapes.ts # GDI shape drawing: lines, rects, ellipses, arcs
|
|
592
|
-
├── emf-gdi-draw-text-bitmap.ts # GDI text output & bitmap block transfers
|
|
593
|
-
├── emf-gdi-coord.ts # GDI coordinate mapping (gmx/gmy/gmw/gmh)
|
|
594
|
-
├── emf-gdi-poly-path-handlers.ts # GDI polygon, polyline, bezier, path operations
|
|
595
|
-
├── emf-gdi-polypolygon-helpers.ts # PolyPolygon specialised helpers
|
|
596
|
-
│
|
|
597
|
-
├── emf-plus-replay.ts # EMF+ record loop & dispatcher
|
|
598
|
-
├── emf-plus-object-parser.ts # EMF+ OBJECT record → type-specific parsers
|
|
599
|
-
├── emf-plus-object-complex.ts # Complex object parsers: Pen, Image, Font
|
|
600
|
-
├── emf-plus-draw-handlers.ts # EMF+ shape fill/draw handlers
|
|
601
|
-
├── emf-plus-text-image-handlers.ts # EMF+ text, image, and path-based drawing
|
|
602
|
-
├── emf-plus-state-handlers.ts # EMF+ transforms, save/restore, clipping
|
|
603
|
-
├── emf-plus-path.ts # EMF+ path parsing & canvas replay
|
|
604
|
-
├── emf-plus-read-helpers.ts # EMF+ compressed/float rect & point readers
|
|
605
|
-
├── emf-plus-bitmap-decoder.ts # EMF+ GDI+ pixel format → BMP decoder
|
|
606
|
-
│
|
|
607
|
-
├── emf-dib-decoder.ts # DIB header parsing & format dispatcher
|
|
608
|
-
├── emf-dib-rle-decoder.ts # RLE4/RLE8 bitmap decompression
|
|
609
|
-
├── emf-dib-uncompressed.ts # Uncompressed & bitfield DIB row decoder
|
|
610
|
-
│
|
|
611
|
-
├── wmf-replay.ts # WMF record loop & dispatcher
|
|
612
|
-
├── wmf-draw-handlers.ts # WMF drawing record handlers
|
|
613
|
-
│
|
|
614
|
-
└── index.test.ts # Test suite
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
---
|
|
618
|
-
|
|
619
|
-
## Limitations
|
|
620
|
-
|
|
621
|
-
- **No EMF+ region objects** — `EMFPLUS_OBJECTTYPE_REGION` is not parsed. Region objects define complex clipping areas via boolean operations on shapes; GDI+ regions have no direct Canvas 2D equivalent.
|
|
622
|
-
- **Gradient brushes are simplified** — GDI+ `LinearGradient` and `PathGradient` brush types extract only the primary colour rather than rendering full multi-stop gradient fills. The Canvas 2D API does not have a direct equivalent for GDI+ path gradients.
|
|
623
|
-
- **No raster operations (ROP)** — `SetROP2` is acknowledged but GDI raster operation blending modes (XOR, NOT, AND, etc.) have no direct Canvas 2D equivalent and are not applied.
|
|
624
|
-
- **Limited clipping** — `IntersectClipRect` and `SelectClipPath` are supported. Complex GDI region clipping (combining multiple regions with union/intersect/exclude operations) is not, as Canvas 2D only supports a single clip path.
|
|
625
|
-
- **Maximum canvas size** — Output is clamped to 4096×4096 pixels to prevent excessive memory usage from malformed or very large metafiles.
|
|
626
|
-
- **Maximum record count** — Processing stops after 50,000 records (EMF/WMF) or 100,000 records (EMF+) as a safety limit to prevent infinite loops from malformed files.
|
|
627
|
-
- **Font rendering** — Text is rendered using the browser's font engine with CSS font matching, so glyph metrics and kerning may differ from the original Windows GDI text rendering.
|
|
628
|
-
- **No EMF spool records** — Print spooler–specific record types are not handled. These are only present in EMF files generated by the Windows print subsystem and are not relevant for embedded metafiles in Office documents.
|
|
629
|
-
- **Canvas API required** — The library needs either `OffscreenCanvas` (for Web Worker support) or `HTMLCanvasElement` to be available in the runtime environment. Pure Node.js without a canvas polyfill is not supported.
|