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,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Emr
5
+ module Binary
6
+ # The lazy and thin shells of a Record (see FastFill). Included by
7
+ # Record, so every method here is BinData's with its @field_objs
8
+ # settled first.
9
+ module ThinShell
10
+ # A record `FastFill` returns is a thin shell: the record's bytes and
11
+ # offset, no @field_objs. Its fields are copied from the filler's
12
+ # template, with the unpacked value assigned, the first time anything
13
+ # asks for them (every BinData path to a field goes through
14
+ # instantiate_obj_at); that first ask also makes @field_objs, in
15
+ # place, so the record keeps its identity and every field object
16
+ # made afterwards is the one every later read answers. Nothing read
17
+ # straight from @field_objs may see an unmade field as a default, so
18
+ # clear and clear? settle the shell first.
19
+ LAZY_IVARS = %i[@lazy_filler @lazy_data @lazy_base @lazy_built @lazy_patched].freeze
20
+
21
+ def initialize_instance
22
+ @lazy_filler = @lazy_data = @lazy_base = @lazy_built = @lazy_patched = nil
23
+ super
24
+ end
25
+
26
+ def clear
27
+ hydrate
28
+ @lazy_filler = nil
29
+ super
30
+ end
31
+
32
+ def clear?
33
+ instantiate_all_objs if @lazy_filler
34
+ super
35
+ end
36
+
37
+ # A frozen record cannot make @field_objs later, so it is made now;
38
+ # its fields still fill in lazily, as a BinData record's do.
39
+ def freeze
40
+ hydrate if @lazy_filler
41
+ super
42
+ end
43
+
44
+ # BinData's dup is shallow: a copy shares @field_objs, so a field
45
+ # either record makes or edits is the other's too. A thin source has
46
+ # no @field_objs yet, so it is made first and shared. The two also
47
+ # share their bytes, so neither is untouched any more.
48
+ def initialize_copy(source)
49
+ super
50
+ return unless @lazy_filler
51
+
52
+ source.send(:hydrate)
53
+ @field_objs = source.instance_variable_get(:@field_objs)
54
+ @lazy_patched = true
55
+ source.instance_variable_set(:@lazy_patched, true) unless source.frozen?
56
+ end
57
+
58
+ # The filler is generated code Marshal cannot dump, so a record is
59
+ # dumped as the plain BinData record it reads as: every field made,
60
+ # no lazy source.
61
+ def marshal_dump
62
+ instantiate_all_objs if @lazy_filler
63
+ @field_objs&.each { |field| field.settle_points if field.is_a?(FastFill::FlatPoints) }
64
+ (instance_variables - LAZY_IVARS).to_h { |name| [name, instance_variable_get(name)] }
65
+ end
66
+
67
+ def marshal_load(ivars)
68
+ ivars.each { |name, value| instance_variable_set(name, value) }
69
+ LAZY_IVARS.each { |name| instance_variable_set(name, nil) }
70
+ end
71
+
72
+ # Fixed-layout records serialize with one Array#pack, as WireAdapter
73
+ # already does; a block or a layout with floats is BinData's writer.
74
+ def to_binary_s(&block)
75
+ packed = built_binary_s unless block
76
+ return packed if packed
77
+
78
+ return thin_binary_s if !block && thin_parsed?
79
+
80
+ packed = FastFill.for_class(self.class)&.pack(self) unless block
81
+ packed || super
82
+ end
83
+
84
+ # [data, offset, untouched] of the bytes a shell is filled from, nil
85
+ # for any other record. `untouched` is true while no field has been
86
+ # made: a field not made is still its bytes, a field made may have
87
+ # been edited and is read from the wire.
88
+ def lazy_source
89
+ return unless @lazy_filler
90
+
91
+ [@lazy_data, @lazy_base, !@lazy_patched && (@field_objs.nil? || @field_objs.all?(&:nil?))]
92
+ end
93
+
94
+ # Makes a fresh record the thin shell of the record at `base` of
95
+ # `data`: no field made and no @field_objs. `params` and `names` are
96
+ # the template's, shared as a copy of it would share them. A built
97
+ # shell's data is the whole record, packed from the attributes.
98
+ def thin(data, base, filler, params, names, built)
99
+ @params = params
100
+ @field_names = names
101
+ @lazy_data = data
102
+ @lazy_base = base
103
+ @lazy_filler = filler
104
+ @lazy_built = built
105
+ self
106
+ end
107
+
108
+ # The size of a built shell no field of which, bar n_size, has been made
109
+ # (fixed layouts never change size, a point array's size is its bytes').
110
+ def num_bytes
111
+ return @lazy_data.bytesize if built_clean?
112
+ return @lazy_filler.record_size(@lazy_data, @lazy_base) if thin_parsed?
113
+
114
+ super
115
+ end
116
+
117
+ # The record's bytes while a built shell still is them (n_size patched
118
+ # in), else nil.
119
+ def built_binary_s
120
+ return unless built_clean?
121
+
122
+ bytes = @lazy_data.dup
123
+ size = patched_size
124
+ bytes[4, 4] = [size].pack("V") if size
125
+ bytes
126
+ end
127
+
128
+ # Appends `built_binary_s` to `out` and answers true, without the copy
129
+ # when n_size needs no patch; false (nothing appended) for any other
130
+ # record.
131
+ def append_built_to?(out)
132
+ return false unless built_clean?
133
+
134
+ out << (patched_size ? built_binary_s : @lazy_data)
135
+ true
136
+ end
137
+
138
+ # Appends the record's bytes to `out`: the built fast path, else
139
+ # `to_binary_s`.
140
+ def append_to(out)
141
+ return out if append_built_to?(out)
142
+
143
+ out << to_binary_s
144
+ end
145
+
146
+ private
147
+
148
+ # `n_size=` on a built shell whose n_size is not made: the new value
149
+ # goes into the shell's own bytes (only n_size is there) and the
150
+ # field is made, from them, if it is asked for. A value the bytes
151
+ # already hold writes nothing: a write would copy the buffer a
152
+ # `to_binary_s` dup still shares. Anything that is not
153
+ # a uint32 Integer is BinData's to assign (or to refuse); nil then,
154
+ # the assigned value otherwise.
155
+ def packed_assign(index, value)
156
+ return unless index == N_SIZE_INDEX && @lazy_built && @lazy_filler &&
157
+ value.instance_of?(Integer) && value >= 0 && value <= 0xFFFF_FFFF
158
+
159
+ @lazy_patched = true
160
+ data = @lazy_data
161
+ low = value & 0xFFFF
162
+ high = value >> 16
163
+ return value if data.getbyte(4) | (data.getbyte(5) << 8) == low &&
164
+ data.getbyte(6) | (data.getbyte(7) << 8) == high
165
+
166
+ data.setbyte(4, low & 0xFF)
167
+ data.setbyte(5, low >> 8)
168
+ data.setbyte(6, high & 0xFF)
169
+ data.setbyte(7, high >> 8)
170
+ value
171
+ end
172
+
173
+ N_SIZE_INDEX = 1
174
+ private_constant :N_SIZE_INDEX
175
+
176
+ # The n_size to patch into a built shell's bytes, nil when the bytes
177
+ # already hold it.
178
+ def patched_size
179
+ size = @field_objs&.[](N_SIZE_INDEX)&.value
180
+ size if size && size != @lazy_data.unpack1("V", offset: 4)
181
+ end
182
+
183
+ # A built shell whose bytes are still the record: only n_size, which
184
+ # is patched into them, may have been made.
185
+ def built_clean?
186
+ return false unless @lazy_built && @lazy_filler
187
+ return true if @field_objs.nil?
188
+
189
+ made = @field_objs.size - @field_objs.count(nil)
190
+ return true if made.zero?
191
+
192
+ # A value that is not an Integer is BinData's to write (or refuse).
193
+ made == 1 && @field_objs[N_SIZE_INDEX]&.value.instance_of?(Integer)
194
+ end
195
+
196
+ # A thin shell of a parsed record: its bytes are in the input.
197
+ def thin_parsed?
198
+ @field_objs.nil? && @lazy_filler && !@lazy_built
199
+ end
200
+
201
+ def thin_binary_s
202
+ @lazy_data.byteslice(@lazy_base, @lazy_filler.record_size(@lazy_data, @lazy_base))
203
+ end
204
+
205
+ # Makes @field_objs of a thin shell: every slot unmade.
206
+ def hydrate
207
+ @field_objs = Array.new(@lazy_filler.field_count) if @field_objs.nil?
208
+ end
209
+
210
+ def find_index_of(obj)
211
+ hydrate
212
+ super
213
+ end
214
+
215
+ def instantiate_obj_at(index)
216
+ return super unless @lazy_filler
217
+
218
+ hydrate
219
+ return unless @field_objs[index].nil?
220
+
221
+ @field_objs[index] = @lazy_filler.lazy_field(index, self, @lazy_data, @lazy_base)
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Emr
5
+ module Binary
6
+ class ValueView
7
+ # The leaf and array layout of one wire class, from a fresh instance.
8
+ class Layout
9
+ # Unpack directive and width of each leaf class.
10
+ LEAVES = {
11
+ "BinData::Uint32le" => ["V", 4],
12
+ "BinData::Int32le" => ["l<", 4],
13
+ "BinData::Uint8" => ["C", 1],
14
+ "Emf::Binary::Float32Le" => ["e", 4]
15
+ }.freeze
16
+ # Unpack directive and element width of each point coordinate class.
17
+ POINTS = {
18
+ "BinData::Int32le" => ["l<", 8],
19
+ "BinData::Int16le" => ["s<", 4]
20
+ }.freeze
21
+ FLOAT = "Emf::Binary::Float32Le"
22
+ private_constant :LEAVES, :POINTS, :FLOAT
23
+
24
+ # tree: [value class, [[name, node], ...]]; a leaf node is [:leaf, index], the array [:array].
25
+ # leaves: { directive:, at:, float:, klass: } in wire order.
26
+ # array: { name:, directive:, bytes:, count:, klass: } or nil.
27
+ attr_reader :tree, :leaves, :array, :size, :classes
28
+
29
+ def initialize(wire_class)
30
+ @wire_class = wire_class
31
+ @leaves = []
32
+ @classes = []
33
+ @array = nil
34
+ @size = 0
35
+ sample = wire_class.new
36
+ @tree = struct_of(sample)
37
+ raise Unsupported, "layout does not tile" unless sample.num_bytes == @size
38
+ end
39
+
40
+ def leaf_paths(node = @tree, prefix = [])
41
+ return [prefix] if node.first == :leaf
42
+ return [] if node.first == :array
43
+
44
+ node.last.flat_map { |name, child| leaf_paths(child, prefix + [name]) }
45
+ end
46
+
47
+ # Index of the leaf holding the point count.
48
+ def count_index
49
+ slot = @tree.last.assoc(@array[:count])
50
+ raise Unsupported, "count field" unless slot && slot.last.first == :leaf
51
+
52
+ slot.last.last
53
+ end
54
+
55
+ private
56
+
57
+ def struct_of(struct)
58
+ children = struct.field_names.map { |name| [name, child_of(struct.public_send(name), name, struct)] }
59
+ klass = ValueView.value_class(struct.class, children.map(&:first))
60
+ @classes << klass
61
+ [klass, children]
62
+ end
63
+
64
+ def child_of(field, name, parent)
65
+ if field.respond_to?(:field_names)
66
+ struct_of(field)
67
+ elsif field.is_a?(BinData::Array)
68
+ array_of(field, name, parent)
69
+ else
70
+ leaf_of(field)
71
+ end
72
+ end
73
+
74
+ def leaf_of(field)
75
+ name = field.class.name
76
+ directive, width = LEAVES.fetch(name) { raise Unsupported, "leaf #{field.class}" }
77
+ @leaves << { directive: directive, at: @size, float: FLOAT.eql?(name), klass: name }
78
+ @size += width
79
+ [:leaf, @leaves.size - 1]
80
+ end
81
+
82
+ def array_of(field, name, parent)
83
+ raise Unsupported, "second array" if @array || !parent.instance_of?(@wire_class)
84
+ raise Unsupported, "array is not last" unless parent.field_names.last == name
85
+
86
+ point = field.send(:new_element)
87
+ directive, bytes = POINTS.fetch(point.x.class.name) { raise Unsupported, "point #{point.x.class}" }
88
+ raise Unsupported, "point members" unless point.field_names == %i[x y] && point.x.instance_of?(point.y.class)
89
+
90
+ count = field.send(:get_parameter, :initial_length)
91
+ raise Unsupported, "array count" unless count.is_a?(Symbol)
92
+
93
+ @array = { name: name, directive: directive, bytes: bytes, count: count,
94
+ klass: ValueView.value_class(point.class, %i[x y]) }
95
+ [:array]
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Emr
5
+ module Binary
6
+ class ValueView
7
+ # Source of the readers of a view: `read_bytes(data, base)`,
8
+ # `read_wire(wire)` and `read_mixed(data, base, wire)`.
9
+ class ReaderSource
10
+ def initialize(layout)
11
+ @layout = layout
12
+ end
13
+
14
+ # Source of `read_bytes(data, base)`, `read_wire(wire)` and
15
+ # `read_mixed(data, base, wire)`; each builds the value object from
16
+ # the locals `v0..vN` (unpacked leaves) and `points`, or from the
17
+ # wire's accessors.
18
+ def to_s
19
+ unpack = "#{locals} = data.unpack(#{@layout.leaves.map { |leaf| leaf[:directive] }.join.inspect}, offset: base)"
20
+ <<~RUBY
21
+ def read_bytes(data, base)
22
+ #{unpack}
23
+ #{array_source}
24
+ #{construct(@layout.tree, :bytes, 'wire')}
25
+ end
26
+
27
+ def read_wire(wire)
28
+ #{construct(@layout.tree, :wire, 'wire')}
29
+ end
30
+
31
+ def read_mixed(data, base, wire)
32
+ #{unpack}
33
+ #{array_source}
34
+ objs = wire.instance_variable_get(:@field_objs)
35
+ #{mixed_construct}
36
+ end
37
+ RUBY
38
+ end
39
+
40
+ private
41
+
42
+ def locals
43
+ @layout.leaves.each_index.map { |index| "v#{index}" }.join(", ")
44
+ end
45
+
46
+ # Source for the local `points` of an array record (nothing for
47
+ # others); the reader returns nil when the count overruns the record.
48
+ def array_source
49
+ array = @layout.array
50
+ return "" unless array
51
+
52
+ count = "v#{@layout.count_index}"
53
+ size = @layout.size
54
+ <<~RUBY
55
+ return if #{size} + (#{count} * #{array[:bytes]}) > data.unpack1("V", offset: base + 4) ||
56
+ base + #{size} + (#{count} * #{array[:bytes]}) > data.bytesize
57
+
58
+ flat = data.unpack(#{array[:directive].inspect} + (#{count} * 2).to_s, offset: base + #{size})
59
+ points = Array.new(#{count}) { |i| POINT.new(flat[i * 2], flat[(i * 2) + 1]) }.freeze
60
+ RUBY
61
+ end
62
+
63
+ def construct(node, mode, chain)
64
+ return (mode == :bytes ? "v#{node.last}" : "#{chain}.value") if node.first == :leaf
65
+ if node.first == :array
66
+ return (mode == :bytes ? "points" : "#{chain}.map { |point| POINT.new(point.x.value, point.y.value) }.freeze")
67
+ end
68
+
69
+ klass, children = node
70
+ args = children.map { |name, child| construct(child, mode, "#{chain}.#{name}") }
71
+ "CLASSES[#{@layout.classes.index(klass)}].new(#{args.join(', ')})"
72
+ end
73
+
74
+ # `construct` for the top struct with each field taken from the wire
75
+ # when the shell has made it (its slot in @field_objs is set; a thin shell
76
+ # has no @field_objs and has made none).
77
+ def mixed_construct
78
+ klass, children = @layout.tree
79
+ args = children.each_with_index.map do |(name, child), slot|
80
+ "(objs&.[](#{slot}) ? #{construct(child, :wire, "wire.#{name}")} : #{construct(child, :bytes, '')})"
81
+ end
82
+ "CLASSES[#{@layout.classes.index(klass)}].new(#{args.join(', ')})"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Emr
5
+ module Binary
6
+ # Field values of a fixed-layout or point-array wire record as plain,
7
+ # frozen Ruby objects, with no BinData field object made.
8
+ #
9
+ # A view is built once per wire class from the class's own field list
10
+ # and proved against BinData on a record with a distinct value in every
11
+ # leaf (the bytes reader, the wire reader and BinData's own read must
12
+ # agree, signedness and float values included). A class that cannot be
13
+ # proved has no view: `ValueView.for_class` returns nil.
14
+ #
15
+ # The value object mirrors the wire: one `Data` member per field name,
16
+ # nested structs (`rcl_bounds`, `xform`, `color`) as nested `Data`
17
+ # objects with the wire's member names, and a counted point array as a
18
+ # frozen Array of `Data` points (`x`, `y`). Integers are Integers
19
+ # (signed or unsigned as the wire declares); float32 leaves are Floats,
20
+ # NaN payloads included (the same float32 widening BinData applies).
21
+ #
22
+ # Three readers produce equal objects: `from_bytes` unpacks a record's
23
+ # bytes with one String#unpack, `from_wire` reads the wire's values, and
24
+ # `from_mixed` reads a lazy shell, taking each field the shell has made
25
+ # from the wire and the others from the bytes.
26
+ class ValueView
27
+ # Namespace of the generated value classes.
28
+ module Values; end
29
+
30
+ # A layout the view does not support.
31
+ class Unsupported < StandardError; end
32
+
33
+ autoload :Layout, "emf/emr/binary/value_view/layout"
34
+ autoload :ReaderSource, "emf/emr/binary/value_view/reader_source"
35
+
36
+ BINDATA_WRITER = BinData::Base.instance_method(:to_binary_s)
37
+ private_constant :BINDATA_WRITER
38
+
39
+ @views = {}.compare_by_identity
40
+ # Marks every value class, whichever base class it is built on.
41
+ module Value; end
42
+
43
+ @value_classes = {}.compare_by_identity
44
+ @lock = Mutex.new
45
+
46
+ class << self
47
+ # The view of a wire class, or nil when the class has none.
48
+ def for_class(wire_class)
49
+ @views.fetch(wire_class) { @lock.synchronize { @views[wire_class] = build(wire_class) } }
50
+ end
51
+
52
+ # The shared value class of a BinData struct class with these members.
53
+ def value_class(struct_class, members)
54
+ @value_classes[struct_class] ||= define_value_class(members).tap do |klass|
55
+ name = struct_class.name&.split("::")&.last
56
+ Values.const_set(name, klass) if name&.match?(/\A[A-Z]\w*\z/) && !Values.const_defined?(name, false)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Data (Ruby 3.2+) where it exists; on Ruby 3.1 a Struct whose
63
+ # instances freeze themselves, so values stay frozen everywhere.
64
+ def define_value_class(members)
65
+ klass = defined?(::Data.define) ? ::Data.define(*members) : frozen_struct(members)
66
+ klass.include(Value)
67
+ end
68
+
69
+ def frozen_struct(members)
70
+ Struct.new(*members) do
71
+ def initialize(*)
72
+ super
73
+ freeze
74
+ end
75
+ end
76
+ end
77
+
78
+ def build(wire_class)
79
+ filler = FastFill.for_class(wire_class)
80
+ return unless filler && !filler.is_a?(FastFill::TextFill)
81
+
82
+ new(wire_class).tap(&:prove)
83
+ rescue StandardError
84
+ nil
85
+ end
86
+ end
87
+
88
+ attr_reader :wire_class, :value_class
89
+
90
+ def initialize(wire_class)
91
+ @wire_class = wire_class
92
+ @layout = Layout.new(wire_class)
93
+ @value_class = @layout.tree.first
94
+ generate
95
+ end
96
+
97
+ # The values of the record at `base` of the binary `data`; nil when
98
+ # its point count overruns the record.
99
+ def from_bytes(data, base) = read_bytes(data, base)
100
+
101
+ # The values of a lazy shell over the record at `base` of `data`:
102
+ # each top-level field the shell has made from the wire, the others
103
+ # from the bytes. Nil when the point count overruns the record.
104
+ def from_mixed(data, base, wire) = read_mixed(data, base, wire)
105
+
106
+ # The values the wire holds now.
107
+ def from_wire(wire) = read_wire(wire)
108
+
109
+ # Raises unless the readers agree with BinData on a distinct-valued record.
110
+ def prove
111
+ sample = distinct_sample
112
+ bytes = BINDATA_WRITER.bind_call(sample)
113
+ values = read_bytes("\xAA\xBB".b + bytes + "\xCC".b, 2)
114
+ reference = read_wire(@wire_class.read(bytes))
115
+ raise Unsupported, "bytes and wire differ" unless values == read_wire(sample) && values == reference
116
+ raise Unsupported, "a count past the record was read" unless overrun_rejected?(bytes)
117
+
118
+ self
119
+ end
120
+
121
+ private
122
+
123
+ def generate
124
+ mod = Module.new
125
+ mod.const_set(:CLASSES, @layout.classes.freeze)
126
+ mod.const_set(:POINT, @layout.array[:klass]) if @layout.array
127
+ mod.module_eval(ReaderSource.new(@layout).to_s, __FILE__, __LINE__)
128
+ extend(mod)
129
+ end
130
+
131
+ # A record with a distinct value in every leaf: negative signed, high
132
+ # unsigned, fractional floats.
133
+ def distinct_sample
134
+ wire = @wire_class.new
135
+ @layout.leaf_paths.each_with_index do |path, index|
136
+ target = path.inject(wire) { |obj, name| obj.public_send(name) }
137
+ target.assign(distinct(@layout.leaves.fetch(index), index))
138
+ end
139
+ fill_array(wire)
140
+ end
141
+
142
+ def distinct(leaf, index)
143
+ return index + 1.5 if leaf[:float]
144
+
145
+ case leaf[:klass]
146
+ when "BinData::Int32le" then -(index + 1) * 1000
147
+ when "BinData::Uint32le" then 0xFFFF_FF00 - index
148
+ else 0x80 + index
149
+ end
150
+ end
151
+
152
+ def fill_array(wire)
153
+ array = @layout.array
154
+ return wire unless array
155
+
156
+ limit = array[:bytes] == 8 ? 2**31 : 2**15
157
+ wire.public_send(array[:count]).assign(3)
158
+ wire.public_send(array[:name]).assign([{ x: -1, y: limit - 1 }, { x: 5, y: -limit }, { x: 0, y: 7 }])
159
+ wire
160
+ end
161
+
162
+ # A count claiming more points than the record holds reads as nil.
163
+ def overrun_rejected?(bytes)
164
+ return true unless @layout.array
165
+
166
+ bad = bytes.dup
167
+ bad[@layout.leaves.fetch(@layout.count_index)[:at], 4] = [1_000_000].pack("V")
168
+ read_bytes(bad, 0).nil?
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Emr
5
+ module Binary
6
+ # Reads the offset/size-addressed variable parts of a record straight from
7
+ # its bytes, so they are visible even when a wire `body` or `trailing`
8
+ # field absorbed them. Offsets are from record start. A part that is
9
+ # absent, empty, starts inside the fixed layout or leaves the record is
10
+ # nil, never an error; nothing is computed at parse time.
11
+ #
12
+ # fixed_layout 100 # bytes of the fixed layout
13
+ # variable_buffer :bmi_bytes, offset: :off_bmi_src, length: :cb_bmi_src
14
+ #
15
+ # offset, length and the fixed size are an Integer, a Symbol naming a
16
+ # reader on the record, or a lambda run on the record.
17
+ module VariableBuffers
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ end
21
+
22
+ # Class macros.
23
+ module ClassMethods
24
+ attr_reader :fixed_layout_size
25
+
26
+ def fixed_layout(size)
27
+ @fixed_layout_size = size
28
+ end
29
+
30
+ def buffer_specs
31
+ @buffer_specs ||= {}
32
+ end
33
+
34
+ # Declare a part and, unless reader is false, a reader named for it.
35
+ def variable_buffer(name, offset:, length:, reader: true)
36
+ buffer_specs[name] = [offset, length]
37
+ define_method(name) { buffer_range(name)&.then { |range| record_bytes.byteslice(range) } } if reader
38
+ end
39
+ end
40
+
41
+ # The bytes inside nSize after the fixed layout and after every declared
42
+ # part that lies inside the record, or "" when there are none. nil when
43
+ # nSize is smaller than the fixed layout or larger than the record.
44
+ def layout_surplus_bytes
45
+ bytes = record_bytes
46
+ size = n_size
47
+ fixed = resolve(self.class.fixed_layout_size)
48
+ return unless fixed && size >= fixed && size <= bytes.bytesize
49
+
50
+ finish = self.class.buffer_specs.each_key.filter_map { |name| buffer_range(name)&.end }.max
51
+ start = [fixed, finish.to_i].max
52
+ start >= size ? "".b : bytes.byteslice(start, size - start)
53
+ end
54
+
55
+ private
56
+
57
+ def record_bytes
58
+ to_binary_s
59
+ end
60
+
61
+ def buffer_range(name)
62
+ offset, length = self.class.buffer_specs.fetch(name).map { |part| resolve(part) }
63
+ return unless offset && length&.positive?
64
+ return if offset < resolve(self.class.fixed_layout_size)
65
+
66
+ finish = offset + length
67
+ (offset...finish) if finish <= record_bytes.bytesize
68
+ end
69
+
70
+ def resolve(part)
71
+ case part
72
+ when Integer then part
73
+ when Symbol then public_send(part)
74
+ else instance_exec(&part)
75
+ end
76
+ end
77
+
78
+ # Unsigned 32-bit value at a record offset, 0 past the end (so a part
79
+ # sized by it is empty).
80
+ def u32_at(offset)
81
+ raw = record_bytes.byteslice(offset, 4)
82
+ raw&.bytesize == 4 ? raw.unpack1("V") : 0
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -7,6 +7,10 @@ module Emf
7
7
  autoload :Record, "emf/emr/binary/record"
8
8
  autoload :WithBounds, "emf/emr/binary/with_bounds"
9
9
  autoload :TypeCodes, "emf/emr/binary/type_codes"
10
+ autoload :FastFill, "emf/emr/binary/fast_fill"
11
+ autoload :PackPlan, "emf/emr/binary/pack_plan"
12
+ autoload :WireCopy, "emf/emr/binary/fast_fill"
13
+ autoload :ValueView, "emf/emr/binary/value_view"
10
14
  autoload :Records, "emf/emr/binary/records"
11
15
  end
12
16
  end