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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class WmfRegionFields
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) == 9 && @raw.bytesize >= 6 && @raw.bytesize.even?)
16
+ klass = Wmf::RegionCodec::RECORDS.fetch(@type & 0xFF)
17
+ fields = klass == Model::Wmf::Records::CreateRegion ? region : scalar(klass)
18
+ record = klass.new(**fields, type_id: @type, offset: @offset)
19
+ prefix = Wmf::RegionCodec.encode_payload(record)
20
+ validate(@raw.bytesize >= prefix.bytesize + 6 && @raw.byteslice(6, prefix.bytesize) == prefix)
21
+ record.suffix = @raw.byteslice((6 + prefix.bytesize)..)
22
+ record
23
+ rescue SerializeError
24
+ validate(false)
25
+ end
26
+
27
+ private
28
+
29
+ def scalar(klass)
30
+ validate(@payload.bytesize == klass::WIRE_FIELDS.length * 4)
31
+ values = @payload.unpack("V*").zip(klass::WIRE_TYPES).map do |value, type|
32
+ if type == :int16
33
+ value -= 0x100000000 if value >= 0x80000000
34
+ validate(value.between?(-0x8000, 0x7FFF))
35
+ else
36
+ validate(value <= 0xFFFF)
37
+ end
38
+ value
39
+ end
40
+ klass::WIRE_FIELDS.zip(values).to_h
41
+ end
42
+
43
+ def region
44
+ validate(@payload.bytesize >= 28)
45
+ next_in_chain, object_count = @payload.unpack("V2")
46
+ bounds = @payload.byteslice(8, 16).unpack("l<4")
47
+ count = @payload.byteslice(24, 4).unpack1("V")
48
+ validate(next_in_chain <= 0xFFFF && bounds.all? { |value| value.between?(-0x8000, 0x7FFF) })
49
+ validate(count <= 0x7FFF && count <= (@payload.bytesize - 28) / 12)
50
+ preflight_scans(count)
51
+ { next_in_chain: next_in_chain, object_count: object_count,
52
+ bounds: Model::Wmf::Records::RegionBounds.new(**Wmf::RegionCodec::BOUNDS_FIELDS.zip(bounds).to_h),
53
+ scans: scans(count) }
54
+ end
55
+
56
+ def preflight_scans(count)
57
+ cursor = 28
58
+ source_size = 22
59
+ count.times do
60
+ validate(@payload.bytesize - cursor >= 12)
61
+ top, bottom, lines = @payload.byteslice(cursor, 12).unpack("V3")
62
+ validate(top <= 0xFFFF && bottom <= 0xFFFF && lines <= 0x3FFF && lines <= (@payload.bytesize - cursor - 12) / 8)
63
+ source_size += 8 + (lines * 4)
64
+ validate(source_size <= 0x7FFF)
65
+ (lines * 2).times do |index|
66
+ validate(@payload.byteslice(cursor + 12 + (index * 4), 4).unpack1("V") <= 0xFFFF)
67
+ end
68
+ cursor += 12 + (lines * 8)
69
+ end
70
+ validate(cursor == @payload.bytesize && @raw.bytesize >= source_size + 6)
71
+ end
72
+
73
+ def scans(count)
74
+ cursor = 28
75
+ Array.new(count) do
76
+ top, bottom, count_lines = @payload.byteslice(cursor, 12).unpack("V3")
77
+ lines = @payload.byteslice(cursor + 12, count_lines * 8).unpack("V*").each_slice(2).map do |left, right|
78
+ Model::Wmf::Records::RegionScanLine.new(left: left, right: right)
79
+ end
80
+ cursor += 12 + (count_lines * 8)
81
+ Model::Wmf::Records::RegionScan.new(top: top, bottom: bottom, lines: lines)
82
+ end
83
+ end
84
+
85
+ def validate(condition)
86
+ return if condition
87
+
88
+ raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF region record at offset #{@offset}")
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Maps the native WMFR answer (codec, reason, offset, function, detail) to the exact error the Ruby contract
6
+ # raises: its class, message, offset and record code. Rust names the first failing phase; this table owns
7
+ # only the wording. Reason codes are codec * 100 + n and are never renumbered.
8
+ module WmfRejection
9
+ Found = Struct.new(:codec, :reason, :offset, :function, :detail)
10
+
11
+ # Whole-input failures: FormatError with the message as is. 203 (placeable checksum), 305 (Size), 406 (EOF
12
+ # length), 1003 (negative text length), 1702 (no live object) and 1801 (MaxRecord) are recovered, not
13
+ # rejected, and 404 is retired with them. 701, 801 and 1704 are rejected only for the shapes Wmf::Recovery
14
+ # cannot keep.
15
+ INPUT = {
16
+ 201 => "truncated WMF placeable header",
17
+ 202 => "WMF placeable reserved field must be zero",
18
+ 301 => "truncated WMF standard header",
19
+ 302 => "invalid WMF Type",
20
+ 303 => "invalid WMF HeaderSize",
21
+ 304 => "invalid WMF Version",
22
+ 306 => "WMF on-disk placeable handle must be zero"
23
+ }.freeze
24
+
25
+ # Record failures: ParseError "WMF <text> at offset <offset>". `%d` takes the numeric detail.
26
+ RECORD = {
27
+ 402 => "truncated record header", 403 => "invalid record length %d",
28
+ 405 => "missing EOF record",
29
+ 501 => "invalid fixed record length", 601 => "invalid state record length", 701 => "invalid font record length",
30
+ 801 => "invalid object record length",
31
+ 901 => "missing point array count", 902 => "truncated polygon counts", 903 => "invalid point count",
32
+ 904 => "point array length mismatch",
33
+ 1001 => "truncated text record", 1002 => "truncated text rectangle",
34
+ 1004 => "truncated text string", 1005 => "truncated text spacing array",
35
+ 1101 => "truncated palette prefix", 1102 => "truncated palette entries",
36
+ 1201 => "truncated region/clipping prefix", 1202 => "truncated region header", 1203 => "invalid region header",
37
+ 1204 => "truncated region scan", 1205 => "invalid region scan", 1206 => "invalid region scan size or maximum",
38
+ 1301 => "truncated pattern brush prefix", 1302 => "invalid pattern brush extent",
39
+ 1303 => "truncated DIB brush prefix", 1304 => "invalid DIB brush color usage",
40
+ 1401 => "truncated bitmap transfer prefix", 1402 => "empty bitmap transfer source",
41
+ 1501 => "truncated geometry prefix", 1502 => "invalid extended flood fill mode",
42
+ 1601 => "truncated RestoreDc prefix", 1602 => "truncated Escape prefix", 1603 => "truncated Escape data or padding",
43
+ 1701 => "object table capacity exceeded",
44
+ 1703 => "object reference %d is not a palette", 1704 => "object reference %d is not a region",
45
+ 1705 => "invalid region reference %d", 1706 => "invalid brush reference %d",
46
+ 1707 => "object reference %d is not a brush"
47
+ }.freeze
48
+
49
+ LAYOUT = "a4V7Q<2"
50
+ LENGTH = 48
51
+
52
+ module_function
53
+
54
+ # Returns nil when the contract accepts the input.
55
+ def read(bytes)
56
+ magic, version, present, codec, reason, has_offset, has_function, function, offset, detail =
57
+ bytes.unpack(LAYOUT)
58
+ raise FormatError, "invalid native WMF rejection" unless bytes.bytesize == LENGTH && magic == "WMFR" && version == 1
59
+
60
+ return if present.zero?
61
+
62
+ Found.new(codec, reason, has_offset == 1 ? offset : nil, has_function == 1 ? function : nil, detail)
63
+ end
64
+
65
+ def error(found, input)
66
+ evidence = Wmf::Evidence.build(found.reason, input, found.offset, found.detail)
67
+ if found.reason == 101
68
+ return ResourceLimitError.new(resource: :input_bytes, limit: Wmf::Binary::MAX_INPUT_BYTES,
69
+ actual: input.bytesize).with_evidence(evidence)
70
+ end
71
+ if found.reason == 401
72
+ return ResourceLimitError.new(resource: :record_count, limit: Wmf::Binary::MAX_RECORDS,
73
+ actual: Wmf::Binary::MAX_RECORDS + 1).with_evidence(evidence)
74
+ end
75
+ return FormatError.new(INPUT.fetch(found.reason)).with_evidence(evidence) if INPUT.key?(found.reason)
76
+
77
+ template = RECORD.fetch(found.reason) { raise FormatError, "unknown native WMF rejection #{found.reason}" }
78
+ text = template.sub("%d", found.detail.to_s)
79
+ ParseError.new(offset: found.offset, record_code: found.function, message: "WMF #{text} at offset #{found.offset}")
80
+ .with_evidence(evidence)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class WmfRemainingFields
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(@raw.bytesize >= 6 && @raw.bytesize.even? && @payload.bytesize <= Wmf::Binary::MAX_INPUT_BYTES)
16
+ validate(@raw.unpack("Vv") == [@raw.bytesize / 2, @type])
17
+ geometry = Wmf::GeometryCodec::RECORDS.key?(@type & 0xFF)
18
+ codec = geometry ? Wmf::GeometryCodec : Wmf::DcEscapeCodec
19
+ validate(@record.fetch(:family) == (geometry ? 12 : 13))
20
+ klass = codec::RECORDS.fetch(@type & 0xFF)
21
+ fields = if geometry
22
+ scalar_fields(klass)
23
+ elsif @type & 0xFF == 0x26
24
+ escape_fields
25
+ else
26
+ dc_fields
27
+ end
28
+ model = klass.new(**fields, type_id: @type, offset: @offset)
29
+ validate(codec.encode(model) == @raw)
30
+ model
31
+ rescue SerializeError
32
+ validate(false)
33
+ end
34
+
35
+ private
36
+
37
+ def scalar_fields(klass)
38
+ validate(@payload.bytesize == klass::WIRE_FIELDS.length * 4 && @raw.bytesize >= 6 + klass::PAYLOAD_BYTES)
39
+ values = klass::WIRE_TYPES.each_with_index.map do |type, index|
40
+ @payload.byteslice(index * 4, 4).unpack1(type == :int16 ? "l<" : "V")
41
+ end
42
+ klass::WIRE_FIELDS.zip(values).to_h.merge(suffix: @raw.byteslice((6 + klass::PAYLOAD_BYTES)..))
43
+ end
44
+
45
+ def dc_fields
46
+ restore = @type & 0xFF == 0x27
47
+ validate(@payload.bytesize == (restore ? 4 : 0) && @raw.bytesize >= (restore ? 8 : 6))
48
+ fields = { suffix: @raw.byteslice((restore ? 8 : 6)..) }
49
+ fields[:saved_dc] = @payload.unpack1("l<") if restore
50
+ fields
51
+ end
52
+
53
+ def escape_fields
54
+ validate(@payload.bytesize >= 12)
55
+ function, length = @payload.unpack("VQ<")
56
+ validate(length <= 0xFFFF && @payload.bytesize == 12 + length)
57
+ prefix = 10 + length + (length % 2)
58
+ validate(@raw.bytesize >= prefix)
59
+ { escape_function: function, data: @payload.byteslice(12, length),
60
+ padding: length.odd? ? @raw.getbyte(10 + length) : nil, suffix: @raw.byteslice(prefix..) }
61
+ end
62
+
63
+ def validate(condition)
64
+ return if condition
65
+
66
+ raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF typed record at offset #{@offset}")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class WmfSnapshot
6
+ attr_reader :header, :placeable, :records, :diagnostics, :trailing, :source_length
7
+
8
+ def initialize(bytes)
9
+ @bytes = bytes
10
+ @position = 0
11
+ raise FormatError, "invalid native WMF snapshot schema" unless take(4) == "WMFS" && u32 == 1
12
+ raise FormatError, "invalid native WMF snapshot length" unless u64 == bytes.bytesize
13
+
14
+ record_count, diagnostic_count, trailing_length, @source_length = Array.new(4) { u64 }
15
+ if record_count > bytes.bytesize / 40 || diagnostic_count > bytes.bytesize / 40
16
+ raise FormatError, "invalid native WMF snapshot counts"
17
+ end
18
+
19
+ @header = Array.new(10) { u32 }
20
+ @placeable = take(32).unpack("Vl<4V3")
21
+ @records = Array.new(record_count) { read_record }
22
+ @diagnostics = Array.new(diagnostic_count) { take(40).unpack("V4Q<3") }
23
+ @trailing = take(trailing_length)
24
+ raise FormatError, "unconsumed native WMF snapshot bytes" unless @position == bytes.bytesize
25
+ end
26
+
27
+ def take(length)
28
+ raise FormatError, "truncated native WMF snapshot" if length.negative? || length > @bytes.bytesize - @position
29
+
30
+ result = @bytes.byteslice(@position, length)
31
+ @position += length
32
+ result
33
+ end
34
+
35
+ def u32
36
+ take(4).unpack1("V")
37
+ end
38
+
39
+ def u64
40
+ take(8).unpack1("Q<")
41
+ end
42
+
43
+ private
44
+
45
+ def read_record
46
+ index, offset, length, type, family, payload_length = take(40).unpack("Q<3V2Q<")
47
+ { index: index, offset: offset, length: length, type: type, family: family, payload: take(payload_length) }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class WmfTextFields
6
+ def initialize(record, raw)
7
+ @record = record
8
+ @raw = raw
9
+ end
10
+
11
+ def call
12
+ payload = @record.fetch(:payload)
13
+ validate(@record.fetch(:family) == 5 && payload.bytesize >= 72)
14
+ x, y, options, has_rect, count, padding_length, dx_length, suffix_length, has_dx, reserved = payload.unpack("l<2V2Q<4V2")
15
+ rectangle = payload.byteslice(56, 16).unpack("l<4")
16
+ extended = @record.fetch(:type) & 0xFF == 0x32
17
+ validate_lengths(payload, count, padding_length, dx_length, suffix_length, has_rect, has_dx, reserved)
18
+ fields = {
19
+ x: x, y: y, text: payload.byteslice(72, count), padding: payload.byteslice(72 + count, padding_length),
20
+ type_id: @record.fetch(:type), offset: @record.fetch(:offset)
21
+ }
22
+ return text_out(fields, options, has_rect, has_dx, suffix_length) unless extended
23
+
24
+ validate(has_rect == (options.anybits?(6) ? 1 : 0) && padding_length <= 1)
25
+ expected = 14 + (has_rect * 8) + count + padding_length + (dx_length * 2) + suffix_length
26
+ validate(@raw.bytesize == expected)
27
+ fields[:rectangle] = Model::Wmf::Records::TextRectangle.new(**%i[left top right bottom].zip(rectangle).to_h) if has_rect == 1
28
+ fields[:options] = options
29
+ fields[:dx] = payload.byteslice(72 + count + padding_length, dx_length * 4).unpack("l<*") if has_dx == 1
30
+ fields[:suffix] = payload.byteslice(72 + count + padding_length + (dx_length * 4), suffix_length)
31
+ Model::Wmf::Records::ExtTextOut.new(**fields)
32
+ end
33
+
34
+ private
35
+
36
+ def validate_lengths(payload, count, padding_length, dx_length, suffix_length, rectangle, spacing, reserved)
37
+ validate(reserved.zero? && count <= 0x7FFF && [0, 1].include?(rectangle) && [0, 1].include?(spacing))
38
+ validate(payload.bytesize == 72 + count + padding_length + (dx_length * 4) + suffix_length)
39
+ validate(dx_length == (spacing == 1 ? count : 0))
40
+ validate(padding_length % 2 == count % 2)
41
+ end
42
+
43
+ def text_out(fields, options, rectangle, spacing, suffix_length)
44
+ validate(options.zero? && rectangle.zero? && spacing.zero? && suffix_length.zero?)
45
+ validate(@raw.bytesize >= 12 && @raw.bytesize == 12 + fields.fetch(:text).bytesize + fields.fetch(:padding).bytesize)
46
+ Model::Wmf::Records::TextOut.new(**fields)
47
+ end
48
+
49
+ def validate(condition)
50
+ return if condition
51
+
52
+ raise ParseError.new(offset: @record.fetch(:offset), record_code: @record.fetch(:type), message: "invalid native WMF text record")
53
+ end
54
+ end
55
+ end
56
+ end
data/lib/emf/native.rb ADDED
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class Unavailable < Error; end
6
+
7
+ def self.backend
8
+ require_relative "native/library_path"
9
+ path = LibraryPath.new.call
10
+ raise Unavailable, "native backend is not installed (set EMF_NATIVE_LIBRARY)" if path.nil? || path.empty?
11
+
12
+ require_relative "native/loader"
13
+ require_relative "native/snapshot"
14
+ require_relative "native/header"
15
+ require_relative "native/hydrator"
16
+ require_relative "native/header_errors"
17
+ require_relative "native/diagnostic_contract"
18
+ Adapter.new(Loader.new(path))
19
+ rescue LoadError => e
20
+ raise Unavailable, "native backend dependencies could not load: #{e.message}"
21
+ end
22
+
23
+ class Adapter
24
+ def initialize(loader)
25
+ @loader = loader
26
+ end
27
+
28
+ def parse(bytes)
29
+ input = bytes.b
30
+ ResourceLimits.preflight(input)
31
+ return parse_wmf(input) if %i[wmf_apm wmf_standard].include?(Detector.call(input))
32
+
33
+ parse_emf(input)
34
+ rescue FormatError, ParseError => e
35
+ raise ParseEvidenceBuilder.attach(e, input)
36
+ end
37
+
38
+ def parse_emf(input)
39
+ @loader.document(input) do |document|
40
+ snapshot = Snapshot.new(@loader.snapshot(document))
41
+ DiagnosticContract.new(snapshot, input).call(Hydrator.new(snapshot, input).call)
42
+ end
43
+ rescue FormatError => e
44
+ raise HeaderErrors.translate(e, input)
45
+ end
46
+
47
+ def parse_wmf(input)
48
+ require_relative "native/wmf_hydrator"
49
+ WmfHydrator.parse(@loader, input)
50
+ end
51
+
52
+ def serialize(metafile)
53
+ case metafile.format
54
+ when :emf, :emf_plus then Emr::Serializer.call(metafile)
55
+ when :wmf then Wmf::Serializer.call(metafile)
56
+ else raise SerializeError, "native backend cannot serialize #{metafile.format.inspect}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ # Backend-neutral account of the check that rejected an input before a metafile model existed.
5
+ #
6
+ # check - Symbol naming the failed check (stable vocabulary; see Detector, Emr and Wmf::Evidence)
7
+ # offset - byte offset in the input where the failing field or record starts
8
+ # declared - what the input states (a size, count, checksum, type), nil when it states nothing comparable
9
+ # actual - what was found or required instead (bytes available, computed checksum, minimum size), nil when
10
+ # the check is a domain test with no single comparable value
11
+ #
12
+ # Units are bytes unless the check name says words. Reachable as `error.evidence` on Emf::FormatError and
13
+ # Emf::ParseError raised by either backend; nil when a rejection has no neutral description.
14
+ ParseEvidence = Struct.new(:check, :offset, :declared, :actual, keyword_init: true)
15
+
16
+ module ParseEvidenceBuilder
17
+ EMF_MAGIC_OFFSET = Detector::EMF_MAGIC_OFFSET
18
+
19
+ module_function
20
+
21
+ # Returns `error` with evidence attached when it can be derived from its message and the input.
22
+ def attach(error, bytes)
23
+ return error if error.evidence
24
+
25
+ error.with_evidence(derive(error, bytes))
26
+ end
27
+
28
+ def derive(error, bytes)
29
+ from_detector(error.message, bytes) || from_emf_header(error.message, bytes) || Wmf::Evidence.from_error(error, bytes)
30
+ end
31
+
32
+ def from_detector(message, bytes)
33
+ case message
34
+ when /\Ainput too short/
35
+ ParseEvidence.new(check: :input_too_short, offset: 0, declared: 4, actual: bytes.bytesize)
36
+ when /\Aunrecognised format \(signature=1/
37
+ ParseEvidence.new(check: :emf_magic_missing, offset: EMF_MAGIC_OFFSET, declared: Detector::EMF_MAGIC,
38
+ actual: bytes.bytesize >= EMF_MAGIC_OFFSET + 4 ? bytes.unpack1("V", offset: EMF_MAGIC_OFFSET) : nil)
39
+ when /\Aunrecognised format/
40
+ ParseEvidence.new(check: :unrecognised_signature, offset: 0, declared: bytes.unpack1("V"), actual: nil)
41
+ end
42
+ end
43
+
44
+ def from_emf_header(message, bytes)
45
+ case message
46
+ when /\Ainput too large/
47
+ ParseEvidence.new(check: :emf_input_too_large, offset: 0, declared: Emr::Parser::MAX_INPUT_BYTES, actual: bytes.bytesize)
48
+ when /\AEMF header nSize too small/
49
+ ParseEvidence.new(check: :emf_header_size_too_small, offset: 4, declared: bytes.unpack1("V", offset: 4), actual: 8)
50
+ when /\Atruncated EMF header \(claimed/
51
+ ParseEvidence.new(check: :emf_header_truncated, offset: 0, declared: bytes.unpack1("V", offset: 4), actual: bytes.bytesize)
52
+ when /\AEMF header nSize \d+ cannot hold its fields/
53
+ ParseEvidence.new(check: :emf_header_cannot_hold_fields, offset: 4, declared: bytes.unpack1("V", offset: 4), actual: nil)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ class ResourceBudget
5
+ # Mirror bitmap.rs range validation before charging each copied buffer.
6
+ module Bitmap
7
+ SHAPES = {
8
+ 76 => [100, 84, nil], 77 => [108, 84, nil],
9
+ 78 => [128, 84, 112], 79 => [140, 96, 124],
10
+ 80 => [76, 48, nil], 81 => [80, 48, nil],
11
+ 93 => [32, 16, nil], 94 => [32, 16, nil],
12
+ 114 => [108, 84, nil], 116 => [108, 84, nil]
13
+ }.freeze
14
+ KINDS = SHAPES.keys.freeze
15
+
16
+ module_function
17
+
18
+ def fixed_payload_len(kind)
19
+ SHAPES.fetch(kind).first - 8
20
+ end
21
+
22
+ def decode(kind, payload, budget) # rubocop:disable Naming/PredicateMethod
23
+ fixed, source, mask = SHAPES.fetch(kind)
24
+ fixed -= 8
25
+ return false if payload.length < fixed
26
+
27
+ spans = buffer_spans(payload, fixed, source, mask)
28
+ return false unless spans
29
+
30
+ cursor = fixed
31
+ spans.each do |start, stop|
32
+ return false if start < cursor
33
+
34
+ cursor = stop
35
+ end
36
+ cursor = fixed
37
+ spans.each do |start, stop|
38
+ [start - cursor, stop - start].each do |length|
39
+ budget.charge_alloc(length)
40
+ budget.charge_work((length + 15) / 16)
41
+ end
42
+ cursor = stop
43
+ end
44
+ true
45
+ end
46
+
47
+ def buffer_spans(payload, fixed, source, mask)
48
+ mask_bits = mask + 8 if mask
49
+ spans = [source, source + 8, mask, mask_bits].filter_map do |at|
50
+ next unless at
51
+
52
+ offset = payload.u32(at - 8)
53
+ length = payload.u32(at - 4)
54
+ next if offset.zero? && length.zero?
55
+
56
+ start = offset - 8
57
+ stop = start + length
58
+ return nil if start < fixed || stop > payload.length
59
+
60
+ [start, stop]
61
+ end
62
+ spans.sort_by! { |start, stop| [start, stop - start] }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ class ResourceBudget
5
+ module Gradient
6
+ module_function
7
+
8
+ def decode(_kind, payload, budget) # rubocop:disable Naming/PredicateMethod
9
+ return false if payload.length < 28
10
+
11
+ vertices = payload.u32(16)
12
+ meshes = payload.u32(20)
13
+ mode = payload.u32(24)
14
+ element = mode == 2 ? 12 : 8
15
+ indexed = 28 + (vertices * 16) + (meshes * element)
16
+ return false if mode > 2 || indexed > payload.length
17
+
18
+ # Native reserves work even when an invalid index later recovers as Raw.
19
+ budget.charge_work(vertices + meshes)
20
+ index_start = 28 + (vertices * 16)
21
+ (meshes * element / 4).times do |index|
22
+ return false if payload.u32(index_start + (index * 4)) >= vertices
23
+ end
24
+ # Native charges twelve bytes per rectangle even when padding is absent.
25
+ budget.charge_alloc((vertices * budget.layout(:tri_vertex)) + (meshes * 12))
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ class ResourceBudget
5
+ module PolyText
6
+ module_function
7
+
8
+ def decode(kind, payload, budget) # rubocop:disable Naming/PredicateMethod
9
+ return false if payload.length < 32
10
+
11
+ count = payload.u32(28)
12
+ return false if count > (payload.length - 32) / 24
13
+
14
+ budget.charge_work(count)
15
+ fixed = table_end(payload, count)
16
+ return false unless fixed
17
+
18
+ width = kind == 96 ? 1 : 2
19
+ budget.vector(count, budget.layout(width == 1 ? :poly_text_entry_a : :poly_text_entry_w))
20
+ budget.vector(count * 2, budget.layout(:poly_text_span))
21
+ spans = []
22
+ cursor = 32
23
+ count.times do |index|
24
+ entry = entry_spans(payload, budget, cursor, fixed, width, index)
25
+ return false unless entry
26
+
27
+ cursor, ranges = entry
28
+ spans.concat(ranges)
29
+ end
30
+ budget.charge_work(spans.length * spans.length.bit_length)
31
+ cursor = fixed
32
+ spans.sort!.each do |start, stop, _index, _is_dx|
33
+ return false if start < cursor
34
+
35
+ unused = start - cursor
36
+ budget.charge_work((unused + 15) / 16)
37
+ budget.vector(unused, 1)
38
+ cursor = stop
39
+ end
40
+ true
41
+ end
42
+
43
+ def entry_spans(payload, budget, cursor, fixed, width, index)
44
+ chars = payload.u32(cursor + 8)
45
+ string = payload.u32(cursor + 12)
46
+ options = payload.u32(cursor + 16)
47
+ descriptor_size = options.anybits?(0x100) ? 24 : 40
48
+ dx = payload.u32(cursor + descriptor_size - 4)
49
+ dx_count = chars * (options.anybits?(0x2000) ? 2 : 1)
50
+ spans = []
51
+ unless string.zero? && chars.zero?
52
+ range = budget.span(payload.length, string, chars * width, width, fixed)
53
+ return unless range
54
+
55
+ spans << [*range, index, 0]
56
+ end
57
+ unless dx.zero?
58
+ range = budget.span(payload.length, dx, dx_count * 4, 4, fixed)
59
+ return unless range
60
+
61
+ spans << [*range, index, 1]
62
+ end
63
+ budget.charge_work(chars)
64
+ budget.vector(chars, width)
65
+ unless dx.zero?
66
+ budget.charge_work(dx_count)
67
+ budget.vector(dx_count, 4)
68
+ end
69
+ [cursor + descriptor_size, spans]
70
+ end
71
+
72
+ def table_end(payload, count)
73
+ cursor = 32
74
+ count.times do
75
+ return if payload.length - cursor < 24
76
+
77
+ options = payload.u32(cursor + 16)
78
+ cursor += options.anybits?(0x100) ? 24 : 40
79
+ return if cursor > payload.length
80
+ end
81
+ cursor
82
+ end
83
+ end
84
+ end
85
+ end