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.
Files changed (167) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +24 -0
  3. data/README.adoc +136 -0
  4. data/exe/emf +113 -0
  5. data/lib/emf/binary/codec.rb +19 -0
  6. data/lib/emf/binary/types.rb +105 -0
  7. data/lib/emf/binary.rb +10 -0
  8. data/lib/emf/detector.rb +58 -0
  9. data/lib/emf/emf_plus/binary.rb +8 -0
  10. data/lib/emf/emf_plus/parser.rb +13 -0
  11. data/lib/emf/emf_plus/serializer.rb +13 -0
  12. data/lib/emf/emf_plus.rb +9 -0
  13. data/lib/emf/emr/binary/header.rb +41 -0
  14. data/lib/emf/emr/binary/record.rb +17 -0
  15. data/lib/emf/emr/binary/records/abort_path.rb +16 -0
  16. data/lib/emf/emr/binary/records/alpha_blend.rb +17 -0
  17. data/lib/emf/emr/binary/records/angle_arc.rb +19 -0
  18. data/lib/emf/emr/binary/records/arc.rb +19 -0
  19. data/lib/emf/emr/binary/records/arc_to.rb +19 -0
  20. data/lib/emf/emr/binary/records/begin_path.rb +16 -0
  21. data/lib/emf/emr/binary/records/bit_blt.rb +37 -0
  22. data/lib/emf/emr/binary/records/chord.rb +19 -0
  23. data/lib/emf/emr/binary/records/close_figure.rb +16 -0
  24. data/lib/emf/emr/binary/records/color_correct_palette.rb +17 -0
  25. data/lib/emf/emr/binary/records/colormatch_to_target_w.rb +17 -0
  26. data/lib/emf/emr/binary/records/comment.rb +20 -0
  27. data/lib/emf/emr/binary/records/create_brush_indirect.rb +20 -0
  28. data/lib/emf/emr/binary/records/create_colorspace.rb +17 -0
  29. data/lib/emf/emr/binary/records/create_colorspace_w.rb +17 -0
  30. data/lib/emf/emr/binary/records/create_dib_pattern_brush_pt.rb +25 -0
  31. data/lib/emf/emr/binary/records/create_font_indirect_w.rb +18 -0
  32. data/lib/emf/emr/binary/records/create_mono_brush.rb +25 -0
  33. data/lib/emf/emr/binary/records/create_palette.rb +17 -0
  34. data/lib/emf/emr/binary/records/create_pen.rb +20 -0
  35. data/lib/emf/emr/binary/records/delete_colorspace.rb +17 -0
  36. data/lib/emf/emr/binary/records/delete_object.rb +16 -0
  37. data/lib/emf/emr/binary/records/draw_escape.rb +17 -0
  38. data/lib/emf/emr/binary/records/ellipse.rb +18 -0
  39. data/lib/emf/emr/binary/records/end_path.rb +16 -0
  40. data/lib/emf/emr/binary/records/eof.rb +21 -0
  41. data/lib/emf/emr/binary/records/exclude_clip_rect.rb +16 -0
  42. data/lib/emf/emr/binary/records/ext_create_pen.rb +17 -0
  43. data/lib/emf/emr/binary/records/ext_escape.rb +17 -0
  44. data/lib/emf/emr/binary/records/ext_flood_fill.rb +17 -0
  45. data/lib/emf/emr/binary/records/ext_select_clip_rgn.rb +19 -0
  46. data/lib/emf/emr/binary/records/ext_text_out_a.rb +27 -0
  47. data/lib/emf/emr/binary/records/ext_text_out_w.rb +41 -0
  48. data/lib/emf/emr/binary/records/fill_path.rb +16 -0
  49. data/lib/emf/emr/binary/records/fill_rgn.rb +17 -0
  50. data/lib/emf/emr/binary/records/flatten_path.rb +16 -0
  51. data/lib/emf/emr/binary/records/force_ufi_mapping.rb +17 -0
  52. data/lib/emf/emr/binary/records/frame_rgn.rb +17 -0
  53. data/lib/emf/emr/binary/records/gls_bounded_record.rb +17 -0
  54. data/lib/emf/emr/binary/records/gls_record.rb +17 -0
  55. data/lib/emf/emr/binary/records/gradient_fill.rb +19 -0
  56. data/lib/emf/emr/binary/records/intersect_clip_rect.rb +16 -0
  57. data/lib/emf/emr/binary/records/invert_rgn.rb +17 -0
  58. data/lib/emf/emr/binary/records/line_to.rb +16 -0
  59. data/lib/emf/emr/binary/records/mask_blt.rb +17 -0
  60. data/lib/emf/emr/binary/records/modify_world_transform.rb +17 -0
  61. data/lib/emf/emr/binary/records/move_to_ex.rb +16 -0
  62. data/lib/emf/emr/binary/records/named_escape.rb +17 -0
  63. data/lib/emf/emr/binary/records/offset_clip_rgn.rb +16 -0
  64. data/lib/emf/emr/binary/records/paint_rgn.rb +17 -0
  65. data/lib/emf/emr/binary/records/pie.rb +19 -0
  66. data/lib/emf/emr/binary/records/pixel_format.rb +17 -0
  67. data/lib/emf/emr/binary/records/plg_blt.rb +17 -0
  68. data/lib/emf/emr/binary/records/poly_bezier.rb +17 -0
  69. data/lib/emf/emr/binary/records/poly_bezier16.rb +17 -0
  70. data/lib/emf/emr/binary/records/poly_bezier_to.rb +17 -0
  71. data/lib/emf/emr/binary/records/poly_bezier_to16.rb +17 -0
  72. data/lib/emf/emr/binary/records/poly_draw.rb +18 -0
  73. data/lib/emf/emr/binary/records/poly_draw16.rb +18 -0
  74. data/lib/emf/emr/binary/records/poly_polygon.rb +19 -0
  75. data/lib/emf/emr/binary/records/poly_polygon16.rb +19 -0
  76. data/lib/emf/emr/binary/records/poly_polyline.rb +19 -0
  77. data/lib/emf/emr/binary/records/poly_polyline16.rb +19 -0
  78. data/lib/emf/emr/binary/records/poly_textout_a.rb +17 -0
  79. data/lib/emf/emr/binary/records/poly_textout_w.rb +17 -0
  80. data/lib/emf/emr/binary/records/polygon.rb +17 -0
  81. data/lib/emf/emr/binary/records/polygon16.rb +17 -0
  82. data/lib/emf/emr/binary/records/polyline.rb +17 -0
  83. data/lib/emf/emr/binary/records/polyline16.rb +17 -0
  84. data/lib/emf/emr/binary/records/polyline_to.rb +17 -0
  85. data/lib/emf/emr/binary/records/polyline_to16.rb +17 -0
  86. data/lib/emf/emr/binary/records/raw.rb +19 -0
  87. data/lib/emf/emr/binary/records/realize_palette.rb +15 -0
  88. data/lib/emf/emr/binary/records/rectangle.rb +18 -0
  89. data/lib/emf/emr/binary/records/resize_palette.rb +17 -0
  90. data/lib/emf/emr/binary/records/restore_dc.rb +17 -0
  91. data/lib/emf/emr/binary/records/round_rect.rb +19 -0
  92. data/lib/emf/emr/binary/records/save_dc.rb +16 -0
  93. data/lib/emf/emr/binary/records/scale_viewport_ext_ex.rb +19 -0
  94. data/lib/emf/emr/binary/records/scale_window_ext_ex.rb +19 -0
  95. data/lib/emf/emr/binary/records/select_clip_path.rb +20 -0
  96. data/lib/emf/emr/binary/records/select_object.rb +16 -0
  97. data/lib/emf/emr/binary/records/select_palette.rb +16 -0
  98. data/lib/emf/emr/binary/records/set_arc_direction.rb +16 -0
  99. data/lib/emf/emr/binary/records/set_bk_color.rb +16 -0
  100. data/lib/emf/emr/binary/records/set_bk_mode.rb +16 -0
  101. data/lib/emf/emr/binary/records/set_brush_org_ex.rb +16 -0
  102. data/lib/emf/emr/binary/records/set_colorspace.rb +17 -0
  103. data/lib/emf/emr/binary/records/set_dibits_to_device.rb +17 -0
  104. data/lib/emf/emr/binary/records/set_icm_mode.rb +16 -0
  105. data/lib/emf/emr/binary/records/set_icm_profile_a.rb +17 -0
  106. data/lib/emf/emr/binary/records/set_icm_profile_w.rb +17 -0
  107. data/lib/emf/emr/binary/records/set_layout.rb +16 -0
  108. data/lib/emf/emr/binary/records/set_linked_ufis.rb +17 -0
  109. data/lib/emf/emr/binary/records/set_map_mode.rb +16 -0
  110. data/lib/emf/emr/binary/records/set_mapper_flags.rb +16 -0
  111. data/lib/emf/emr/binary/records/set_metargn.rb +16 -0
  112. data/lib/emf/emr/binary/records/set_miter_limit.rb +16 -0
  113. data/lib/emf/emr/binary/records/set_palette_entries.rb +17 -0
  114. data/lib/emf/emr/binary/records/set_pixel_v.rb +18 -0
  115. data/lib/emf/emr/binary/records/set_poly_fill_mode.rb +16 -0
  116. data/lib/emf/emr/binary/records/set_rop2.rb +16 -0
  117. data/lib/emf/emr/binary/records/set_stretch_blt_mode.rb +16 -0
  118. data/lib/emf/emr/binary/records/set_text_align.rb +16 -0
  119. data/lib/emf/emr/binary/records/set_text_color.rb +17 -0
  120. data/lib/emf/emr/binary/records/set_text_justification.rb +17 -0
  121. data/lib/emf/emr/binary/records/set_viewport_ext_ex.rb +16 -0
  122. data/lib/emf/emr/binary/records/set_viewport_org_ex.rb +16 -0
  123. data/lib/emf/emr/binary/records/set_window_ext_ex.rb +16 -0
  124. data/lib/emf/emr/binary/records/set_window_org_ex.rb +16 -0
  125. data/lib/emf/emr/binary/records/set_world_transform.rb +16 -0
  126. data/lib/emf/emr/binary/records/small_text_out.rb +28 -0
  127. data/lib/emf/emr/binary/records/stretch_blt.rb +39 -0
  128. data/lib/emf/emr/binary/records/stretch_dibits.rb +42 -0
  129. data/lib/emf/emr/binary/records/stroke_and_fill_path.rb +16 -0
  130. data/lib/emf/emr/binary/records/stroke_path.rb +16 -0
  131. data/lib/emf/emr/binary/records/transparent_blt.rb +17 -0
  132. data/lib/emf/emr/binary/records/widen_path.rb +16 -0
  133. data/lib/emf/emr/binary/records.rb +258 -0
  134. data/lib/emf/emr/binary/type_codes.rb +132 -0
  135. data/lib/emf/emr/binary/with_bounds.rb +15 -0
  136. data/lib/emf/emr/binary.rb +13 -0
  137. data/lib/emf/emr/parser.rb +185 -0
  138. data/lib/emf/emr/path_association.rb +16 -0
  139. data/lib/emf/emr/serializer.rb +24 -0
  140. data/lib/emf/emr.rb +10 -0
  141. data/lib/emf/error.rb +19 -0
  142. data/lib/emf/model/emr/header.rb +93 -0
  143. data/lib/emf/model/emr/records.rb +48 -0
  144. data/lib/emf/model/emr.rb +10 -0
  145. data/lib/emf/model/geometry/color.rb +67 -0
  146. data/lib/emf/model/geometry/matrix.rb +52 -0
  147. data/lib/emf/model/geometry/point.rb +41 -0
  148. data/lib/emf/model/geometry/point_f.rb +33 -0
  149. data/lib/emf/model/geometry/point_s.rb +33 -0
  150. data/lib/emf/model/geometry/rect.rb +57 -0
  151. data/lib/emf/model/geometry/rect_s.rb +49 -0
  152. data/lib/emf/model/geometry/size.rb +33 -0
  153. data/lib/emf/model/geometry.rb +16 -0
  154. data/lib/emf/model/metafile.rb +34 -0
  155. data/lib/emf/model/record.rb +19 -0
  156. data/lib/emf/model/visitor.rb +42 -0
  157. data/lib/emf/model.rb +11 -0
  158. data/lib/emf/version.rb +5 -0
  159. data/lib/emf/visitors/dump.rb +57 -0
  160. data/lib/emf/visitors/stats.rb +63 -0
  161. data/lib/emf/visitors.rb +8 -0
  162. data/lib/emf/wmf/binary.rb +9 -0
  163. data/lib/emf/wmf/parser.rb +13 -0
  164. data/lib/emf/wmf/serializer.rb +13 -0
  165. data/lib/emf/wmf.rb +9 -0
  166. data/lib/emf.rb +56 -0
  167. 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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Emr
5
+ autoload :Parser, "emf/emr/parser"
6
+ autoload :Serializer, "emf/emr/serializer"
7
+ autoload :PathAssociation, "emf/emr/path_association"
8
+ autoload :Binary, "emf/emr/binary"
9
+ end
10
+ end
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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Model
5
+ module Emr
6
+ autoload :Header, "emf/model/emr/header"
7
+ autoload :Records, "emf/model/emr/records"
8
+ end
9
+ end
10
+ 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