emf 0.1.2 → 0.2.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 (360) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +224 -19
  3. data/exe/emf +12 -0
  4. data/lib/emf/backend.rb +60 -0
  5. data/lib/emf/binary/class_field_accessors.rb +49 -0
  6. data/lib/emf/binary/float32_le.rb +54 -0
  7. data/lib/emf/binary/types.rb +40 -6
  8. data/lib/emf/conformance/context.rb +36 -0
  9. data/lib/emf/conformance/diagnostic.rb +18 -0
  10. data/lib/emf/conformance/manifest/emf_bitmap_misc.yml +622 -0
  11. data/lib/emf/conformance/manifest/emf_control_buffers.yml +61 -0
  12. data/lib/emf/conformance/manifest/emf_core.yml +117 -0
  13. data/lib/emf/conformance/manifest/emf_corpus_rules.yml +21 -0
  14. data/lib/emf/conformance/manifest/emf_objects_geometry.yml +229 -0
  15. data/lib/emf/conformance/manifest/emf_plus.yml +357 -0
  16. data/lib/emf/conformance/manifest/emf_plus_core.yml +33 -0
  17. data/lib/emf/conformance/manifest/emf_state_path.yml +403 -0
  18. data/lib/emf/conformance/manifest/emf_text.yml +97 -0
  19. data/lib/emf/conformance/manifest/wmf.yml +874 -0
  20. data/lib/emf/conformance/manifest.rb +79 -0
  21. data/lib/emf/conformance/profile.rb +35 -0
  22. data/lib/emf/conformance/record_types.rb +24 -0
  23. data/lib/emf/conformance/registry.rb +76 -0
  24. data/lib/emf/conformance/report.rb +83 -0
  25. data/lib/emf/conformance/rule.rb +16 -0
  26. data/lib/emf/conformance/rules/emf_bitmap.rb +357 -0
  27. data/lib/emf/conformance/rules/emf_comment_escape.rb +309 -0
  28. data/lib/emf/conformance/rules/emf_control_buffers.rb +137 -0
  29. data/lib/emf/conformance/rules/emf_corpus_named.rb +83 -0
  30. data/lib/emf/conformance/rules/emf_framing.rb +94 -0
  31. data/lib/emf/conformance/rules/emf_geometry.rb +282 -0
  32. data/lib/emf/conformance/rules/emf_header.rb +114 -0
  33. data/lib/emf/conformance/rules/emf_objects.rb +291 -0
  34. data/lib/emf/conformance/rules/emf_parse_recovery.rb +97 -0
  35. data/lib/emf/conformance/rules/emf_path_clip.rb +165 -0
  36. data/lib/emf/conformance/rules/emf_plus.rb +75 -0
  37. data/lib/emf/conformance/rules/emf_plus_common.rb +58 -0
  38. data/lib/emf/conformance/rules/emf_plus_drawing.rb +78 -0
  39. data/lib/emf/conformance/rules/emf_plus_effects.rb +104 -0
  40. data/lib/emf/conformance/rules/emf_plus_fields.rb +169 -0
  41. data/lib/emf/conformance/rules/emf_plus_fields_b.rb +102 -0
  42. data/lib/emf/conformance/rules/emf_plus_framing.rb +172 -0
  43. data/lib/emf/conformance/rules/emf_plus_objects.rb +97 -0
  44. data/lib/emf/conformance/rules/emf_plus_payloads.rb +320 -0
  45. data/lib/emf/conformance/rules/emf_plus_properties.rb +96 -0
  46. data/lib/emf/conformance/rules/emf_plus_state.rb +77 -0
  47. data/lib/emf/conformance/rules/emf_state.rb +244 -0
  48. data/lib/emf/conformance/rules/emf_text.rb +221 -0
  49. data/lib/emf/conformance/rules/emf_transform.rb +85 -0
  50. data/lib/emf/conformance/rules/wmf_buffers.rb +259 -0
  51. data/lib/emf/conformance/rules/wmf_framing.rb +146 -0
  52. data/lib/emf/conformance/rules/wmf_layout_records.rb +112 -0
  53. data/lib/emf/conformance/rules/wmf_objects.rb +168 -0
  54. data/lib/emf/conformance/rules/wmf_records.rb +191 -0
  55. data/lib/emf/conformance/rules/wmf_rejections.rb +184 -0
  56. data/lib/emf/conformance/runner.rb +61 -0
  57. data/lib/emf/conformance/ternary_raster_operations.rb +52 -0
  58. data/lib/emf/conformance.rb +31 -0
  59. data/lib/emf/emf_plus/effects/blur.rb +24 -0
  60. data/lib/emf/emf_plus/effects/brightness_contrast.rb +22 -0
  61. data/lib/emf/emf_plus/effects/color_balance.rb +24 -0
  62. data/lib/emf/emf_plus/effects/color_curve.rb +24 -0
  63. data/lib/emf/emf_plus/effects/color_lookup_table.rb +26 -0
  64. data/lib/emf/emf_plus/effects/color_matrix.rb +23 -0
  65. data/lib/emf/emf_plus/effects/effect.rb +50 -0
  66. data/lib/emf/emf_plus/effects/hue_saturation_lightness.rb +24 -0
  67. data/lib/emf/emf_plus/effects/levels.rb +24 -0
  68. data/lib/emf/emf_plus/effects/opaque.rb +19 -0
  69. data/lib/emf/emf_plus/effects/red_eye_correction.rb +35 -0
  70. data/lib/emf/emf_plus/effects/sharpen.rb +24 -0
  71. data/lib/emf/emf_plus/effects/tint.rb +22 -0
  72. data/lib/emf/emf_plus/effects.rb +45 -0
  73. data/lib/emf/emf_plus/frame.rb +41 -0
  74. data/lib/emf/emf_plus/links/effect_link.rb +16 -0
  75. data/lib/emf/emf_plus/links/get_dc_span.rb +18 -0
  76. data/lib/emf/emf_plus/links/link_error.rb +15 -0
  77. data/lib/emf/emf_plus/links.rb +77 -0
  78. data/lib/emf/emf_plus/object_chain.rb +108 -0
  79. data/lib/emf/emf_plus/object_table.rb +182 -0
  80. data/lib/emf/emf_plus/objects/brush.rb +296 -0
  81. data/lib/emf/emf_plus/objects/custom_line_cap.rb +106 -0
  82. data/lib/emf/emf_plus/objects/font.rb +47 -0
  83. data/lib/emf/emf_plus/objects/image.rb +68 -0
  84. data/lib/emf/emf_plus/objects/image_attributes.rb +28 -0
  85. data/lib/emf/emf_plus/objects/opaque.rb +19 -0
  86. data/lib/emf/emf_plus/objects/parts.rb +103 -0
  87. data/lib/emf/emf_plus/objects/path.rb +21 -0
  88. data/lib/emf/emf_plus/objects/pen.rb +132 -0
  89. data/lib/emf/emf_plus/objects/region.rb +39 -0
  90. data/lib/emf/emf_plus/objects/string_format.rb +55 -0
  91. data/lib/emf/emf_plus/objects/typed.rb +50 -0
  92. data/lib/emf/emf_plus/objects.rb +46 -0
  93. data/lib/emf/emf_plus/parser.rb +64 -2
  94. data/lib/emf/emf_plus/records/base.rb +107 -0
  95. data/lib/emf/emf_plus/records/begin_container.rb +31 -0
  96. data/lib/emf/emf_plus/records/begin_container_no_params.rb +20 -0
  97. data/lib/emf/emf_plus/records/body.rb +16 -0
  98. data/lib/emf/emf_plus/records/clear.rb +22 -0
  99. data/lib/emf/emf_plus/records/clip_transform_flags.rb +26 -0
  100. data/lib/emf/emf_plus/records/comment.rb +21 -0
  101. data/lib/emf/emf_plus/records/draw_arc.rb +33 -0
  102. data/lib/emf/emf_plus/records/draw_beziers.rb +31 -0
  103. data/lib/emf/emf_plus/records/draw_closed_curve.rb +34 -0
  104. data/lib/emf/emf_plus/records/draw_curve.rb +38 -0
  105. data/lib/emf/emf_plus/records/draw_driver_string.rb +61 -0
  106. data/lib/emf/emf_plus/records/draw_ellipse.rb +28 -0
  107. data/lib/emf/emf_plus/records/draw_image.rb +42 -0
  108. data/lib/emf/emf_plus/records/draw_image_points.rb +62 -0
  109. data/lib/emf/emf_plus/records/draw_lines.rb +34 -0
  110. data/lib/emf/emf_plus/records/draw_path.rb +23 -0
  111. data/lib/emf/emf_plus/records/draw_pie.rb +33 -0
  112. data/lib/emf/emf_plus/records/draw_rects.rb +30 -0
  113. data/lib/emf/emf_plus/records/draw_string.rb +41 -0
  114. data/lib/emf/emf_plus/records/end_container.rb +20 -0
  115. data/lib/emf/emf_plus/records/end_of_file.rb +17 -0
  116. data/lib/emf/emf_plus/records/fill_brush.rb +21 -0
  117. data/lib/emf/emf_plus/records/fill_closed_curve.rb +38 -0
  118. data/lib/emf/emf_plus/records/fill_ellipse.rb +29 -0
  119. data/lib/emf/emf_plus/records/fill_path.rb +23 -0
  120. data/lib/emf/emf_plus/records/fill_pie.rb +34 -0
  121. data/lib/emf/emf_plus/records/fill_polygon.rb +32 -0
  122. data/lib/emf/emf_plus/records/fill_rects.rb +31 -0
  123. data/lib/emf/emf_plus/records/fill_region.rb +23 -0
  124. data/lib/emf/emf_plus/records/get_dc.rb +18 -0
  125. data/lib/emf/emf_plus/records/header.rb +35 -0
  126. data/lib/emf/emf_plus/records/multiply_world_transform.rb +24 -0
  127. data/lib/emf/emf_plus/records/object.rb +32 -0
  128. data/lib/emf/emf_plus/records/offset_clip.rb +24 -0
  129. data/lib/emf/emf_plus/records/primitives/argb.rb +20 -0
  130. data/lib/emf/emf_plus/records/primitives/bitmap.rb +32 -0
  131. data/lib/emf/emf_plus/records/primitives/bitmap_data.rb +21 -0
  132. data/lib/emf/emf_plus/records/primitives/blend_colors.rb +22 -0
  133. data/lib/emf/emf_plus/records/primitives/blend_factors.rb +21 -0
  134. data/lib/emf/emf_plus/records/primitives/bounded_array.rb +38 -0
  135. data/lib/emf/emf_plus/records/primitives/byte_reader.rb +25 -0
  136. data/lib/emf/emf_plus/records/primitives/character_range.rb +20 -0
  137. data/lib/emf/emf_plus/records/primitives/compressed_image.rb +17 -0
  138. data/lib/emf/emf_plus/records/primitives/focus_scale_data.rb +20 -0
  139. data/lib/emf/emf_plus/records/primitives/graphics_version.rb +21 -0
  140. data/lib/emf/emf_plus/records/primitives/language_identifier.rb +22 -0
  141. data/lib/emf/emf_plus/records/primitives/metafile.rb +19 -0
  142. data/lib/emf/emf_plus/records/primitives/palette.rb +21 -0
  143. data/lib/emf/emf_plus/records/primitives/path.rb +89 -0
  144. data/lib/emf/emf_plus/records/primitives/path_point_type.rb +21 -0
  145. data/lib/emf/emf_plus/records/primitives/path_point_type_rle.rb +23 -0
  146. data/lib/emf/emf_plus/records/primitives/point.rb +18 -0
  147. data/lib/emf/emf_plus/records/primitives/point_array.rb +42 -0
  148. data/lib/emf/emf_plus/records/primitives/point_f.rb +19 -0
  149. data/lib/emf/emf_plus/records/primitives/point_r.rb +19 -0
  150. data/lib/emf/emf_plus/records/primitives/rect.rb +20 -0
  151. data/lib/emf/emf_plus/records/primitives/rect_array.rb +24 -0
  152. data/lib/emf/emf_plus/records/primitives/rect_f.rb +21 -0
  153. data/lib/emf/emf_plus/records/primitives/region_node.rb +68 -0
  154. data/lib/emf/emf_plus/records/primitives/region_node_child_nodes.rb +22 -0
  155. data/lib/emf/emf_plus/records/primitives/region_node_path.rb +37 -0
  156. data/lib/emf/emf_plus/records/primitives/relative_coordinate.rb +32 -0
  157. data/lib/emf/emf_plus/records/primitives/string_format_data.rb +23 -0
  158. data/lib/emf/emf_plus/records/primitives/transform_matrix.rb +23 -0
  159. data/lib/emf/emf_plus/records/primitives.rb +41 -0
  160. data/lib/emf/emf_plus/records/reset_clip.rb +17 -0
  161. data/lib/emf/emf_plus/records/reset_world_transform.rb +17 -0
  162. data/lib/emf/emf_plus/records/restore.rb +20 -0
  163. data/lib/emf/emf_plus/records/rotate_world_transform.rb +24 -0
  164. data/lib/emf/emf_plus/records/save.rb +20 -0
  165. data/lib/emf/emf_plus/records/scale_world_transform.rb +26 -0
  166. data/lib/emf/emf_plus/records/serializable_object.rb +32 -0
  167. data/lib/emf/emf_plus/records/set_anti_alias_mode.rb +25 -0
  168. data/lib/emf/emf_plus/records/set_clip_path.rb +20 -0
  169. data/lib/emf/emf_plus/records/set_clip_rect.rb +24 -0
  170. data/lib/emf/emf_plus/records/set_clip_region.rb +20 -0
  171. data/lib/emf/emf_plus/records/set_compositing_mode.rb +21 -0
  172. data/lib/emf/emf_plus/records/set_compositing_quality.rb +21 -0
  173. data/lib/emf/emf_plus/records/set_interpolation_mode.rb +21 -0
  174. data/lib/emf/emf_plus/records/set_page_transform.rb +25 -0
  175. data/lib/emf/emf_plus/records/set_pixel_offset_mode.rb +21 -0
  176. data/lib/emf/emf_plus/records/set_rendering_origin.rb +22 -0
  177. data/lib/emf/emf_plus/records/set_text_contrast.rb +24 -0
  178. data/lib/emf/emf_plus/records/set_text_rendering_hint.rb +21 -0
  179. data/lib/emf/emf_plus/records/set_ts_clip.rb +40 -0
  180. data/lib/emf/emf_plus/records/set_ts_graphics.rb +88 -0
  181. data/lib/emf/emf_plus/records/set_world_transform.rb +22 -0
  182. data/lib/emf/emf_plus/records/shape_flags.rb +23 -0
  183. data/lib/emf/emf_plus/records/translate_world_transform.rb +26 -0
  184. data/lib/emf/emf_plus/records/unknown.rb +20 -0
  185. data/lib/emf/emf_plus/records.rb +107 -0
  186. data/lib/emf/emf_plus/serializer.rb +9 -2
  187. data/lib/emf/emf_plus/stream/conformance.rb +68 -0
  188. data/lib/emf/emf_plus/stream/editor.rb +21 -0
  189. data/lib/emf/emf_plus/stream/emf_gap.rb +20 -0
  190. data/lib/emf/emf_plus/stream/link_index.rb +30 -0
  191. data/lib/emf/emf_plus/stream/rebuild.rb +47 -0
  192. data/lib/emf/emf_plus/stream/record_handle.rb +26 -0
  193. data/lib/emf/emf_plus/stream/segment.rb +70 -0
  194. data/lib/emf/emf_plus/stream.rb +153 -0
  195. data/lib/emf/emf_plus.rb +9 -0
  196. data/lib/emf/emr/binary/dib_header.rb +142 -0
  197. data/lib/emf/emr/binary/ext_text_out_fields.rb +43 -0
  198. data/lib/emf/emr/binary/extended_pen_buffers.rb +23 -0
  199. data/lib/emf/emr/binary/fast_fill.rb +1164 -0
  200. data/lib/emf/emr/binary/font.rb +159 -0
  201. data/lib/emf/emr/binary/lazy_utf16_array.rb +82 -0
  202. data/lib/emf/emr/binary/pack_plan.rb +144 -0
  203. data/lib/emf/emr/binary/pack_plan_check.rb +75 -0
  204. data/lib/emf/emr/binary/pack_plan_exact.rb +130 -0
  205. data/lib/emf/emr/binary/pack_plan_generate.rb +116 -0
  206. data/lib/emf/emr/binary/pack_plan_values.rb +92 -0
  207. data/lib/emf/emr/binary/palette_body.rb +19 -0
  208. data/lib/emf/emr/binary/palette_entry.rb +18 -0
  209. data/lib/emf/emr/binary/record.rb +33 -0
  210. data/lib/emf/emr/binary/records/alpha_blend.rb +43 -0
  211. data/lib/emf/emr/binary/records/angle_arc.rb +2 -2
  212. data/lib/emf/emr/binary/records/bit_blt.rb +14 -0
  213. data/lib/emf/emr/binary/records/color_correct_palette.rb +55 -1
  214. data/lib/emf/emr/binary/records/colormatch_to_target_w.rb +76 -1
  215. data/lib/emf/emr/binary/records/create_colorspace.rb +67 -1
  216. data/lib/emf/emr/binary/records/create_colorspace_w.rb +90 -1
  217. data/lib/emf/emr/binary/records/create_dib_pattern_brush_pt.rb +14 -0
  218. data/lib/emf/emr/binary/records/create_font_indirect_w.rb +6 -2
  219. data/lib/emf/emr/binary/records/create_mono_brush.rb +14 -0
  220. data/lib/emf/emr/binary/records/create_palette.rb +19 -1
  221. data/lib/emf/emr/binary/records/delete_colorspace.rb +22 -1
  222. data/lib/emf/emr/binary/records/draw_escape.rb +45 -1
  223. data/lib/emf/emr/binary/records/ellipse.rb +1 -1
  224. data/lib/emf/emr/binary/records/eof.rb +9 -0
  225. data/lib/emf/emr/binary/records/ext_create_pen.rb +80 -1
  226. data/lib/emf/emr/binary/records/ext_escape.rb +45 -1
  227. data/lib/emf/emr/binary/records/ext_flood_fill.rb +9 -2
  228. data/lib/emf/emr/binary/records/ext_select_clip_rgn.rb +9 -1
  229. data/lib/emf/emr/binary/records/ext_text_out_a.rb +16 -3
  230. data/lib/emf/emr/binary/records/ext_text_out_w.rb +14 -3
  231. data/lib/emf/emr/binary/records/fill_rgn.rb +11 -2
  232. data/lib/emf/emr/binary/records/force_ufi_mapping.rb +38 -1
  233. data/lib/emf/emr/binary/records/frame_rgn.rb +12 -2
  234. data/lib/emf/emr/binary/records/gls_bounded_record.rb +61 -1
  235. data/lib/emf/emr/binary/records/gls_record.rb +42 -1
  236. data/lib/emf/emr/binary/records/gradient_fill.rb +7 -0
  237. data/lib/emf/emr/binary/records/invert_rgn.rb +11 -3
  238. data/lib/emf/emr/binary/records/mask_blt.rb +66 -0
  239. data/lib/emf/emr/binary/records/named_escape.rb +78 -1
  240. data/lib/emf/emr/binary/records/paint_rgn.rb +11 -3
  241. data/lib/emf/emr/binary/records/pixel_format.rb +76 -1
  242. data/lib/emf/emr/binary/records/plg_blt.rb +66 -0
  243. data/lib/emf/emr/binary/records/poly_textout_a.rb +128 -1
  244. data/lib/emf/emr/binary/records/poly_textout_w.rb +128 -1
  245. data/lib/emf/emr/binary/records/raw.rb +3 -0
  246. data/lib/emf/emr/binary/records/realize_palette.rb +1 -0
  247. data/lib/emf/emr/binary/records/rectangle.rb +1 -1
  248. data/lib/emf/emr/binary/records/resize_palette.rb +16 -2
  249. data/lib/emf/emr/binary/records/round_rect.rb +2 -2
  250. data/lib/emf/emr/binary/records/select_palette.rb +1 -0
  251. data/lib/emf/emr/binary/records/set_color_adjustment.rb +136 -0
  252. data/lib/emf/emr/binary/records/set_colorspace.rb +22 -1
  253. data/lib/emf/emr/binary/records/set_dibits_to_device.rb +43 -0
  254. data/lib/emf/emr/binary/records/set_icm_profile_a.rb +71 -1
  255. data/lib/emf/emr/binary/records/set_icm_profile_w.rb +71 -1
  256. data/lib/emf/emr/binary/records/set_linked_ufis.rb +49 -1
  257. data/lib/emf/emr/binary/records/set_miter_limit.rb +1 -1
  258. data/lib/emf/emr/binary/records/set_palette_entries.rb +20 -2
  259. data/lib/emf/emr/binary/records/set_text_justification.rb +8 -2
  260. data/lib/emf/emr/binary/records/small_text_out.rb +55 -4
  261. data/lib/emf/emr/binary/records/stretch_blt.rb +14 -0
  262. data/lib/emf/emr/binary/records/stretch_dibits.rb +14 -0
  263. data/lib/emf/emr/binary/records/transparent_blt.rb +43 -0
  264. data/lib/emf/emr/binary/records.rb +4 -2
  265. data/lib/emf/emr/binary/region_data.rb +33 -0
  266. data/lib/emf/emr/binary/state_record_body.rb +33 -0
  267. data/lib/emf/emr/binary/thin_shell.rb +226 -0
  268. data/lib/emf/emr/binary/value_view/layout.rb +101 -0
  269. data/lib/emf/emr/binary/value_view/reader_source.rb +88 -0
  270. data/lib/emf/emr/binary/value_view.rb +173 -0
  271. data/lib/emf/emr/binary/variable_buffers.rb +87 -0
  272. data/lib/emf/emr/binary.rb +4 -0
  273. data/lib/emf/emr/filled_record.rb +77 -0
  274. data/lib/emf/emr/parser.rb +239 -50
  275. data/lib/emf/emr/serializer.rb +4 -5
  276. data/lib/emf/emr.rb +1 -0
  277. data/lib/emf/error.rb +24 -1
  278. data/lib/emf/model/diagnostic.rb +41 -0
  279. data/lib/emf/model/emr/header.rb +40 -1
  280. data/lib/emf/model/emr/records.rb +142 -6
  281. data/lib/emf/model/emr/wire_values.rb +43 -0
  282. data/lib/emf/model/emr.rb +1 -0
  283. data/lib/emf/model/geometry/color.rb +19 -1
  284. data/lib/emf/model/metafile.rb +22 -2
  285. data/lib/emf/model/wmf/bitmap_records.rb +155 -0
  286. data/lib/emf/model/wmf/brush_records.rb +45 -0
  287. data/lib/emf/model/wmf/dc_escape_records.rb +67 -0
  288. data/lib/emf/model/wmf/font_record.rb +40 -0
  289. data/lib/emf/model/wmf/geometry_records.rb +107 -0
  290. data/lib/emf/model/wmf/header.rb +36 -0
  291. data/lib/emf/model/wmf/object_records.rb +71 -0
  292. data/lib/emf/model/wmf/palette_records.rb +68 -0
  293. data/lib/emf/model/wmf/records.rb +247 -0
  294. data/lib/emf/model/wmf/region_records.rb +112 -0
  295. data/lib/emf/model/wmf/state_records.rb +188 -0
  296. data/lib/emf/model/wmf/text_records.rb +36 -0
  297. data/lib/emf/model/wmf.rb +11 -0
  298. data/lib/emf/model.rb +2 -0
  299. data/lib/emf/native/bitmap_fields.rb +58 -0
  300. data/lib/emf/native/diagnostic_contract.rb +39 -0
  301. data/lib/emf/native/diagnostics.rb +81 -0
  302. data/lib/emf/native/emf_plus_loader.rb +35 -0
  303. data/lib/emf/native/emf_plus_snapshot.rb +96 -0
  304. data/lib/emf/native/emf_plus_source.rb +20 -0
  305. data/lib/emf/native/emf_plus_stream.rb +150 -0
  306. data/lib/emf/native/fields.rb +369 -0
  307. data/lib/emf/native/font_fields.rb +87 -0
  308. data/lib/emf/native/gradient_fields.rb +23 -0
  309. data/lib/emf/native/header.rb +35 -0
  310. data/lib/emf/native/header_errors.rb +30 -0
  311. data/lib/emf/native/hydrator.rb +151 -0
  312. data/lib/emf/native/lazy_wire.rb +100 -0
  313. data/lib/emf/native/legacy_state_fields.rb +22 -0
  314. data/lib/emf/native/library_path.rb +68 -0
  315. data/lib/emf/native/loader.rb +120 -0
  316. data/lib/emf/native/palette_fields.rb +45 -0
  317. data/lib/emf/native/poly_text_fields.rb +116 -0
  318. data/lib/emf/native/resource_failure.rb +28 -0
  319. data/lib/emf/native/small_text_fields.rb +27 -0
  320. data/lib/emf/native/snapshot.rb +102 -0
  321. data/lib/emf/native/wmf_bitmap_fields.rb +58 -0
  322. data/lib/emf/native/wmf_brush_fields.rb +62 -0
  323. data/lib/emf/native/wmf_fields.rb +117 -0
  324. data/lib/emf/native/wmf_hydrator.rb +143 -0
  325. data/lib/emf/native/wmf_loading.rb +49 -0
  326. data/lib/emf/native/wmf_palette_fields.rb +60 -0
  327. data/lib/emf/native/wmf_region_fields.rb +92 -0
  328. data/lib/emf/native/wmf_rejection.rb +84 -0
  329. data/lib/emf/native/wmf_remaining_fields.rb +70 -0
  330. data/lib/emf/native/wmf_snapshot.rb +51 -0
  331. data/lib/emf/native/wmf_text_fields.rb +56 -0
  332. data/lib/emf/native.rb +61 -0
  333. data/lib/emf/parse_evidence.rb +57 -0
  334. data/lib/emf/resource_budget/bitmap.rb +66 -0
  335. data/lib/emf/resource_budget/gradient.rb +30 -0
  336. data/lib/emf/resource_budget/poly_text.rb +85 -0
  337. data/lib/emf/resource_budget.rb +382 -0
  338. data/lib/emf/resource_limits/emf_records.rb +130 -0
  339. data/lib/emf/resource_limits.rb +58 -0
  340. data/lib/emf/version.rb +1 -1
  341. data/lib/emf/visitors/dump.rb +4 -0
  342. data/lib/emf/visitors/stats.rb +6 -5
  343. data/lib/emf/wmf/binary.rb +150 -1
  344. data/lib/emf/wmf/bitmap_codec.rb +118 -0
  345. data/lib/emf/wmf/brush_codec.rb +102 -0
  346. data/lib/emf/wmf/dc_escape_codec.rb +75 -0
  347. data/lib/emf/wmf/dib.rb +187 -0
  348. data/lib/emf/wmf/embedded_emf.rb +137 -0
  349. data/lib/emf/wmf/escape_views.rb +89 -0
  350. data/lib/emf/wmf/evidence.rb +255 -0
  351. data/lib/emf/wmf/geometry_codec.rb +49 -0
  352. data/lib/emf/wmf/palette_codec.rb +85 -0
  353. data/lib/emf/wmf/parser.rb +147 -2
  354. data/lib/emf/wmf/recovery.rb +95 -0
  355. data/lib/emf/wmf/region_codec.rb +151 -0
  356. data/lib/emf/wmf/serializer.rb +164 -2
  357. data/lib/emf/wmf/text_codec.rb +123 -0
  358. data/lib/emf/wmf.rb +7 -0
  359. data/lib/emf.rb +13 -9
  360. metadata +287 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2adf7662f217430cb3a716140567c8c5cfdf53e5da6b49b94a57c7ba6dc8e92
4
- data.tar.gz: '01019e1db86ed47be7494287fc164f2aa345ceaa72c8b66ade894d92e49f556c'
3
+ metadata.gz: f5ee6e6c5a49eabf172b92810e3ec3ab0591122a97ac4960a9f4af32efe6e79f
4
+ data.tar.gz: 033ccee0b06ca850f57aca486f0d07692cd4e5ac52bfcbb907f1889fd9fd9b5f
5
5
  SHA512:
6
- metadata.gz: 12935613493f55d59f77bd3a233293df0e14de40fc0f69674c48cdc6c4633a6f0560734de6faced0dd9dab146a48000345695e67a9b8f34141d5e06ac266ccbb
7
- data.tar.gz: 261484bdcf95539ca134bb7d2ad5602cdc9bf9140b48f340692292cbb024c14e8448085dfbf3b4a86dd7ceef48d0d0bd56f6a753081d094e6da3fbcada7eefbf
6
+ metadata.gz: 9746ee5767b814b7da75ee2ee6116a9963dbd260eebf8539cfce50d4ad91631b88339a373c47a7acfd8ee1beaab11bbfd20849db2630d31e7502f1b61e427b99
7
+ data.tar.gz: caae6ff8f26b3536b55486bca2c95a3bc5d42196cbaff9a4f1e0ea7cd18e03009e1b3447b162139a5f667c2e1755f06b19d32beec1c07b4d75d4722dcb105ff5
data/README.adoc CHANGED
@@ -1,6 +1,6 @@
1
- = emf: pure-Ruby parser for WMF, EMF, and EMF+ metafiles
1
+ = emf: parser for WMF, EMF, and EMF+ metafiles
2
2
 
3
- `emf` is a clean-room, pure-Ruby parser for the three Microsoft Windows
3
+ `emf` is a clean-room Ruby parser with an optional Rust native backend for the three Microsoft Windows
4
4
  metafile formats:
5
5
 
6
6
  * **WMF** — Windows Metafile (see MS-WMF, kaitai `wmf.ksy`)
@@ -15,13 +15,17 @@ wrapper around the GPLv2 `libemf2svg` C library.
15
15
 
16
16
  == Status
17
17
 
18
- * **EMF**: full parse + byte-identical round-trip on every fixture
19
- (186/186 in `spec/fixtures/emf/`, 21/21 in `spec/fixtures/emf-ea/`,
20
- 1/1 in `spec/fixtures/simple/`).
21
- * **EMF+**: EMF+ payloads are extracted from EMF carrier records as raw
22
- bytes (`Metafile#emf_plus`). Structured EMF+ parsing is TODO
23
- (see `TODO.impl/12-14`).
24
- * **WMF**: detected but not yet parsed (`TODO.impl/07`).
18
+ * **EMF**: parsing, mutable wire records and binary serialization are implemented.
19
+ * **EMF+**: raw carrier payloads (`Metafile#emf_plus`) and a typed stream view
20
+ (`Metafile#emf_plus_stream`) are implemented.
21
+ * **WMF**: standard and placeable headers, records and binary serialization
22
+ are implemented.
23
+ * **Native backend**: Rust decoding and a Ruby adapter are implemented and match the
24
+ Ruby backend on the valid and corrupt fixture corpora, every EMF record type and
25
+ mutated WMF input; installed platform qualification remains open.
26
+ * **Qualification**: implementation does not imply complete format coverage or
27
+ release readiness. See link:TODO.plan/10-progress.md[delivery progress] and
28
+ link:TODO.plan/09-format-extension.md[format extension acceptance].
25
29
  * **License**: BSD-2-Clause. No GPL obligation.
26
30
 
27
31
  == Installation
@@ -36,6 +40,45 @@ Or install directly:
36
40
 
37
41
  Requires Ruby >= 3.1.
38
42
 
43
+ == Backend selection
44
+
45
+ The public `Emf.parse`, `Emf.parse_file` and `Emf.serialize` APIs use
46
+ `EMF_BACKEND` at call time:
47
+
48
+ [cols="1,3",options="header"]
49
+ |===
50
+ |Value |Behavior
51
+ |`auto` (default) |Use the native backend when available; fall back to Ruby if native setup raises `Emf::Native::Unavailable`.
52
+ |`ruby` |Use Ruby directly, without loading the native library.
53
+ |`native` |Require the native backend; unavailable setup raises `Emf::Native::Unavailable`.
54
+ |===
55
+
56
+ `Emf.backend` resolves the backend at call time and returns `:ruby` or `:native`.
57
+ It uses the same selection as parsing and serialization, including the `auto`
58
+ availability fallback. It returns a symbol rather than a backend adapter.
59
+ Explicit `native` selection raises `Emf::Native::Unavailable` if unavailable;
60
+ unexpected setup errors propagate. This query may load the native library, and
61
+ its result describes the current settings rather than pinning future operations.
62
+
63
+ Other values raise `ArgumentError`. A parsing or serialization error after
64
+ backend selection propagates to the caller; it is not retried using Ruby.
65
+ Both backends return the existing Ruby model and use the Ruby binary writers
66
+ for serialization, including mutable wire records.
67
+
68
+ For example, to explicitly use Ruby:
69
+
70
+ EMF_BACKEND=ruby ruby -remf -e 'p Emf.parse_file(ARGV.fetch(0)).format' image.emf
71
+
72
+ Native loading uses a packaged platform manifest when present. For development
73
+ with an existing compatible library, `EMF_NATIVE_LIBRARY` overrides discovery:
74
+
75
+ EMF_BACKEND=native EMF_NATIVE_LIBRARY=/absolute/path/to/libemf_ffi.dylib ruby -remf -e 'p Emf.parse_file(ARGV.fetch(0)).format' image.emf
76
+
77
+ Use the appropriate shared-library filename for the platform. An empty override
78
+ makes native loading unavailable. Native installation and platform support must
79
+ be qualified using link:TODO.plan/08-platform-delivery.md[the delivery checks];
80
+ the source implementation alone does not establish an available platform gem.
81
+
39
82
  == Synopsis
40
83
 
41
84
  require "emf"
@@ -50,9 +93,23 @@ Requires Ruby >= 3.1.
50
93
  stats = Emf::Visitors::Stats.new.visit_all(metafile)
51
94
  puts stats.to_s
52
95
 
53
- # Serialise back to binary — byte-identical on every fixture
96
+ # Serialise the model back to binary
54
97
  bytes = Emf.serialize(metafile)
55
98
 
99
+ === Reading field values without BinData
100
+
101
+ `record.values` returns a record's field values as a frozen, plain Ruby object
102
+ (a `Data` with the wire's member names; nested structs as nested `Data`; a counted
103
+ point array as an Array of `Data` points with `x` and `y`) without making
104
+ BinData field objects. It is `nil` for records without a fixed layout or a
105
+ counted point array (for example ExtTextOut, bitmaps, fonts, Comment, Raw); use
106
+ `record.wire` for those. The values are equal on the Ruby and native backends and
107
+ always reflect edits made through `record.wire`.
108
+
109
+ record = metafile.records.find { |r| r.type_id == 86 } # POLYGON16
110
+ record.values.rcl_bounds.left
111
+ record.values.apts.map { |point| [point.x, point.y] }
112
+
56
113
  == Command-line tool
57
114
 
58
115
  emf version Print version
@@ -62,6 +119,145 @@ Requires Ruby >= 3.1.
62
119
  emf validate FILE Parse + report errors (exit 0 ok, 1 invalid)
63
120
  emf round-trip FILE [--out] Re-serialize after parse, compare or write
64
121
 
122
+ == Conformance lens
123
+
124
+ `Emf::Conformance` checks a metafile against the Microsoft specifications
125
+ (MS-EMF, MS-EMFPLUS and MS-WMF) and reports what it finds. It does not repair
126
+ files and it does not change what the parser returns. The report uses the key
127
+ layout of `svg_conform`, so tools that read one can read the other.
128
+
129
+ require "emf"
130
+
131
+ report = Emf::Conformance.check("image.emf", profile: :tolerant)
132
+ report.valid? # => true when there are no errors (warnings do not count)
133
+ report.to_h # => Hash with string keys
134
+ report.to_yaml
135
+
136
+ The lens reads only the public model, so the Ruby and the native backend give
137
+ the same report. It adds no runtime dependency: `bindata` is still the only one.
138
+
139
+ === Input
140
+
141
+ `check` accepts four forms:
142
+
143
+ [cols="1,3",options="header"]
144
+ |===
145
+ |Input |Behavior
146
+ |path `String` |Read with `File.binread`. The path becomes the report's `filename`.
147
+ |`Pathname` |Same as a path `String`.
148
+ |bytes `String` |Parsed once. `filename` is `nil`. A `String` is taken as a path only when it names an existing file, has no NUL byte and is under 4096 bytes long.
149
+ |`Emf::Model::Metafile` |Evaluated without parsing again. `filename` is `nil`.
150
+ |===
151
+
152
+ A parsed `Metafile` carries no input bytes. Rules that compare a declared
153
+ length with the real input length are skipped for it. One example is
154
+ `emf.header.bytes`. The WMF size check against the input is another. Pass the
155
+ path or the bytes when you want those rules to run.
156
+
157
+ === Profiles
158
+
159
+ `profile:` is `:strict` (the default) or `:tolerant`. Any other value raises
160
+ `ArgumentError`.
161
+
162
+ * `:strict` reports every spec violation as an error.
163
+ * `:tolerant` accepts what real producers write. A rule that carries a
164
+ `tolerant_reason` becomes a warning instead of an error. The warning keeps
165
+ its `requirement_id`, `kind`, citation and offset. The reason appears as
166
+ `context.waiver`.
167
+
168
+ A waiver belongs to the rule, not to a file. Every input that triggers that
169
+ rule gets the downgrade. There is no per-file allowlist. A rule without a
170
+ `tolerant_reason` is an error under both profiles.
171
+
172
+ Two files in `spec/fixtures/emf-corrupted/` cannot be parsed into a model. One
173
+ has a truncated header and one has an unrecognised first word. They report
174
+ `valid: false` under both profiles, with the error `emf.parse.unparseable`.
175
+ The other 19 corrupted fixtures are `valid: true` under `:tolerant`.
176
+
177
+ === The report
178
+
179
+ `Report#to_h` has these keys: `filename`, `profile`, `tool`, `version`,
180
+ `timestamp`, `valid`, `errors` and `warnings`. `errors` and `warnings` each hold
181
+ `total_count`, `by_requirement` and the ordered `issues`. Issues are ordered by
182
+ offset, then requirement id. Each issue has `type`, `requirement_id`,
183
+ `message`, `element`, `attribute`, `value`, `offset`, `severity`, `kind`,
184
+ `category`, `spec_citation`, `remediation_available` and `context`.
185
+ `offset` is the absolute byte offset in the file. `kind` is a stable
186
+ machine-readable name. `category` repeats `kind` and `severity` repeats `type`,
187
+ for `svg_conform` compatibility.
188
+
189
+ This is the strict report for a repo fixture. It comes from
190
+ `Emf::Conformance.check("spec/fixtures/emf/test-000.emf").to_h(timestamp: false).to_yaml`.
191
+ The `timestamp` line is left out by `timestamp: false`.
192
+
193
+ filename: spec/fixtures/emf/test-000.emf
194
+ profile: strict
195
+ tool: emf
196
+ version: 0.1.2
197
+ valid: false
198
+ errors:
199
+ total_count: 1
200
+ by_requirement:
201
+ emfplus.property.enum: 1
202
+ issues:
203
+ - type: error
204
+ requirement_id: emfplus.property.enum
205
+ message: SetCompositingQuality CompositingQuality value 0 is outside 1..5 (EMF+
206
+ record 0x4024 at offset 0 of EMR_COMMENT_EMFPLUS record 50)
207
+ element: emf_plus:0x4024
208
+ attribute:
209
+ value:
210
+ offset: 52152
211
+ severity: error
212
+ kind: property_enum
213
+ category: property_enum
214
+ spec_citation: MS-EMFPLUS 2.3.6
215
+ remediation_available: false
216
+ context:
217
+ spec_citation: MS-EMFPLUS 2.3.6
218
+ format: emf_plus
219
+ record_index: 50
220
+ record_type: 16420
221
+ warnings:
222
+ total_count: 0
223
+ by_requirement: {}
224
+ issues: []
225
+
226
+ With `profile: :tolerant` the same file is `valid: true`. The finding moves to
227
+ `warnings`, and `context.waiver` carries the written reason.
228
+
229
+ === Comparing backends
230
+
231
+ The report holds a creation `timestamp`, so two runs never compare equal as they
232
+ are. To compare two reports, drop the timestamp or pin it:
233
+
234
+ Emf::Conformance.check(path).to_h(timestamp: false)
235
+ Emf::Conformance.check(path, now: Time.utc(2026, 1, 1)).to_h # "timestamp" => "2026-01-01T00:00:00Z"
236
+
237
+ Run once with `EMF_BACKEND=ruby` and once with `EMF_BACKEND=native` (see
238
+ <<Backend selection>>) and compare the two results. They must be equal. The
239
+ `native` value raises `Emf::Native::Unavailable` when no native library loads.
240
+
241
+ === The rule manifest
242
+
243
+ Every rule the lens implements, and every check it does not yet make, is listed
244
+ in `lib/emf/conformance/manifest/*.yml`. A row has an `id`, a `format`, the
245
+ `record_types` it covers, a `citation`, a `status` and a `reason`. The reason is
246
+ required for every status except `implemented`.
247
+
248
+ [cols="1,3",options="header"]
249
+ |===
250
+ |Status |Meaning
251
+ |`implemented` |A registered rule with this id exists.
252
+ |`deferred` |The data is readable from the public model today, but no rule is written yet.
253
+ |`blocked:model` |The check needs a public model field that does not exist yet. The reason names the field.
254
+ |`not_checkable` |No input can violate it, or the bytes are ignored on receipt.
255
+ |===
256
+
257
+ Read the manifest to see what a clean report does not prove. A `deferred` or
258
+ `blocked:model` row is a check the lens does not make. Every record type of the
259
+ three specifications appears in at least one row.
260
+
65
261
  == Architecture
66
262
 
67
263
  Three layers, kept strictly separate (MECE):
@@ -95,7 +291,7 @@ Three layers, kept strictly separate (MECE):
95
291
  * No AI attribution in any commit. All changes go through PRs.
96
292
 
97
293
  See `docs/architecture.adoc` and `docs/format_notes.adoc` for the deep
98
- dive, and `TODO.impl/PROGRESS.md` for what's done vs what remains.
294
+ dive, and link:TODO.plan/10-progress.md[delivery progress] for implementation status.
99
295
 
100
296
  === Reference documentation
101
297
 
@@ -110,7 +306,7 @@ revision bumps.
110
306
  git clone https://github.com/claricle/emf
111
307
  cd emf
112
308
  bundle install
113
- bundle exec rspec # ~280 examples across fixtures + units
309
+ bundle exec rspec # fixture and unit checks
114
310
  bundle exec rubocop # clean
115
311
 
116
312
  The round-trip harness in `spec/emf/round_trip_spec.rb` is the strongest
@@ -119,13 +315,22 @@ byte-identical parse -> serialise -> parse.
119
315
 
120
316
  == Roadmap
121
317
 
122
- Full TODO list lives in `TODO.impl/`. Highlights:
123
-
124
- * TODO 09 -- finish remaining ~60 EMR wire types (currently 117 of ~122 done)
125
- * TODO 10 -- semantic domain records (currently WireAdapter for most types)
126
- * TODO 11 -- 2-pass path association (BEGINPATH <-> FILLPATH/STROKEPATH)
127
- * TODO 07 -- WMF parser + fixtures
128
- * TODO 12-14 -- EMF+ wire + domain + parser
318
+ The execution plan for issue #15 starts at
319
+ link:TODO.plan/00-overview.md[the shared roadmap]. Implementation is in progress; remaining acceptance and
320
+ qualification work is tracked in the linked sections.
321
+
322
+ * link:TODO.plan/05-ruby-execution.md[Ruby implementation]: existing API dispatch,
323
+ model compatibility, mutable wire serialization, fallback and CLI behavior.
324
+ * link:TODO.plan/03-rust-core.md[Rust core] and
325
+ link:TODO.plan/04-c-abi.md[C ABI]: typed decoding, serialization and native ownership.
326
+ * link:TODO.plan/09-format-extension.md[Format extensions]: WMF and structured EMF+.
327
+ * link:TODO.plan/06-resilience.md[Resilience],
328
+ link:TODO.plan/07-performance.md[performance] and
329
+ link:TODO.plan/08-platform-delivery.md[platform delivery].
330
+
331
+ Other roadmap items include semantic domain records beyond `WireAdapter` and
332
+ two-pass path association (`BEGINPATH` with `FILLPATH`/`STROKEPATH`). These remain
333
+ separate from the issue #15 delivery criteria.
129
334
 
130
335
  == License
131
336
 
data/exe/emf CHANGED
@@ -45,12 +45,24 @@ module Emf
45
45
  return unless mf.header
46
46
 
47
47
  h = mf.header
48
+ return wmf_info(h) if mf.format == :wmf
49
+
48
50
  puts "Bounds: left=#{h.bounds.left} top=#{h.bounds.top} right=#{h.bounds.right} bottom=#{h.bounds.bottom}"
49
51
  puts "Frame: left=#{h.frame.left} top=#{h.frame.top} right=#{h.frame.right} bottom=#{h.frame.bottom}"
50
52
  puts "Device: #{h.device_pixels.cx}x#{h.device_pixels.cy} px (#{h.device_mm.cx / 100.0}x#{h.device_mm.cy / 100.0} mm)"
51
53
  puts "nRecords: #{h.n_records} nHandles: #{h.n_handles} nBytes: #{h.n_bytes}"
52
54
  end
53
55
 
56
+ def wmf_info(header)
57
+ puts "Type: #{header.type} Version: #{header.version} Size: #{header.size_words} words"
58
+ puts "Objects: #{header.number_of_objects} MaxRecord: #{header.max_record_words} words Members: #{header.number_of_members}"
59
+ return unless header.placeable
60
+
61
+ placeable = header.placeable
62
+ puts "Bounds: left=#{placeable.left} top=#{placeable.top} right=#{placeable.right} bottom=#{placeable.bottom}"
63
+ puts "Units: #{placeable.inch} per inch"
64
+ end
65
+
54
66
  def dump(argv)
55
67
  path = parse_path(argv)
56
68
  mf = Emf.parse_file(path)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Backend
5
+ module Ruby
6
+ class << self
7
+ def parse(bytes)
8
+ deferred = ResourceLimits.preflight(bytes, defer: true)
9
+ case Detector.call(bytes)
10
+ when :wmf_apm, :wmf_standard then Wmf::Parser.call(bytes)
11
+ when :emf then Emr::Parser.call(bytes, deferred_admission: deferred)
12
+ end
13
+ rescue FormatError, ParseError => e
14
+ raise ParseEvidenceBuilder.attach(e, bytes)
15
+ end
16
+
17
+ def serialize(metafile)
18
+ case metafile.format
19
+ when :wmf then Wmf::Serializer.call(metafile)
20
+ when :emf, :emf_plus then Emr::Serializer.call(metafile)
21
+ else raise SerializeError, "unknown format: #{metafile.format.inspect}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ private_constant :Ruby
27
+
28
+ class << self
29
+ def resolved
30
+ selected.equal?(Ruby) ? :ruby : :native
31
+ end
32
+
33
+ def parse(bytes)
34
+ selected.parse(bytes)
35
+ end
36
+
37
+ def serialize(metafile)
38
+ selected.serialize(metafile)
39
+ end
40
+
41
+ private
42
+
43
+ def selected
44
+ override = ENV.fetch("EMF_BACKEND", "auto")
45
+ case override
46
+ when "ruby" then Ruby
47
+ when "auto" then native_or_ruby
48
+ when "native" then Native.backend
49
+ else raise ArgumentError, "invalid EMF_BACKEND: #{override.inspect} (expected auto, ruby, or native)"
50
+ end
51
+ end
52
+
53
+ def native_or_ruby
54
+ Native.backend
55
+ rescue Native::Unavailable
56
+ Ruby
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Binary
5
+ # BinData defines three singleton accessors per field on every instance,
6
+ # which dominates allocation when a metafile holds tens of thousands of
7
+ # records. The accessors depend only on the field's index, so they are
8
+ # defined once per class on first instantiation and shared afterwards.
9
+ module ClassFieldAccessors
10
+ private
11
+
12
+ def define_field_accessors
13
+ klass = self.class
14
+ return if klass.instance_variable_get(:@field_accessors_defined)
15
+
16
+ get_parameter(:fields).each_with_index do |field, index|
17
+ name = field.name_as_sym
18
+ define_class_accessors(klass, name, index) if name
19
+ end
20
+ klass.instance_variable_set(:@field_accessors_defined, true)
21
+ end
22
+
23
+ def define_setter(klass, name, index)
24
+ klass.define_method("#{name}=") { |val| assign_field_at(index, val) }
25
+ end
26
+
27
+ # The generic assignment; a class that can take one into the bytes it
28
+ # is still made from overrides it.
29
+ def assign_field_at(index, val)
30
+ instantiate_obj_at(index) if @field_objs.nil? || @field_objs[index].nil?
31
+ @field_objs[index].assign(val)
32
+ end
33
+
34
+ def define_class_accessors(klass, name, index)
35
+ klass.define_method(name) do
36
+ @field_objs&.at(index) || begin
37
+ instantiate_obj_at(index)
38
+ @field_objs[index]
39
+ end
40
+ end
41
+ define_setter(klass, name, index)
42
+ klass.define_method("#{name}?") do
43
+ instantiate_obj_at(index) if @field_objs.nil? || @field_objs[index].nil?
44
+ include_obj?(@field_objs[index])
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bindata"
4
+
5
+ module Emf
6
+ module Binary
7
+ # Little-endian IEEE 754 single that writes back the exact bits it read.
8
+ # Ruby widens a float32 to a double on read but `pack("e")` canonicalises
9
+ # every NaN to 0x7fc00000, so NaN payloads (and signalling NaNs) would
10
+ # otherwise change on a parse -> serialize round trip. Only NaNs carry
11
+ # their bits; every other value re-encodes exactly through the Float.
12
+ # Assigning a value drops the retained bits.
13
+ class Float32Le < BinData::BasePrimitive
14
+ def initialize_instance
15
+ super
16
+ @nan_bits = nil
17
+ end
18
+
19
+ def assign(val)
20
+ @nan_bits = nil
21
+ super
22
+ end
23
+
24
+ def assign_bits(bits)
25
+ @value = [bits].pack("V").unpack1("e")
26
+ @nan_bits = @value.nan? ? bits : nil
27
+ bits
28
+ end
29
+
30
+ def do_num_bytes
31
+ 4
32
+ end
33
+
34
+ private
35
+
36
+ def sensible_default
37
+ 0.0
38
+ end
39
+
40
+ def read_and_return_value(io)
41
+ raw = io.readbytes(4)
42
+ value = raw.unpack1("e")
43
+ @nan_bits = value.nan? ? raw.unpack1("V") : nil
44
+ value
45
+ end
46
+
47
+ def value_to_binary_string(val)
48
+ return [@nan_bits].pack("V") if @nan_bits
49
+
50
+ [val].pack("e")
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,9 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bindata"
4
+ require "emf/binary/class_field_accessors"
5
+ require "emf/binary/float32_le"
4
6
 
5
7
  module Emf
6
8
  module Binary
9
+ # `Type.new(x: 1, y: 2)` for an all-integer layout copies one instantiated
10
+ # sample and assigns the given leaves, as BinData's constructor does
11
+ # field by field; any other call shape, or an unknown key, is BinData's.
12
+ module KeywordNew
13
+ def new(*args, **attrs)
14
+ if args.empty? && !attrs.empty?
15
+ template, slots = (@keyword_template ||= keyword_template)
16
+ wire = Emf::Emr::Binary::WireCopy.build(template, slots, attrs)
17
+ return wire if wire
18
+ end
19
+ super
20
+ end
21
+
22
+ private
23
+
24
+ def keyword_template
25
+ sample = allocate_sample
26
+ [sample, Emf::Emr::Binary::WireCopy.slots(sample)].freeze
27
+ end
28
+
29
+ def allocate_sample
30
+ sample = method(:new).super_method.call
31
+ sample.field_names.each { |name| sample.public_send(name) }
32
+ sample
33
+ end
34
+ end
35
+
7
36
  # Bindata primitives shared by all three formats (WMF, EMF, EMF+).
8
37
  # Each record type is declared once and used as a `type:` argument in
9
38
  # larger records. See TODO.impl/03-binary-primitives.md.
@@ -58,12 +87,12 @@ module Emf
58
87
 
59
88
  class XForm < BinData::Record
60
89
  endian :little
61
- float :m11
62
- float :m12
63
- float :m21
64
- float :m22
65
- float :dx
66
- float :dy
90
+ float32_le :m11
91
+ float32_le :m12
92
+ float32_le :m21
93
+ float32_le :m22
94
+ float32_le :dx
95
+ float32_le :dy
67
96
  end
68
97
 
69
98
  class PaletteEntry < BinData::Record
@@ -86,12 +115,17 @@ module Emf
86
115
  uint32 :dw_low_date_time
87
116
  uint32 :dw_high_date_time
88
117
  end
118
+
119
+ constants.each { |name| const_get(name).prepend ClassFieldAccessors }
120
+
121
+ [PointL, PointS, SizeL, RectL, RectS, ColorRef].each { |type| type.singleton_class.prepend(Emf::Binary::KeywordNew) }
89
122
  end
90
123
  end
91
124
  end
92
125
 
93
126
  # Register the primitives so they can be referenced by name (e.g. `rectl :bounds`)
94
127
  # inside other BinData records. Bindata resolves types lazily through this registry.
128
+ BinData::RegisteredClasses.register("float32_le", Emf::Binary::Float32Le)
95
129
  BinData::RegisteredClasses.register("point_l", Emf::Binary::Types::PointL)
96
130
  BinData::RegisteredClasses.register("point_s", Emf::Binary::Types::PointS)
97
131
  BinData::RegisteredClasses.register("point_d", Emf::Binary::Types::PointD)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Conformance
5
+ # What a rule sees: the parsed model plus the input length. `format` is the
6
+ # Metafile format, or :unparseable when the parser rejected the input
7
+ # (then `metafile` is nil and `parse_failure` holds the exception).
8
+ class Context
9
+ attr_reader :metafile, :bytes, :parse_failure
10
+
11
+ def initialize(metafile: nil, bytes: nil, parse_failure: nil)
12
+ @metafile = metafile
13
+ @bytes = bytes
14
+ @parse_failure = parse_failure
15
+ end
16
+
17
+ def format = metafile ? metafile.format : :unparseable
18
+
19
+ def header = metafile.header
20
+
21
+ def records = metafile.records
22
+
23
+ # Input length in bytes, or nil when the metafile was passed in without its bytes.
24
+ def byte_length = bytes&.bytesize
25
+
26
+ # Declared EMR size of a record (its nSize field).
27
+ # EMF record size in bytes; nil for records without an EMF wire (WMF).
28
+ def record_size(record) = (record.wire.n_size.to_i if record.respond_to?(:wire))
29
+
30
+ def record_type(record) = record.type_id.to_i
31
+
32
+ # Total number of EMF records the header counts, header included.
33
+ def record_count = records.size + 1
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Conformance
5
+ # One finding. `severity` is already resolved by the profile; `waiver` holds
6
+ # the written reason when the tolerant profile downgraded an error.
7
+ Diagnostic = Struct.new(:requirement_id, :spec_citation, :severity, :kind, :format, :offset,
8
+ :record_index, :record_type, :message, :waiver, keyword_init: true) do
9
+ def initialize(**)
10
+ super
11
+ freeze
12
+ end
13
+
14
+ def error? = severity == :error
15
+ def warning? = severity == :warning
16
+ end
17
+ end
18
+ end