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,382 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ # Replays the native parser's admission charges without materializing decoded
5
+ # records. Malformed fatal headers return control to the existing parser.
6
+ class ResourceBudget # rubocop:disable Metrics/ClassLength
7
+ DEFAULT_LIMITS = {
8
+ input_bytes: 200 * 1024 * 1024,
9
+ record_bytes: 128 * 1024 * 1024,
10
+ record_count: 4 * 1024 * 1024,
11
+ allocated_bytes: 640 * 1024 * 1024,
12
+ work_units: 64 * 1024 * 1024
13
+ }.freeze
14
+
15
+ # Native accounting widths, checked by resource_budget_layout.rs. The font
16
+ # charge conservatively covers both 392- and 400-byte Rust layouts so budget
17
+ # admission is independent of compiler layout; native has a compile-time guard.
18
+ LAYOUT = {
19
+ record_span: 336, diagnostic: 32, font_record: 400,
20
+ poly_text_entry_a: 136, poly_text_entry_w: 136,
21
+ poly_text_span: 32, tri_vertex: 16
22
+ }.freeze
23
+ RECORD_SPAN_BYTES = LAYOUT.fetch(:record_span)
24
+ DIAGNOSTIC_BYTES = LAYOUT.fetch(:diagnostic)
25
+ FONT_RECORD_BYTES = LAYOUT.fetch(:font_record)
26
+
27
+ FIXED_PAYLOADS = {
28
+ 9 => 8, 10 => 8, 11 => 8, 12 => 8, 13 => 8, 14 => 12, 15 => 12,
29
+ 16 => 4, 17 => 4, 18 => 4, 19 => 4, 20 => 4, 21 => 4, 22 => 4,
30
+ 24 => 4, 25 => 4, 26 => 8, 27 => 8, 28 => 0, 29 => 16, 30 => 16,
31
+ 31 => 16, 32 => 16, 33 => 0, 34 => 4, 35 => 24, 36 => 28, 37 => 4,
32
+ 38 => 20, 39 => 16, 40 => 4, 41 => 20, 42 => 16, 43 => 16, 44 => 24,
33
+ 45 => 32, 46 => 32, 47 => 32, 48 => 4, 51 => 8, 52 => 0, 53 => 16, 54 => 8,
34
+ 55 => 32, 57 => 4, 58 => 4, 59 => 0, 60 => 0, 61 => 0, 62 => 16,
35
+ 63 => 16, 64 => 16, 65 => 0, 66 => 0, 67 => 4, 68 => 0, 98 => 4,
36
+ 115 => 4, 120 => 8
37
+ }.freeze
38
+
39
+ # A borrowed payload; reading fields must not copy a potentially large body.
40
+ class Payload
41
+ attr_reader :length
42
+ alias bytesize length
43
+
44
+ def initialize(bytes, base, length)
45
+ @bytes = bytes
46
+ @base = base
47
+ @length = length
48
+ end
49
+
50
+ def u32(offset)
51
+ @bytes.unpack1("V", offset: @base + offset)
52
+ end
53
+
54
+ def u16(offset)
55
+ @bytes.unpack1("v", offset: @base + offset)
56
+ end
57
+
58
+ def slice(offset, length)
59
+ self.class.new(@bytes, @base + offset, length)
60
+ end
61
+ end
62
+
63
+ def self.check!(bytes, limits: {}, &admission)
64
+ new(limits).replay!(bytes, &admission)
65
+ end
66
+
67
+ def initialize(limits = {})
68
+ @limits = DEFAULT_LIMITS.merge(limits)
69
+ @totals = { record_count: 0, allocated_bytes: 0, work_units: 0 }
70
+ @vectors = {}
71
+ end
72
+
73
+ def charge_alloc(bytes)
74
+ charge(:allocated_bytes, bytes)
75
+ end
76
+
77
+ def charge_work(units)
78
+ charge(:work_units, units)
79
+ end
80
+
81
+ def layout(name)
82
+ LAYOUT.fetch(name)
83
+ end
84
+
85
+ def vector(count, width)
86
+ charge_alloc(count * width)
87
+ end
88
+
89
+ def span(payload_length, offset, length, alignment, fixed, empty_boundary: false)
90
+ return if offset < 8 || offset % alignment != 0
91
+
92
+ start = offset - 8
93
+ start = [start, fixed].max if empty_boundary && length.zero?
94
+ finish = start + length
95
+ [start, finish] if start >= fixed && finish <= payload_length
96
+ end
97
+
98
+ def replay!(bytes, &) # rubocop:disable Naming/PredicateMethod
99
+ check(:input_bytes, bytes.bytesize)
100
+ return true unless Detector.call(bytes) == :emf
101
+
102
+ input = Payload.new(bytes, 0, bytes.bytesize)
103
+ header_size = input.u32(4)
104
+ minimum = if header_size > 100 then 108
105
+ elsif header_size > 88 then 100
106
+ else 88
107
+ end
108
+ return true if header_size < minimum
109
+
110
+ check(:record_bytes, header_size)
111
+ charge_record(header_size)
112
+ return true if header_size > input.length
113
+
114
+ charge_work(ceil16(input.length))
115
+ charge_alloc(input.length)
116
+ replay_records(input, header_size, &)
117
+ true
118
+ end
119
+
120
+ private
121
+
122
+ def check(resource, actual)
123
+ limit = @limits.fetch(resource)
124
+ return if actual <= limit
125
+
126
+ raise ResourceLimitError.new(resource: resource, limit: limit, actual: actual)
127
+ end
128
+
129
+ def charge(resource, amount)
130
+ total = @totals.fetch(resource) + amount
131
+ check(resource, total)
132
+ @totals[resource] = total
133
+ end
134
+
135
+ def ceil16(bytes)
136
+ (bytes + 15) / 16
137
+ end
138
+
139
+ def charge_record(size)
140
+ charge(:record_count, 1)
141
+ charge_work(1 + ceil16(size))
142
+ end
143
+
144
+ def reserve(name, width)
145
+ length, capacity = @vectors.fetch(name, [0, 0])
146
+ if length == capacity
147
+ capacity = [capacity * 2, 1].max
148
+ charge_alloc(capacity * width)
149
+ end
150
+ @vectors[name] = [length + 1, capacity]
151
+ end
152
+
153
+ def diagnostic
154
+ reserve(:diagnostics, DIAGNOSTIC_BYTES)
155
+ end
156
+
157
+ def replay_records(input, offset)
158
+ while offset < input.length
159
+ remaining = input.length - offset
160
+ if remaining < 8 || (size = input.u32(offset + 4)) < 8 || size > remaining
161
+ diagnostic
162
+ break
163
+ end
164
+ kind = input.u32(offset)
165
+ check(:record_bytes, size)
166
+ charge_record(size)
167
+ reserve(:records, RECORD_SPAN_BYTES)
168
+ payload = input.slice(offset + 8, size - 8)
169
+ typed = decode(kind, payload)
170
+ diagnostic if !typed && invalid_when_raw?(kind, payload.length)
171
+ yield kind, size, offset if block_given?
172
+ offset += size
173
+ break if kind == 14 && typed
174
+ end
175
+ end
176
+
177
+ def invalid_when_raw?(kind, length)
178
+ prefix = if kind == 118 then 28
179
+ elsif Bitmap::SHAPES.key?(kind) then Bitmap::SHAPES.fetch(kind).first - 8
180
+ end
181
+ return length < prefix if prefix
182
+ return false if [23, 96, 97].include?(kind)
183
+ return length < 28 if kind == 108
184
+
185
+ (2..68).cover?(kind) || (70..98).cover?(kind) || (114..116).cover?(kind) || kind == 120
186
+ end
187
+
188
+ def decode_delegated(kind, payload)
189
+ case kind
190
+ when 96, 97 then PolyText.decode(kind, payload, self)
191
+ when 118 then Gradient.decode(kind, payload, self)
192
+ else Bitmap.decode(kind, payload, self)
193
+ end
194
+ end
195
+
196
+ # Keep native match-arm order visible for charge and recovery parity.
197
+ def decode(kind, payload) # rubocop:disable Metrics/CyclomaticComplexity
198
+ case kind
199
+ when 2..6, 85..89 then decode_poly?(payload, kind < 85 ? 8 : 4)
200
+ when 7, 8, 90, 91 then decode_multi_poly?(payload, kind < 90 ? 8 : 4)
201
+ when 56, 92 then decode_poly?(payload, (kind == 56 ? 8 : 4) + 1, 2)
202
+ when 49, 50 then decode_palette?(kind, payload)
203
+ when 70 then decode_comment?(payload)
204
+ when 71..75 then decode_region?(kind, payload)
205
+ when 82 then decode_font?(payload)
206
+ when 83, 84 then decode_text?(payload, kind == 83 ? 1 : 2)
207
+ when 95 then decode_pen?(payload)
208
+ when 23 then true
209
+ when 76..81, 93, 94, 96, 97, 114, 116, 118 then decode_delegated(kind, payload)
210
+ else
211
+ fixed = FIXED_PAYLOADS[kind]
212
+ fixed && payload.length >= fixed
213
+ end
214
+ end
215
+
216
+ def decode_poly?(payload, width, work_multiplier = 1)
217
+ return false if payload.length < 20
218
+
219
+ count = payload.u32(16)
220
+ bytes = count * width
221
+ return false if bytes > payload.length - 20
222
+
223
+ charge_alloc(bytes)
224
+ charge_work(count * work_multiplier)
225
+ true
226
+ end
227
+
228
+ def decode_multi_poly?(payload, width)
229
+ return false if payload.length < 24
230
+
231
+ polygons = payload.u32(16)
232
+ points = payload.u32(20)
233
+ bytes = (polygons * 4) + (points * width)
234
+ return false if bytes > payload.length - 24
235
+
236
+ charge_alloc(bytes)
237
+ charge_work(polygons + points)
238
+ true
239
+ end
240
+
241
+ def decode_palette?(kind, payload)
242
+ fixed = kind == 49 ? 8 : 12
243
+ return false if payload.length < fixed
244
+
245
+ count = kind == 49 ? payload.u16(6) : payload.u32(8)
246
+ return false if count * 4 > payload.length - fixed
247
+
248
+ charge_alloc(count * 4)
249
+ charge_work(count)
250
+ true
251
+ end
252
+
253
+ def decode_comment?(payload)
254
+ return false if payload.length < 4
255
+
256
+ count = payload.u32(0)
257
+ return false if count > payload.length - 4
258
+
259
+ charge_alloc(count)
260
+ charge_work(ceil16(count))
261
+ true
262
+ end
263
+
264
+ def decode_region?(kind, payload)
265
+ fixed = { 71 => 24, 72 => 32, 73 => 20, 74 => 20, 75 => 8 }.fetch(kind)
266
+ return false if payload.length < fixed
267
+
268
+ count = payload.u32(kind == 75 ? 0 : 16)
269
+ return false if count > payload.length - fixed
270
+ return true if count.zero? && kind == 75
271
+ return false if count < 32
272
+
273
+ region = payload.slice(fixed, count)
274
+ rectangles = region.u32(8)
275
+ bytes = rectangles * 16
276
+ return false unless region.u32(0) == 32 && region.u32(12) == bytes && bytes <= count - 32
277
+
278
+ extra = count - 32 - bytes
279
+ charge_alloc(bytes)
280
+ charge_alloc(extra)
281
+ charge_work(rectangles)
282
+ charge_work(ceil16(extra))
283
+ true
284
+ end
285
+
286
+ def decode_font?(payload)
287
+ return false if payload.length < 96
288
+
289
+ size = payload.length - 4
290
+ if size > 320
291
+ return false if size < 356
292
+
293
+ count = payload.u32(356)
294
+ return false unless payload.u32(352) == 0x08007664 && count <= 16 && count * 4 <= payload.length - 360
295
+
296
+ charge_alloc(count * 4)
297
+ charge_work(count)
298
+ end
299
+ charge_alloc(FONT_RECORD_BYTES)
300
+ true
301
+ end
302
+
303
+ # Span validation and charging follow native decode_text in order.
304
+ def decode_text?(payload, width) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
305
+ return false if payload.length < 52
306
+
307
+ count = payload.u32(36)
308
+ options = payload.u32(44)
309
+ fixed = options.nobits?(0x100) ? 68 : 52
310
+ return false if payload.length < fixed
311
+
312
+ string_len = count * width
313
+ dx_count = count * (options.nobits?(0x2000) ? 1 : 2)
314
+ string_offset = payload.u32(40)
315
+ dx_offset = payload.u32(fixed - 4)
316
+ string = span(payload.length, string_offset, string_len, width, fixed) unless string_offset.zero? && count.zero?
317
+ dx = span(payload.length, dx_offset, dx_count * 4, 4, fixed) unless dx_offset.zero?
318
+ return false if (!string_offset.zero? || !count.zero?) && !string
319
+ return false if !dx_offset.zero? && !dx
320
+ return false if string && dx && string[0] < dx[1] && dx[0] < string[1]
321
+
322
+ ranges = [string, dx].compact.sort_by(&:first)
323
+ cursor = fixed
324
+ ranges.each do |start, finish|
325
+ return false if start < cursor
326
+
327
+ charge_work(ceil16(start - cursor))
328
+ charge_alloc(start - cursor)
329
+ cursor = finish
330
+ end
331
+ charge_work(count)
332
+ charge_alloc(string_len)
333
+ if dx
334
+ charge_work(dx_count)
335
+ charge_alloc(dx_count * 4)
336
+ end
337
+ true
338
+ end
339
+
340
+ # Validate all spans before the first native pen allocation charge.
341
+ def decode_pen?(payload) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
342
+ return false if payload.length < 44
343
+
344
+ count = payload.u32(40)
345
+ fixed = 44 + (count * 4)
346
+ return false if fixed > payload.length
347
+
348
+ ranges = [[payload.u32(4), payload.u32(8)], [payload.u32(12), payload.u32(16)]].map do |offset, length|
349
+ next if offset.zero? && length.zero?
350
+
351
+ range = span(payload.length, offset, length, 4, fixed, empty_boundary: true)
352
+ return false unless range
353
+
354
+ range
355
+ end
356
+ info, bits = ranges
357
+ bits_first = bits && (!info || bits[0] < info[0] || (bits[0] == info[0] && bits[0] == bits[1]))
358
+ ranges = (bits_first ? [bits, info] : [info, bits]).compact
359
+ cursor = fixed
360
+ ranges.each do |start, finish|
361
+ return false if start < cursor
362
+
363
+ cursor = finish
364
+ end
365
+ charge_alloc(count * 4)
366
+ charge_work(count)
367
+ cursor = fixed
368
+ ranges.each do |start, finish|
369
+ [start - cursor, finish - start].each do |length|
370
+ charge_alloc(length)
371
+ charge_work(ceil16(length))
372
+ end
373
+ cursor = finish
374
+ end
375
+ true
376
+ end
377
+ end
378
+ end
379
+
380
+ require_relative "resource_budget/bitmap"
381
+ require_relative "resource_budget/poly_text"
382
+ require_relative "resource_budget/gradient"
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module ResourceLimits
5
+ # Admission follows the native framing rules without decoding a Ruby model.
6
+ # Corrupt spans are left to the selected parser's existing recovery path.
7
+ module EmfRecords
8
+ MAX_RECORD_BYTES = 128 * 1024 * 1024
9
+ MAX_RECORDS = 4 * 1024 * 1024
10
+ POINT_WIDTHS = { 2 => 8, 3 => 8, 4 => 8, 5 => 8, 6 => 8,
11
+ 85 => 4, 86 => 4, 87 => 4, 88 => 4, 89 => 4,
12
+ 56 => 9, 92 => 5 }.freeze
13
+ MULTI_WIDTHS = { 7 => 8, 8 => 8, 90 => 4, 91 => 4 }.freeze
14
+ REGION_OFFSETS = { 71 => 32, 72 => 40, 73 => 28, 74 => 28, 75 => 16 }.freeze
15
+
16
+ module_function
17
+
18
+ def check(bytes)
19
+ count = 0
20
+ each(bytes) do |type, size, base|
21
+ ResourceLimits.check!(:record_bytes, MAX_RECORD_BYTES, size)
22
+ count += 1
23
+ ResourceLimits.check!(:record_count, MAX_RECORDS, count)
24
+ check_arrays(bytes, type, size, base) if base.positive?
25
+ end
26
+ end
27
+
28
+ def each(bytes)
29
+ return if bytes.bytesize < 8
30
+
31
+ size = bytes.unpack1("V", offset: 4)
32
+ minimum = if size > 100
33
+ 108
34
+ else
35
+ size > 88 ? 100 : 88
36
+ end
37
+ return if size < minimum
38
+
39
+ # The native header checks its claimed size before reading its body.
40
+ yield 1, size, 0
41
+ base = size
42
+ while bytes.bytesize - base >= 8
43
+ type = bytes.unpack1("V", offset: base)
44
+ size = bytes.unpack1("V", offset: base + 4)
45
+ return if size < 8 || size > bytes.bytesize - base
46
+
47
+ yield type, size, base
48
+ return if type == 14 && size >= 20
49
+
50
+ base += size
51
+ end
52
+ end
53
+
54
+ def check_arrays(bytes, type, size, base)
55
+ if (width = POINT_WIDTHS[type])
56
+ check_single(bytes, size, base, 24, 28, width)
57
+ elsif (width = MULTI_WIDTHS[type])
58
+ check_multi(bytes, size, base, width)
59
+ elsif type == 50
60
+ check_single(bytes, size, base, 16, 20, 4)
61
+ elsif type == 95
62
+ check_pen(bytes, size, base)
63
+ elsif REGION_OFFSETS.key?(type)
64
+ check_region(bytes, type, size, base)
65
+ end
66
+ end
67
+
68
+ def check_single(bytes, size, base, count_at, fixed, width)
69
+ return if size < fixed
70
+
71
+ count = bytes.unpack1("V", offset: base + count_at)
72
+ check_count(count) if fixed + (count * width) <= size
73
+ end
74
+
75
+ def check_multi(bytes, size, base, width)
76
+ return if size < 32
77
+
78
+ polys, points = bytes.unpack("V2", offset: base + 24)
79
+ return if 32 + (polys * 4) + (points * width) > size
80
+
81
+ check_count(polys)
82
+ check_count(points)
83
+ end
84
+
85
+ def check_pen(bytes, size, base)
86
+ return if size < 52
87
+
88
+ count = bytes.unpack1("V", offset: base + 48)
89
+ fixed = 52 + (count * 4)
90
+ return if fixed > size
91
+
92
+ info, info_length, bits, bits_length = bytes.unpack("V4", offset: base + 12)
93
+ spans = [[info, info_length], [bits, bits_length]].map { |offset, length| pen_span(offset, length, fixed, size) }
94
+ return if spans.any?(&:nil?)
95
+
96
+ spans.sort_by! { |start, length| [start, length.zero? ? 0 : 1] }
97
+ return if spans.size == 2 && spans[0].sum > spans[1][0]
98
+
99
+ check_count(count)
100
+ end
101
+
102
+ def pen_span(offset, length, fixed, size)
103
+ return [fixed, 0] if offset.zero? && length.zero?
104
+
105
+ start = length.zero? ? [offset, fixed].max : offset
106
+ return unless offset >= 8 && (offset % 4).zero? && start >= fixed && start + length <= size
107
+
108
+ [start, length]
109
+ end
110
+
111
+ def check_region(bytes, type, size, base)
112
+ fixed = REGION_OFFSETS.fetch(type)
113
+ return if size < fixed
114
+
115
+ length = bytes.unpack1("V", offset: base + (type == 75 ? 8 : 24))
116
+ return if length < 32 || fixed + length > size
117
+
118
+ header, _kind, count, rectangle_bytes = bytes.unpack("V4", offset: base + fixed)
119
+ return unless header == 32 && count * 16 == rectangle_bytes && 32 + rectangle_bytes <= length
120
+
121
+ check_count(count)
122
+ end
123
+
124
+ def check_count(count, record_bytes: 0)
125
+ ResourceLimits.check!(:array_elements, Emr::Parser::MAX_SANE_ARRAY_LENGTH, count)
126
+ ResourceLimits.check!(:record_bytes, 0xFFFFFFFF, record_bytes)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "resource_limits/emf_records"
4
+ require_relative "resource_budget"
5
+
6
+ module Emf
7
+ # Shared admission checks run before either parser allocates its input model.
8
+ module ResourceLimits
9
+ module_function
10
+
11
+ def check!(resource, limit, actual)
12
+ return if actual <= limit
13
+
14
+ error = ResourceLimitError.new(resource: resource, limit: limit, actual: actual)
15
+ error.with_evidence(yield) if block_given?
16
+ raise error
17
+ end
18
+
19
+ def preflight(bytes, defer: false)
20
+ format = Detector.call(bytes)
21
+ limit = case format
22
+ when :wmf_apm, :wmf_standard then Wmf::Binary::MAX_INPUT_BYTES
23
+ when :emf then Emr::Parser::MAX_INPUT_BYTES
24
+ end
25
+ check!(:input_bytes, limit, bytes.bytesize) do
26
+ check = format == :emf ? :emf_input_too_large : :input_too_large
27
+ ParseEvidence.new(check: check, offset: 0, declared: limit, actual: bytes.bytesize)
28
+ end
29
+ return unless format == :emf
30
+ return :deferred if defer && bounded_emf?(bytes)
31
+
32
+ ResourceBudget.check!(bytes, limits: { input_bytes: limit,
33
+ record_bytes: EmfRecords::MAX_RECORD_BYTES,
34
+ record_count: EmfRecords::MAX_RECORDS }) do |type, size, base|
35
+ EmfRecords.check_arrays(bytes, type, size, base)
36
+ end
37
+ nil
38
+ end
39
+
40
+ # Except for PolyText's potentially overlapping descriptors, native charges
41
+ # are bounded by input length. Geometric record/diagnostic reservations cost
42
+ # less than 184 bytes per input byte; input, font and payload copies fit in
43
+ # the remaining 72. Framing and payload work fit within four units per byte.
44
+ # The Ruby parser must request exact admission if it encounters PolyText.
45
+ def bounded_emf?(bytes)
46
+ size = bytes.bytesize
47
+ header = bytes.unpack1("V", offset: 4)
48
+ return false unless header <= size && (header == 88 || header == 100 || header >= 108)
49
+
50
+ limits = ResourceBudget::DEFAULT_LIMITS
51
+ size <= EmfRecords::MAX_RECORD_BYTES &&
52
+ size / 8 <= EmfRecords::MAX_RECORDS &&
53
+ size / 4 <= Emr::Parser::MAX_SANE_ARRAY_LENGTH &&
54
+ size * 256 <= limits.fetch(:allocated_bytes) &&
55
+ size * 4 <= limits.fetch(:work_units)
56
+ end
57
+ end
58
+ end
data/lib/emf/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Emf
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -35,6 +35,10 @@ module Emf
35
35
  @lines << " rec[#{adapter.offset}] type_id=#{adapter.type_id} #{wire.class.name&.split('::')&.last}"
36
36
  end
37
37
 
38
+ def visit_wmf_record(record)
39
+ @lines << " rec[#{record.offset}] type_id=#{record.type_id} #{record.class.name&.split('::')&.last}"
40
+ end
41
+
38
42
  def method_missing(name, *args)
39
43
  return super unless name.to_s.start_with?("visit_")
40
44
 
@@ -5,11 +5,8 @@ module Emf
5
5
  # Produces a record-class histogram for a Metafile. Used by spec assertions
6
6
  # (TODO 16) and as a quick sanity-check dump via `emf stats` (TODO 19).
7
7
  #
8
- # The base Visitor auto-registers `visit_*` methods as no-ops. Stats
9
- # overrides the generic dispatch to tally every record by its class name.
10
- # Subclasses that override specific visit_* methods for richer behaviour
11
- # still get tallied here because the record's accept calls the visit_*
12
- # method, which (if not overridden) calls method_missing -> tally.
8
+ # Registered record families have explicit handlers; unregistered visit_*
9
+ # methods fall back to tallying the record's class name.
13
10
  class Stats < Emf::Model::Visitor
14
11
  def initialize
15
12
  @counts = Hash.new(0)
@@ -48,6 +45,10 @@ module Emf
48
45
  @counts["WireAdapter(#{wire_class})"] += 1
49
46
  end
50
47
 
48
+ def visit_wmf_record(record)
49
+ @counts[record.class.name&.split("::")&.last || record.class.to_s] += 1
50
+ end
51
+
51
52
  def method_missing(name, *args)
52
53
  return super unless name.to_s.start_with?("visit_")
53
54