emfsvg converts between Microsoft Enhanced Metafile (EMF) and SVG
in both directions. It is a clean-room Ruby replacement for the FFI
wrapper around the GPLv2 libemf2svg C library.
Status
-
EMF → SVG: 208/208 fixtures (100%) semantically match libemf2svg. Verified with the lossy
SvgMatcherat 0.1px tolerance. 79/208 are byte-identical to libemf2svg’s output. -
SVG → EMF: functional for shapes, transforms, clips, text, and images. Synthetic SVGs round-trip byte-equal through
SVG → EMF → SVG. The lossy matcher and golden-master tooling are in place for iterative fidelity improvement.
Synopsis
require "emfsvg"
# EMF → SVG
svg = Emfsvg.from_file("image.emf")
svg = Emfsvg.from_bytes(emf_bytes, width: 400, height: 300)
# SVG → EMF
emf = Emfsvg.from_svg_file("drawing.svg")
emf = Emfsvg.from_svg(svg_string)
Command-line tool
emfsvg convert INPUT.emf OUTPUT.svg [options]
emfsvg to-emf INPUT.svg OUTPUT.emf
emfsvg version
emfsvg help
Options (convert):
-w, --width PIXELS Override viewport width
-h, --height PIXELS Override viewport height
-n, --namespace NAME XML namespace prefix (e.g. svg)
-v, --verbose Print record-by-record dispatch to STDERR
--emf-plus Render EMF+ records instead of EMF fallback
Architecture
EMF → SVG |
|
SVG → EMF |
SVG bytes → Nokogiri → |
Both directions share:
-
Emfsvg::DeviceContext/ObjectTable/TransformStack— GDI state -
Emf::*— theemfgem’s wire record classes + serializer -
Emfsvg::SvgMatcher(spec/support) — lossy semantic SVG comparison
SVG → EMF handler registry (OCP)
Adding a new SVG element type means writing a handler class and registering it — no existing handler code changes:
# lib/emfsvg/translation/handlers/my_element_handler.rb
class MyElementHandler
include SharedGdi
def self.call(element, context)
# emit EMF records via context.emit(...)
end
end
# Register in EmfRenderer.register_default_handlers!
DEFAULT_REGISTRY.register(Svg::Elements::MyElement, Handlers::MyElementHandler)
Lossy SVG matcher
Emfsvg::SvgMatcher compares two SVG documents semantically:
-
Float tolerance (configurable, default 1.0px)
-
Auto-generated ID canonicalisation (
clip-N,img-N) -
libemf2svg typo normalisation (
0.00 00→0.0000) -
<defs>hoisting + multiset comparison -
<g transform><X/></g>≡<X transform/>flattening
Used by:
-
spec/round_trip_spec.rb— SVG→EMF→SVG round-trip verification -
spec/fidelity_regression_spec.rb— EMF→SVG fidelity vs libemf2svg -
scripts/run_round_trip.rb— full golden-master round-trip scan -
scripts/run_emf_svg_compare.rb— full fidelity scan vs libemf2svg
Installation
gem install emfsvg
Or in your Gemfile:
gem "emfsvg", "~> 0.1"
gem "emf", "~> 0.1"
Requires Ruby >= 3.1.
Development
git clone https://github.com/claricle/emfsvg
cd emfsvg
bundle install
bundle exec rspec
bundle exec rubocop
Developer scripts
|
EMF→SVG fidelity vs libemf2svg (208 fixtures, 100% pass at 0.1px) |
|
SVG→EMF→SVG round-trip scan |
|
Element-by-element diff between input and round-trip output |
|
Per-fixture failure reasons for round-trip scan |
|
Regenerate libemf2svg + emfsvg golden baselines |
|
Legacy compat matrix vs libemf2svg |
Dependencies
-
emf(~> 0.1) — EMF binary parser, domain model, and serializer -
nokogiri(~> 1.16) — SVG parser -
fontisan— cmap parsing for glyph-index text -
libpng(~> 1.6) — PNG encode/decode for image round-trip -
base64,bigdecimal— stdlib
License
BSD-2-Clause. See LICENSE.txt.