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,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Emr
5
+ # The lazy source of a fixed-layout or point-array record that is only its
6
+ # bytes: a WireAdapter over it holds the frozen input and the record's
7
+ # span, and makes no BinData wire until `#wire` is asked for. `#values`
8
+ # reads the bytes directly. Materialising reads the span with the same
9
+ # FastFill the eager parse uses, so the wire is the one a parse makes.
10
+ class FilledRecord
11
+ EMPTY_SUFFIX = "".b.freeze
12
+ private_constant :EMPTY_SUFFIX
13
+
14
+ # The source for the record at `offset` of the frozen binary `data`,
15
+ # or nil when it needs the eager parse: no fixed-layout filler, a span
16
+ # shorter than the layout, a point count past the span, or a header
17
+ # disagreeing with the span. The caller has checked the span against
18
+ # `data`.
19
+ def self.for(type_id, offset, length, data)
20
+ filler = Binary::FastFill.for(type_id) || Binary::FastFill.for_points(type_id)
21
+ return unless filler && length >= filler.size && filler.fits?(data, offset, length)
22
+
23
+ new(filler, data, offset, length, type_id)
24
+ end
25
+
26
+ def initialize(filler, data, offset, length, type_id)
27
+ @filler = filler
28
+ @data = data
29
+ @offset = offset
30
+ @length = length
31
+ @type_id = type_id
32
+ end
33
+
34
+ attr_reader :length
35
+
36
+ # The Ruby parser has always answered a BinData Uint32le here (the wire's
37
+ # i_type), so a byte-backed record keeps that type.
38
+ def type_id
39
+ BinData::Uint32le.new(@type_id)
40
+ end
41
+
42
+ def to_binary_s
43
+ @data.byteslice(@offset, @length)
44
+ end
45
+
46
+ def append_to(out)
47
+ out << to_binary_s
48
+ end
49
+
50
+ def wire_class
51
+ @filler.wire_class
52
+ end
53
+
54
+ # [frozen binary data, record offset, untouched]: the bytes the wire is
55
+ # filled from, and no wire exists yet.
56
+ def value_source
57
+ [@data, @offset, true]
58
+ end
59
+
60
+ def materialize
61
+ if (wire = @filler.exact_wire(@data, @offset, @length))
62
+ @suffix = EMPTY_SUFFIX
63
+ return wire
64
+ end
65
+
66
+ wire, @suffix = @filler.call_at(@data, @offset, @length)
67
+ raise FormatError, "record disagrees with its bytes" unless wire
68
+
69
+ wire
70
+ end
71
+
72
+ def suffix(_wire)
73
+ @suffix
74
+ end
75
+ end
76
+ end
77
+ end
@@ -7,38 +7,71 @@ module Emf
7
7
  module Parser
8
8
  module_function
9
9
 
10
- def call(bytes)
11
- raise FormatError, "input too large (#{bytes.bytesize} > #{MAX_INPUT_BYTES}); refusing to parse" if bytes.bytesize > MAX_INPUT_BYTES
10
+ def call(bytes, deferred_admission: nil)
11
+ ResourceLimits.check!(:input_bytes, MAX_INPUT_BYTES, bytes.bytesize)
12
12
  raise FormatError, "not an EMF file (signature mismatch)" unless Emf::Detector.call(bytes) == :emf
13
13
 
14
14
  io = StringIO.new(bytes, "rb")
15
15
  header_wire = read_header(io)
16
16
  header = Model::Emr::Header.from_wire(header_wire)
17
+ data = bytes.b.freeze
18
+ pos = io.pos
17
19
 
18
20
  records = []
19
21
  errors = []
22
+ diagnostics = []
20
23
  emf_plus_bytes = +""
21
- trailing = +""
24
+ trailing = "".b
25
+
26
+ size = data.bytesize
27
+ while pos < size
28
+ offset = pos
29
+ if (source = filled_record_at(data, offset, size))
30
+ records << Model::Emr::Records::WireAdapter.lazy(source: source, offset: offset)
31
+ pos = offset + source.length
32
+ next
33
+ end
34
+
35
+ deferred_admission = admit_deferred(data, offset, deferred_admission)
36
+
37
+ wire, suffix = read_record_at(data, offset, errors, diagnostics: diagnostics, record_index: records.length + 1)
38
+ if wire.nil?
39
+ trailing << bytes.byteslice(offset..)
40
+ break
41
+ end
22
42
 
23
- until io.eof?
24
- offset = io.pos
25
- wire = read_record(io, offset, errors)
26
- break if wire.nil?
43
+ pos = offset + data.unpack1("V", offset: offset + 4)
27
44
 
28
- records << Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset)
29
- emf_plus_bytes << extract_emf_plus(wire) if wire.is_a?(Emf::Emr::Binary::Records::Comment)
45
+ records << Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset, suffix: suffix)
46
+ emf_plus_bytes << comment_emf_plus(wire)
30
47
 
31
48
  if wire.is_a?(Emf::Emr::Binary::Records::Eof)
32
- trailing << io.read until io.eof?
49
+ trailing << data.byteslice(pos..) if pos < data.bytesize
33
50
  break
34
51
  end
35
52
  end
36
53
 
54
+ metafile(header, records, errors, emf_plus_bytes, trailing, diagnostics)
55
+ end
56
+
57
+ # PolyText can charge the same bytes repeatedly before rejecting
58
+ # overlapping descriptors. It has no FilledRecord fast path.
59
+ def admit_deferred(data, offset, deferred)
60
+ return unless deferred
61
+
62
+ type = data.unpack1("V", offset: offset)
63
+ return deferred unless [96, 97].include?(type)
64
+
65
+ ResourceLimits.preflight(data)
66
+ end
67
+
68
+ def metafile(header, records, errors, emf_plus_bytes, trailing, diagnostics = [])
37
69
  Model::Metafile.new(
38
70
  format: emf_plus_bytes.empty? ? :emf : :emf_plus,
39
71
  header: header,
40
72
  records: records,
41
73
  errors: errors,
74
+ diagnostics: diagnostics,
42
75
  emf_plus: emf_plus_bytes.empty? ? nil : emf_plus_bytes,
43
76
  trailing: trailing
44
77
  )
@@ -60,6 +93,8 @@ module Emf
60
93
  end
61
94
 
62
95
  Emf::Emr::Binary::Header.read(full)
96
+ rescue IOError
97
+ raise FormatError, "EMF header nSize #{n_size} cannot hold its fields"
63
98
  end
64
99
 
65
100
  # Per-type byte-size validation for records with variable-length arrays.
@@ -67,6 +102,9 @@ module Emf
67
102
  # corrupt and we should preserve them as Raw rather than letting bindata
68
103
  # allocate a giant array. The element size is bytes per array entry.
69
104
  VARIABLE_ARRAY_RECORDS = {
105
+ Binary::TypeCodes::EXTCREATEPEN => { count_offset: 48, element_size: 4 },
106
+ Binary::TypeCodes::CREATEPALETTE => { count_offset: 14, count_width: 2, element_size: 4 },
107
+ Binary::TypeCodes::SETPALETTEENTRIES => { count_offset: 16, element_size: 4 },
70
108
  Binary::TypeCodes::POLYGON => { count_offset: 24, element_size: 8 },
71
109
  Binary::TypeCodes::POLYLINE => { count_offset: 24, element_size: 8 },
72
110
  Binary::TypeCodes::POLYBEZIER => { count_offset: 24, element_size: 8 },
@@ -93,14 +131,39 @@ module Emf
93
131
  # Bounds the whole-file parse: a 100 MB EMF is unusual; refuse bigger.
94
132
  MAX_INPUT_BYTES = 200 * 1024 * 1024
95
133
 
96
- def variable_array_sane?(type_id, n_size, head, payload)
134
+ REGION_DATA_OFFSETS = { 71 => 32, 72 => 40, 73 => 28, 74 => 28, 75 => 16 }.freeze
135
+
136
+ # The checks read the record in place: `base` is its offset in `data`
137
+ # (0 for a record already sliced), `n_size` its length, so no slice or
138
+ # string is made per checked record.
139
+ def variable_array_sane?(type_id, n_size, record_bytes)
140
+ sane_at?(type_id, n_size, record_bytes, 0)
141
+ end
142
+
143
+ def sane_at?(type_id, n_size, data, base)
144
+ region_data_sane?(type_id, n_size, data, base) &&
145
+ sized_arrays_sane?(type_id, n_size, data, base) &&
146
+ ext_text_out_sane?(type_id, n_size, data, base)
147
+ end
148
+
149
+ def region_data_sane?(type_id, n_size, data, base)
150
+ data_offset = REGION_DATA_OFFSETS[type_id]
151
+ return true unless data_offset
152
+
153
+ length_offset = type_id == 75 ? 8 : 24
154
+ return false if n_size < length_offset + 4
155
+
156
+ data.unpack1(UINT32, offset: base + length_offset) <= n_size - data_offset
157
+ end
158
+
159
+ def sized_arrays_sane?(type_id, n_size, data, base)
97
160
  spec = VARIABLE_ARRAY_RECORDS[type_id]
98
161
  return true unless spec
99
162
 
100
- full = head + payload
101
- return true if full.bytesize < spec[:count_offset] + 4
163
+ count_width = spec.fetch(:count_width, 4)
164
+ return true if n_size < spec[:count_offset] + count_width
102
165
 
103
- count = read_uint32(full, spec[:count_offset])
166
+ count = data.unpack1(count_width == 2 ? UINT16 : UINT32, offset: base + spec[:count_offset])
104
167
  return false if count > MAX_SANE_ARRAY_LENGTH
105
168
 
106
169
  # For single-array records, element_size is the per-entry size.
@@ -108,77 +171,203 @@ module Emf
108
171
  # are always uint32 (4 bytes); element_size applies only to the
109
172
  # second array (aptl/apts).
110
173
  first_array_entry_size = spec[:count2_offset] ? 4 : spec[:element_size]
111
- bytes_needed = spec[:count_offset] + 4 + (count * first_array_entry_size)
174
+ bytes_needed = spec[:count_offset] + count_width + (count * first_array_entry_size)
112
175
  return false if bytes_needed > n_size
113
176
 
114
177
  # Multi-array records also need to validate the second count.
115
178
  return true unless spec[:count2_offset]
116
179
 
117
- count2 = read_uint32(full, spec[:count2_offset])
180
+ count2 = data.unpack1(UINT32, offset: base + spec[:count2_offset])
118
181
  return false if count2 > MAX_SANE_ARRAY_LENGTH
119
182
 
120
183
  bytes_needed + (count2 * spec[:element_size]) <= n_size
121
184
  end
122
185
 
123
- def read_uint32(bytes, offset)
124
- bytes.getbyte(offset) |
125
- (bytes.getbyte(offset + 1) << 8) |
126
- (bytes.getbyte(offset + 2) << 16) |
127
- (bytes.getbyte(offset + 3) << 24)
186
+ UINT32 = "V"
187
+ UINT16 = "v"
188
+ ETO_NO_RECT = 0x100
189
+ ETO_PDY = 0x2000
190
+ EXT_TEXT_OUT_CHAR_WIDTH = {
191
+ Binary::TypeCodes::EXTTEXTOUTA => 1,
192
+ Binary::TypeCodes::EXTTEXTOUTW => 2
193
+ }.freeze
194
+
195
+ # Types whose declared counts or spans are checked against nSize.
196
+ CHECKED_TYPES = [REGION_DATA_OFFSETS, VARIABLE_ARRAY_RECORDS, EXT_TEXT_OUT_CHAR_WIDTH]
197
+ .flat_map(&:keys).to_h { |type_id| [type_id, true] }.freeze
198
+
199
+ # Mirrors the native decoder: the string and Dx spans implied by nChars
200
+ # must lie inside the record, after the fixed fields, and not overlap.
201
+ def ext_text_out_sane?(type_id, n_size, data, base)
202
+ width = EXT_TEXT_OUT_CHAR_WIDTH[type_id]
203
+ return true unless width
204
+
205
+ spans, fixed = ext_text_out_spans(n_size, data, base, width)
206
+ return false unless spans
207
+
208
+ spans.all? { |off, len, align| span_inside?(off, len, align, fixed, n_size) } &&
209
+ (spans.size < 2 || !spans_overlap?(*spans))
210
+ end
211
+
212
+ def ext_text_out_spans(n_size, data, base, width)
213
+ return if n_size < 60
214
+
215
+ count = data.unpack1(UINT32, offset: base + 44)
216
+ off_string = data.unpack1(UINT32, offset: base + 48)
217
+ options = data.unpack1(UINT32, offset: base + 52)
218
+ dx_at = options.anybits?(ETO_NO_RECT) ? 56 : 72
219
+ return if n_size < dx_at + 4
220
+
221
+ off_dx = data.unpack1(UINT32, offset: base + dx_at)
222
+ dx_entry = options.anybits?(ETO_PDY) ? 8 : 4
223
+ string = [off_string, count * width, width] unless off_string.zero? && count.zero?
224
+ dx = [off_dx, count * dx_entry, 4] unless off_dx.zero?
225
+ [[string, dx].compact, dx_at + 4]
128
226
  end
129
227
 
228
+ def span_inside?(off, len, align, fixed, n_size)
229
+ (off % align).zero? && off >= fixed && off + len <= n_size
230
+ end
231
+
232
+ def spans_overlap?(first, second)
233
+ first[0] < second[0] + second[1] && second[0] < first[0] + first[1]
234
+ end
235
+
236
+ def short_header?(available, offset, errors)
237
+ return false if available >= 8
238
+
239
+ errors << ParseError.new(offset: offset, record_code: nil,
240
+ message: "truncated record header at offset #{offset} (#{available} bytes)")
241
+ true
242
+ end
243
+
244
+ # Reads the record at `offset` of the stream and advances past it.
130
245
  def read_record(io, offset, errors)
131
- head = io.read(8)
132
- return nil if head.nil? || head.empty?
246
+ data = io.string.b
247
+ result = read_record_at(data, offset, errors)
248
+ io.seek(offset + data.unpack1("V", offset: offset + 4)) if result
249
+ result
250
+ end
133
251
 
134
- type_id, n_size = head.unpack("VV")
135
- if n_size < 8
136
- errors << ParseError.new(offset: offset, record_code: type_id,
137
- message: "record nSize #{n_size} < 8 bytes (offset #{offset})")
138
- return nil
139
- end
252
+ # The byte-backed source of the fixed-layout or point-array record at
253
+ # `offset` of the frozen binary `data`, or nil when the record needs
254
+ # `read_record_at` (any other type, a short or truncated span, a count
255
+ # that overruns its record, or an error to report). The wire is made
256
+ # only when asked for, from the same filler `read_typed` uses.
257
+ def filled_record_at(data, offset, size)
258
+ return if size - offset < 8
140
259
 
141
- payload = io.read(n_size - 8)
142
- if payload.nil? || payload.bytesize < n_size - 8
143
- errors << ParseError.new(offset: offset, record_code: type_id,
144
- message: "truncated record at offset #{offset} (claimed #{n_size}, got #{payload&.bytesize || 0})")
145
- return nil
146
- end
260
+ type_id = data.unpack1(UINT32, offset: offset)
261
+ n_size = data.unpack1(UINT32, offset: offset + 4)
262
+ return if n_size < 8 || size - offset < n_size
263
+
264
+ source = Emr::FilledRecord.for(type_id, offset, n_size, data)
265
+ return unless source
266
+ return source unless CHECKED_TYPES.key?(type_id)
267
+
268
+ source if sane_at?(type_id, n_size, data, offset)
269
+ end
147
270
 
148
- unless variable_array_sane?(type_id, n_size, head, payload)
271
+ # The record starting at `offset` of the binary `data` as [wire, suffix],
272
+ # or nil when none can be read there (the caller keeps the rest as
273
+ # trailing bytes).
274
+ def read_record_at(data, offset, errors, diagnostics: nil, record_index: nil)
275
+ header = record_header_at(data, offset, errors, diagnostics, record_index)
276
+ return unless header
277
+
278
+ type_id, n_size = header
279
+ record_bytes = data.byteslice(offset, n_size) if CHECKED_TYPES.key?(type_id)
280
+ unless record_bytes.nil? || variable_array_sane?(type_id, n_size, record_bytes)
149
281
  errors << ParseError.new(
150
282
  offset: offset, record_code: type_id,
151
283
  message: "variable-array record at offset #{offset} declares " \
152
284
  "more entries than nSize allows; preserving as Raw"
153
285
  )
154
- return build_raw(head, payload)
286
+ diagnostic(diagnostics, record_index, type_id, offset, :preserved_raw)
287
+ return [build_raw(record_bytes), "".b]
155
288
  end
156
289
 
157
- wire_class = Emf::Emr::Binary::Records.lookup(type_id)
158
- wire_class.read(head + payload)
290
+ read_typed(type_id, n_size, data, offset)
291
+ rescue ResourceLimitError
292
+ raise
159
293
  rescue StandardError => e
294
+ diagnostic(diagnostics, record_index, type_id, offset, :preserved_raw)
160
295
  errors << ParseError.new(offset: offset, record_code: type_id,
161
296
  message: "#{e.class}: #{e.message}")
162
- build_raw(head, payload)
297
+ [build_raw(record_bytes || data.byteslice(offset, n_size)), "".b]
298
+ end
299
+
300
+ def record_header_at(data, offset, errors, diagnostics, record_index)
301
+ available = data.bytesize - offset
302
+ return nil if available.zero?
303
+
304
+ if short_header?(available, offset, errors)
305
+ diagnostic(diagnostics, record_index, data.unpack1("V", offset: offset) || 0, offset, :skipped)
306
+ return nil
307
+ end
308
+
309
+ type_id = data.unpack1("V", offset: offset)
310
+ n_size = data.unpack1("V", offset: offset + 4)
311
+ if n_size < 8
312
+ diagnostic(diagnostics, record_index, type_id, offset, :skipped)
313
+ errors << ParseError.new(offset: offset, record_code: type_id,
314
+ message: "record nSize #{n_size} < 8 bytes (offset #{offset})")
315
+ return nil
316
+ end
317
+
318
+ if available < n_size
319
+ diagnostic(diagnostics, record_index, type_id, offset, :skipped)
320
+ errors << ParseError.new(offset: offset, record_code: type_id,
321
+ message: "truncated record at offset #{offset} (claimed #{n_size}, got #{available - 8})")
322
+ return nil
323
+ end
324
+
325
+ [type_id, n_size]
326
+ end
327
+
328
+ def diagnostic(diagnostics, record_index, type_id, offset, disposition)
329
+ return unless diagnostics
330
+
331
+ diagnostics << Model::Diagnostic.new(record_index: record_index, record_code: type_id,
332
+ byte_offset: offset, disposition: disposition)
163
333
  end
164
334
 
165
- def build_raw(head, payload)
166
- Emf::Emr::Binary::Records::Raw.read(head + (payload || +""))
335
+ def read_typed(type_id, n_size, data, offset)
336
+ filler = Emf::Emr::Binary::FastFill.for(type_id) || Emf::Emr::Binary::FastFill.for_points(type_id) ||
337
+ Emf::Emr::Binary::FastFill.for_text(type_id)
338
+ if filler && n_size >= filler.size
339
+ filled = filler.call_at(data, offset, n_size)
340
+ return filled if filled
341
+ end
342
+
343
+ record_bytes = data.byteslice(offset, n_size)
344
+ wire = Emf::Emr::Binary::Records.lookup(type_id).read(record_bytes)
345
+ [wire, Model::Emr::Records::WireAdapter.suffix_of(record_bytes, wire)]
346
+ end
347
+
348
+ # Public API since before the offset-aware checks; kept for callers.
349
+ def read_uint32(bytes, offset)
350
+ bytes.unpack1(UINT32, offset: offset)
351
+ end
352
+
353
+ def build_raw(record_bytes)
354
+ Emf::Emr::Binary::Records::Raw.read(record_bytes)
167
355
  end
168
356
 
169
357
  EMF_PLUS_IDENTIFIER = 0x2B464D45 # "EMF+" little-endian
170
358
 
171
- def extract_emf_plus(comment_wire)
172
- return +"" unless comment_wire.cb_data&.>= 4
359
+ def comment_emf_plus(wire)
360
+ wire.is_a?(Emf::Emr::Binary::Records::Comment) ? extract_emf_plus(wire) : ""
361
+ end
173
362
 
174
- identifier = comment_wire.data.getbyte(0) |
175
- (comment_wire.data.getbyte(1) << 8) |
176
- (comment_wire.data.getbyte(2) << 16) |
177
- (comment_wire.data.getbyte(3) << 24)
178
- return +"" unless identifier == EMF_PLUS_IDENTIFIER
363
+ def extract_emf_plus(comment_wire)
364
+ data = comment_wire.data.to_s.b
365
+ # cbData can claim more bytes than the record holds; read only what is there.
366
+ return +"" unless comment_wire.cb_data&.>=(4) && data.bytesize >= 4
367
+ return +"" unless data.unpack1("V") == EMF_PLUS_IDENTIFIER
179
368
 
180
369
  # Payload after the 4-byte identifier is the EMF+ record(s).
181
- comment_wire.data.bytes[4..].pack("C*")
370
+ data.byteslice(4..)
182
371
  end
183
372
  end
184
373
  end
@@ -6,15 +6,14 @@ module Emf
6
6
  module_function
7
7
 
8
8
  def call(metafile)
9
- bytes = +""
9
+ bytes = String.new(encoding: Encoding::BINARY)
10
10
  bytes << metafile.header.to_wire.to_binary_s
11
11
  metafile.records.each do |record|
12
- case record
13
- when Model::Emr::Records::WireAdapter
14
- bytes << record.wire.to_binary_s
15
- else
12
+ unless record.is_a?(Model::Emr::Records::WireAdapter)
16
13
  raise SerializeError, "cannot serialize #{record.class} (TODO 10 lands domain to_wire)"
17
14
  end
15
+
16
+ record.append_to(bytes)
18
17
  end
19
18
  bytes << metafile.trailing
20
19
  bytes
data/lib/emf/emr.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  module Emf
4
4
  module Emr
5
5
  autoload :Parser, "emf/emr/parser"
6
+ autoload :FilledRecord, "emf/emr/filled_record"
6
7
  autoload :Serializer, "emf/emr/serializer"
7
8
  autoload :PathAssociation, "emf/emr/path_association"
8
9
  autoload :Binary, "emf/emr/binary"
data/lib/emf/error.rb CHANGED
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Emf
4
- class Error < StandardError; end
4
+ class Error < StandardError
5
+ # Backend-neutral ParseEvidence for a failed header or framing check, or nil when none is available.
6
+ attr_reader :evidence
7
+
8
+ def with_evidence(evidence)
9
+ @evidence ||= evidence
10
+ self
11
+ end
12
+ end
5
13
 
6
14
  class FormatError < Error; end
7
15
 
@@ -15,5 +23,20 @@ module Emf
15
23
  end
16
24
  end
17
25
 
26
+ class ResourceLimitError < Error
27
+ attr_reader :resource, :limit, :actual
28
+
29
+ def initialize(resource:, limit:, actual:)
30
+ @resource = resource
31
+ @limit = limit
32
+ @actual = actual
33
+ super("#{resource} limit exceeded (#{actual} > #{limit})")
34
+ end
35
+ end
36
+
18
37
  class SerializeError < Error; end
38
+
39
+ module EmfPlus
40
+ class StaleStreamError < Error; end
41
+ end
19
42
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Model
5
+ class Diagnostic
6
+ TYPE_NAMES = Emf::Emr::Binary::TypeCodes.constants.to_h do |name|
7
+ [Emf::Emr::Binary::TypeCodes.const_get(name), "EMR_#{name}".freeze]
8
+ end.freeze
9
+
10
+ attr_reader :record_index, :record_code, :record_type, :byte_offset, :disposition
11
+
12
+ def initialize(record_index:, record_code:, byte_offset:, disposition:)
13
+ raise ArgumentError, "invalid diagnostic disposition" unless %i[skipped preserved_raw].include?(disposition)
14
+ unless [record_index, record_code, byte_offset].all? { |value| value.is_a?(Integer) && value >= 0 }
15
+ raise ArgumentError, "diagnostic positions and code must be nonnegative integers"
16
+ end
17
+
18
+ @record_index = record_index
19
+ @record_code = record_code
20
+ @record_type = TYPE_NAMES.fetch(record_code) { format("UNKNOWN(0x%08X)", record_code).freeze }
21
+ @byte_offset = byte_offset
22
+ @disposition = disposition
23
+ freeze
24
+ end
25
+
26
+ def to_h
27
+ { record_index: record_index, record_code: record_code, record_type: record_type,
28
+ byte_offset: byte_offset, disposition: disposition }
29
+ end
30
+
31
+ def ==(other)
32
+ other.is_a?(self.class) && to_h == other.to_h
33
+ end
34
+ alias eql? ==
35
+
36
+ def hash
37
+ to_h.hash
38
+ end
39
+ end
40
+ end
41
+ end
@@ -37,7 +37,31 @@ module Emf
37
37
  :n_handles, :s_reserved, :n_description, :off_description,
38
38
  :n_pal_entries, :device_pixels, :device_mm, :cb_pixel_format,
39
39
  :off_pixel_format, :b_open_gl, :szl_micrometers, :n_size,
40
- :trailing, :description
40
+ :trailing
41
+
42
+ # The description as UTF-8 (UTF-16LE nDescription chars at offDescription,
43
+ # trailing NULs dropped), or nil when absent or outside the header record.
44
+ # An explicit `description:` given at construction wins.
45
+ def description
46
+ return @description if @description
47
+
48
+ bytes = description_bytes
49
+ return unless bytes
50
+
51
+ bytes.dup.force_encoding(Encoding::UTF_16LE).encode(Encoding::UTF_8, invalid: :replace, undef: :replace).sub(/\0+\z/, "")
52
+ end
53
+
54
+ # Raw description bytes (2 * nDescription at offDescription from record
55
+ # start), or nil when nDescription is 0 or the range leaves the record.
56
+ def description_bytes
57
+ slice_of_record(off_description, n_description * 2)
58
+ end
59
+
60
+ # Raw PIXELFORMATDESCRIPTOR bytes (cbPixelFormat at offPixelFormat), or
61
+ # nil when cbPixelFormat is 0/absent or the range leaves the record.
62
+ def pixel_format_bytes
63
+ slice_of_record(off_pixel_format, cb_pixel_format)
64
+ end
41
65
 
42
66
  def self.from_wire(wire)
43
67
  new(
@@ -63,6 +87,12 @@ module Emf
63
87
  )
64
88
  end
65
89
 
90
+ # The header record bytes as #to_wire writes them now (equal to the source bytes until an edit).
91
+ # Not cached: the fields and `trailing` stay mutable, and the buffers must follow them.
92
+ def record_bytes
93
+ to_wire.to_binary_s
94
+ end
95
+
66
96
  def to_wire
67
97
  Emf::Emr::Binary::Header.new(
68
98
  i_type: 1,
@@ -87,6 +117,15 @@ module Emf
87
117
  trailing: trailing
88
118
  )
89
119
  end
120
+
121
+ private
122
+
123
+ def slice_of_record(offset, length)
124
+ return unless offset&.positive? && length&.positive?
125
+
126
+ bytes = record_bytes
127
+ bytes.byteslice(offset, length) if offset + length <= bytes.bytesize
128
+ end
90
129
  end
91
130
  end
92
131
  end