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,309 @@
1
+ # frozen_string_literal: true
2
+
3
+ # COMMENT dispatch, Escape, OpenGL and the opaque record types' outer size and count
4
+ # bounds (MS-EMF 2.3.3, 2.3.6, 2.3.9 and the record sections cited per rule). Payload
5
+ # semantics stay out: only offsets, sizes and counts are read from the record bytes.
6
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
7
+
8
+ module Emf
9
+ module Conformance
10
+ module CommentEscapeRules
11
+ EMF = %i[emf emf_plus].freeze
12
+ COMMENT = 70
13
+ PUBLIC = 0x43494447
14
+ SPOOL = 0
15
+ FORMAT_SIGNATURES = [0x464D4520, 0x46535045].freeze
16
+ EPS_SIGNATURE = 0x46535045
17
+ COLOR_SPACE_TYPES = [0, 0x73524742, 0x57696E20].freeze
18
+ GAMUT_INTENTS = [1, 2, 4, 8].freeze
19
+ PUBLIC_IDS = { 2 => :begin_group, 3 => :end_group, 0x40000004 => :multiformats, 0x80000001 => :windows_metafile }.freeze
20
+
21
+ # type => [name, minimum size, exact size or nil, [[count field offsets...], per-count bytes (0 = bytes), variable start]]
22
+ # One diagnostic per defect (W37): types whose size or counts another rule states more precisely are not listed here.
23
+ # EMR_SETCOLORADJUSTMENT emf.state.fixed_size (decoded), emf.record.fixed_size_raw (Raw)
24
+ # EMR_SETCOLORSPACE, _COLORCORRECTPALETTE emf.object.fixed_size
25
+ # EMR_POLYTEXTOUTA / W emf.opaque.text_arrays (fixed part and descriptor bounds)
26
+ OPAQUE = {
27
+ 99 => ["EMR_CREATECOLORSPACE", 340, false, nil],
28
+ 101 => ["EMR_DELETECOLORSPACE", 12, false, nil],
29
+ 104 => ["EMR_PIXELFORMAT", 48, false, nil],
30
+ 109 => ["EMR_FORCEUFIMAPPING", 16, false, nil],
31
+ 112 => ["EMR_SETICMPROFILEA", 20, false, [[12, 16], 1]],
32
+ 113 => ["EMR_SETICMPROFILEW", 20, false, [[12, 16], 1]],
33
+ 119 => ["EMR_SETLINKEDUFIS", 20, false, [[8], 8]],
34
+ 121 => ["EMR_COLORMATCHTOTARGETW", 24, false, [[16, 20], 1]],
35
+ 122 => ["EMR_CREATECOLORSPACEW", 608, false, [[604], 1]]
36
+ }.freeze
37
+ # Types whose minimum size and counts a decoded record gets from the more specific emf.state.profile_bounds and
38
+ # emf.state.linked_ufis_bounds (the same constraint, same threshold). A Raw record is only seen here.
39
+ STATE_BOUNDS = [112, 113, 119, 121].freeze
40
+ ESCAPES = { 105 => ["EMR_DRAWESCAPE", 16], 106 => ["EMR_EXTESCAPE", 16], 110 => ["EMR_NAMEDESCAPE", 20] }.freeze
41
+ OPENGL = { 102 => ["EMR_GLSRECORD", 12], 103 => ["EMR_GLSBOUNDEDRECORD", 28] }.freeze
42
+
43
+ PARSER_RECOVERS = "the parser keeps a malformed record's bytes and skips it by Size, so the stream stays readable"
44
+ # id => [citation, kind, severity, tolerant_reason]
45
+ RULES = {
46
+ "emf.comment.padding" => ["MS-EMF 2.3.3", :comment_padding, :error, nil],
47
+ "emf.comment.header_size" => ["MS-EMF 2.3.3.4", :comment_header_size, :error, nil],
48
+ "emf.comment.public_payload" => ["MS-EMF 2.3.3.4", :comment_public_payload, :error, nil],
49
+ "emf.comment.public_identifier" => ["MS-EMF 2.3.3.4", :comment_public_identifier, :warning, nil],
50
+ "emf.comment.windows_metafile_reserved" => ["MS-EMF 2.3.3.4.4", :comment_wmf_reserved, :warning, nil],
51
+ "emf.comment.group_unclosed" => ["MS-EMF 2.3.3.4.1", :comment_group_unclosed, :error,
52
+ "producers leave groups open at the end of the file and readers only skip the group markers"],
53
+ "emf.comment.group_end" => ["MS-EMF 2.3.3.4.2", :comment_group_end, :error, nil],
54
+ "emf.comment.group_description" => ["MS-EMF 2.3.3.4.1", :comment_group_description, :warning, nil],
55
+ "emf.comment.multiformats_signature" => ["MS-EMF 2.2.4", :comment_multiformats_signature, :error,
56
+ "Quartz-style producers embed PDF data (signature \"PDF \", test-186) in MULTIFORMATS"],
57
+ "emf.comment.multiformats_eps_version" => ["MS-EMF 2.2.4", :comment_multiformats_eps_version, :error, nil],
58
+ "emf.escape.record_size" => ["MS-EMF 2.3.6", :escape_size, :error, PARSER_RECOVERS],
59
+ "emf.escape.payload_bounds" => ["MS-EMF 2.3.6", :escape_payload, :error, nil],
60
+ "emf.escape.driver_name" => ["MS-EMF 2.3.6.3", :escape_driver_name, :error, nil],
61
+ "emf.opengl.record_size" => ["MS-EMF 2.3.9", :opengl_size, :error, PARSER_RECOVERS],
62
+ "emf.opengl.payload_bounds" => ["MS-EMF 2.3.9", :opengl_payload, :error, nil],
63
+ "emf.opaque.fixed_size" => ["MS-EMF 2.3", :opaque_size, :error, PARSER_RECOVERS],
64
+ "emf.opaque.payload_bounds" => ["MS-EMF 2.3", :opaque_payload, :error, nil],
65
+ "emf.opaque.colorspace_fields" => ["MS-WMF 2.2.2.11", :colorspace_fields, :error, nil]
66
+ }.freeze
67
+
68
+ CACHE = ObjectSpace::WeakMap.new
69
+
70
+ module_function
71
+
72
+ def findings(context, id)
73
+ # The WeakMap holds its values weakly: keep a strong reference until the lookup is done.
74
+ grouped = CACHE[context] || (CACHE[context] = analyze(context))
75
+ grouped.fetch(id, [])
76
+ end
77
+
78
+ def analyze(context)
79
+ out = Hash.new { |hash, id| hash[id] = [] }
80
+ open_groups = []
81
+ context.records.each_with_index do |record, index|
82
+ type = context.record_type(record)
83
+ scan = Scan.new(record, index, context.record_size(record), out)
84
+ if type == COMMENT then scan.comment(open_groups)
85
+ elsif ESCAPES.key?(type) then scan.escape(*ESCAPES.fetch(type), type)
86
+ elsif OPENGL.key?(type) then scan.opengl(*OPENGL.fetch(type))
87
+ elsif OPAQUE.key?(type) && !state_bounded?(record, type) then scan.opaque(*OPAQUE.fetch(type))
88
+ end
89
+ end
90
+ open_groups.each { |group| group.add("emf.comment.group_unclosed", "EMR_COMMENT_BEGINGROUP has no matching ENDGROUP") }
91
+ out
92
+ end
93
+
94
+ def state_bounded?(record, type)
95
+ STATE_BOUNDS.include?(type) && record.wire.is_a?(Emf::Emr::Binary::Record)
96
+ end
97
+
98
+ # Checks of the EMR_COMMENT_PUBLIC sub-records (MS-EMF 2.3.3.4.x); mixed into Scan.
99
+ module PublicChecks
100
+ private
101
+
102
+ def public_comment(data_size, open_groups)
103
+ return add("emf.comment.header_size", "public comment DataSize #{data_size} cannot hold PublicCommentIdentifier") if data_size < 8
104
+
105
+ kind = PUBLIC_IDS[u32(16)]
106
+ unless kind
107
+ return add("emf.comment.public_identifier",
108
+ format("public comment identifier 0x%08X is not in the EmrComment enumeration", u32(16)))
109
+ end
110
+
111
+ send(:"public_#{kind}", data_size, open_groups)
112
+ end
113
+
114
+ def public_begin_group(data_size, open_groups)
115
+ return add("emf.comment.header_size", "BEGINGROUP DataSize #{data_size} is smaller than its 28-byte fixed part") if data_size < 28
116
+
117
+ open_groups << self
118
+ unless (u32(36) * 2) <= data_size - 28
119
+ return add("emf.comment.public_payload",
120
+ "BEGINGROUP description of #{u32(36)} characters runs past DataSize")
121
+ end
122
+
123
+ group_description(data_size)
124
+ end
125
+
126
+ # The description is a null-terminated string; nDescription may or may not count the terminator.
127
+ def group_description(data_size)
128
+ chars = u32(36)
129
+ return if chars.zero?
130
+
131
+ text = @bytes.byteslice(40, [(chars + 1) * 2, data_size - 28].min)
132
+ return if text.unpack("v*").first(chars + 1).include?(0)
133
+
134
+ add("emf.comment.group_description", "BEGINGROUP description of #{chars} characters is not null-terminated")
135
+ end
136
+
137
+ def public_end_group(_data_size, open_groups)
138
+ return add("emf.comment.group_end", "EMR_COMMENT_ENDGROUP has no matching BEGINGROUP") if open_groups.empty?
139
+
140
+ open_groups.pop
141
+ end
142
+
143
+ def public_multiformats(data_size, _open_groups)
144
+ if data_size < 28
145
+ return add("emf.comment.header_size",
146
+ "MULTIFORMATS DataSize #{data_size} is smaller than its 28-byte fixed part")
147
+ end
148
+
149
+ count = u32(36)
150
+ if count * 16 > data_size - 28
151
+ return add("emf.comment.public_payload",
152
+ "MULTIFORMATS CountFormats #{count} needs #{count * 16} bytes, #{data_size - 28} remain")
153
+ end
154
+
155
+ count.times do |i|
156
+ format_identity(i)
157
+ size = u32(48 + (i * 16))
158
+ offset = u32(52 + (i * 16))
159
+ if offset % 4 != 0 || offset + size > data_size
160
+ add("emf.comment.public_payload",
161
+ "MULTIFORMATS format #{i} data #{offset}+#{size} is misaligned or past DataSize")
162
+ end
163
+ end
164
+ end
165
+
166
+ def format_identity(index)
167
+ signature = u32(40 + (index * 16))
168
+ unless FORMAT_SIGNATURES.include?(signature)
169
+ return add("emf.comment.multiformats_signature",
170
+ "MULTIFORMATS format #{index} Signature #{hex(signature)} is not in the FormatSignature enumeration")
171
+ end
172
+ return unless signature == EPS_SIGNATURE && u32(44 + (index * 16)) != 1
173
+
174
+ add("emf.comment.multiformats_eps_version",
175
+ "MULTIFORMATS format #{index} is EPS but its Version is #{u32(44 + (index * 16))}, MUST be 1")
176
+ end
177
+
178
+ def public_windows_metafile(data_size, _open_groups)
179
+ if data_size < 24
180
+ return add("emf.comment.header_size",
181
+ "WINDOWS_METAFILE DataSize #{data_size} is smaller than its 24-byte fixed part")
182
+ end
183
+
184
+ reserved = @bytes.unpack1("v", offset: 22)
185
+ flags = u32(28)
186
+ unless reserved.zero? && flags.zero?
187
+ add("emf.comment.windows_metafile_reserved",
188
+ "Reserved #{reserved} and Flags #{flags} MUST be 0")
189
+ end
190
+ return unless u32(32) > data_size - 24
191
+
192
+ add("emf.comment.public_payload",
193
+ "WinMetafileSize #{u32(32)} exceeds the #{data_size - 24} bytes DataSize leaves")
194
+ end
195
+ end
196
+
197
+ # One record's checks; every method adds to `out[rule id]`.
198
+ class Scan
199
+ include PublicChecks
200
+
201
+ def initialize(record, index, size, out)
202
+ @record = record
203
+ @index = index
204
+ @size = size
205
+ @out = out
206
+ @bytes = record.to_binary_s
207
+ end
208
+
209
+ def add(id, message)
210
+ @out[id] << { message: message, offset: @record.offset, record_index: @index + 1, record_type: @record.type_id.to_i }
211
+ end
212
+
213
+ def comment(open_groups)
214
+ return if @size < 12 || @bytes.bytesize < 12
215
+
216
+ data_size = u32(8)
217
+ return if data_size > @size - 12
218
+
219
+ pad = @size - 12 - data_size
220
+ if pad > 3
221
+ add("emf.comment.padding",
222
+ "comment has #{pad} bytes after DataSize bytes of data, at most 3 padding bytes are allowed")
223
+ end
224
+ return if data_size < 4
225
+
226
+ case u32(12)
227
+ when PUBLIC then public_comment(data_size, open_groups)
228
+ when SPOOL then if data_size < 8
229
+ add("emf.comment.header_size",
230
+ "EMFSPOOL comment DataSize #{data_size} cannot hold its record identifier")
231
+ end
232
+ end
233
+ end
234
+
235
+ def escape(name, fixed, type)
236
+ return add("emf.escape.record_size", "#{name} Size #{@size} is smaller than its #{fixed}-byte fixed part") if @size < fixed
237
+
238
+ if type == 110
239
+ named_escape(name)
240
+ elsif u32(12) > @size - fixed
241
+ add("emf.escape.payload_bounds", "#{name} cjIn #{u32(12)} exceeds the #{@size - fixed} bytes the record holds")
242
+ end
243
+ end
244
+
245
+ def opengl(name, fixed)
246
+ return add("emf.opengl.record_size", "#{name} Size #{@size} is smaller than its #{fixed}-byte fixed part") if @size < fixed
247
+
248
+ count = u32(fixed - 4)
249
+ return unless count > @size - fixed
250
+
251
+ add("emf.opengl.payload_bounds",
252
+ "#{name} cbData #{count} exceeds the #{@size - fixed} bytes the record holds")
253
+ end
254
+
255
+ def opaque(name, minimum, exact, counted)
256
+ if @size < minimum || (exact && @size != minimum)
257
+ return add("emf.opaque.fixed_size", "#{name} Size #{@size} is not #{'at least ' unless exact}#{minimum}")
258
+ end
259
+
260
+ colorspace_fields(name) if [99, 122].include?(@record.type_id.to_i)
261
+ return unless counted
262
+
263
+ fields, unit = counted
264
+ total = fields.sum { |pos| u32(pos) } * unit
265
+ return unless total > @size - minimum
266
+
267
+ add("emf.opaque.payload_bounds",
268
+ "#{name} counts need #{total} bytes, the record holds #{@size - minimum}")
269
+ end
270
+
271
+ private
272
+
273
+ def u32(pos) = @bytes.unpack1("V", offset: pos)
274
+
275
+ def hex(value) = format("0x%<value>08X", value: value)
276
+
277
+ # The embedded LogColorSpace / LogColorSpaceW starts after ihCS (MS-WMF 2.2.2.11, 2.2.2.12).
278
+ def colorspace_fields(name)
279
+ return if @bytes.bytesize < 32
280
+
281
+ {
282
+ "Signature" => [u32(12) == 0x50534F43, hex(u32(12)), "0x50534F43"],
283
+ "Version" => [u32(16) == 0x400, hex(u32(16)), "0x00000400"],
284
+ "ColorSpaceType" => [COLOR_SPACE_TYPES.include?(u32(24)), hex(u32(24)), "a LogicalColorSpace value"],
285
+ "Intent" => [GAMUT_INTENTS.include?(u32(28)), u32(28).to_s, "a GamutMappingIntent value"]
286
+ }.each do |field, (ok, got, want)|
287
+ add("emf.opaque.colorspace_fields", "#{name} LogColorSpace #{field} #{got} MUST be #{want}") unless ok
288
+ end
289
+ end
290
+
291
+ def named_escape(name)
292
+ driver = u32(12)
293
+ data = u32(16)
294
+ add("emf.escape.driver_name", "#{name} cjDriver #{driver} MUST be an even number") if driver.odd?
295
+ if driver + data > @size - 20
296
+ add("emf.escape.payload_bounds", "#{name} cjDriver #{driver} + cjIn #{data} exceed the #{@size - 20} bytes the record holds")
297
+ elsif driver < 2 || @bytes.byteslice(20 + driver - 2, 2) != "\0\0".b
298
+ add("emf.escape.driver_name", "#{name} DriverName is not null-terminated within cjDriver #{driver}")
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ CommentEscapeRules::RULES.each do |id, (citation, kind, severity, reason)|
305
+ Registry.loading.register(id: id, formats: CommentEscapeRules::EMF, citation: citation, kind: kind, severity: severity,
306
+ tolerant_reason: reason) { |context| CommentEscapeRules.findings(context, id) }
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ # EMR_HEADER description / pixel-format buffers and the EMR_EOF palette
4
+ # (MS-EMF 2.2.9, 2.2.10, 2.2.22, 2.3.4.1, 2.3.4.2). Reads the buffers through
5
+ # the public accessors: header.description_bytes / pixel_format_bytes and the
6
+ # EOF wire's palette_bytes.
7
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
8
+
9
+ module Emf
10
+ module Conformance
11
+ module ControlBufferRules
12
+ EMF = %i[emf emf_plus].freeze
13
+ EOF_FIXED = 16 # Type, Size, nPalEntries, offPalEntries; the palette starts right after (GDI writes offset 16)
14
+ PFD_SIZE = 40
15
+ PFD_DOUBLEBUFFER = 0x1
16
+ PFD_SUPPORT_GDI = 0x10
17
+ DRIFT = "the descriptive header buffers are advisory; renderers do not read them"
18
+ PALETTE_DRIFT = "the palette is read from the offset EOF declares, so a mismatched count does not stop rendering"
19
+
20
+ module_function
21
+
22
+ def header_finding(message) = { message: message, offset: 0, record_index: 0, record_type: RecordTypes::HEADER }
23
+
24
+ # EmfDescription is a null-terminated UTF-16LE string of nDescription characters.
25
+ def description_terminator(context)
26
+ bytes = context.header.description_bytes
27
+ return [] if bytes.nil? || bytes.byteslice(-2, 2) == "\0\0".b
28
+
29
+ [header_finding("description (#{context.header.n_description} UTF-16 chars) is not null-terminated")]
30
+ end
31
+
32
+ # Each buffer must lie at or after the fixed part and the two must not overlap.
33
+ def buffers_overlap(context)
34
+ header = context.header
35
+ return [] unless pixel_format_fields?(header)
36
+
37
+ ranges = { "description" => range(header.off_description, header.n_description.to_i * 2),
38
+ "pixel format" => range(header.off_pixel_format, header.cb_pixel_format) }.compact
39
+ return [] unless ranges.size == 2
40
+
41
+ (first, a), (second, b) = ranges.to_a
42
+ return [] unless a.begin < b.end && b.begin < a.end
43
+
44
+ [header_finding("#{first} (#{a}) overlaps #{second} (#{b})")]
45
+ end
46
+
47
+ def pixel_format_size(context)
48
+ header = context.header
49
+ return [] unless pixel_format_fields?(header)
50
+
51
+ size = header.cb_pixel_format.to_i
52
+ return [] if size.zero? || header.off_pixel_format.to_i.zero? || size == PFD_SIZE
53
+
54
+ [header_finding("cbPixelFormat is #{size}, a PixelFormatDescriptor is #{PFD_SIZE} bytes")]
55
+ end
56
+
57
+ def pixel_format_content(context)
58
+ return [] unless pixel_format_fields?(context.header)
59
+
60
+ bytes = context.header.pixel_format_bytes
61
+ return [] if bytes.nil? || bytes.bytesize != PFD_SIZE
62
+
63
+ size, version, flags, type = bytes.unpack("vvVC")
64
+ problems = []
65
+ problems << "PixelFormatDescriptor nSize is #{size}, MUST describe its #{PFD_SIZE} bytes" unless size == PFD_SIZE
66
+ problems << "PixelFormatDescriptor nVersion is #{version}, MUST be 1" unless version == 1
67
+ problems << format("PixelFormatDescriptor iPixelType is %d, MUST be 0 (RGBA) or 1 (color index)", type) if type > 1
68
+ both = PFD_DOUBLEBUFFER | PFD_SUPPORT_GDI
69
+ problems << "PixelFormatDescriptor sets both PFD_DOUBLEBUFFER and PFD_SUPPORT_GDI" if flags & both == both
70
+ problems.map { |message| header_finding(message) }
71
+ end
72
+
73
+ # nPalEntries in the header counts the palette that the EOF record carries.
74
+ def palette_count(context)
75
+ eof = eof_record(context)
76
+ return [] unless eof
77
+
78
+ declared = context.header.n_pal_entries.to_i
79
+ actual = eof.wire.n_pal_entries.to_i
80
+ return [] if declared == actual
81
+
82
+ [at(eof, context, "header nPalEntries is #{declared} but EMR_EOF carries #{actual} palette entries")]
83
+ end
84
+
85
+ # The nPalEntries LogPaletteEntry objects (4 bytes each) must lie inside the
86
+ # record, after the fixed part and before SizeLast.
87
+ def palette_bounds(context)
88
+ eof = eof_record(context)
89
+ return [] unless eof
90
+
91
+ count = eof.wire.n_pal_entries.to_i
92
+ return [] if count.zero?
93
+
94
+ offset = eof.wire.off_pal_entries.to_i
95
+ size = context.record_size(eof)
96
+ return [at(eof, context, "palette offset #{offset} is inside the #{EOF_FIXED}-byte fixed part of EMR_EOF")] if offset < EOF_FIXED
97
+
98
+ return [] if offset + (4 * count) <= size - 4 && eof.wire.palette_bytes
99
+
100
+ [at(eof, context, "palette (#{count} entries at offset #{offset}) does not fit before SizeLast in a #{size}-byte EMR_EOF")]
101
+ end
102
+
103
+ # cbPixelFormat/offPixelFormat exist only in a HeaderExtension1 or later header.
104
+ def pixel_format_fields?(header) = HeaderRules.extension1?(header)
105
+
106
+ def range(offset, length)
107
+ offset = offset.to_i
108
+ length = length.to_i
109
+ offset...(offset + length) if offset.positive? && length.positive?
110
+ end
111
+
112
+ def eof_record(context)
113
+ last = context.records.last
114
+ last if last && context.record_type(last) == RecordTypes::EOF_RECORD
115
+ end
116
+
117
+ def at(record, context, message)
118
+ { message: message, offset: record.offset, record_index: context.records.size, record_type: RecordTypes::EOF_RECORD }
119
+ end
120
+ end
121
+
122
+ registry = Registry.loading
123
+ {
124
+ "emf.header.description_terminator" => [:description_terminator, "MS-EMF 2.3.4.2", :description_terminator,
125
+ ControlBufferRules::DRIFT],
126
+ "emf.header.buffers_overlap" => [:buffers_overlap, "MS-EMF 2.3.4.2", :buffers_overlap, ControlBufferRules::DRIFT],
127
+ "emf.header.pixel_format_size" => [:pixel_format_size, "MS-EMF 2.2.10", :pixel_format_size, ControlBufferRules::DRIFT],
128
+ "emf.header.pixel_format_content" => [:pixel_format_content, "MS-EMF 2.2.22", :pixel_format_content, ControlBufferRules::DRIFT],
129
+ "emf.eof.palette_entries" => [:palette_count, "MS-EMF 2.2.9", :palette_count, ControlBufferRules::PALETTE_DRIFT],
130
+ "emf.eof.palette_bounds" => [:palette_bounds, "MS-EMF 2.3.4.1", :palette_bounds, ControlBufferRules::PALETTE_DRIFT]
131
+ }.each do |id, (method, citation, kind, reason)|
132
+ registry.register(id: id, formats: ControlBufferRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
133
+ ControlBufferRules.public_send(method, context)
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Rules that name two corruptions in spec/fixtures/emf-corrupted whose records the parser keeps as
4
+ # Raw (a Size below the record's fixed layout), which the decoded-wire rules of the state and text
5
+ # families skip:
6
+ # bad_corrupted_2014-12-13-132605.emf EMR_SETICMMODE with Size 8 (stated 12)
7
+ # bad_corrupted_2017-05-12-074000.emf EMR_EXTTEXTOUTW with Size 12 (EmrText needs 24 bytes)
8
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
9
+
10
+ module Emf
11
+ module Conformance
12
+ module CorpusNamedRules
13
+ EMF = %i[emf emf_plus].freeze
14
+ RECOVERED = "the parser recovered and kept the bytes as a raw record, and readers advance by the declared Size"
15
+ # Type and Size (8) + Bounds (16) + iGraphicsMode, exScale, eyScale (12).
16
+ EXT_TEXT_AT = 36
17
+ # EmrText without the optional Rectangle: Reference, Chars, offString, Options, offDx.
18
+ EMR_TEXT_MIN = 24
19
+ EXT_TEXT_TYPES = { 83 => "EMR_EXTTEXTOUTA", 84 => "EMR_EXTTEXTOUTW" }.freeze
20
+
21
+ module_function
22
+
23
+ def raw?(record) = !record.wire.is_a?(Emf::Emr::Binary::Record)
24
+
25
+ def at(record, index, message)
26
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
27
+ end
28
+
29
+ # type => [required Size, exact?]. A decodable state record is checked by emf.state.fixed_size and
30
+ # emf.state.layout_size; only a Raw one escapes them.
31
+ def size_table
32
+ table = StateRules::STATED_SIZE.transform_values { |size| [size, true] }
33
+ StateRules::LAYOUT_SIZE.each { |type, size| table[type] ||= [size, false] }
34
+ table
35
+ end
36
+
37
+ def raw_fixed_size(context)
38
+ table = size_table
39
+ found = []
40
+ context.records.each_with_index do |record, index|
41
+ type = context.record_type(record)
42
+ required, exact = table[type]
43
+ next unless required && raw?(record)
44
+
45
+ size = context.record_size(record)
46
+ next if exact ? size == required : size >= required
47
+ next if StateRules::SHORT_BY_OPAQUE.include?(type) # a short one is emf.opaque.fixed_size, any record
48
+
49
+ found << at(record, index,
50
+ "#{RecordTypes.name(type)} (#{StateRules::SECTION.fetch(type)}) Size is #{size}, " \
51
+ "#{exact ? 'the record section states' : 'its fixed fields occupy'} #{required}; kept as a raw record")
52
+ end
53
+ found
54
+ end
55
+
56
+ def exttextout_fixed_part(context)
57
+ minimum = EXT_TEXT_AT + EMR_TEXT_MIN
58
+ found = []
59
+ context.records.each_with_index do |record, index|
60
+ name = EXT_TEXT_TYPES[context.record_type(record)]
61
+ next unless name && raw?(record)
62
+
63
+ size = context.record_size(record)
64
+ next if size >= minimum
65
+
66
+ found << at(record, index, "#{name} Size #{size} is smaller than its #{minimum}-byte fixed part " \
67
+ "(header, Bounds, iGraphicsMode, scales and an EmrText without Rectangle)")
68
+ end
69
+ found
70
+ end
71
+ end
72
+
73
+ registry = Registry.loading
74
+ registry.register(id: "emf.record.fixed_size_raw", formats: CorpusNamedRules::EMF, citation: "MS-EMF 2.3.11",
75
+ kind: :fixed_size_raw, tolerant_reason: CorpusNamedRules::RECOVERED) do |context|
76
+ CorpusNamedRules.raw_fixed_size(context)
77
+ end
78
+ registry.register(id: "emf.record.exttextout_fixed_part", formats: CorpusNamedRules::EMF, citation: "MS-EMF 2.2.5",
79
+ kind: :exttextout_fixed_part, tolerant_reason: CorpusNamedRules::RECOVERED) do |context|
80
+ CorpusNamedRules.exttextout_fixed_part(context)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Record framing, EOF and comment rules (MS-EMF 2.1.1, 2.3, 2.3.3, 2.3.4.1).
4
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
5
+
6
+ module Emf
7
+ module Conformance
8
+ module FramingRules
9
+ EMF = %i[emf emf_plus].freeze
10
+ TOLERATED = "records of unassigned type are skipped by size, so the stream stays readable"
11
+
12
+ module_function
13
+
14
+ def alignment(context)
15
+ context.records.each_with_index.filter_map do |record, index|
16
+ size = context.record_size(record)
17
+ next if (size % 4).zero?
18
+
19
+ at(record, index, "record Size #{size} is not a multiple of 4")
20
+ end
21
+ end
22
+
23
+ def undefined_type(context)
24
+ context.records.each_with_index.filter_map do |record, index|
25
+ type = context.record_type(record)
26
+ next if RecordTypes.defined?(type)
27
+
28
+ at(record, index, format("record type 0x%04X is not in the RecordType enumeration", type))
29
+ end
30
+ end
31
+
32
+ def eof_missing(context)
33
+ last = context.records.last
34
+ return [] if last && context.record_type(last) == RecordTypes::EOF_RECORD
35
+
36
+ [{ message: "metafile does not end with an EMR_EOF record", offset: last&.offset || context.header.n_size.to_i,
37
+ record_index: context.records.size, record_type: last && context.record_type(last) }]
38
+ end
39
+
40
+ def eof_size_last(context)
41
+ last = context.records.last
42
+ return [] unless last && context.record_type(last) == RecordTypes::EOF_RECORD
43
+
44
+ bytes = last.to_binary_s
45
+ size = context.record_size(last)
46
+ size_last = bytes.byteslice(-4, 4)&.unpack1("V")
47
+ return [] if size_last == size
48
+
49
+ at(last, context.records.size - 1, "EMR_EOF SizeLast is #{size_last.inspect}, MUST equal Size (#{size})")
50
+ end
51
+
52
+ def trailing_bytes(context)
53
+ extra = context.metafile.trailing.bytesize
54
+ return [] if extra.zero?
55
+
56
+ [{ message: "#{extra} bytes follow the last record", offset: context.byte_length && (context.byte_length - extra),
57
+ record_index: context.records.size }]
58
+ end
59
+
60
+ def comment_data_size(context)
61
+ context.records.each_with_index.filter_map do |record, index|
62
+ next unless context.record_type(record) == RecordTypes::COMMENT
63
+
64
+ size = context.record_size(record)
65
+ data_size = record.to_binary_s.unpack1("V", offset: 8)
66
+ next if data_size.nil? || data_size <= size - 12
67
+
68
+ at(record, index, "comment DataSize #{data_size} exceeds the #{[size - 12, 0].max} bytes the record holds after DataSize")
69
+ end
70
+ end
71
+
72
+ def at(record, index, message)
73
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
74
+ end
75
+ end
76
+
77
+ registry = Registry.loading
78
+ {
79
+ "emf.record.size_alignment" => [:alignment, "MS-EMF 2.3", :size_alignment,
80
+ "readers advance by the declared Size, so a misaligned record still delimits the stream"],
81
+ "emf.record.undefined_type" => [:undefined_type, "MS-EMF 2.1.1", :undefined_type, FramingRules::TOLERATED],
82
+ "emf.eof.missing" => [:eof_missing, "MS-EMF 2.3.4.1", :eof_missing, "truncated files still render the records that survive"],
83
+ "emf.eof.size_last" => [:eof_size_last, "MS-EMF 2.3.4.1", :eof_size_last,
84
+ "no reader uses SizeLast; some producers write the metafile length there"],
85
+ "emf.eof.trailing_bytes" => [:trailing_bytes, "MS-EMF 2.3.4.1", :trailing_bytes, "bytes after EMR_EOF are ignored by readers"],
86
+ "emf.comment.data_size" => [:comment_data_size, "MS-EMF 2.3.3", :comment_data_size,
87
+ "readers clamp DataSize to the record, so the oversized value is never followed"]
88
+ }.each do |id, (method, citation, kind, reason)|
89
+ registry.register(id: id, formats: FramingRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
90
+ FramingRules.public_send(method, context)
91
+ end
92
+ end
93
+ end
94
+ end