WMF (Windows Metafile) is a relatively early vector image format introduced for Microsoft Windows in 1990.
Inside, it provides a serialized list of Windows GDI (Graphics Device Interface) function calls, which, if played back, result in an image being drawn on a given surface (display, off-screen buffer, printer, etc).
This page hosts a formal specification of Windows Metafile (WMF) vector image using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.
The Ruby runtime library can be installed from RubyGems:
gem install kaitai-struct
Parse a local file and get structure in memory:
data = Wmf.from_file("path/to/local/file.wmf")
Or parse structure from a string of bytes:
bytes = "\x00\x01\x02..."
data = Wmf.new(Kaitai::Struct::Stream.new(bytes))
After that, one can get various attributes from the structure by invoking getter methods like:
data.special_header # => get special header
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
require 'kaitai/struct/struct'
unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end
##
# WMF (Windows Metafile) is a relatively early vector image format
# introduced for Microsoft Windows in 1990.
#
# Inside, it provides a serialized list of Windows GDI (Graphics
# Device Interface) function calls, which, if played back, result in
# an image being drawn on a given surface (display, off-screen buffer,
# printer, etc).
# @see https://www.loc.gov/preservation/digital/formats/digformatspecs/WindowsMetafileFormat(wmf)Specification.pdf Source
class Wmf < Kaitai::Struct::Struct
FUNC = {
0 => :func_eof,
30 => :func_savedc,
53 => :func_realizepalette,
55 => :func_setpalentries,
247 => :func_createpalette,
258 => :func_setbkmode,
259 => :func_setmapmode,
260 => :func_setrop2,
261 => :func_setrelabs,
262 => :func_setpolyfillmode,
263 => :func_setstretchbltmode,
264 => :func_settextcharextra,
295 => :func_restoredc,
298 => :func_invertregion,
299 => :func_paintregion,
300 => :func_selectclipregion,
301 => :func_selectobject,
302 => :func_settextalign,
313 => :func_resizepalette,
322 => :func_dibcreatepatternbrush,
329 => :func_setlayout,
496 => :func_deleteobject,
505 => :func_createpatternbrush,
513 => :func_setbkcolor,
521 => :func_settextcolor,
522 => :func_settextjustification,
523 => :func_setwindoworg,
524 => :func_setwindowext,
525 => :func_setviewportorg,
526 => :func_setviewportext,
527 => :func_offsetwindoworg,
529 => :func_offsetviewportorg,
531 => :func_lineto,
532 => :func_moveto,
544 => :func_offsetcliprgn,
552 => :func_fillregion,
561 => :func_setmapperflags,
564 => :func_selectpalette,
762 => :func_createpenindirect,
763 => :func_createfontindirect,
764 => :func_createbrushindirect,
804 => :func_polygon,
805 => :func_polyline,
1040 => :func_scalewindowext,
1042 => :func_scaleviewportext,
1045 => :func_excludecliprect,
1046 => :func_intersectcliprect,
1048 => :func_ellipse,
1049 => :func_floodfill,
1051 => :func_rectangle,
1055 => :func_setpixel,
1065 => :func_frameregion,
1078 => :func_animatepalette,
1313 => :func_textout,
1336 => :func_polypolygon,
1352 => :func_extfloodfill,
1564 => :func_roundrect,
1565 => :func_patblt,
1574 => :func_escape,
1791 => :func_createregion,
2071 => :func_arc,
2074 => :func_pie,
2096 => :func_chord,
2338 => :func_bitblt,
2368 => :func_dibbitblt,
2610 => :func_exttextout,
2851 => :func_stretchblt,
2881 => :func_dibstretchblt,
3379 => :func_setdibtodev,
3907 => :func_stretchdib,
}
I__FUNC = FUNC.invert
BIN_RASTER_OP = {
1 => :bin_raster_op_black,
2 => :bin_raster_op_notmergepen,
3 => :bin_raster_op_masknotpen,
4 => :bin_raster_op_notcopypen,
5 => :bin_raster_op_maskpennot,
6 => :bin_raster_op_not,
7 => :bin_raster_op_xorpen,
8 => :bin_raster_op_notmaskpen,
9 => :bin_raster_op_maskpen,
10 => :bin_raster_op_notxorpen,
11 => :bin_raster_op_nop,
12 => :bin_raster_op_mergenotpen,
13 => :bin_raster_op_copypen,
14 => :bin_raster_op_mergepennot,
15 => :bin_raster_op_mergepen,
16 => :bin_raster_op_white,
}
I__BIN_RASTER_OP = BIN_RASTER_OP.invert
MIX_MODE = {
1 => :mix_mode_transparent,
2 => :mix_mode_opaque,
}
I__MIX_MODE = MIX_MODE.invert
POLY_FILL_MODE = {
1 => :poly_fill_mode_alternate,
2 => :poly_fill_mode_winding,
}
I__POLY_FILL_MODE = POLY_FILL_MODE.invert
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@special_header = SpecialHeader.new(@_io, self, @_root)
@header = Header.new(@_io, self, @_root)
@records = []
i = 0
begin
_ = Record.new(@_io, self, @_root)
@records << _
i += 1
end until _.function == :func_eof
self
end
##
# @see '' section 2.3.5.31
class ParamsSetwindoworg < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@y = @_io.read_s2le
@x = @_io.read_s2le
self
end
##
# Y coordinate of the window origin, in logical units.
attr_reader :y
##
# X coordinate of the window origin, in logical units.
attr_reader :x
end
##
# @see '' section 2.3.5.15
class ParamsSetbkmode < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@bk_mode = Kaitai::Struct::Stream::resolve_enum(Wmf::MIX_MODE, @_io.read_u2le)
self
end
##
# Defines current graphic context background mix mode.
attr_reader :bk_mode
end
##
# @see '' section 2.2.1.12
class PointS < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@x = @_io.read_s2le
@y = @_io.read_s2le
self
end
##
# X coordinate of the point, in logical units.
attr_reader :x
##
# Y coordinate of the point, in logical units.
attr_reader :y
end
##
# @see '' section 2.3.5.30
class ParamsSetwindowext < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@y = @_io.read_s2le
@x = @_io.read_s2le
self
end
##
# Vertical extent of the window in logical units.
attr_reader :y
##
# Horizontal extent of the window in logical units.
attr_reader :x
end
##
# @see '' section 2.3.3.15 = params_polyline
class ParamsPolygon < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@num_points = @_io.read_s2le
@points = []
(num_points).times { |i|
@points << PointS.new(@_io, self, @_root)
}
self
end
attr_reader :num_points
attr_reader :points
end
class Header < Kaitai::Struct::Struct
METAFILE_TYPE = {
1 => :metafile_type_memory_metafile,
2 => :metafile_type_disk_metafile,
}
I__METAFILE_TYPE = METAFILE_TYPE.invert
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@metafile_type = Kaitai::Struct::Stream::resolve_enum(METAFILE_TYPE, @_io.read_u2le)
@header_size = @_io.read_u2le
@version = @_io.read_u2le
@size = @_io.read_u4le
@number_of_objects = @_io.read_u2le
@max_record = @_io.read_u4le
@number_of_members = @_io.read_u2le
self
end
attr_reader :metafile_type
attr_reader :header_size
attr_reader :version
attr_reader :size
attr_reader :number_of_objects
attr_reader :max_record
attr_reader :number_of_members
end
##
# @see '' section 2.2.1.7
class ColorRef < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@red = @_io.read_u1
@green = @_io.read_u1
@blue = @_io.read_u1
@reserved = @_io.read_u1
self
end
attr_reader :red
attr_reader :green
attr_reader :blue
attr_reader :reserved
end
##
# @see '' section 2.3.5.22
class ParamsSetrop2 < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@draw_mode = Kaitai::Struct::Stream::resolve_enum(Wmf::BIN_RASTER_OP, @_io.read_u2le)
self
end
##
# Defines current foreground binary raster operation mixing mode.
attr_reader :draw_mode
end
##
# @see '' section 2.3.5.20
class ParamsSetpolyfillmode < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@poly_fill_mode = Kaitai::Struct::Stream::resolve_enum(Wmf::POLY_FILL_MODE, @_io.read_u2le)
self
end
##
# Defines current polygon fill mode.
attr_reader :poly_fill_mode
end
##
# @see '' section 2.3.3.14
class ParamsPolyline < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@num_points = @_io.read_s2le
@points = []
(num_points).times { |i|
@points << PointS.new(@_io, self, @_root)
}
self
end
attr_reader :num_points
attr_reader :points
end
class SpecialHeader < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@magic = @_io.read_bytes(4)
raise Kaitai::Struct::ValidationNotEqualError.new([215, 205, 198, 154].pack('C*'), magic, _io, "/types/special_header/seq/0") if not magic == [215, 205, 198, 154].pack('C*')
@handle = @_io.read_bytes(2)
raise Kaitai::Struct::ValidationNotEqualError.new([0, 0].pack('C*'), handle, _io, "/types/special_header/seq/1") if not handle == [0, 0].pack('C*')
@left = @_io.read_s2le
@top = @_io.read_s2le
@right = @_io.read_s2le
@bottom = @_io.read_s2le
@inch = @_io.read_u2le
@reserved = @_io.read_bytes(4)
raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), reserved, _io, "/types/special_header/seq/7") if not reserved == [0, 0, 0, 0].pack('C*')
@checksum = @_io.read_u2le
self
end
attr_reader :magic
attr_reader :handle
attr_reader :left
attr_reader :top
attr_reader :right
attr_reader :bottom
attr_reader :inch
attr_reader :reserved
attr_reader :checksum
end
class Record < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@size = @_io.read_u4le
@function = Kaitai::Struct::Stream::resolve_enum(Wmf::FUNC, @_io.read_u2le)
case function
when :func_setbkmode
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ParamsSetbkmode.new(_io__raw_params, self, @_root)
when :func_polygon
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ParamsPolygon.new(_io__raw_params, self, @_root)
when :func_setbkcolor
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ColorRef.new(_io__raw_params, self, @_root)
when :func_setpolyfillmode
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ParamsSetpolyfillmode.new(_io__raw_params, self, @_root)
when :func_setwindoworg
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ParamsSetwindoworg.new(_io__raw_params, self, @_root)
when :func_setrop2
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ParamsSetrop2.new(_io__raw_params, self, @_root)
when :func_setwindowext
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ParamsSetwindowext.new(_io__raw_params, self, @_root)
when :func_polyline
@_raw_params = @_io.read_bytes(((size - 3) * 2))
_io__raw_params = Kaitai::Struct::Stream.new(@_raw_params)
@params = ParamsPolyline.new(_io__raw_params, self, @_root)
else
@params = @_io.read_bytes(((size - 3) * 2))
end
self
end
attr_reader :size
attr_reader :function
attr_reader :params
attr_reader :_raw_params
end
attr_reader :special_header
attr_reader :header
attr_reader :records
end