Parse and render WMF, EMF, and EMF+
Metafile Renderer is a TypeScript parser and renderer for Windows Metafile formats. Parsing has no runtime dependency. PNG rendering uses CanvasKit/Skia and bundled metric-compatible fonts, so the same input can render consistently in a browser or Node.js.
Choose an operating mode
| Mode | Dependencies | Result |
|---|---|---|
| Parse only | TypeScript core | Records and decoded fields |
| Browser render | TypeScript core, CanvasKit WASM, bundled fonts | PNG Blob |
| Node.js render | TypeScript core, CanvasKit, bundled or Windows system fonts | PNG Uint8Array |
The package is currently private and is not published to a public npm registry. Pin an approved source commit or release artifact rather than copying individual source files.
Render in Node.js
import {readFile, writeFile} from 'node:fs/promises';
import {initCanvasKitNode} from './src/render/canvaskit-node.ts';
import {CanvasKitRenderer} from './src/render/canvaskit-renderer.ts';
import {parseWmf} from './src/wmf/parse.ts';
import {playWmf} from './src/wmf/playback.ts';
import {playEmf} from './src/emf/playback.ts';
const dependencies = await initCanvasKitNode();
async function render(inputPath, outputPath, format) {
const bytes = new Uint8Array(await readFile(inputPath));
const renderer = new CanvasKitRenderer(dependencies);
if (format === 'emf') playEmf(bytes, renderer, 1200, 800);
else playWmf(parseWmf(bytes), renderer, 1200, 800);
const png = renderer.finish();
if (!png) throw new Error('The metafile did not produce visible output.');
await writeFile(outputPath, png);
}
await render('diagram.emf', 'diagram.png', 'emf');
Initialize CanvasKit and fonts once, then reuse the returned dependencies. Create a Renderer for each output operation.
Parse without rendering
Parsing does not require WASM or fonts and is suitable for inspection, indexing, and security preflight checks.
import {parseWmf} from './src/wmf/parse.ts';
import {parseEmf} from './src/emf/parse.ts';
import {enumerateEmf} from './src/emf/parse-plus.ts';
const parsedWmf = parseWmf(wmfBytes);
const parsedEmf = parseEmf(emfBytes);
for (const record of enumerateEmf(emfBytes)) {
console.log(record.type, record.fields);
}
Render in the browser
The supplied browser entry exposes window.MetafileFE. Files remain in the browser and are not uploaded.
<input id="source" type="file" accept=".wmf,.emf" />
<img id="preview" alt="Rendered metafile" />
<script>window.process = window.process || {env: {}};</script>
<script src="/vendor/canvaskit/canvaskit.js"></script>
<script type="module" src="/frontend/dist/main.js"></script>
<script type="module">
await MetafileFE.init((done, total) => console.log(`fonts ${done}/${total}`));
source.addEventListener('change', async () => {
const bytes = new Uint8Array(await source.files[0].arrayBuffer());
const result = MetafileFE.render(bytes);
preview.src = URL.createObjectURL(result.blob);
console.log(result.format, result.width, result.height, result.ms);
});
</script>
Performance and deployment
The complete font set includes large CJK fonts. Loading every font up front can add tens of megabytes to the first visit. For a production application, cache CanvasKit and fonts with immutable URLs and load Korean, Japanese, Simplified Chinese, and Traditional Chinese families only when the document requires them.
Limit input byte size and output dimensions before rendering. The browser API caps dimensions, but an application should also apply its own memory and execution-time limits. Do not treat a successfully parsed metafile as trusted active content.
Troubleshooting
| Symptom | Check |
|---|---|
CanvasKitInit is missing | Load canvaskit.js before the frontend module |
| WASM fails to load | Verify /vendor/canvaskit/canvaskit.wasm, MIME type, and CSP |
| Text is missing or substituted | Confirm the required font family was loaded |
| Browser starts slowly | Reduce the initial font set and enable long-lived asset caching |
| Output size is unexpected | Pass explicit dimensions or derive the native size from the metafile header |
Open the live Metafile Renderer Playground to upload a WMF or EMF file and compare browser and reference rendering after the renderer image and its large font assets are deployed.