English | 简体中文
A pure front-end rendering library for Windows metafiles — classic EMF, EMF+, and WMF. It renders binary metafile content to a Canvas in the browser and exports PNG Blobs or data URLs.
Try the live demo → Drop in an EMF/WMF file or pick a built-in sample; files never leave your machine.
- A browser-side EMF/EMF+/WMF rendering engine
- A unified public API:
renderEmf,renderEmfToBlob,renderEmfToDataUrl, plus the isomorphicrenderWmffamily - Centered on a render-result object rather than a single export format
- Browser main thread (
document.createElement('canvas')) - Worker contexts with
OffscreenCanvassupport
Not currently promised:
- Node.js environments without a native Canvas
- General-purpose rendering in non-browser hosts
The package ships as ESM only.
pnpm add emf-renderer
# or
npm install emf-rendererimport { renderEmf, renderEmfToBlob, renderEmfToDataUrl } from 'emf-renderer'
async function main() {
const buffer = await fetch('/path/to/example.emf').then((response) => response.arrayBuffer())
const result = await renderEmf(buffer, {
width: 800,
height: 600,
trimTransparentBounds: true
})
const canvas = result.canvas
const blob = await result.toBlob()
const dataUrl = await result.toDataUrl()
const blob2 = await renderEmfToBlob(buffer)
const dataUrl2 = await renderEmfToDataUrl(buffer)
document.body.append(canvas)
console.log({ blob, dataUrl, blob2, dataUrl2 })
}
main()Input:
buffer:ArrayBuffer | Uint8Arrayoptions.width?: output width overrideoptions.height?: output height overrideoptions.trimTransparentBounds?: trim transparent borders from the output
Returns a render-result object:
canvas:HTMLCanvasElement | OffscreenCanvaswidth: numberheight: numbermeta.hasEmfPlus: booleanmeta.records: number[]meta.warnings: string[]meta.unsupported: string[]meta.diagnostics:RenderDiagnostic[]— structured per-record diagnostics; each entry carrieslevel(e.g."warning"/"unsupported"),code, andmessage, optionally withsource/recordType/recordOffset/objectId/capability. Degraded or unsupported records are reported here explicitly instead of being silently approximated.toBlob(): Promise<Blob>toDataUrl(): Promise<string>
- Convenience wrapper, equivalent to
await (await renderEmf(buffer, options)).toBlob()
- Convenience wrapper, equivalent to
await (await renderEmf(buffer, options)).toDataUrl()
- Isomorphic to the
renderEmffamily; the input is a WMF (Windows Metafile) binary - Returns the same render-result shape (
canvas,meta,toBlob,toDataUrl) optionssupportswidth,height, andtrimTransparentBounds
import { renderWmf, renderWmfToDataUrl } from 'emf-renderer'
const result = await renderWmf(wmfBuffer)
const dataUrl = await renderWmfToDataUrl(wmfBuffer)- Both classic EMF and EMF+ have working render pipelines
- Supports common primitives, paths, clipping, text, bitmaps, and a range of EMF+ effects
- Protocol gaps remain (some classic record semantics, complex EMF+ text layout, incomplete bitmap format coverage)
meta.unsupported = []does not mean pixel-perfect parity with GDI/GDI+; visual differences can still come from text layout, gradient/interpolation strategies, and effect implementation details
pnpm dev:demoThen open http://127.0.0.1:4173/demo/.
To load a sample shipped with the repository:
http://127.0.0.1:4173/demo/?sample=synthetic%2Fclassic%2Fsynthetic-classic-shapes.emf