@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.
Files changed (47) hide show
  1. package/dist/index.js +3 -1
  2. package/dist/index.mjs +3 -1
  3. package/dist/viewer/index.js +3 -1
  4. package/dist/viewer/index.mjs +3 -1
  5. package/package.json +2 -9
  6. package/node_modules/emf-converter/LICENSE +0 -21
  7. package/node_modules/emf-converter/README.md +0 -629
  8. package/node_modules/emf-converter/dist/index.d.mts +0 -86
  9. package/node_modules/emf-converter/dist/index.d.ts +0 -86
  10. package/node_modules/emf-converter/dist/index.js +0 -4257
  11. package/node_modules/emf-converter/dist/index.mjs +0 -4253
  12. package/node_modules/emf-converter/package.json +0 -53
  13. package/node_modules/mtx-decompressor/LICENSE +0 -373
  14. package/node_modules/mtx-decompressor/README.md +0 -271
  15. package/node_modules/mtx-decompressor/dist/index.d.mts +0 -84
  16. package/node_modules/mtx-decompressor/dist/index.d.ts +0 -84
  17. package/node_modules/mtx-decompressor/dist/index.js +0 -1532
  18. package/node_modules/mtx-decompressor/dist/index.mjs +0 -1528
  19. package/node_modules/mtx-decompressor/package.json +0 -44
  20. package/node_modules/pptx-viewer-core/LICENSE +0 -21
  21. package/node_modules/pptx-viewer-core/NOTICE +0 -16
  22. package/node_modules/pptx-viewer-core/README.md +0 -1294
  23. package/node_modules/pptx-viewer-core/dist/SvgExporter-BtZczTlB.d.ts +0 -557
  24. package/node_modules/pptx-viewer-core/dist/SvgExporter-D4mBWJHE.d.mts +0 -557
  25. package/node_modules/pptx-viewer-core/dist/cli/index.d.mts +0 -150
  26. package/node_modules/pptx-viewer-core/dist/cli/index.d.ts +0 -150
  27. package/node_modules/pptx-viewer-core/dist/cli/index.js +0 -0
  28. package/node_modules/pptx-viewer-core/dist/cli/index.mjs +0 -0
  29. package/node_modules/pptx-viewer-core/dist/converter/index.d.mts +0 -48
  30. package/node_modules/pptx-viewer-core/dist/converter/index.d.ts +0 -48
  31. package/node_modules/pptx-viewer-core/dist/converter/index.js +0 -0
  32. package/node_modules/pptx-viewer-core/dist/converter/index.mjs +0 -0
  33. package/node_modules/pptx-viewer-core/dist/index.d.mts +0 -12744
  34. package/node_modules/pptx-viewer-core/dist/index.d.ts +0 -12744
  35. package/node_modules/pptx-viewer-core/dist/index.js +0 -66894
  36. package/node_modules/pptx-viewer-core/dist/index.mjs +0 -66420
  37. package/node_modules/pptx-viewer-core/dist/presentation-nZxgWvXq.d.mts +0 -5645
  38. package/node_modules/pptx-viewer-core/dist/presentation-nZxgWvXq.d.ts +0 -5645
  39. package/node_modules/pptx-viewer-core/dist/signature-inspection-status-BCUpfCQh.d.mts +0 -220
  40. package/node_modules/pptx-viewer-core/dist/signature-inspection-status-BCUpfCQh.d.ts +0 -220
  41. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.mts +0 -177
  42. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.ts +0 -177
  43. package/node_modules/pptx-viewer-core/dist/signature-node/index.js +0 -1206
  44. package/node_modules/pptx-viewer-core/dist/signature-node/index.mjs +0 -1143
  45. package/node_modules/pptx-viewer-core/dist/text-operations-DCTGMltY.d.mts +0 -134
  46. package/node_modules/pptx-viewer-core/dist/text-operations-DYmhoi7U.d.ts +0 -134
  47. 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.