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
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Model
5
+ module Wmf
6
+ module Records
7
+ class StateRecord < Record
8
+ def initialize(type_id:, offset: nil, suffix: "")
9
+ super(type_id: type_id, offset: offset)
10
+ @suffix = suffix
11
+ end
12
+
13
+ attr_accessor :suffix
14
+ end
15
+
16
+ class SetBkColor < StateRecord
17
+ WIRE_FIELDS = %i[color_ref].freeze
18
+ WIRE_FORMAT = "V"
19
+ WIRE_TYPES = %i[uint32].freeze
20
+ PAYLOAD_BYTES = 4
21
+
22
+ def initialize(color_ref:, offset: nil, type_id: 0x0201, suffix: "")
23
+ super(type_id: type_id, offset: offset, suffix: suffix)
24
+ @color_ref = color_ref
25
+ end
26
+
27
+ attr_accessor :color_ref
28
+ end
29
+
30
+ class SetBkMode < StateRecord
31
+ WIRE_FIELDS = %i[mode].freeze
32
+ WIRE_FORMAT = "v"
33
+ WIRE_TYPES = %i[uint16].freeze
34
+ PAYLOAD_BYTES = 2
35
+
36
+ def initialize(mode:, offset: nil, type_id: 0x0102, suffix: "")
37
+ super(type_id: type_id, offset: offset, suffix: suffix)
38
+ @mode = mode
39
+ end
40
+
41
+ attr_accessor :mode
42
+ end
43
+
44
+ class SetMapMode < StateRecord
45
+ WIRE_FIELDS = %i[mode].freeze
46
+ WIRE_FORMAT = "v"
47
+ WIRE_TYPES = %i[uint16].freeze
48
+ PAYLOAD_BYTES = 2
49
+
50
+ def initialize(mode:, offset: nil, type_id: 0x0103, suffix: "")
51
+ super(type_id: type_id, offset: offset, suffix: suffix)
52
+ @mode = mode
53
+ end
54
+
55
+ attr_accessor :mode
56
+ end
57
+
58
+ class SetMapperFlags < StateRecord
59
+ WIRE_FIELDS = %i[flags].freeze
60
+ WIRE_FORMAT = "V"
61
+ WIRE_TYPES = %i[uint32].freeze
62
+ PAYLOAD_BYTES = 4
63
+
64
+ def initialize(flags:, offset: nil, type_id: 0x0231, suffix: "")
65
+ super(type_id: type_id, offset: offset, suffix: suffix)
66
+ @flags = flags
67
+ end
68
+
69
+ attr_accessor :flags
70
+ end
71
+
72
+ class SetPolyFillMode < StateRecord
73
+ WIRE_FIELDS = %i[mode].freeze
74
+ WIRE_FORMAT = "v"
75
+ WIRE_TYPES = %i[uint16].freeze
76
+ PAYLOAD_BYTES = 2
77
+
78
+ def initialize(mode:, offset: nil, type_id: 0x0106, suffix: "")
79
+ super(type_id: type_id, offset: offset, suffix: suffix)
80
+ @mode = mode
81
+ end
82
+
83
+ attr_accessor :mode
84
+ end
85
+
86
+ class SetRop2 < StateRecord
87
+ WIRE_FIELDS = %i[mode].freeze
88
+ WIRE_FORMAT = "v"
89
+ WIRE_TYPES = %i[uint16].freeze
90
+ PAYLOAD_BYTES = 2
91
+
92
+ def initialize(mode:, offset: nil, type_id: 0x0104, suffix: "")
93
+ super(type_id: type_id, offset: offset, suffix: suffix)
94
+ @mode = mode
95
+ end
96
+
97
+ attr_accessor :mode
98
+ end
99
+
100
+ class SetStretchBltMode < StateRecord
101
+ WIRE_FIELDS = %i[mode].freeze
102
+ WIRE_FORMAT = "v"
103
+ WIRE_TYPES = %i[uint16].freeze
104
+ PAYLOAD_BYTES = 2
105
+
106
+ def initialize(mode:, offset: nil, type_id: 0x0107, suffix: "")
107
+ super(type_id: type_id, offset: offset, suffix: suffix)
108
+ @mode = mode
109
+ end
110
+
111
+ attr_accessor :mode
112
+ end
113
+
114
+ class SetTextAlign < StateRecord
115
+ WIRE_FIELDS = %i[alignment].freeze
116
+ WIRE_FORMAT = "v"
117
+ WIRE_TYPES = %i[uint16].freeze
118
+ PAYLOAD_BYTES = 2
119
+
120
+ def initialize(alignment:, offset: nil, type_id: 0x012E, suffix: "")
121
+ super(type_id: type_id, offset: offset, suffix: suffix)
122
+ @alignment = alignment
123
+ end
124
+
125
+ attr_accessor :alignment
126
+ end
127
+
128
+ class SetTextCharExtra < StateRecord
129
+ WIRE_FIELDS = %i[char_extra].freeze
130
+ WIRE_FORMAT = "v"
131
+ WIRE_TYPES = %i[uint16].freeze
132
+ PAYLOAD_BYTES = 2
133
+
134
+ def initialize(char_extra:, offset: nil, type_id: 0x0108, suffix: "")
135
+ super(type_id: type_id, offset: offset, suffix: suffix)
136
+ @char_extra = char_extra
137
+ end
138
+
139
+ attr_accessor :char_extra
140
+ end
141
+
142
+ class SetTextColor < StateRecord
143
+ WIRE_FIELDS = %i[color_ref].freeze
144
+ WIRE_FORMAT = "V"
145
+ WIRE_TYPES = %i[uint32].freeze
146
+ PAYLOAD_BYTES = 4
147
+
148
+ def initialize(color_ref:, offset: nil, type_id: 0x0209, suffix: "")
149
+ super(type_id: type_id, offset: offset, suffix: suffix)
150
+ @color_ref = color_ref
151
+ end
152
+
153
+ attr_accessor :color_ref
154
+ end
155
+
156
+ class SetTextJustification < StateRecord
157
+ WIRE_FIELDS = %i[break_count break_extra].freeze
158
+ WIRE_FORMAT = "vv"
159
+ WIRE_TYPES = %i[uint16 uint16].freeze
160
+ PAYLOAD_BYTES = 4
161
+
162
+ def initialize(break_count:, break_extra:, offset: nil, type_id: 0x020A, suffix: "")
163
+ super(type_id: type_id, offset: offset, suffix: suffix)
164
+ @break_count = break_count
165
+ @break_extra = break_extra
166
+ end
167
+
168
+ attr_accessor :break_count, :break_extra
169
+ end
170
+
171
+ class SetLayout < StateRecord
172
+ WIRE_FIELDS = %i[layout reserved].freeze
173
+ WIRE_FORMAT = "vv"
174
+ WIRE_TYPES = %i[uint16 uint16].freeze
175
+ PAYLOAD_BYTES = 4
176
+
177
+ def initialize(layout:, reserved:, offset: nil, type_id: 0x0149, suffix: "")
178
+ super(type_id: type_id, offset: offset, suffix: suffix)
179
+ @layout = layout
180
+ @reserved = reserved
181
+ end
182
+
183
+ attr_accessor :layout, :reserved
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Model
5
+ module Wmf
6
+ module Records
7
+ TextRectangle = Struct.new(:left, :top, :right, :bottom, keyword_init: true)
8
+
9
+ class TextOut < Record
10
+ def initialize(text:, x:, y:, padding: "\x00".b, offset: nil, type_id: 0x0521)
11
+ super(type_id: type_id, offset: offset)
12
+ @text = text
13
+ @x = x
14
+ @y = y
15
+ @padding = padding
16
+ end
17
+
18
+ attr_accessor :text, :x, :y, :padding
19
+ end
20
+
21
+ class ExtTextOut < TextOut
22
+ def initialize(text:, x:, y:, options: 0, rectangle: nil, dx: nil, padding: "\x00".b, suffix: "".b,
23
+ offset: nil, type_id: 0x0A32)
24
+ super(text: text, x: x, y: y, padding: padding, offset: offset, type_id: type_id)
25
+ @options = options
26
+ @rectangle = rectangle
27
+ @dx = dx
28
+ @suffix = suffix
29
+ end
30
+
31
+ attr_accessor :options, :rectangle, :dx, :suffix
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Model
5
+ module Wmf
6
+ autoload :Header, "emf/model/wmf/header"
7
+ autoload :PlaceableHeader, "emf/model/wmf/header"
8
+ autoload :Records, "emf/model/wmf/records"
9
+ end
10
+ end
11
+ end
data/lib/emf/model.rb CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  module Emf
4
4
  module Model
5
+ autoload :Diagnostic, "emf/model/diagnostic"
5
6
  autoload :Metafile, "emf/model/metafile"
6
7
  autoload :Record, "emf/model/record"
7
8
  autoload :Visitor, "emf/model/visitor"
8
9
  autoload :Geometry, "emf/model/geometry"
9
10
  autoload :Emr, "emf/model/emr"
11
+ autoload :Wmf, "emf/model/wmf"
10
12
  end
11
13
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ module BitmapFields
6
+ # family => [EMR type, fixed size including EMR header]
7
+ LAYOUTS = {
8
+ 94 => [76, 100], 95 => [77, 108], 96 => [78, 128],
9
+ 97 => [79, 140], 98 => [80, 76], 99 => [81, 80],
10
+ 100 => [93, 32], 101 => [94, 32],
11
+ 102 => [114, 108], 103 => [116, 108]
12
+ }.freeze
13
+ POINTERS = %i[off_bmi_src cb_bmi_src off_bits_src cb_bits_src].freeze
14
+ BLT = %i[x_dest y_dest cx_dest cy_dest dw_rop x_src y_src].freeze
15
+ DIB = %i[x_dest y_dest x_src y_src cx_src cy_src off_bmi_src cb_bmi_src off_bits_src cb_bits_src i_usage_src dw_rop cx_dest
16
+ cy_dest].freeze
17
+ BRUSH = %i[ih_brush i_usage off_bmi cb_bmi off_bits cb_bits].freeze
18
+
19
+ def self.call(payload, family, raw)
20
+ return GradientFields.call(payload, raw) if family == 93
21
+
22
+ type, fixed = LAYOUTS.fetch(family)
23
+ validate(payload, raw, fixed)
24
+ suffix = raw.byteslice((payload.bytesize + 8)..)
25
+ fields = if [93, 94].include?(type)
26
+ BRUSH.zip(payload.unpack("V6")).to_h.merge(body: payload.byteslice(24..) + suffix)
27
+ else
28
+ bounds = { rcl_bounds: %i[left top right bottom].zip(payload.unpack("l<4")).to_h }
29
+ bounds.merge(transfer(payload, type, fixed, suffix))
30
+ end
31
+ [Emr::Binary::Records.lookup(type), fields]
32
+ end
33
+
34
+ def self.transfer(payload, type, fixed, suffix)
35
+ case type
36
+ when 76, 77
37
+ fields = BLT.zip(payload.byteslice(16, 28).unpack("l<4Vl<2")).to_h
38
+ fields[:xform_usage_etc] = payload.byteslice(44, 32).sub(/\x00+\z/n, "")
39
+ fields.merge!(POINTERS.zip(payload.byteslice(76, 16).unpack("V4")).to_h)
40
+ fields.merge!(%i[cx_src cy_src].zip(payload.byteslice(92, 8).unpack("l<2")).to_h) if type == 77
41
+ fields.merge(body: payload.byteslice((fixed - 8)..) + suffix)
42
+ when 81
43
+ DIB.zip(payload.byteslice(16, 56).unpack("l<6V6l<2")).to_h.merge(trailing: payload.byteslice(72..) + suffix)
44
+ else
45
+ { body: payload.byteslice(16..) + suffix }
46
+ end
47
+ end
48
+
49
+ # The snapshot payload always starts with the fixed prefix; descriptors are
50
+ # not checked against the record, because Ruby keeps the rest as opaque body.
51
+ def self.validate(payload, raw, fixed)
52
+ return unless payload.bytesize < fixed - 8 || raw.bytesize < payload.bytesize + 8
53
+
54
+ raise FormatError, "invalid native bitmap payload length"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Snapshot recovery kinds are provenance, independently of public error text.
6
+ class DiagnosticContract
7
+ def initialize(snapshot, input)
8
+ @snapshot = snapshot
9
+ @input = input
10
+ end
11
+
12
+ def call(metafile)
13
+ return metafile if @snapshot.diagnostics.empty?
14
+
15
+ offsets = @snapshot.table.each_slice(5).map { |entry| entry[1] }
16
+ diagnostics = @snapshot.diagnostics.flat_map do |kind, has_type, type, _reserved, offset|
17
+ index = offsets.bsearch_index { |candidate| candidate >= offset } || offsets.length
18
+ code = has_type == 1 ? type : @input.unpack1("V", offset: offset) || 0
19
+ if kind == Diagnostics::INVALID_PAYLOAD
20
+ recovered_diagnostics(offset, index + 1, code)
21
+ else
22
+ [Model::Diagnostic.new(record_index: index + 1, record_code: code, byte_offset: offset, disposition: :skipped)]
23
+ end
24
+ end
25
+ metafile.with_diagnostics(diagnostics.sort_by(&:byte_offset))
26
+ end
27
+
28
+ private
29
+
30
+ def recovered_diagnostics(offset, index, code)
31
+ return [] if Hydrator::OPAQUE_BODY_TYPES.include?(code)
32
+
33
+ diagnostics = []
34
+ Emr::Parser.read_record_at(@input, offset, [], diagnostics: diagnostics, record_index: index)
35
+ diagnostics
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module Emf
6
+ module Native
7
+ # Turns native snapshot diagnostics into the Ruby parser's ParseErrors.
8
+ #
9
+ # Framing diagnostics (kinds 1-3) carry their numbers, so the Ruby message
10
+ # is rebuilt from them. A kind-4 diagnostic only says that native could not
11
+ # decode a record's payload, not why, and native decodes some bodies Ruby
12
+ # keeps opaque. The Ruby parser is the contract for such a record, so its own
13
+ # per-record reader decides: it accepts the record as typed, or preserves it as
14
+ # Raw with its own message. Record bodies native hydrates as opaque classes are
15
+ # never rejected by Ruby, so their diagnostic is dropped without a Ruby read.
16
+ class Diagnostics
17
+ INVALID_PAYLOAD = 4
18
+ Recovery = Struct.new(:adapter, :errors, :wire)
19
+
20
+ def initialize(snapshot, input)
21
+ @snapshot = snapshot
22
+ @input = input
23
+ @recovered = invalid_payload_offsets.to_h { |offset, type| [offset, recover(offset, type)] }
24
+ end
25
+
26
+ def recovering?
27
+ !@recovered.empty?
28
+ end
29
+
30
+ def recovered?(offset)
31
+ @recovered.key?(offset)
32
+ end
33
+
34
+ def adapter_at(offset)
35
+ @recovered[offset]&.adapter
36
+ end
37
+
38
+ def emf_plus_at(offset)
39
+ wire = @recovered.fetch(offset).wire
40
+ Emr::Parser.extract_emf_plus(wire) if wire.is_a?(Emr::Binary::Records::Comment)
41
+ end
42
+
43
+ def errors
44
+ @snapshot.diagnostics.flat_map do |kind, has_type, type, reserved, offset, detail1, detail2|
45
+ raise FormatError, "invalid native snapshot diagnostic" unless (1..4).cover?(kind) && (0..1).cover?(has_type) && reserved.zero?
46
+ next @recovered.fetch(offset).errors if kind == INVALID_PAYLOAD
47
+
48
+ [ParseError.new(offset: offset, record_code: has_type == 1 ? type : nil, message: message(kind, offset, detail1, detail2))]
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def invalid_payload_offsets
55
+ @snapshot.diagnostics.filter_map do |kind, has_type, type, _reserved, offset|
56
+ [offset, has_type == 1 ? type : nil] if kind == INVALID_PAYLOAD
57
+ end
58
+ end
59
+
60
+ def recover(offset, type)
61
+ return Recovery.new(nil, [], nil) if Hydrator::OPAQUE_BODY_TYPES.include?(type)
62
+
63
+ errors = []
64
+ io = StringIO.new(@input)
65
+ io.pos = offset
66
+ wire, suffix = Emr::Parser.read_record(io, offset, errors)
67
+ adapter = Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset, suffix: suffix) if wire
68
+ Recovery.new(adapter, errors, wire)
69
+ end
70
+
71
+ def message(kind, offset, detail1, detail2)
72
+ case kind
73
+ when 1 then "truncated record header at offset #{offset} (#{detail1} bytes)"
74
+ when 2 then "record nSize #{detail1} < 8 bytes (offset #{offset})"
75
+ when 3 then "truncated record at offset #{offset} (claimed #{detail1}, got #{detail2 - 8})"
76
+ else "native parse diagnostic #{kind} at offset #{offset} (#{detail1}, #{detail2})"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "loader"
4
+
5
+ module Emf
6
+ module Native
7
+ # Adds the independent EMF+ snapshot call (emf_document_emf_plus_snapshot_v1)
8
+ # to the production Loader without touching its outer-snapshot signatures.
9
+ class EmfPlusLoader < Loader
10
+ # Version 2 frames each comment on its own, recovering from malformed framing
11
+ # like the Ruby framer, and types SetTSClip (family 0x106).
12
+ SNAPSHOT_VERSION = 2
13
+ # struct_size, version, max_output_bytes
14
+ DEFAULT_OPTIONS = [16, SNAPSHOT_VERSION, 1 << 30].pack("VVQ<").freeze
15
+
16
+ def initialize(path)
17
+ super
18
+ signature = [POINTER, POINTER, POINTER, POINTER, POINTER]
19
+ @functions[:emf_document_emf_plus_snapshot_v1] = Fiddle::Function.new(
20
+ @library["emf_document_emf_plus_snapshot_v1"], signature, Fiddle::TYPE_INT
21
+ )
22
+ rescue Fiddle::DLError => e
23
+ raise Unavailable, "native EMF+ snapshot could not load: #{e.message}"
24
+ end
25
+
26
+ # The EPSN buffer for the EMF file `bytes`; raises FormatError for malformed
27
+ # nested EMF+ and Error for a resource limit.
28
+ def emf_plus_snapshot(bytes)
29
+ document(bytes.b) do |handle|
30
+ buffer(:emf_document_emf_plus_snapshot_v1, handle, DEFAULT_OPTIONS, 0)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Reader for the native EMF+ snapshot (EPSN versions 1 and 2, emf-plus-snapshot-v1.md).
6
+ # It validates every length against the buffer, so a truncated or inconsistent
7
+ # snapshot raises FormatError rather than yielding partial data.
8
+ class EmfPlusSnapshot
9
+ HEADER_BYTES = 40
10
+ SEGMENT_BYTES = 32
11
+ FRAME_HEADER_BYTES = 64
12
+ DIAGNOSTIC_BYTES = 24
13
+
14
+ Segment = Struct.new(:record_index, :logical_start, :payload_length, :data_start, keyword_init: true)
15
+ Frame = Struct.new(:index, :offset, :first_segment, :last_segment, :type, :flags, :frame_size, :data_size,
16
+ :family, :raw, :typed, keyword_init: true)
17
+ # kind: :empty_slot, :wrong_type or :object_id_out_of_range (the names of
18
+ # Emf::EmfPlus::ObjectTable diagnostics); role: the required object type.
19
+ Diagnostic = Struct.new(:kind, :object_type, :frame_index, :id, :actual_type, keyword_init: true)
20
+
21
+ KINDS = { 1 => :empty_slot, 2 => :wrong_type, 3 => :object_id_out_of_range }.freeze
22
+ # The family tags of the frame table.
23
+ FAMILIES = { 0 => :opaque, 1 => :header, 2 => :empty, 3 => :clear, 4 => :rendering_origin, 5 => :stack_index,
24
+ 6 => :begin_container, 7 => :matrix, 8 => :pair, 9 => :scalar, 10 => :clip_rect,
25
+ 0x101 => :drawing, 0x102 => :text, 0x103 => :effect, 0x104 => :ts_graphics,
26
+ 0x105 => :object, 0x106 => :ts_clip }.freeze
27
+ VERSIONS = [1, 2].freeze
28
+
29
+ attr_reader :segments, :frames, :diagnostics
30
+
31
+ def initialize(buffer)
32
+ @buffer = buffer.b
33
+ read_header
34
+ @segments = read_segments
35
+ @frames = read_frames
36
+ @diagnostics = read_diagnostics
37
+ raise FormatError, "EMF+ snapshot has trailing bytes" unless @cursor == @buffer.bytesize
38
+ end
39
+
40
+ private
41
+
42
+ def read_header
43
+ raise FormatError, "EMF+ snapshot is truncated" if @buffer.bytesize < HEADER_BYTES
44
+
45
+ magic, version, total, @segment_count, @frame_count, @diagnostic_count = @buffer.unpack("a4VQ<4")
46
+ raise FormatError, "not an EMF+ snapshot" unless magic == "EPSN"
47
+ raise FormatError, "unsupported EMF+ snapshot version #{version}" unless VERSIONS.include?(version)
48
+ raise FormatError, "EMF+ snapshot length mismatch" unless total == @buffer.bytesize
49
+
50
+ @cursor = HEADER_BYTES
51
+ end
52
+
53
+ def take(length)
54
+ raise FormatError, "EMF+ snapshot is truncated" if length.negative? || length > @buffer.bytesize - @cursor
55
+
56
+ @buffer.byteslice(@cursor, length).tap { @cursor += length }
57
+ end
58
+
59
+ def read_segments
60
+ Array.new(checked_count(@segment_count, SEGMENT_BYTES)) do
61
+ index, start, length, data_start = take(SEGMENT_BYTES).unpack("Q<4")
62
+ Segment.new(record_index: index, logical_start: start, payload_length: length, data_start: data_start)
63
+ end
64
+ end
65
+
66
+ def read_frames
67
+ Array.new(checked_count(@frame_count, FRAME_HEADER_BYTES)) do
68
+ head = take(FRAME_HEADER_BYTES)
69
+ index, offset, first, last, type, flags, size, data_size, tag, typed_length, raw_length = head.unpack("Q<4vvVVVQ<2")
70
+ raw = take(raw_length)
71
+ raise FormatError, "EMF+ snapshot frame size mismatch" unless raw_length == size
72
+
73
+ Frame.new(index: index, offset: offset, first_segment: first, last_segment: last, type: type, flags: flags,
74
+ frame_size: size, data_size: data_size, family: FAMILIES.fetch(tag) { raise FormatError, "unknown EMF+ family #{tag}" },
75
+ raw: raw, typed: take(typed_length))
76
+ end
77
+ end
78
+
79
+ def read_diagnostics
80
+ Array.new(checked_count(@diagnostic_count, DIAGNOSTIC_BYTES)) do
81
+ kind, role, frame, id, actual = take(DIAGNOSTIC_BYTES).unpack("VVQ<VV")
82
+ Diagnostic.new(kind: KINDS.fetch(kind) { raise FormatError, "unknown EMF+ diagnostic kind #{kind}" },
83
+ object_type: role, frame_index: frame, id: id, actual_type: actual)
84
+ end
85
+ end
86
+
87
+ # A count the remaining bytes cannot possibly hold is corrupt; this also
88
+ # bounds Array.new before any allocation.
89
+ def checked_count(count, minimum)
90
+ raise FormatError, "EMF+ snapshot count exceeds its length" if count > (@buffer.bytesize - @cursor) / minimum
91
+
92
+ count
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Builds the Metafile#emf_plus_stream source for a native-backed metafile.
6
+ # The library is resolved at parse time; Native::EmfPlusStream loads lazily.
7
+ # A reload rebuilds from the current comment bytes, not the original input.
8
+ module EmfPlusSource
9
+ module_function
10
+
11
+ def call(input)
12
+ library = LibraryPath.new.call
13
+ lambda do |metafile, reload|
14
+ require_relative "emf_plus_stream"
15
+ EmfPlusStream.from_metafile(metafile, bytes: reload ? nil : input, library: library)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end