A Visual Studio Code extension for previewing WMF (Windows Metafile), EMF (Enhanced Metafile) and EMF+ images directly in the editor.
Try it online (no install): https://congduan.github.io/WmfEmfViewer/ — drag & drop a .wmf / .emf file, everything runs locally in your browser.
Use the engine in your code: npm install wmf-emf-renderer — the same parser/renderer as a zero-dependency library for Node.js and browsers (details).
- Open
.wmf/.emffiles in a custom editor with built-in preview - Automatic detection of WMF, Placeable WMF, EMF and EMF+
- High-quality Canvas 2D rendering of vector graphics, text and GDI objects
- Explorer context menu integration
- Zero runtime dependencies
Formats
| Format | Detection | Rendering |
|---|---|---|
| WMF (standard) | ✅ | ✅ |
| Placeable WMF (22-byte prefix header) | ✅ | ✅ |
| EMF | ✅ | ✅ |
| EMF+ (within EMF, incl. dual-mode) | ✅ | ✅ |
Coordinate systems & transforms
- Window/Viewport mapping with all
SetMapModeunits (MM_TEXT,MM_LO/HIMETRIC,MM_LO/HIENGLISH,MM_TWIPS,MM_ISOTROPIC,MM_ANISOTROPIC) - World transform (
SetWorldTransform/ModifyWorldTransformwithMWT_IDENTITY,MWT_LEFTMULTIPLY,MWT_RIGHTMULTIPLY,MWT_SET), plus EMF+ world/container transforms (translate / rotate / scale / multiply / reset) - DC state stack (
SaveDC/RestoreDC), clipping viaIntersectClipRect/ExcludeClipRect/OffsetClipRgn/SelectClipPathand EMF+ clip rect/path/region
Drawing primitives (WMF + EMF)
- Lines & curves:
MoveToEx,LineTo,Polyline(To),Bezier(To),PolyDraw,Arc,ArcTo,AngleArc,Chord,Pie(all in 16-bit and 32-bit variants where applicable) - Shapes:
Rectangle,RoundRect,Ellipse,Polygon,PolyPolygon,PolyPolyline - Paths:
BeginPath/EndPath,FillPath,StrokePath,StrokeAndFillPath,FlattenPath,WidenPath,AbortPath - Fills:
ExtFloodFill,SetPixelV, poly-fill mode (ALTERNATE/WINDING), ROP2 background/foreground mix, arc direction
GDI objects
- Pens:
CreatePen,ExtCreatePen(cosmetic/geometric, dash styles), brushes:CreateBrushIndirect(solid / null / hatched),CreateMonoBrush, palettes (CreatePalette,SetPaletteEntries,ResizePalette,RealizePalette), fonts:ExtCreateFontIndirectW - Object handles stored by handle map (producer-assigned, reusable after delete); text attributes (
SetTextColor,SetBkColor,SetBkMode,SetTextAlign,SetMiterLimit,SetMapperFlags, color adjustment)
Raster operations / bitmaps
BitBlt,StretchBlt,StretchDIBits,AlphaBlend,TransparentBlt- DIB decoding: 1/4/8/16/24/32 bpp,
BI_RGB/BI_RLE8/BI_RLE4, top-down and bottom-up, color-key transparency and alpha channels
Text
TextOut,ExtTextOutA/W,EMR_SMALLTEXTOUT,EMR_TEXT(withoffDxinter-character spacing), EMF+DrawString/DrawDriverString
EMF+ objects & properties (GDI+ surface style)
- Pens, brushes (solid / texture / hatch / path-gradient / linear-gradient), images, fonts, string formats, regions
- Rendering properties: anti-alias, text rendering hint, compositing mode/quality, interpolation mode, pixel offset mode, page transform, containers (
Begin/EndContainer), save/restore
Known limitations
- Records embedded as EMF+ inside
EMR_GDI_COMMENTof a pure-EMF file are only partially rendered - Some region records (
EMR_FILLRGN/EMR_FRAMERGN) and advanced region clipping fall back to approximations - Font metrics are approximated (no system font matching); text may deviate slightly in layout-sensitive metafiles
npm install # install dependencies
npm run build # build browser bundle + npm package + compile TypeScriptPress F5 in VS Code to launch the Extension Development Host, then open a .wmf or .emf file from test_files/.
The same rendering engine also ships as a standalone static website — drag & drop a .wmf / .emf file and preview it directly in the browser. All parsing and rendering happen locally; files are never uploaded.
Online: https://congduan.github.io/WmfEmfViewer/ (GitHub Pages)
npm run build:bundle # generates website/metafileParser.browser.js
npx serve website # or: python3 -m http.server 8000 --directory websiteThen open http://localhost:3000 (or the port shown). Features:
- Drag & drop or click to open WMF / Placeable WMF / EMF / EMF+
- Built-in sample files (shape demo, MathType formula, small icon) — try it without any file at hand
- Canvas / SVG rendering modes, zoom controls (buttons or
Ctrl+ wheel) - Export to PNG / SVG / PDF
The deploy-website.yml workflow builds and publishes website/ automatically on every push to master. One-time setup: Settings → Pages → Source: GitHub Actions.
The core parsing/rendering engine is extracted into a standalone module, published as a zero-dependency npm package. It shares the same source code as the extension (packages/wmf-emf-renderer is built from src/modules + src/utils) and can be used in any Node.js or browser project.
Full API documentation: packages/wmf-emf-renderer/README.md
npm install wmf-emf-rendererRender to SVG (headless, works in Node.js without any DOM):
import { renderToSvg } from 'wmf-emf-renderer';
import { readFileSync, writeFileSync } from 'fs';
const svg = renderToSvg(readFileSync('drawing.wmf'), { viewWidth: 1024 });
writeFileSync('drawing.svg', svg);Render to a browser canvas:
import { renderToContext } from 'wmf-emf-renderer';
const data = new Uint8Array(await file.arrayBuffer());
const ctx = document.querySelector('canvas').getContext('2d');
const info = renderToContext(data, ctx, { viewWidth: 800, viewHeight: 600 });
console.log(`Rendered ${info.fileType} at ${info.width}x${info.height}`);Render with node-canvas (server-side PNG export):
const { createCanvas } = require('canvas');
const { renderToContext } = require('wmf-emf-renderer');
const canvas = createCanvas(800, 600);
renderToContext(fs.readFileSync('chart.emf'), canvas.getContext('2d'));
fs.writeFileSync('chart.png', canvas.toBuffer('image/png'));Parse only (inspect records without rendering):
import { parseMetafile } from 'wmf-emf-renderer';
const { fileType, records, error } = parseMetafile(bytes);
console.log(fileType, records.length, 'records');| Function | Description |
|---|---|
detectFileType(data) |
Returns 'wmf' | 'placeable-wmf' | 'emf' | 'emf+' | 'unknown'. |
parseMetafile(data) |
Parses binary data into { fileType, header, records, error? }. Never throws. |
renderToSvg(data, options?) |
Renders to a complete SVG document string. |
renderToContext(data, ctx, options?) |
Renders onto any Canvas 2D context; returns { width, height, fileType }. |
createDrawer(ctx, fileType) |
Creates the format-appropriate drawer for custom rendering flows. |
setDebugEnabled(true) |
Enables verbose parser/drawer logging (off by default). |
data accepts Uint8Array, ArrayBuffer, or number[]; options: { viewWidth = 800, viewHeight = 600 } (image is fit inside while preserving aspect ratio). Low-level classes (WmfParser, EmfParser, EmfPlusParser, SvgContext, etc.) are also exported — see the package README.
cd packages/wmf-emf-renderer
npm run build # rebuild index.js / index.mjs / index.d.ts from ../../src
npm test # smoke test: parse + render sample files| Command | Description |
|---|---|
npm run build |
Full build (browser bundle + npm package + TypeScript compile) |
npm run build:bundle |
Build the browser/website bundle only |
npm run build:lib |
Build the npm package bundle only |
npm run compile |
Compile TypeScript to out/ |
npm run watch |
Compile in watch mode |
npm run lint |
Lint src/ and scripts/ sources |
npm run typecheck |
Type check the JavaScript engine (checkJs) |
npm run test |
Smoke tests (bundle) + snapshot regression (src) |
npm run test:lib |
Smoke test the npm package artifact |
npm run package |
Package the extension into a .vsix file |
packages/
└── wmf-emf-renderer/ # Standalone npm library extracted from src/
scripts/
└── build-bundles.js # esbuild: browser IIFE bundle + npm CJS bundle
src/
├── commands/ # VSCode command implementations
├── modules/ # Core logic
│ ├── parsers/ # WMF / EMF / EMF+ binary parsers
│ ├── drawers/ # Canvas 2D renderers
│ └── svgContext.js # Canvas2D-compatible SVG-emitting context
├── providers/ # VSCode custom editor provider
├── resources/ # Webview HTML template, extension icon
├── utils/ # Constants, coordinate transforms, GDI object manager, etc.
├── browser.js # Browser bundle entry point
└── extension.ts # Extension entry point
See docs/PROJECT_STRUCTURE.md for the full architecture documentation.
- Separation of concerns: TypeScript extension layer, JavaScript parsing/rendering layer
- Format-specific modules: each format has a dedicated parser and drawer
- Shared base classes:
BaseParser/BaseDrawerprovide common functionality - Browser bundling:
scripts/build-bundles.js(esbuild) produces a single-file browser bundle for the webview and a CommonJS bundle for the npm package
test/runTest.js— smoke tests against the built browser bundletest/snapshot-test.js— full-corpus snapshot regression againstsrc/test_files/— sample WMF/EMF corpus
- [MS-WMF] Windows Metafile Format —
docs/[MS-WMF].pdf - [MS-EMF] Enhanced Metafile Format —
docs/[MS-EMF].pdf - [MS-EMFPLUS] Enhanced Metafile Plus Format —
docs/[MS-EMFPLUS].pdf
The Marketplace detail page uses MARKETPLACE.md, configured via the package / publish scripts in package.json.
