emf 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +24 -0
- data/README.adoc +136 -0
- data/exe/emf +113 -0
- data/lib/emf/binary/codec.rb +19 -0
- data/lib/emf/binary/types.rb +105 -0
- data/lib/emf/binary.rb +10 -0
- data/lib/emf/detector.rb +58 -0
- data/lib/emf/emf_plus/binary.rb +8 -0
- data/lib/emf/emf_plus/parser.rb +13 -0
- data/lib/emf/emf_plus/serializer.rb +13 -0
- data/lib/emf/emf_plus.rb +9 -0
- data/lib/emf/emr/binary/header.rb +41 -0
- data/lib/emf/emr/binary/record.rb +17 -0
- data/lib/emf/emr/binary/records/abort_path.rb +16 -0
- data/lib/emf/emr/binary/records/alpha_blend.rb +17 -0
- data/lib/emf/emr/binary/records/angle_arc.rb +19 -0
- data/lib/emf/emr/binary/records/arc.rb +19 -0
- data/lib/emf/emr/binary/records/arc_to.rb +19 -0
- data/lib/emf/emr/binary/records/begin_path.rb +16 -0
- data/lib/emf/emr/binary/records/bit_blt.rb +37 -0
- data/lib/emf/emr/binary/records/chord.rb +19 -0
- data/lib/emf/emr/binary/records/close_figure.rb +16 -0
- data/lib/emf/emr/binary/records/color_correct_palette.rb +17 -0
- data/lib/emf/emr/binary/records/colormatch_to_target_w.rb +17 -0
- data/lib/emf/emr/binary/records/comment.rb +20 -0
- data/lib/emf/emr/binary/records/create_brush_indirect.rb +20 -0
- data/lib/emf/emr/binary/records/create_colorspace.rb +17 -0
- data/lib/emf/emr/binary/records/create_colorspace_w.rb +17 -0
- data/lib/emf/emr/binary/records/create_dib_pattern_brush_pt.rb +25 -0
- data/lib/emf/emr/binary/records/create_font_indirect_w.rb +18 -0
- data/lib/emf/emr/binary/records/create_mono_brush.rb +25 -0
- data/lib/emf/emr/binary/records/create_palette.rb +17 -0
- data/lib/emf/emr/binary/records/create_pen.rb +20 -0
- data/lib/emf/emr/binary/records/delete_colorspace.rb +17 -0
- data/lib/emf/emr/binary/records/delete_object.rb +16 -0
- data/lib/emf/emr/binary/records/draw_escape.rb +17 -0
- data/lib/emf/emr/binary/records/ellipse.rb +18 -0
- data/lib/emf/emr/binary/records/end_path.rb +16 -0
- data/lib/emf/emr/binary/records/eof.rb +21 -0
- data/lib/emf/emr/binary/records/exclude_clip_rect.rb +16 -0
- data/lib/emf/emr/binary/records/ext_create_pen.rb +17 -0
- data/lib/emf/emr/binary/records/ext_escape.rb +17 -0
- data/lib/emf/emr/binary/records/ext_flood_fill.rb +17 -0
- data/lib/emf/emr/binary/records/ext_select_clip_rgn.rb +19 -0
- data/lib/emf/emr/binary/records/ext_text_out_a.rb +27 -0
- data/lib/emf/emr/binary/records/ext_text_out_w.rb +41 -0
- data/lib/emf/emr/binary/records/fill_path.rb +16 -0
- data/lib/emf/emr/binary/records/fill_rgn.rb +17 -0
- data/lib/emf/emr/binary/records/flatten_path.rb +16 -0
- data/lib/emf/emr/binary/records/force_ufi_mapping.rb +17 -0
- data/lib/emf/emr/binary/records/frame_rgn.rb +17 -0
- data/lib/emf/emr/binary/records/gls_bounded_record.rb +17 -0
- data/lib/emf/emr/binary/records/gls_record.rb +17 -0
- data/lib/emf/emr/binary/records/gradient_fill.rb +19 -0
- data/lib/emf/emr/binary/records/intersect_clip_rect.rb +16 -0
- data/lib/emf/emr/binary/records/invert_rgn.rb +17 -0
- data/lib/emf/emr/binary/records/line_to.rb +16 -0
- data/lib/emf/emr/binary/records/mask_blt.rb +17 -0
- data/lib/emf/emr/binary/records/modify_world_transform.rb +17 -0
- data/lib/emf/emr/binary/records/move_to_ex.rb +16 -0
- data/lib/emf/emr/binary/records/named_escape.rb +17 -0
- data/lib/emf/emr/binary/records/offset_clip_rgn.rb +16 -0
- data/lib/emf/emr/binary/records/paint_rgn.rb +17 -0
- data/lib/emf/emr/binary/records/pie.rb +19 -0
- data/lib/emf/emr/binary/records/pixel_format.rb +17 -0
- data/lib/emf/emr/binary/records/plg_blt.rb +17 -0
- data/lib/emf/emr/binary/records/poly_bezier.rb +17 -0
- data/lib/emf/emr/binary/records/poly_bezier16.rb +17 -0
- data/lib/emf/emr/binary/records/poly_bezier_to.rb +17 -0
- data/lib/emf/emr/binary/records/poly_bezier_to16.rb +17 -0
- data/lib/emf/emr/binary/records/poly_draw.rb +18 -0
- data/lib/emf/emr/binary/records/poly_draw16.rb +18 -0
- data/lib/emf/emr/binary/records/poly_polygon.rb +19 -0
- data/lib/emf/emr/binary/records/poly_polygon16.rb +19 -0
- data/lib/emf/emr/binary/records/poly_polyline.rb +19 -0
- data/lib/emf/emr/binary/records/poly_polyline16.rb +19 -0
- data/lib/emf/emr/binary/records/poly_textout_a.rb +17 -0
- data/lib/emf/emr/binary/records/poly_textout_w.rb +17 -0
- data/lib/emf/emr/binary/records/polygon.rb +17 -0
- data/lib/emf/emr/binary/records/polygon16.rb +17 -0
- data/lib/emf/emr/binary/records/polyline.rb +17 -0
- data/lib/emf/emr/binary/records/polyline16.rb +17 -0
- data/lib/emf/emr/binary/records/polyline_to.rb +17 -0
- data/lib/emf/emr/binary/records/polyline_to16.rb +17 -0
- data/lib/emf/emr/binary/records/raw.rb +19 -0
- data/lib/emf/emr/binary/records/realize_palette.rb +15 -0
- data/lib/emf/emr/binary/records/rectangle.rb +18 -0
- data/lib/emf/emr/binary/records/resize_palette.rb +17 -0
- data/lib/emf/emr/binary/records/restore_dc.rb +17 -0
- data/lib/emf/emr/binary/records/round_rect.rb +19 -0
- data/lib/emf/emr/binary/records/save_dc.rb +16 -0
- data/lib/emf/emr/binary/records/scale_viewport_ext_ex.rb +19 -0
- data/lib/emf/emr/binary/records/scale_window_ext_ex.rb +19 -0
- data/lib/emf/emr/binary/records/select_clip_path.rb +20 -0
- data/lib/emf/emr/binary/records/select_object.rb +16 -0
- data/lib/emf/emr/binary/records/select_palette.rb +16 -0
- data/lib/emf/emr/binary/records/set_arc_direction.rb +16 -0
- data/lib/emf/emr/binary/records/set_bk_color.rb +16 -0
- data/lib/emf/emr/binary/records/set_bk_mode.rb +16 -0
- data/lib/emf/emr/binary/records/set_brush_org_ex.rb +16 -0
- data/lib/emf/emr/binary/records/set_colorspace.rb +17 -0
- data/lib/emf/emr/binary/records/set_dibits_to_device.rb +17 -0
- data/lib/emf/emr/binary/records/set_icm_mode.rb +16 -0
- data/lib/emf/emr/binary/records/set_icm_profile_a.rb +17 -0
- data/lib/emf/emr/binary/records/set_icm_profile_w.rb +17 -0
- data/lib/emf/emr/binary/records/set_layout.rb +16 -0
- data/lib/emf/emr/binary/records/set_linked_ufis.rb +17 -0
- data/lib/emf/emr/binary/records/set_map_mode.rb +16 -0
- data/lib/emf/emr/binary/records/set_mapper_flags.rb +16 -0
- data/lib/emf/emr/binary/records/set_metargn.rb +16 -0
- data/lib/emf/emr/binary/records/set_miter_limit.rb +16 -0
- data/lib/emf/emr/binary/records/set_palette_entries.rb +17 -0
- data/lib/emf/emr/binary/records/set_pixel_v.rb +18 -0
- data/lib/emf/emr/binary/records/set_poly_fill_mode.rb +16 -0
- data/lib/emf/emr/binary/records/set_rop2.rb +16 -0
- data/lib/emf/emr/binary/records/set_stretch_blt_mode.rb +16 -0
- data/lib/emf/emr/binary/records/set_text_align.rb +16 -0
- data/lib/emf/emr/binary/records/set_text_color.rb +17 -0
- data/lib/emf/emr/binary/records/set_text_justification.rb +17 -0
- data/lib/emf/emr/binary/records/set_viewport_ext_ex.rb +16 -0
- data/lib/emf/emr/binary/records/set_viewport_org_ex.rb +16 -0
- data/lib/emf/emr/binary/records/set_window_ext_ex.rb +16 -0
- data/lib/emf/emr/binary/records/set_window_org_ex.rb +16 -0
- data/lib/emf/emr/binary/records/set_world_transform.rb +16 -0
- data/lib/emf/emr/binary/records/small_text_out.rb +28 -0
- data/lib/emf/emr/binary/records/stretch_blt.rb +39 -0
- data/lib/emf/emr/binary/records/stretch_dibits.rb +42 -0
- data/lib/emf/emr/binary/records/stroke_and_fill_path.rb +16 -0
- data/lib/emf/emr/binary/records/stroke_path.rb +16 -0
- data/lib/emf/emr/binary/records/transparent_blt.rb +17 -0
- data/lib/emf/emr/binary/records/widen_path.rb +16 -0
- data/lib/emf/emr/binary/records.rb +258 -0
- data/lib/emf/emr/binary/type_codes.rb +132 -0
- data/lib/emf/emr/binary/with_bounds.rb +15 -0
- data/lib/emf/emr/binary.rb +13 -0
- data/lib/emf/emr/parser.rb +185 -0
- data/lib/emf/emr/path_association.rb +16 -0
- data/lib/emf/emr/serializer.rb +24 -0
- data/lib/emf/emr.rb +10 -0
- data/lib/emf/error.rb +19 -0
- data/lib/emf/model/emr/header.rb +93 -0
- data/lib/emf/model/emr/records.rb +48 -0
- data/lib/emf/model/emr.rb +10 -0
- data/lib/emf/model/geometry/color.rb +67 -0
- data/lib/emf/model/geometry/matrix.rb +52 -0
- data/lib/emf/model/geometry/point.rb +41 -0
- data/lib/emf/model/geometry/point_f.rb +33 -0
- data/lib/emf/model/geometry/point_s.rb +33 -0
- data/lib/emf/model/geometry/rect.rb +57 -0
- data/lib/emf/model/geometry/rect_s.rb +49 -0
- data/lib/emf/model/geometry/size.rb +33 -0
- data/lib/emf/model/geometry.rb +16 -0
- data/lib/emf/model/metafile.rb +34 -0
- data/lib/emf/model/record.rb +19 -0
- data/lib/emf/model/visitor.rb +42 -0
- data/lib/emf/model.rb +11 -0
- data/lib/emf/version.rb +5 -0
- data/lib/emf/visitors/dump.rb +57 -0
- data/lib/emf/visitors/stats.rb +63 -0
- data/lib/emf/visitors.rb +8 -0
- data/lib/emf/wmf/binary.rb +9 -0
- data/lib/emf/wmf/parser.rb +13 -0
- data/lib/emf/wmf/serializer.rb +13 -0
- data/lib/emf/wmf.rb +9 -0
- data/lib/emf.rb +56 -0
- metadata +226 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module Emf
|
|
6
|
+
module Emr
|
|
7
|
+
module Parser
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def call(bytes)
|
|
11
|
+
raise FormatError, "input too large (#{bytes.bytesize} > #{MAX_INPUT_BYTES}); refusing to parse" if bytes.bytesize > MAX_INPUT_BYTES
|
|
12
|
+
raise FormatError, "not an EMF file (signature mismatch)" unless Emf::Detector.call(bytes) == :emf
|
|
13
|
+
|
|
14
|
+
io = StringIO.new(bytes, "rb")
|
|
15
|
+
header_wire = read_header(io)
|
|
16
|
+
header = Model::Emr::Header.from_wire(header_wire)
|
|
17
|
+
|
|
18
|
+
records = []
|
|
19
|
+
errors = []
|
|
20
|
+
emf_plus_bytes = +""
|
|
21
|
+
trailing = +""
|
|
22
|
+
|
|
23
|
+
until io.eof?
|
|
24
|
+
offset = io.pos
|
|
25
|
+
wire = read_record(io, offset, errors)
|
|
26
|
+
break if wire.nil?
|
|
27
|
+
|
|
28
|
+
records << Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset)
|
|
29
|
+
emf_plus_bytes << extract_emf_plus(wire) if wire.is_a?(Emf::Emr::Binary::Records::Comment)
|
|
30
|
+
|
|
31
|
+
if wire.is_a?(Emf::Emr::Binary::Records::Eof)
|
|
32
|
+
trailing << io.read until io.eof?
|
|
33
|
+
break
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Model::Metafile.new(
|
|
38
|
+
format: emf_plus_bytes.empty? ? :emf : :emf_plus,
|
|
39
|
+
header: header,
|
|
40
|
+
records: records,
|
|
41
|
+
errors: errors,
|
|
42
|
+
emf_plus: emf_plus_bytes.empty? ? nil : emf_plus_bytes,
|
|
43
|
+
trailing: trailing
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def read_header(io)
|
|
48
|
+
# Peek at the first 8 bytes to determine the record's nSize.
|
|
49
|
+
head = io.read(8)
|
|
50
|
+
raise FormatError, "truncated EMF header" if head.nil? || head.bytesize < 8
|
|
51
|
+
|
|
52
|
+
_type, n_size = head.unpack("VV")
|
|
53
|
+
raise FormatError, "EMF header nSize too small: #{n_size}" if n_size < 8
|
|
54
|
+
|
|
55
|
+
io.seek(0)
|
|
56
|
+
full = io.read(n_size)
|
|
57
|
+
if full.nil? || full.bytesize < n_size
|
|
58
|
+
raise FormatError,
|
|
59
|
+
"truncated EMF header (claimed #{n_size}, got #{full&.bytesize || 0})"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Emf::Emr::Binary::Header.read(full)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Per-type byte-size validation for records with variable-length arrays.
|
|
66
|
+
# If the declared count exceeds what nSize can hold, the bytes are
|
|
67
|
+
# corrupt and we should preserve them as Raw rather than letting bindata
|
|
68
|
+
# allocate a giant array. The element size is bytes per array entry.
|
|
69
|
+
VARIABLE_ARRAY_RECORDS = {
|
|
70
|
+
Binary::TypeCodes::POLYGON => { count_offset: 24, element_size: 8 },
|
|
71
|
+
Binary::TypeCodes::POLYLINE => { count_offset: 24, element_size: 8 },
|
|
72
|
+
Binary::TypeCodes::POLYBEZIER => { count_offset: 24, element_size: 8 },
|
|
73
|
+
Binary::TypeCodes::POLYBEZIERTO => { count_offset: 24, element_size: 8 },
|
|
74
|
+
Binary::TypeCodes::POLYLINETO => { count_offset: 24, element_size: 8 },
|
|
75
|
+
Binary::TypeCodes::POLYGON16 => { count_offset: 24, element_size: 4 },
|
|
76
|
+
Binary::TypeCodes::POLYLINE16 => { count_offset: 24, element_size: 4 },
|
|
77
|
+
Binary::TypeCodes::POLYBEZIER16 => { count_offset: 24, element_size: 4 },
|
|
78
|
+
Binary::TypeCodes::POLYBEZIERTO16 => { count_offset: 24, element_size: 4 },
|
|
79
|
+
Binary::TypeCodes::POLYLINETO16 => { count_offset: 24, element_size: 4 },
|
|
80
|
+
Binary::TypeCodes::POLYDRAW => { count_offset: 24, element_size: 9 },
|
|
81
|
+
Binary::TypeCodes::POLYDRAW16 => { count_offset: 24, element_size: 5 },
|
|
82
|
+
# Multi-polygon records have TWO variable arrays: aPolyCounts
|
|
83
|
+
# (nPolys uint32s, 4 bytes each) and aptl/apts (cTotal entries).
|
|
84
|
+
# The count1 entries are ALWAYS uint32 (4 bytes); element_size
|
|
85
|
+
# applies only to the count2 (point) array.
|
|
86
|
+
Binary::TypeCodes::POLYPOLYGON => { count_offset: 24, count2_offset: 28, element_size: 8 },
|
|
87
|
+
Binary::TypeCodes::POLYPOLYLINE => { count_offset: 24, count2_offset: 28, element_size: 8 },
|
|
88
|
+
Binary::TypeCodes::POLYPOLYGON16 => { count_offset: 24, count2_offset: 28, element_size: 4 },
|
|
89
|
+
Binary::TypeCodes::POLYPOLYLINE16 => { count_offset: 24, count2_offset: 28, element_size: 4 }
|
|
90
|
+
}.freeze
|
|
91
|
+
|
|
92
|
+
MAX_SANE_ARRAY_LENGTH = 1_000_000
|
|
93
|
+
# Bounds the whole-file parse: a 100 MB EMF is unusual; refuse bigger.
|
|
94
|
+
MAX_INPUT_BYTES = 200 * 1024 * 1024
|
|
95
|
+
|
|
96
|
+
def variable_array_sane?(type_id, n_size, head, payload)
|
|
97
|
+
spec = VARIABLE_ARRAY_RECORDS[type_id]
|
|
98
|
+
return true unless spec
|
|
99
|
+
|
|
100
|
+
full = head + payload
|
|
101
|
+
return true if full.bytesize < spec[:count_offset] + 4
|
|
102
|
+
|
|
103
|
+
count = read_uint32(full, spec[:count_offset])
|
|
104
|
+
return false if count > MAX_SANE_ARRAY_LENGTH
|
|
105
|
+
|
|
106
|
+
# For single-array records, element_size is the per-entry size.
|
|
107
|
+
# For multi-array records, the first array (aPolyCounts) entries
|
|
108
|
+
# are always uint32 (4 bytes); element_size applies only to the
|
|
109
|
+
# second array (aptl/apts).
|
|
110
|
+
first_array_entry_size = spec[:count2_offset] ? 4 : spec[:element_size]
|
|
111
|
+
bytes_needed = spec[:count_offset] + 4 + (count * first_array_entry_size)
|
|
112
|
+
return false if bytes_needed > n_size
|
|
113
|
+
|
|
114
|
+
# Multi-array records also need to validate the second count.
|
|
115
|
+
return true unless spec[:count2_offset]
|
|
116
|
+
|
|
117
|
+
count2 = read_uint32(full, spec[:count2_offset])
|
|
118
|
+
return false if count2 > MAX_SANE_ARRAY_LENGTH
|
|
119
|
+
|
|
120
|
+
bytes_needed + (count2 * spec[:element_size]) <= n_size
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def read_uint32(bytes, offset)
|
|
124
|
+
bytes.getbyte(offset) |
|
|
125
|
+
(bytes.getbyte(offset + 1) << 8) |
|
|
126
|
+
(bytes.getbyte(offset + 2) << 16) |
|
|
127
|
+
(bytes.getbyte(offset + 3) << 24)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def read_record(io, offset, errors)
|
|
131
|
+
head = io.read(8)
|
|
132
|
+
return nil if head.nil? || head.empty?
|
|
133
|
+
|
|
134
|
+
type_id, n_size = head.unpack("VV")
|
|
135
|
+
if n_size < 8
|
|
136
|
+
errors << ParseError.new(offset: offset, record_code: type_id,
|
|
137
|
+
message: "record nSize #{n_size} < 8 bytes (offset #{offset})")
|
|
138
|
+
return nil
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
payload = io.read(n_size - 8)
|
|
142
|
+
if payload.nil? || payload.bytesize < n_size - 8
|
|
143
|
+
errors << ParseError.new(offset: offset, record_code: type_id,
|
|
144
|
+
message: "truncated record at offset #{offset} (claimed #{n_size}, got #{payload&.bytesize || 0})")
|
|
145
|
+
return nil
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
unless variable_array_sane?(type_id, n_size, head, payload)
|
|
149
|
+
errors << ParseError.new(
|
|
150
|
+
offset: offset, record_code: type_id,
|
|
151
|
+
message: "variable-array record at offset #{offset} declares " \
|
|
152
|
+
"more entries than nSize allows; preserving as Raw"
|
|
153
|
+
)
|
|
154
|
+
return build_raw(head, payload)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
wire_class = Emf::Emr::Binary::Records.lookup(type_id)
|
|
158
|
+
wire_class.read(head + payload)
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
errors << ParseError.new(offset: offset, record_code: type_id,
|
|
161
|
+
message: "#{e.class}: #{e.message}")
|
|
162
|
+
build_raw(head, payload)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def build_raw(head, payload)
|
|
166
|
+
Emf::Emr::Binary::Records::Raw.read(head + (payload || +""))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
EMF_PLUS_IDENTIFIER = 0x2B464D45 # "EMF+" little-endian
|
|
170
|
+
|
|
171
|
+
def extract_emf_plus(comment_wire)
|
|
172
|
+
return +"" unless comment_wire.cb_data&.>= 4
|
|
173
|
+
|
|
174
|
+
identifier = comment_wire.data.getbyte(0) |
|
|
175
|
+
(comment_wire.data.getbyte(1) << 8) |
|
|
176
|
+
(comment_wire.data.getbyte(2) << 16) |
|
|
177
|
+
(comment_wire.data.getbyte(3) << 24)
|
|
178
|
+
return +"" unless identifier == EMF_PLUS_IDENTIFIER
|
|
179
|
+
|
|
180
|
+
# Payload after the 4-byte identifier is the EMF+ record(s).
|
|
181
|
+
comment_wire.data.bytes[4..].pack("C*")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Placeholder for TODO.impl/11-emf-parser.md's path-association pass.
|
|
4
|
+
module Emf
|
|
5
|
+
module Emr
|
|
6
|
+
class PathAssociation
|
|
7
|
+
def initialize
|
|
8
|
+
@table = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def lookup(render_offset)
|
|
12
|
+
@table.fetch(render_offset, nil)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Emr
|
|
5
|
+
module Serializer
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(metafile)
|
|
9
|
+
bytes = +""
|
|
10
|
+
bytes << metafile.header.to_wire.to_binary_s
|
|
11
|
+
metafile.records.each do |record|
|
|
12
|
+
case record
|
|
13
|
+
when Model::Emr::Records::WireAdapter
|
|
14
|
+
bytes << record.wire.to_binary_s
|
|
15
|
+
else
|
|
16
|
+
raise SerializeError, "cannot serialize #{record.class} (TODO 10 lands domain to_wire)"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
bytes << metafile.trailing
|
|
20
|
+
bytes
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/emf/emr.rb
ADDED
data/lib/emf/error.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class FormatError < Error; end
|
|
7
|
+
|
|
8
|
+
class ParseError < Error
|
|
9
|
+
attr_reader :offset, :record_code
|
|
10
|
+
|
|
11
|
+
def initialize(offset:, record_code:, message:)
|
|
12
|
+
@offset = offset
|
|
13
|
+
@record_code = record_code
|
|
14
|
+
super(message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class SerializeError < Error; end
|
|
19
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Emr
|
|
6
|
+
# EMF metafile header. Preserves all wire fields verbatim so the
|
|
7
|
+
# serializer can round-trip byte-faithfully.
|
|
8
|
+
class Header
|
|
9
|
+
def initialize(bounds:, frame:, signature:, version:, n_bytes:, n_records:,
|
|
10
|
+
n_handles:, s_reserved:, n_description:, off_description:,
|
|
11
|
+
n_pal_entries:, device_pixels:, device_mm:, cb_pixel_format:,
|
|
12
|
+
off_pixel_format:, b_open_gl:, szl_micrometers:,
|
|
13
|
+
n_size: 100, trailing: +"", description: nil)
|
|
14
|
+
@bounds = bounds
|
|
15
|
+
@frame = frame
|
|
16
|
+
@signature = signature
|
|
17
|
+
@version = version
|
|
18
|
+
@n_bytes = n_bytes
|
|
19
|
+
@n_records = n_records
|
|
20
|
+
@n_handles = n_handles
|
|
21
|
+
@s_reserved = s_reserved
|
|
22
|
+
@n_description = n_description
|
|
23
|
+
@off_description = off_description
|
|
24
|
+
@n_pal_entries = n_pal_entries
|
|
25
|
+
@device_pixels = device_pixels
|
|
26
|
+
@device_mm = device_mm
|
|
27
|
+
@cb_pixel_format = cb_pixel_format
|
|
28
|
+
@off_pixel_format = off_pixel_format
|
|
29
|
+
@b_open_gl = b_open_gl
|
|
30
|
+
@szl_micrometers = szl_micrometers
|
|
31
|
+
@n_size = n_size
|
|
32
|
+
@trailing = trailing
|
|
33
|
+
@description = description
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :bounds, :frame, :signature, :version, :n_bytes, :n_records,
|
|
37
|
+
:n_handles, :s_reserved, :n_description, :off_description,
|
|
38
|
+
:n_pal_entries, :device_pixels, :device_mm, :cb_pixel_format,
|
|
39
|
+
:off_pixel_format, :b_open_gl, :szl_micrometers, :n_size,
|
|
40
|
+
:trailing, :description
|
|
41
|
+
|
|
42
|
+
def self.from_wire(wire)
|
|
43
|
+
new(
|
|
44
|
+
bounds: Geometry::Rect.from_wire(wire.rcl_bounds),
|
|
45
|
+
frame: Geometry::Rect.from_wire(wire.rcl_frame),
|
|
46
|
+
signature: wire.d_signature,
|
|
47
|
+
version: wire.n_version,
|
|
48
|
+
n_bytes: wire.n_bytes,
|
|
49
|
+
n_records: wire.n_records,
|
|
50
|
+
n_handles: wire.n_handles,
|
|
51
|
+
s_reserved: wire.s_reserved,
|
|
52
|
+
n_description: wire.n_description,
|
|
53
|
+
off_description: wire.off_description,
|
|
54
|
+
n_pal_entries: wire.n_pal_entries,
|
|
55
|
+
device_pixels: Geometry::Size.from_wire(wire.szl_device),
|
|
56
|
+
device_mm: Geometry::Size.from_wire(wire.szl_millimeters),
|
|
57
|
+
cb_pixel_format: wire.cb_pixel_format,
|
|
58
|
+
off_pixel_format: wire.off_pixel_format,
|
|
59
|
+
b_open_gl: wire.b_open_gl,
|
|
60
|
+
szl_micrometers: Geometry::Size.from_wire(wire.szl_micrometers),
|
|
61
|
+
n_size: wire.n_size,
|
|
62
|
+
trailing: wire.trailing.to_s
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_wire
|
|
67
|
+
Emf::Emr::Binary::Header.new(
|
|
68
|
+
i_type: 1,
|
|
69
|
+
n_size: n_size,
|
|
70
|
+
rcl_bounds: bounds.to_wire,
|
|
71
|
+
rcl_frame: frame.to_wire,
|
|
72
|
+
d_signature: signature,
|
|
73
|
+
n_version: version,
|
|
74
|
+
n_bytes: n_bytes,
|
|
75
|
+
n_records: n_records,
|
|
76
|
+
n_handles: n_handles,
|
|
77
|
+
s_reserved: s_reserved,
|
|
78
|
+
n_description: n_description,
|
|
79
|
+
off_description: off_description,
|
|
80
|
+
n_pal_entries: n_pal_entries,
|
|
81
|
+
szl_device: device_pixels.to_wire,
|
|
82
|
+
szl_millimeters: device_mm.to_wire,
|
|
83
|
+
cb_pixel_format: cb_pixel_format,
|
|
84
|
+
off_pixel_format: off_pixel_format,
|
|
85
|
+
b_open_gl: b_open_gl,
|
|
86
|
+
szl_micrometers: szl_micrometers.to_wire,
|
|
87
|
+
trailing: trailing
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Emr
|
|
6
|
+
module Records
|
|
7
|
+
# Adapter that wraps a bindata wire record so the Metafile container
|
|
8
|
+
# can hold domain-shape objects even for record types whose dedicated
|
|
9
|
+
# domain class hasn't been written yet (TODO 10). When a real domain
|
|
10
|
+
# class exists, the parser uses it instead.
|
|
11
|
+
#
|
|
12
|
+
# `wire` is the parsed Emf::Emr::Binary::Records::* instance.
|
|
13
|
+
# `offset` is the byte offset within the source stream (for diagnostics).
|
|
14
|
+
class WireAdapter < Emf::Model::Record
|
|
15
|
+
Emf::Model::Visitor.register_visit(:visit_emr_wire_record, self)
|
|
16
|
+
|
|
17
|
+
def initialize(wire:, offset: nil)
|
|
18
|
+
@wire = wire
|
|
19
|
+
@offset = offset
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :wire, :offset
|
|
23
|
+
|
|
24
|
+
def type_id
|
|
25
|
+
@wire.i_type
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def accept(visitor)
|
|
29
|
+
visitor.visit_emr_wire_record(self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ==(other)
|
|
33
|
+
other.is_a?(self.class) && wire.to_binary_s == other.wire.to_binary_s
|
|
34
|
+
end
|
|
35
|
+
alias eql? ==
|
|
36
|
+
|
|
37
|
+
def hash
|
|
38
|
+
[self.class, wire.to_binary_s].hash
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inspect
|
|
42
|
+
"#<#{self.class.name} type_id=#{type_id} (#{(@wire.class.name || '').split('::').last})>"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Geometry
|
|
6
|
+
class Color
|
|
7
|
+
def initialize(red:, green:, blue:, alpha: 255)
|
|
8
|
+
@red = coerce(red)
|
|
9
|
+
@green = coerce(green)
|
|
10
|
+
@blue = coerce(blue)
|
|
11
|
+
@alpha = coerce(alpha)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :red, :green, :blue, :alpha
|
|
15
|
+
|
|
16
|
+
def self.from_wire(wire)
|
|
17
|
+
new(red: wire.red, green: wire.green, blue: wire.blue, alpha: wire.reserved.zero? ? 255 : wire.reserved)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_wire
|
|
21
|
+
Binary::Types::ColorRef.new(red: red, green: green, blue: blue, reserved: alpha)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_hex
|
|
25
|
+
alpha == 255 ? format("#%02X%02X%02X", red, green, blue) : to_hex_rgba
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_hex_rgba
|
|
29
|
+
format("#%02X%02X%02X%02X", red, green, blue, alpha)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.black
|
|
33
|
+
new(red: 0, green: 0, blue: 0)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.white
|
|
37
|
+
new(red: 255, green: 255, blue: 255)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.transparent
|
|
41
|
+
new(red: 0, green: 0, blue: 0, alpha: 0)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ==(other)
|
|
45
|
+
other.is_a?(self.class) &&
|
|
46
|
+
red == other.red && green == other.green &&
|
|
47
|
+
blue == other.blue && alpha == other.alpha
|
|
48
|
+
end
|
|
49
|
+
alias eql? ==
|
|
50
|
+
|
|
51
|
+
def hash
|
|
52
|
+
[self.class, red, green, blue, alpha].hash
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def inspect
|
|
56
|
+
"#<#{self.class.name} #{to_hex_rgba}>"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def coerce(value)
|
|
62
|
+
Integer(value)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Geometry
|
|
6
|
+
class Matrix
|
|
7
|
+
IDENTITY = {
|
|
8
|
+
m11: 1.0, m12: 0.0, m21: 0.0, m22: 1.0, dx: 0.0, dy: 0.0
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(m11:, m12:, m21:, m22:, dx:, dy:)
|
|
12
|
+
@m11 = m11.to_f
|
|
13
|
+
@m12 = m12.to_f
|
|
14
|
+
@m21 = m21.to_f
|
|
15
|
+
@m22 = m22.to_f
|
|
16
|
+
@dx = dx.to_f
|
|
17
|
+
@dy = dy.to_f
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :m11, :m12, :m21, :m22, :dx, :dy
|
|
21
|
+
|
|
22
|
+
def self.from_wire(wire)
|
|
23
|
+
new(m11: wire.m11, m12: wire.m12, m21: wire.m21, m22: wire.m22, dx: wire.dx, dy: wire.dy)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_wire
|
|
27
|
+
Binary::Types::XForm.new(m11: m11, m12: m12, m21: m21, m22: m22, dx: dx, dy: dy)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.identity
|
|
31
|
+
new(**IDENTITY)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def identity?
|
|
35
|
+
m11 == 1.0 && m12 == 0.0 && m21 == 0.0 && m22 == 1.0 && dx == 0.0 && dy == 0.0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ==(other)
|
|
39
|
+
other.is_a?(self.class) &&
|
|
40
|
+
m11 == other.m11 && m12 == other.m12 &&
|
|
41
|
+
m21 == other.m21 && m22 == other.m22 &&
|
|
42
|
+
dx == other.dx && dy == other.dy
|
|
43
|
+
end
|
|
44
|
+
alias eql? ==
|
|
45
|
+
|
|
46
|
+
def hash
|
|
47
|
+
[self.class, m11, m12, m21, m22, dx, dy].hash
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Geometry
|
|
6
|
+
class Point
|
|
7
|
+
def initialize(x:, y:)
|
|
8
|
+
@x = x
|
|
9
|
+
@y = y
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :x, :y
|
|
13
|
+
|
|
14
|
+
def self.from_wire(wire)
|
|
15
|
+
new(x: wire.x, y: wire.y)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_wire
|
|
19
|
+
Binary::Types::PointL.new(x: x, y: y)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def translate(dx, dy)
|
|
23
|
+
self.class.new(x: x + dx, y: y + dy)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
other.is_a?(self.class) && x == other.x && y == other.y
|
|
28
|
+
end
|
|
29
|
+
alias eql? ==
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
[self.class, x, y].hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
"#<#{self.class.name} x=#{x} y=#{y}>"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Geometry
|
|
6
|
+
class PointF
|
|
7
|
+
def initialize(x:, y:)
|
|
8
|
+
@x = x.to_f
|
|
9
|
+
@y = y.to_f
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :x, :y
|
|
13
|
+
|
|
14
|
+
def self.from_wire(wire)
|
|
15
|
+
new(x: wire.x, y: wire.y)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_wire
|
|
19
|
+
Binary::Types::PointD.new(x: x, y: y)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.is_a?(self.class) && x == other.x && y == other.y
|
|
24
|
+
end
|
|
25
|
+
alias eql? ==
|
|
26
|
+
|
|
27
|
+
def hash
|
|
28
|
+
[self.class, x, y].hash
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Geometry
|
|
6
|
+
class PointS
|
|
7
|
+
def initialize(x:, y:)
|
|
8
|
+
@x = x
|
|
9
|
+
@y = y
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :x, :y
|
|
13
|
+
|
|
14
|
+
def self.from_wire(wire)
|
|
15
|
+
new(x: wire.x, y: wire.y)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_wire
|
|
19
|
+
Binary::Types::PointS.new(x: x, y: y)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.is_a?(self.class) && x == other.x && y == other.y
|
|
24
|
+
end
|
|
25
|
+
alias eql? ==
|
|
26
|
+
|
|
27
|
+
def hash
|
|
28
|
+
[self.class, x, y].hash
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
module Geometry
|
|
6
|
+
class Rect
|
|
7
|
+
def initialize(left:, top:, right:, bottom:)
|
|
8
|
+
@left = left
|
|
9
|
+
@top = top
|
|
10
|
+
@right = right
|
|
11
|
+
@bottom = bottom
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :left, :top, :right, :bottom
|
|
15
|
+
|
|
16
|
+
def self.from_wire(wire)
|
|
17
|
+
new(left: wire.left, top: wire.top, right: wire.right, bottom: wire.bottom)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_wire
|
|
21
|
+
Binary::Types::RectL.new(left: left, top: top, right: right, bottom: bottom)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def width
|
|
25
|
+
right - left
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def height
|
|
29
|
+
bottom - top
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def empty?
|
|
33
|
+
width <= 0 || height <= 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def contains?(point)
|
|
37
|
+
point.x.between?(left, right) && point.y.between?(top, bottom)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ==(other)
|
|
41
|
+
other.is_a?(self.class) &&
|
|
42
|
+
left == other.left && top == other.top &&
|
|
43
|
+
right == other.right && bottom == other.bottom
|
|
44
|
+
end
|
|
45
|
+
alias eql? ==
|
|
46
|
+
|
|
47
|
+
def hash
|
|
48
|
+
[self.class, left, top, right, bottom].hash
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def inspect
|
|
52
|
+
"#<#{self.class.name} left=#{left} top=#{top} right=#{right} bottom=#{bottom}>"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|