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,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Hydrates the fixed EMRSMALLTEXTOUT prefix and leaves option-dependent
6
+ # text bytes opaque for the Ruby visitor.
7
+ module SmallTextFields
8
+ FIXED_SIZE = 36
9
+
10
+ def self.call(raw)
11
+ return [Emr::Binary::Records::Raw, { payload: raw.byteslice(8..) }] if raw.bytesize < FIXED_SIZE
12
+
13
+ [Emr::Binary::Records::SmallTextOut,
14
+ {
15
+ x_dest: raw.byteslice(8, 4).unpack1("l<"),
16
+ y_dest: raw.byteslice(12, 4).unpack1("l<"),
17
+ c_chars: raw.byteslice(16, 4).unpack1("V"),
18
+ fu_options: raw.byteslice(20, 4).unpack1("V"),
19
+ i_graphics_mode: raw.byteslice(24, 4).unpack1("V"),
20
+ ex_scale: raw.byteslice(28, 4).unpack1("e"),
21
+ ey_scale: raw.byteslice(32, 4).unpack1("e"),
22
+ body: raw.byteslice(FIXED_SIZE..)
23
+ }]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Decodes the versioned transport, never the original EMF wire format.
6
+ class Snapshot
7
+ # Five integers per record, in record order: index, offset, length,
8
+ # type | family << 32 and payload length (the 40-byte entry as u64 words).
9
+ attr_reader :header, :raw_header, :table, :record_count, :diagnostics, :trailing, :unframed
10
+
11
+ TYPE_MASK = 0xFFFF_FFFF
12
+
13
+ def initialize(bytes)
14
+ @bytes = bytes
15
+ @position = 0
16
+ raise FormatError, "invalid native snapshot schema" unless take(4) == "EMFS"
17
+
18
+ schema = u32
19
+ raise FormatError, "invalid native snapshot schema" unless [2, 3].include?(schema)
20
+ raise FormatError, "invalid native snapshot length" unless u64 == bytes.bytesize
21
+
22
+ header_length, record_count, diagnostic_count, trailing_length, unframed_length = Array.new(5) { u64 }
23
+ raise FormatError, "invalid native snapshot counts" if record_count > bytes.bytesize / 40 || diagnostic_count > bytes.bytesize / 40
24
+
25
+ @header = Array.new(28) { u32 }
26
+ @raw_header = take(header_length)
27
+ @record_count = record_count
28
+ schema == 3 ? read_table : read_entries
29
+ @diagnostics = Array.new(diagnostic_count) { take(40).unpack("V4Q<3") }
30
+ @trailing = take(trailing_length)
31
+ @unframed = take(unframed_length)
32
+ raise FormatError, "unconsumed native snapshot bytes" unless @position == bytes.bytesize
33
+ end
34
+
35
+ def take(length)
36
+ raise FormatError, "truncated native snapshot" if length > @bytes.bytesize - @position
37
+
38
+ result = @bytes.byteslice(@position, length)
39
+ @position += length
40
+ result
41
+ end
42
+
43
+ def u32
44
+ take(4).unpack1("V")
45
+ end
46
+
47
+ def u64
48
+ take(8).unpack1("Q<")
49
+ end
50
+
51
+ # The record at `index` as a Hash with its typed payload.
52
+ def record(index)
53
+ base = index * 5
54
+ type_family = @table[base + 3]
55
+ { index: @table[base], offset: @table[base + 1], length: @table[base + 2], type: type_family & TYPE_MASK,
56
+ family: type_family >> 32, payload: payload(index) }
57
+ end
58
+
59
+ def records
60
+ @records ||= Array.new(@record_count) { |index| record(index) }
61
+ end
62
+
63
+ def payload(index)
64
+ length = @table[(index * 5) + 4]
65
+ length.zero? ? EMPTY_PAYLOAD : @bytes.byteslice(@payload_at[index], length)
66
+ end
67
+
68
+ private
69
+
70
+ EMPTY_PAYLOAD = "".b.freeze
71
+ private_constant :EMPTY_PAYLOAD
72
+
73
+ # Schema 3: every entry in one fixed-stride table, then the payloads.
74
+ def read_table
75
+ raise FormatError, "truncated native snapshot" if @bytes.bytesize - @position < @record_count * 40
76
+
77
+ @table = @bytes.unpack("Q<#{@record_count * 5}", offset: @position)
78
+ @position += @record_count * 40
79
+ @payload_at = Array.new(@record_count) do |index|
80
+ at = @position
81
+ @position += @table[(index * 5) + 4]
82
+ at
83
+ end
84
+ raise FormatError, "truncated native snapshot" if @position > @bytes.bytesize
85
+ end
86
+
87
+ # Schema 2: each entry is followed by its payload.
88
+ def read_entries
89
+ @table = []
90
+ @payload_at = Array.new(@record_count) do
91
+ raise FormatError, "truncated native snapshot" if @bytes.bytesize - @position < 40
92
+
93
+ @table.concat(@bytes.unpack("Q<5", offset: @position))
94
+ @position += 40
95
+ at = @position
96
+ take(@table.last)
97
+ at
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class WmfBitmapFields
6
+ def initialize(record, raw)
7
+ @record = record
8
+ @raw = raw
9
+ @type = record.fetch(:type)
10
+ @offset = record.fetch(:offset)
11
+ @payload = record.fetch(:payload)
12
+ end
13
+
14
+ def call
15
+ validate(@record.fetch(:family) == 11 && @raw.bytesize >= 6 && @raw.bytesize.even? &&
16
+ @payload.bytesize <= Wmf::Binary::MAX_INPUT_BYTES)
17
+ validate(@raw.unpack("Vv") == [@raw.bytesize / 2, @type])
18
+ kind = @type & 0xFF
19
+ fields = decode_fields(kind)
20
+ body = Wmf::BitmapCodec.encode_fields(fields, @type)
21
+ validate(@raw.bytesize == body.bytesize + 6 && @raw.byteslice(6..) == body)
22
+ Wmf::BitmapCodec::RECORDS.fetch(kind).new(**fields, type_id: @type, offset: @offset)
23
+ rescue SerializeError
24
+ validate(false)
25
+ end
26
+
27
+ private
28
+
29
+ def decode_fields(kind)
30
+ names = Wmf::BitmapCodec::FIELDS.fetch(kind)
31
+ fixed = names.length * 4
32
+ validate(@payload.bytesize >= fixed + 8)
33
+ values = names.each_with_index.map do |field, index|
34
+ format = Wmf::BitmapCodec.field_type(kind, field) == :int16 ? "l<" : "V"
35
+ @payload.byteslice(index * 4, 4).unpack1(format)
36
+ end
37
+ names.zip(values).to_h.merge(image_fields(kind, fixed))
38
+ end
39
+
40
+ def image_fields(kind, fixed)
41
+ presence, length = @payload.byteslice(fixed, 8).unpack("V2")
42
+ validate([0, 1].include?(presence) && @payload.bytesize == fixed + 8 + length)
43
+ absent = presence.zero?
44
+ validate(absent ? Wmf::BitmapCodec.optional?(kind) && length.zero? : length.positive? && length.even?)
45
+ prefix = Wmf::BitmapCodec.wire_fields(kind, absent).sum { |field| field == :raster_operation ? 4 : 2 }
46
+ validate(@raw.bytesize >= 6 + prefix)
47
+ { bitmap: absent ? nil : @payload.byteslice(fixed + 8, length),
48
+ suffix: absent ? @raw.byteslice((6 + prefix)..) : "".b }
49
+ end
50
+
51
+ def validate(condition)
52
+ return if condition
53
+
54
+ raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF bitmap record at offset #{@offset}")
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class WmfBrushFields
6
+ def initialize(record, raw)
7
+ @record = record
8
+ @raw = raw
9
+ @type = record.fetch(:type)
10
+ @offset = record.fetch(:offset)
11
+ @payload = record.fetch(:payload)
12
+ end
13
+
14
+ def call
15
+ validate(@record.fetch(:family) == 10 && @raw.bytesize >= 6 && @raw.bytesize.even? &&
16
+ @payload.bytesize <= Wmf::Binary::MAX_INPUT_BYTES)
17
+ fields = @type & 0xFF == 0xF9 ? pattern : dib
18
+ record = Wmf::BrushCodec::RECORDS.fetch(@type & 0xFF).new(**fields, type_id: @type, offset: @offset)
19
+ prefix = Wmf::BrushCodec.encode_payload(record)
20
+ validate(@raw.bytesize >= prefix.bytesize + 6 && @raw.byteslice(6, prefix.bytesize) == prefix)
21
+ if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
22
+ record.suffix = @raw.byteslice((6 + prefix.bytesize)..)
23
+ else
24
+ validate(@raw.bytesize == prefix.bytesize + 6)
25
+ end
26
+ record
27
+ rescue SerializeError
28
+ validate(false)
29
+ end
30
+
31
+ private
32
+
33
+ def pattern
34
+ validate(@payload.bytesize >= 50)
35
+ signed = @payload.unpack("l<4")
36
+ planes, bits_pixel = @payload.byteslice(16, 8).unpack("V2")
37
+ length = @payload.byteslice(46, 4).unpack1("V")
38
+ validate(signed.all? { |value| value.between?(-0x8000, 0x7FFF) } && planes <= 0xFF && bits_pixel <= 0xFF)
39
+ expected = Wmf::BrushCodec.pattern_length(signed[1], signed[2], planes, bits_pixel)
40
+ validate(expected && expected == length && @payload.bytesize == 50 + length && @raw.bytesize >= 38 + length)
41
+ Wmf::BrushCodec::SIGNED_FIELDS.zip(signed).to_h.merge(
42
+ planes: planes, bits_pixel: bits_pixel, bits: @payload.byteslice(24, 4), reserved: @payload.byteslice(28, 18),
43
+ pattern: @payload.byteslice(50, length)
44
+ )
45
+ end
46
+
47
+ def dib
48
+ validate(@payload.bytesize >= 14)
49
+ style, usage, length = @payload.unpack("V3")
50
+ validate(style <= 0xFFFF && usage <= 0xFFFF && (style == 3 || usage <= 2) && length.positive? && length.even? &&
51
+ @payload.bytesize == 12 + length && @raw.bytesize == 10 + length)
52
+ { style: style, color_usage: usage, target: @payload.byteslice(12, length) }
53
+ end
54
+
55
+ def validate(condition)
56
+ return if condition
57
+
58
+ raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF brush record at offset #{@offset}")
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wmf_text_fields"
4
+ require_relative "wmf_palette_fields"
5
+ require_relative "wmf_region_fields"
6
+ require_relative "wmf_brush_fields"
7
+ require_relative "wmf_bitmap_fields"
8
+ require_relative "wmf_remaining_fields"
9
+
10
+ module Emf
11
+ module Native
12
+ class WmfFields
13
+ def initialize(record, raw)
14
+ @record = record
15
+ @raw = raw
16
+ @type = record.fetch(:type)
17
+ @offset = record.fetch(:offset)
18
+ @payload = record.fetch(:payload)
19
+ @function = @type & 0xFF
20
+ end
21
+
22
+ def call
23
+ if @type.zero?
24
+ validate(@record.fetch(:family) == 1 && @payload.empty? && @raw.bytesize == 6)
25
+ return Model::Wmf::Records::Eof.new(offset: @offset)
26
+ end
27
+ return prefix_record if Wmf::Binary::PREFIX_FUNCTIONS.include?(@function)
28
+ return fixed(Wmf::Binary::OBJECT_RECORDS.fetch(@function), 4) if Wmf::Binary::OBJECT_RECORDS.key?(@function)
29
+ return WmfTextFields.new(@record, @raw).call if Wmf::Binary::TEXT_FUNCTIONS.include?(@function)
30
+ return points(Wmf::Binary::POINT_ARRAY_RECORDS.fetch(@function)) if Wmf::Binary::POINT_ARRAY_RECORDS.key?(@function)
31
+ return fixed(Wmf::Binary::FIXED_RECORDS.fetch(@function), 2) if Wmf::Binary::FIXED_RECORDS.key?(@function)
32
+
33
+ Model::Wmf::Records::UnknownRecord.new(type_id: @type, raw_bytes: @raw, offset: @offset)
34
+ end
35
+
36
+ private
37
+
38
+ def prefix_record
39
+ if Wmf::GeometryCodec::RECORDS.key?(@function) || Wmf::DcEscapeCodec::RECORDS.key?(@function)
40
+ return WmfRemainingFields.new(@record, @raw).call
41
+ end
42
+ return WmfBitmapFields.new(@record, @raw).call if Wmf::BitmapCodec::RECORDS.key?(@function)
43
+
44
+ return WmfBrushFields.new(@record, @raw).call if Wmf::BrushCodec::RECORDS.key?(@function)
45
+ return font if @function == 0xFB
46
+ return state(Wmf::Binary::STATE_RECORDS.fetch(@function)) if Wmf::Binary::STATE_RECORDS.key?(@function)
47
+
48
+ if Wmf::RegionCodec::RECORDS.key?(@function)
49
+ WmfRegionFields.new(@record, @raw).call
50
+ else
51
+ WmfPaletteFields.new(@record, @raw).call
52
+ end
53
+ end
54
+
55
+ def font
56
+ klass = Model::Wmf::Records::CreateFontIndirect
57
+ prefix_size = 6 + klass::PAYLOAD_BYTES
58
+ validate(@record.fetch(:family) == 7 && @payload.bytesize == 84 &&
59
+ @raw.bytesize >= prefix_size && @raw.bytesize.even?)
60
+ signed = @payload.byteslice(0, 20).unpack("l<5")
61
+ bytes = @payload.byteslice(20, 32).unpack("V8")
62
+ validate(signed.all? { |value| value.between?(-0x8000, 0x7FFF) } && bytes.all? { |value| value <= 0xFF })
63
+ values = signed + bytes + [@payload.byteslice(52, 32)]
64
+ fields = klass::WIRE_FIELDS.zip(values).to_h
65
+ klass.new(**fields, type_id: @type, offset: @offset, suffix: @raw.byteslice(prefix_size..))
66
+ end
67
+
68
+ def state(klass)
69
+ fields = klass::WIRE_FIELDS
70
+ prefix_size = 6 + klass::PAYLOAD_BYTES
71
+ validate(@record.fetch(:family) == 6 && @payload.bytesize == fields.length * 4 &&
72
+ @raw.bytesize >= prefix_size && @raw.bytesize.even?)
73
+ values = @payload.unpack("V*")
74
+ values.zip(klass::WIRE_TYPES).each do |value, type|
75
+ validate(type != :uint16 || value <= 0xFFFF)
76
+ end
77
+ klass.new(**fields.zip(values).to_h, type_id: @type, offset: @offset, suffix: @raw.byteslice(prefix_size..))
78
+ end
79
+
80
+ def fixed(klass, family)
81
+ fields = klass::WIRE_FIELDS
82
+ wire_size = family == 4 ? klass::PAYLOAD_BYTES : fields.length * 2
83
+ validate(@record.fetch(:family) == family && @payload.bytesize == fields.length * 4 && @raw.bytesize == wire_size + 6)
84
+ values = @payload.unpack(family == 4 ? "V*" : "l<*")
85
+ if family == 4
86
+ values = values.zip(klass::WIRE_TYPES).map { |value, type| type == :int16 && value >= 0x80000000 ? value - 0x100000000 : value }
87
+ end
88
+ klass.new(**fields.zip(values).to_h, type_id: @type, offset: @offset)
89
+ end
90
+
91
+ def points(klass)
92
+ validate(@record.fetch(:family) == 3 && @payload.bytesize >= 16)
93
+ count, polygons = @payload.unpack("Q<2")
94
+ validate(@payload.bytesize == 16 + (polygons * 4) + (count * 8))
95
+ counts = @payload.byteslice(16, polygons * 4).unpack("V*")
96
+ multi = klass == Model::Wmf::Records::PolyPolygon
97
+ if multi
98
+ validate(@raw.bytesize == 8 + (polygons * 2) + (count * 4) && counts.sum == count)
99
+ else
100
+ minimum = klass == Model::Wmf::Records::Polygon ? 2 : 0
101
+ validate(polygons.zero? && count.between?(minimum, 0x7FFF) && @raw.bytesize == 8 + (count * 4))
102
+ end
103
+ coordinates = @payload.byteslice((16 + (polygons * 4))..).unpack("l<*")
104
+ points = coordinates.each_slice(2).map { |x, y| Model::Wmf::Records::PointS.new(x: x, y: y) }
105
+ fields = { points: points, type_id: @type, offset: @offset }
106
+ fields[:points_per_polygon] = counts if multi
107
+ klass.new(**fields)
108
+ end
109
+
110
+ def validate(condition)
111
+ return if condition
112
+
113
+ raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF typed record at offset #{@offset}")
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wmf_fields"
4
+ require_relative "wmf_snapshot"
5
+ require_relative "wmf_rejection"
6
+
7
+ module Emf
8
+ module Native
9
+ class WmfHydrator
10
+ # Native parsing is authoritative for accepted input. A rejection, from the core or the hydrator, is reported
11
+ # with the native first-rejection detail mapped to the contract's error; when the contract would accept the
12
+ # input the native rejection is raised as is.
13
+ def self.parse(loader, input)
14
+ loader.wmf_document(input) do |document|
15
+ new(WmfSnapshot.new(loader.wmf_snapshot(document)), input).call
16
+ end
17
+ rescue Emf::Error => e
18
+ found = WmfRejection.read(loader.wmf_rejection(input, Wmf::Binary::MAX_INPUT_BYTES, Wmf::Binary::MAX_RECORDS))
19
+ raise WmfRejection.error(found, input) if found
20
+
21
+ raise e
22
+ end
23
+
24
+ def initialize(snapshot, input)
25
+ @snapshot = snapshot
26
+ @input = input
27
+ end
28
+
29
+ def call
30
+ @errors = []
31
+ header = hydrate_header
32
+ records = hydrate_records
33
+ recovered = Wmf::Evidence.reference_recovery(@input, @errors)
34
+ Wmf::Binary.validate_objects(records, header.number_of_objects, recovered: recovered) do |record, message|
35
+ fail_record(record.offset, record.type_id, message)
36
+ end
37
+ Model::Metafile.new(format: :wmf, header: header, records: records,
38
+ errors: Wmf::Evidence.ordered(@errors + Wmf::Evidence.drift_errors(@input)),
39
+ trailing: @input.byteslice(@limit..).to_s)
40
+ end
41
+
42
+ private
43
+
44
+ def hydrate_header
45
+ type, size, version, words, objects, maximum, members, placeable, eof, reserved = @snapshot.header
46
+ validate_header(type, size, version, placeable, eof, reserved)
47
+ @start = placeable == 1 ? 22 : 0
48
+ # The record stream ends after the first EOF record, whatever its length or the declared Size (both are
49
+ # recovered); the core keeps reading past an EOF it does not accept as final, so the stream is cut here.
50
+ @records = framed_records
51
+
52
+ placeable_header = hydrate_placeable(type) if placeable == 1
53
+ raise_framing_error if @records.empty?
54
+ @limit = @records.last.fetch(:offset) + @records.last.fetch(:length)
55
+
56
+ Model::Wmf::Header.new(type: type, version: version, size_words: words, number_of_objects: objects,
57
+ max_record_words: maximum, number_of_members: members, placeable: placeable_header)
58
+ end
59
+
60
+ def validate_header(type, size, version, placeable, eof, reserved)
61
+ valid = [1, 2].include?(type) && size == 9 && [0x100, 0x300].include?(version) &&
62
+ [0, 1].include?(placeable) && [0, 1].include?(eof) && reserved.zero?
63
+ raise FormatError, "invalid native WMF header" unless valid
64
+ raise FormatError, "invalid native WMF source length" unless @snapshot.source_length == @input.bytesize
65
+ raise FormatError, "WMF input too large" if @input.bytesize > Wmf::Binary::MAX_INPUT_BYTES
66
+ end
67
+
68
+ # The snapshot's records up to and including the first EOF record; empty when the core found none.
69
+ def framed_records
70
+ index = @snapshot.records.index { |record| record.fetch(:type).zero? }
71
+ index ? @snapshot.records.first(index + 1) : []
72
+ end
73
+
74
+ # Core diagnostics 6-10 are record framing failures; Ruby raises ParseError for them.
75
+ FRAMING_DIAGNOSTICS = {
76
+ 6 => "invalid record length", 7 => "truncated record", 8 => "invalid EOF record",
77
+ 9 => "missing EOF record", 10 => "malformed record payload"
78
+ }.freeze
79
+ private_constant :FRAMING_DIAGNOSTICS
80
+
81
+ def raise_framing_error
82
+ kind, has_function, function, _detail, offset = @snapshot.diagnostics.find { |entry| FRAMING_DIAGNOSTICS.key?(entry.first) }
83
+ raise FormatError, "invalid native WMF header" unless kind
84
+
85
+ raise ParseError.new(offset: offset, record_code: has_function == 1 ? function : nil,
86
+ message: "WMF #{FRAMING_DIAGNOSTICS.fetch(kind)} at offset #{offset}")
87
+ end
88
+
89
+ def hydrate_placeable(type)
90
+ handle, left, top, right, bottom, inch, reserved, checksum = @snapshot.placeable
91
+ raise FormatError, "invalid native WMF placeable header" unless reserved.zero? && (type != 2 || handle.zero?)
92
+
93
+ @errors << Wmf::Evidence.recovered_error(203, @input, nil) unless checksum == Wmf::Binary.checksum(@input.byteslice(0, 20))
94
+
95
+ Model::Wmf::PlaceableHeader.new(handle: handle, left: left, top: top, right: right, bottom: bottom,
96
+ inch: inch, checksum: checksum)
97
+ end
98
+
99
+ def hydrate_records
100
+ if @records.length > Wmf::Binary::MAX_RECORDS
101
+ raise ResourceLimitError.new(resource: :record_count, limit: Wmf::Binary::MAX_RECORDS,
102
+ actual: Wmf::Binary::MAX_RECORDS + 1)
103
+ end
104
+
105
+ position = @start + 18
106
+ records = @records.each_with_index.map do |record, index|
107
+ length = record.fetch(:length)
108
+ offset = record.fetch(:offset)
109
+ validate_span(record, index, position)
110
+ position += length
111
+ hydrate_record(record, @input.byteslice(offset, length)).declare_size(length / 2)
112
+ end
113
+ unless position == @limit && records.last.is_a?(Model::Wmf::Records::Eof) && records.one?(Model::Wmf::Records::Eof)
114
+ raise FormatError, "WMF requires one final EOF record"
115
+ end
116
+
117
+ records
118
+ end
119
+
120
+ # A recoverable shape is decoded by the Ruby contract itself (Wmf::Recovery) on the record's own bytes, so both
121
+ # backends build the same record and error; every other record comes from the native typed fields.
122
+ def hydrate_record(record, raw)
123
+ recovered, reason = Wmf::Recovery.record(raw, record.fetch(:type), record.fetch(:offset))
124
+ return WmfFields.new(record, raw).call unless reason
125
+
126
+ @errors << Wmf::Evidence.recovered_error(reason, @input, record.fetch(:offset), record.fetch(:type))
127
+ recovered
128
+ end
129
+
130
+ def validate_span(record, index, position)
131
+ length = record.fetch(:length)
132
+ offset = record.fetch(:offset)
133
+ return if record.fetch(:index) == index && offset == position && length >= 6 && length.even? && length <= @limit - offset
134
+
135
+ raise FormatError, "invalid native WMF record span"
136
+ end
137
+
138
+ def fail_record(offset, type, message)
139
+ raise ParseError.new(offset: offset, record_code: type, message: "WMF #{message} at offset #{offset}")
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # The WMF entry points of the native library, loaded on first use. Mixed into Loader, which owns the
6
+ # buffer, status and pointer helpers these call.
7
+ module WmfLoading
8
+ def wmf_document(bytes)
9
+ load_wmf_functions
10
+ output = slot
11
+ checked(:emf_wmf_parse, bytes, bytes.bytesize, 0, output)
12
+ yield pointer(output)
13
+ ensure
14
+ invoke(:emf_wmf_document_free, pointer(output)) if output && pointer(output) != 0
15
+ end
16
+
17
+ def wmf_snapshot(document)
18
+ buffer(:emf_wmf_document_snapshot, document)
19
+ end
20
+
21
+ # The first phase of the Ruby WMF contract that rejects `bytes`, under the caller's limits (WMFR v1).
22
+ def wmf_rejection(bytes, max_input_bytes, max_records)
23
+ load_wmf_functions
24
+ buffer(:emf_wmf_rejection, bytes, bytes.bytesize, max_input_bytes, max_records)
25
+ end
26
+
27
+ private
28
+
29
+ def load_wmf_functions
30
+ return if @functions.key?(:emf_wmf_parse)
31
+
32
+ pointer = Loader::POINTER
33
+ uint64 = Loader::UINT64
34
+ signatures = {
35
+ emf_wmf_parse: [[pointer, uint64, pointer, pointer, pointer], Fiddle::TYPE_INT],
36
+ emf_wmf_document_snapshot: [[pointer, pointer, pointer], Fiddle::TYPE_INT],
37
+ emf_wmf_rejection: [[pointer, uint64, uint64, uint64, pointer, pointer], Fiddle::TYPE_INT],
38
+ emf_wmf_document_free: [[pointer], Fiddle::TYPE_VOID]
39
+ }
40
+ functions = signatures.to_h do |name, (arguments, result)|
41
+ [name, Fiddle::Function.new(@library[name.to_s], arguments, result)]
42
+ end
43
+ @functions.merge!(functions)
44
+ rescue Fiddle::DLError => e
45
+ raise Unavailable, "native WMF backend could not load: #{e.message}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class WmfPaletteFields
6
+ def initialize(record, raw)
7
+ @record = record
8
+ @raw = raw
9
+ @type = record.fetch(:type)
10
+ @offset = record.fetch(:offset)
11
+ @payload = record.fetch(:payload)
12
+ end
13
+
14
+ def call
15
+ klass = Wmf::PaletteCodec::RECORDS.fetch(@type & 0xFF)
16
+ validate(@record.fetch(:family) == 8 && @raw.bytesize.even?)
17
+ fields, prefix = if klass <= Model::Wmf::Records::PaletteRecord
18
+ entries
19
+ elsif klass == Model::Wmf::Records::RealizePalette
20
+ validate(@payload.empty?)
21
+ [{}, 6]
22
+ else
23
+ scalar(klass)
24
+ end
25
+ validate(@raw.bytesize >= prefix)
26
+ klass.new(**fields, type_id: @type, offset: @offset, suffix: @raw.byteslice(prefix..))
27
+ end
28
+
29
+ private
30
+
31
+ def entries
32
+ validate(@payload.bytesize >= 8)
33
+ start, count = @payload.unpack("V2")
34
+ validate(start <= 0xFFFF && count <= 0xFFFF && @payload.bytesize == 8 + (count * 16))
35
+ prefix = 10 + (count * 4)
36
+ validate(@raw.bytesize >= prefix)
37
+ values = @payload.byteslice(8..).unpack("V*")
38
+ validate(values.all? { |value| value <= 0xFF })
39
+ palette = values.each_slice(4).map do |entry|
40
+ Model::Wmf::Records::PaletteEntry.new(**Wmf::PaletteCodec::ENTRY_FIELDS.zip(entry).to_h)
41
+ end
42
+ [{ start: start, entries: palette }, prefix]
43
+ end
44
+
45
+ def scalar(klass)
46
+ validate(@payload.bytesize == 4)
47
+ value = @payload.unpack1("V")
48
+ validate(value <= 0xFFFF)
49
+ field = klass == Model::Wmf::Records::SelectPalette ? :object_index : :entry_count
50
+ [{ field => value }, 8]
51
+ end
52
+
53
+ def validate(condition)
54
+ return if condition
55
+
56
+ raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF palette record at offset #{@offset}")
57
+ end
58
+ end
59
+ end
60
+ end