Project

emfsvg

0.0
The project is in a healthy, maintained state
Convert Microsoft Enhanced Metafile (EMF) to SVG and back. Consumes the `emf` gem's domain model. Replaces the FFI wrapper around libemf2svg with a pure-Ruby transformer.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 0
~> 0.1
~> 1.6
~> 1.9.362
 Project Readme

emfsvg: pure-Ruby EMF ↔ SVG transformer

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

Both conversion directions are implemented. Compatibility is enforced by strict byte comparisons against the pinned corpus and committed SVG artifacts; the lossy matcher is a separate diagnostic tool, not the parity acceptance gate.

Current integration uses a temporary emf Git pin. The image1 round trip now matches exactly, and a deliberately pinned C build reproduces all 208 committed C goldens byte for byte. The original historical builder remains unknown. Full Ruby corpus parity, corrupted-input resource behavior, native backend, and a containing released emf package remain unqualified. See the compatibility matrix for contracts and current evidence, and fixture setup before running corpus-dependent checks.

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

Emf::Model::Metafile → Emfsvg::Renderer → Emfsvg::Visitors::EmrVisitor → Emfsvg::SvgBuilder

SVG → EMF

SVG bytes → leptris → Emfsvg::Svg::Document → Emfsvg::Translation::EmfRenderer → Emf::Model::Metafile → Emf.serialize

Both directions share:

  • Emfsvg::DeviceContext / ObjectTable / TransformStack — GDI state

  • Emf::* — the emf gem’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

The checkout’s Gemfile temporarily pins emf to 5c2c019138bab6f3c67f7602e8b410ba5a67c429 for model integration. This is separate from the external fixture corpus revision and does not establish compatibility with a released emf package.

Follow the explicit corpus setup to materialize EMF_FIXTURE_ROOT at the manifest’s exact revision. Missing roots, changed inventories, or changed fixture bytes fail setup; there is no sibling-checkout or developer-machine fallback.

bundle exec rspec spec/parity_manifest_spec.rb spec/parity_corpus_spec.rb
bundle exec rspec spec/golden_spec.rb spec/round_trip_golden_spec.rb spec/compat_with_libemf2svg_spec.rb --dry-run
bundle exec rubocop

Dry-run verifies collection, not parity. The strict comparison groups currently have known failures; the complete suite is not a passing qualification gate. Ordinary compatibility specs use committed artifacts without invoking C.

Live C tools require both EMF2SVG_REF_PATH (an executable wrapper) and EMF2SVG_REF_LIB_DIR (its library directory), resolved only by the spec support Emfsvg::Compat helper. Neither has a machine-specific default. The live fidelity spec is opt-in with EMFSVG_LIVE_REFERENCE=1; see the fixture setup for commands and the verified current reference build recipe.

Developer scripts

scripts/run_emf_svg_compare.rb [limit] [tolerance]

Lossy EMF→SVG diagnostic against the explicitly configured live C reference

scripts/run_round_trip.rb [limit] [tolerance]

Lossy SVG→EMF→SVG scan of the verified artifact inventory

scripts/diff_fixture.rb <fixture.svg>

Element-by-element diff between input and round-trip output

scripts/diagnose_round_trip.rb [limit]

Per-fixture failure reasons for round-trip scan

scripts/regenerate_golden.rb --check

Stage both baseline sets and report drift without changing committed artifacts

scripts/regenerate_golden.rb --write

Promote both sets and manifest digests after verified C provenance checks

scripts/run_compat_check.rb

Diagnostic matrix against the explicitly configured live C reference

Regeneration requires the verified corpus and explicit C setup. --write also requires a verified reference status, exact source revision, build recipe, and matching executable checksum in the manifest. Review artifact and manifest diffs before committing. See the regeneration procedure for promotion safeguards and limits.

Resource calibration qualification

The resource-calibration command is gated by production qualification. The repository carries a reviewed ledger for 21 corrupted inputs in spec/fixtures/reviewed_corrupt_outcomes.json. Those independently reviewed expectations define the outcome kind, exact ordered warnings, and exception details where applicable; an SVG digest is an optional additional constraint. A reviewed expectation is not a runtime observation. A ledger entry counted as contracted means its review requirements are complete, while matched means an actual observation also passed the contract. The qualification gate runs the public conversion for the pinned 9 valid controls and all 21 corrupted inputs through Ruby, native, and auto-selected-native backends before resource calibration can proceed.

Qualification requires explicit artifact paths through EMF_RELEASE_PACKAGE (the exact released emf .gem whose SHA-256 and version match the manifest), EMF_NATIVE_LIBRARY (the native artifact matching its manifest SHA-256), and EMF_NATIVE_PROVENANCE (JSON whose artifact_sha256, source_revision, and abi exactly match the native-library provenance in the manifest). The gate also checks the installed runtime against the release archive, native selection, input identities, and evidence for drift. Missing, mismatched, or incomplete metadata and artifacts fail closed before samples run.

After the release and native artifacts and the manifest are qualified, run the verification mode with explicit host safety caps:

EMF_RELEASE_PACKAGE=/absolute/path/emf-version.gem \
EMF_NATIVE_LIBRARY=/absolute/path/libemf.so \
EMF_NATIVE_PROVENANCE=/absolute/path/native-provenance.json \
bundle exec ruby scripts/calibrate_resource_limits.rb check \
  --manifest spec/fixtures/parity_manifest.yml \
  --fixture-root "$EMF_FIXTURE_ROOT" \
  --outer-rss-cap-mib "$EMFSVG_CALIBRATION_RSS_CAP_MIB" \
  --outer-as-cap-mib "$EMFSVG_CALIBRATION_AS_CAP_MIB" \
  --timeout-seconds "$EMFSVG_CALIBRATION_TIMEOUT_SECONDS" \
  --baseline spec/fixtures/resource_limits/emfsvg-v1.yml

This checkout is not qualified: its manifest marks the runtime unqualified, identifies development revision 99a8c21256cbe902e243cc0bcac65b282ec52bf4 without a released package digest, marks the native artifact unavailable, and has not pinned the diagnostics and resources contract revisions. The command therefore refuses with the current evidence. A reviewed ledger, passing synthetic tests, or a current-source run does not establish released-package, native-artifact, whole-corpus parity, or platform qualification. See the compatibility matrix for detailed contracts and remaining evidence.

Dependencies

  • emf (~> 0.1) — EMF binary parser, domain model, and serializer

  • leptris (>= 1.9.365.1) — 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.