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,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WMF bitmap, text, palette, region, escape and embedded-EMF buffer rules over typed public
4
+ # records (MS-WMF 2.3.1 bitmap, 2.3.3 drawing, 2.3.4 object, 2.3.5 state and 2.3.6 escape
5
+ # records). Only values the model exposes are judged: records the model keeps opaque
6
+ # (UnknownRecord, escape subtypes without a view, undecodable bytes) are preserved and never
7
+ # reported, except a text record the parser kept verbatim because its StringLength is negative, and no rule
8
+ # here needs the declared record size.
9
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
10
+
11
+ module Emf
12
+ module Conformance
13
+ module WmfBufferRules
14
+ WMF = %i[wmf].freeze
15
+ R = Emf::Model::Wmf::Records
16
+ EE = Emf::Wmf::EmbeddedEmf
17
+ UINT16 = 0xFFFF
18
+ INT16 = 0x7FFF
19
+ DIB_RECORDS = [R::DibBitBlt, R::DibStretchBlt, R::SetDibToDev, R::StretchDib].freeze
20
+ BITMAP16_RECORDS = [R::BitBlt, R::StretchBlt].freeze
21
+ PALETTE_RECORDS = [R::CreatePalette, R::SetPalEntries, R::AnimatePalette].freeze
22
+ CLIPPED_OR_OPAQUE = 0x0006
23
+ REGION_HEADER_BYTES = 22
24
+ SEQUENCE_REASONS = %i[sequence_mismatch limit_exceeded remaining_mismatch record_count_mismatch incomplete].freeze
25
+
26
+ PRESERVED = "bytes are preserved verbatim; a consumer skips the unusable part instead of rejecting the record"
27
+
28
+ module_function
29
+
30
+ def dib_layout(context)
31
+ each_of(context, *DIB_RECORDS) do |record|
32
+ next if record.bitmap.nil? || record.typed_image
33
+
34
+ "#{name(record)} DeviceIndependentBitmap does not decode: header, color table or pixel extent disagrees " \
35
+ "with the #{record.bitmap.bytesize} bytes carried"
36
+ end
37
+ end
38
+
39
+ def bitmap16_layout(context)
40
+ each_of(context, *BITMAP16_RECORDS) do |record|
41
+ next if record.bitmap.nil? || record.typed_image
42
+
43
+ "#{name(record)} Bitmap16 does not decode: Width, Height, Planes, BitsPixel or pixel extent disagrees " \
44
+ "with the #{record.bitmap.bytesize} bytes carried"
45
+ end
46
+ end
47
+
48
+ def text_length(context)
49
+ each_of(context, R::TextOut) do |record|
50
+ count = record.text.bytesize
51
+ if count > INT16
52
+ "#{name(record)} string is #{count} bytes, StringLength is a signed 16-bit count"
53
+ elsif (count + record.padding.bytesize).odd?
54
+ "#{name(record)} string and padding take #{count + record.padding.bytesize} bytes, MUST be word aligned"
55
+ end
56
+ end
57
+ end
58
+
59
+ # A text record with a negative StringLength cannot be framed as text, so the parser keeps it as an
60
+ # UnknownRecord (Wmf::Recovery); the sign is read from its own bytes.
61
+ def text_negative(context)
62
+ context.records.each_with_index.filter_map do |record, index|
63
+ next unless record.is_a?(R::UnknownRecord) && Emf::Wmf::Recovery.record(record.raw_bytes, record.type_id, nil)&.last == 1003
64
+
65
+ count_at = Emf::Wmf::Recovery::TEXT_COUNTS.fetch(record.type_id & 0xFF).first
66
+ { message: "#{record.type_id & 0xFF == 0x32 ? 'ExtTextOut' : 'TextOut'} StringLength is " \
67
+ "#{record.raw_bytes.byteslice(count_at, 2).unpack1('s<')}, MUST NOT be negative",
68
+ offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
69
+ end
70
+ end
71
+
72
+ # A region whose ObjectCount is two bytes wide (libUEMF) has no readable header under the spec layout, so the
73
+ # parser keeps it verbatim as an UnknownRecord (Wmf::Recovery).
74
+ def region_header(context)
75
+ context.records.each_with_index.filter_map do |record, index|
76
+ next unless record.is_a?(R::UnknownRecord) && record.type_id & 0xFF == Emf::Wmf::Recovery::REGION &&
77
+ Emf::Wmf::Recovery.record(record.raw_bytes, record.type_id, nil)&.last == 1203
78
+
79
+ { message: "region header does not follow the Region Object layout (ObjectCount is not 4 bytes wide)",
80
+ offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
81
+ end
82
+ end
83
+
84
+ def text_spacing(context)
85
+ each_of(context, R::ExtTextOut) do |record|
86
+ next if record.dx.nil? || record.dx.size == record.text.bytesize
87
+
88
+ "ExtTextOut has #{record.dx.size} Dx entries for a #{record.text.bytesize}-byte string, MUST match StringLength"
89
+ end
90
+ end
91
+
92
+ def text_rectangle(context)
93
+ each_of(context, R::ExtTextOut) do |record|
94
+ wanted = record.options.to_i.anybits?(CLIPPED_OR_OPAQUE)
95
+ next if wanted == !record.rectangle.nil?
96
+
97
+ if wanted
98
+ "ExtTextOut options select ETO_OPAQUE or ETO_CLIPPED but the Rectangle is absent"
99
+ else
100
+ "ExtTextOut has a Rectangle but its options select neither ETO_OPAQUE nor ETO_CLIPPED"
101
+ end
102
+ end
103
+ end
104
+
105
+ def palette_count(context)
106
+ each_of(context, *PALETTE_RECORDS, R::ResizePalette) do |record|
107
+ count = record.is_a?(R::ResizePalette) ? record.entry_count : record.entries.size
108
+ next if count.is_a?(Integer) && count.between?(0, UINT16)
109
+
110
+ "#{name(record)} entry count #{count.inspect} does not fit the unsigned 16-bit field"
111
+ end
112
+ end
113
+
114
+ def palette_entries(context)
115
+ each_of(context, *PALETTE_RECORDS) do |record|
116
+ bad = record.entries.find do |entry|
117
+ [entry.red, entry.green, entry.blue, entry.flags].any? { |value| !value.is_a?(Integer) || !value.between?(0, 0xFF) }
118
+ end
119
+ "#{name(record)} holds a PaletteEntry with a component outside 0..255" if bad
120
+ end
121
+ end
122
+
123
+ def palette_start(context)
124
+ each_of(context, R::CreatePalette) do |record|
125
+ format("CreatePalette Start is 0x%04X, MUST be 0x0300", record.start.to_i) unless record.start == 0x0300
126
+ end
127
+ end
128
+
129
+ def region_scans(context)
130
+ each_of(context, R::CreateRegion) do |record|
131
+ scans = record.scans
132
+ if scans.size > INT16
133
+ "region has #{scans.size} scans, ScanCount is a signed 16-bit count"
134
+ elsif (scan = scans.find { |item| item.lines.size * 2 > UINT16 })
135
+ "region scan at Top #{scan.top} has #{scan.lines.size * 2} scan points, the count is an unsigned 16-bit field"
136
+ end
137
+ end
138
+ end
139
+
140
+ def region_size(context)
141
+ each_of(context, R::CreateRegion) do |record|
142
+ next if record.scans.size > INT16 || record.scans.any? { |scan| scan.lines.size * 2 > UINT16 }
143
+
144
+ size = REGION_HEADER_BYTES + record.scans.sum { |scan| 8 + (scan.lines.size * 4) }
145
+ "region needs #{size} bytes, RegionSize is a signed 16-bit field" if size > INT16
146
+ end
147
+ end
148
+
149
+ def escape_count(context)
150
+ each_of(context, R::Escape) do |record|
151
+ bytes = record.data.bytesize
152
+ if bytes > UINT16
153
+ "Escape carries #{bytes} data bytes, ByteCount is an unsigned 16-bit field"
154
+ elsif record.padding.nil? == bytes.odd?
155
+ "Escape data of #{bytes} bytes #{bytes.odd? ? 'lacks' : 'has'} a padding byte, the record MUST stay word aligned"
156
+ end
157
+ end
158
+ end
159
+
160
+ def embedded_segment(context)
161
+ each_of(context, R::Escape) do |record|
162
+ record.embedded_emf_segment
163
+ nil
164
+ rescue EE::Invalid => e
165
+ "embedded EMF escape segment is invalid: #{e.reason}" unless e.reason == :not_embedded_emf
166
+ end
167
+ end
168
+
169
+ def embedded_sequence(context)
170
+ embedded_runs(context) do |run|
171
+ "embedded EMF escape sequence is inconsistent: #{run.error}" if SEQUENCE_REASONS.include?(run.error)
172
+ end
173
+ end
174
+
175
+ def embedded_checksum(context)
176
+ embedded_runs(context) do |run|
177
+ next unless run.stream && !run.stream.checksum_valid
178
+
179
+ "embedded EMF stream does not match the Checksum of its first escape segment"
180
+ end
181
+ end
182
+
183
+ # Findings for each run of consecutive 0x000F escapes, positioned at its first record.
184
+ def embedded_runs(context)
185
+ EE.sequences(context.records).filter_map do |run|
186
+ message = yield run
187
+ next unless message
188
+
189
+ record = context.records[run.first_record]
190
+ { message: message, offset: record.offset, record_index: run.first_record + 1, record_type: record.type_id.to_i }
191
+ end
192
+ end
193
+
194
+ def name(record) = record.class.name.split("::").last
195
+
196
+ def each_of(context, *classes)
197
+ context.records.each_with_index.filter_map do |record, index|
198
+ next unless classes.any? { |klass| record.is_a?(klass) }
199
+
200
+ message = yield record
201
+ next unless message
202
+
203
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
204
+ end
205
+ end
206
+ end
207
+
208
+ # [id, method, citation, kind, tolerant_reason]
209
+ [
210
+ ["wmf.bitmap.dib_layout", :dib_layout,
211
+ "MS-WMF 2.2.2.9 DeviceIndependentBitmap Object; 2.3.1.2.1 META_DIBBITBLT, 2.3.1.3.1 " \
212
+ "META_DIBSTRETCHBLT (Target), 2.3.1.4 META_SETDIBTODEV, 2.3.1.6 META_STRETCHDIB (DIB)",
213
+ :buffer_layout, WmfBufferRules::PRESERVED],
214
+ ["wmf.bitmap.bitmap16_layout", :bitmap16_layout, "MS-WMF 2.2.2.1 Bitmap16 Object; 2.3.1.1.1 META_BITBLT, 2.3.1.5.1 META_STRETCHBLT " \
215
+ "(Target)",
216
+ :buffer_layout, WmfBufferRules::PRESERVED],
217
+ ["wmf.text.length", :text_length, "MS-WMF 2.3.3.20 META_TEXTOUT, 2.3.3.5 META_EXTTEXTOUT (StringLength, String)",
218
+ :string_length, nil],
219
+ ["wmf.text.negative_length", :text_negative,
220
+ "MS-WMF 2.3.3.20 META_TEXTOUT, 2.3.3.5 META_EXTTEXTOUT (StringLength, String)", :string_length,
221
+ "the parser keeps the record verbatim as an opaque record; readers (LibreOffice) import the file"],
222
+ ["wmf.region.header", :region_header, "MS-WMF 2.2.1.5 Region Object (ObjectCount, RegionSize); 2.3.4.6 META_CREATEREGION",
223
+ :buffer_layout,
224
+ "libUEMF, the library Inkscape writes WMF with, declares ObjectCount as 2 bytes; the parser keeps the record " \
225
+ "verbatim as an opaque region and readers (LibreOffice) import the file"],
226
+ ["wmf.text.spacing_count", :text_spacing, "MS-WMF 2.3.3.5 META_EXTTEXTOUT (Dx)", :array_counts, nil],
227
+ ["wmf.text.rectangle_options", :text_rectangle, "MS-WMF 2.3.3.5 META_EXTTEXTOUT (fwOpts, Rectangle)", :field_dependency,
228
+ "readers ignore a Rectangle the options do not select"],
229
+ ["wmf.palette.entry_count", :palette_count,
230
+ "MS-WMF 2.3.4.3 META_CREATEPALETTE, 2.3.5.19 META_SETPALENTRIES, 2.3.5.1 META_ANIMATEPALETTE, " \
231
+ "2.3.5.9 META_RESIZEPALETTE (NumberOfEntries); 2.2.1.3 Palette Object (NumberOfEntries)",
232
+ :array_counts, nil],
233
+ ["wmf.palette.entry_fields", :palette_entries,
234
+ "MS-WMF 2.2.2.13 PaletteEntry Object; 2.2.1.3 Palette Object (aPaletteEntries); " \
235
+ "2.3.4.3 META_CREATEPALETTE, 2.3.5.19 META_SETPALENTRIES, 2.3.5.1 META_ANIMATEPALETTE",
236
+ :coordinate_range, nil],
237
+ ["wmf.palette.start", :palette_start, "MS-WMF 2.2.1.3 Palette Object (Start, MUST be 0x0300 for META_CREATEPALETTE); 2.3.4.3 " \
238
+ "META_CREATEPALETTE", :fixed_value,
239
+ "readers ignore the Start of a created palette"],
240
+ ["wmf.region.scan_counts", :region_scans, "MS-WMF 2.2.1.5 Region Object (ScanCount, aScans), 2.2.2.21 Scan Object; 2.3.4.6 " \
241
+ "META_CREATEREGION", :array_counts, nil],
242
+ ["wmf.region.size", :region_size, "MS-WMF 2.2.1.5 Region Object (RegionSize); 2.3.4.6 META_CREATEREGION", :buffer_layout, nil],
243
+ ["wmf.escape.byte_count", :escape_count, "MS-WMF 2.3.6.1 META_ESCAPE (ByteCount, EscapeData)", :byte_count, nil],
244
+ ["wmf.escape.embedded_emf.segment", :embedded_segment, "MS-WMF 2.3.6.25 META_ESCAPE_ENHANCED_METAFILE", :buffer_layout,
245
+ "producers write other data under Escape 0x000F (Micrografx Flowcharter); readers skip an embedded stream they " \
246
+ "cannot use and play the WMF records"],
247
+ ["wmf.escape.embedded_emf.sequence", :embedded_sequence,
248
+ "MS-WMF 2.3.6.25 META_ESCAPE_ENHANCED_METAFILE (CommentRecordCount, CurrentRecordSize, " \
249
+ "RemainingBytes, EnhancedMetafileDataSize)",
250
+ :array_counts, nil],
251
+ ["wmf.escape.embedded_emf.checksum", :embedded_checksum, "MS-WMF 2.3.6.25 META_ESCAPE_ENHANCED_METAFILE (Checksum)", :checksum,
252
+ "readers do not validate the embedded stream Checksum"]
253
+ ].each do |id, method, citation, kind, reason|
254
+ Registry.loading.register(id: id, formats: WmfBufferRules::WMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
255
+ WmfBufferRules.public_send(method, context)
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WMF header, placeable header and record-framing rules over the accepted public
4
+ # model (MS-WMF 2.2.2 headers, 2.3.2 control records). They read only
5
+ # Metafile#header, Metafile#records and the input length; declared record sizes
6
+ # are not on the public WMF model, so size rules stay blocked (see manifest).
7
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
8
+
9
+ module Emf
10
+ module Conformance
11
+ module WmfFramingRules
12
+ WMF = %i[wmf].freeze
13
+ PLACEABLE_KEY = 0x9AC6CDD7
14
+ HEADER_WORDS = 9
15
+ PLACEABLE_BYTES = 22
16
+ EOF_FUNCTION = 0
17
+
18
+ module_function
19
+
20
+ def header_type(context)
21
+ type = context.header.type
22
+ return [] if [1, 2].include?(type)
23
+
24
+ [{ message: "WMF header Type is #{type.inspect}, MUST be 1 (memory) or 2 (disk)", offset: header_offset(context) }]
25
+ end
26
+
27
+ def header_version(context)
28
+ version = context.header.version
29
+ return [] if [0x0100, 0x0300].include?(version)
30
+
31
+ [{ message: format("WMF header Version is 0x%04X, MUST be 0x0100 or 0x0300", version.to_i), offset: header_offset(context) + 4 }]
32
+ end
33
+
34
+ def header_size(context)
35
+ words = context.header.size_words.to_i
36
+ found = []
37
+ found << "WMF header Size is #{words} words, less than the #{HEADER_WORDS}-word header" if words < HEADER_WORDS
38
+ if context.byte_length && (words * 2) + header_offset(context) > context.byte_length
39
+ found << "WMF header Size #{words} words runs past the #{context.byte_length}-byte input"
40
+ end
41
+ found.map { |message| { message: message, offset: header_offset(context) + 6 } }
42
+ end
43
+
44
+ def header_members(context)
45
+ members = context.header.number_of_members.to_i
46
+ return [] if members.zero?
47
+
48
+ [{ message: "WMF header NumberOfMembers is #{members}, SHOULD be 0", offset: header_offset(context) + 16 }]
49
+ end
50
+
51
+ # HWmf MUST be 0 only for a metafile on disk (Type 2); an in-memory metafile (Type 1) carries its handle.
52
+ def placeable_handle(context)
53
+ placeable = context.header.placeable
54
+ return [] unless placeable && context.header.type.to_i == 2 && !placeable.handle.to_i.zero?
55
+
56
+ [{ message: "WMF placeable HWmf is #{placeable.handle} on a disk metafile (Type 2), MUST be 0", offset: 4 }]
57
+ end
58
+
59
+ def placeable_checksum(context)
60
+ placeable = context.header.placeable
61
+ return [] unless placeable
62
+
63
+ expected = checksum(placeable)
64
+ return [] if placeable.checksum == expected
65
+
66
+ [{ message: format("WMF placeable Checksum is 0x%<actual>04X, the XOR of the preceding ten words is 0x%<expected>04X",
67
+ actual: placeable.checksum.to_i, expected: expected), offset: 20 }]
68
+ end
69
+
70
+ def eof_missing(context)
71
+ last = context.records.last
72
+ return [] if last && last.type_id.to_i == EOF_FUNCTION
73
+
74
+ [{ message: "WMF metafile does not end with a META_EOF record", offset: last&.offset, record_index: context.records.size,
75
+ record_type: last&.type_id }]
76
+ end
77
+
78
+ def eof_not_last(context)
79
+ context.records.each_with_index.filter_map do |record, index|
80
+ next unless record.type_id.to_i == EOF_FUNCTION && index != context.records.size - 1
81
+
82
+ at(record, index, "META_EOF is followed by #{context.records.size - 1 - index} more record(s)")
83
+ end
84
+ end
85
+
86
+ def unknown_function(context)
87
+ context.records.each_with_index.filter_map do |record, index|
88
+ next unless record.is_a?(Emf::Model::Wmf::Records::UnknownRecord)
89
+ # A specified text function kept verbatim for its negative StringLength is reported by wmf.text.negative_length.
90
+ next if Emf::Wmf::Binary::TYPED_FUNCTIONS.include?(record.type_id.to_i & 0xFF)
91
+
92
+ at(record, index, format("record function 0x%04X is not a specified MS-WMF record type", record.type_id.to_i))
93
+ end
94
+ end
95
+
96
+ def trailing_bytes(context)
97
+ extra = context.metafile.trailing.to_s.bytesize
98
+ return [] if extra.zero?
99
+
100
+ [{ message: "#{extra} bytes follow the first META_EOF record", offset: context.byte_length && (context.byte_length - extra),
101
+ record_index: context.records.size }]
102
+ end
103
+
104
+ def header_offset(context) = context.header.placeable ? PLACEABLE_BYTES : 0
105
+
106
+ def checksum(placeable)
107
+ words = [PLACEABLE_KEY & 0xFFFF, PLACEABLE_KEY >> 16, placeable.handle, placeable.left, placeable.top, placeable.right,
108
+ placeable.bottom, placeable.inch, 0, 0]
109
+ words.map { |word| word.to_i & 0xFFFF }.reduce(0, :^)
110
+ end
111
+
112
+ def at(record, index, message)
113
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
114
+ end
115
+ end
116
+
117
+ registry = Registry.loading
118
+ [
119
+ ["wmf.header.type", :header_type, "MS-WMF 2.3.2.2 META_HEADER (Type); 2.1.1.18 MetafileType Enumeration", :header_type, nil],
120
+ ["wmf.header.version", :header_version, "MS-WMF 2.3.2.2 META_HEADER (Version); " \
121
+ "2.1.1.19 MetafileVersion Enumeration", :header_version, nil],
122
+ ["wmf.header.size", :header_size, "MS-WMF 2.3.2.2 META_HEADER (SizeLow, SizeHigh)", :header_size,
123
+ "the parser reads the records actually present and records the Size drift in Metafile#errors " \
124
+ "(seen in files from Inkscape)"],
125
+ ["wmf.header.number_of_members", :header_members, "MS-WMF 2.3.2.2 META_HEADER (NumberOfMembers)", :header_members,
126
+ "NumberOfMembers is reserved and no reader consults it", :warning],
127
+ ["wmf.placeable.handle", :placeable_handle, "MS-WMF 2.3.2.3 META_PLACEABLE (HWmf)", :placeable_handle,
128
+ "readers ignore the handle of an in-file placeable header"],
129
+ ["wmf.placeable.checksum", :placeable_checksum, "MS-WMF 2.3.2.3 META_PLACEABLE (Checksum)", :placeable_checksum,
130
+ "readers ignore the placeable checksum; the parser keeps the declared value and records the mismatch in " \
131
+ "Metafile#errors (seen in LibreOffice test files)"],
132
+ ["wmf.eof.missing", :eof_missing, "MS-WMF 2.3.2.1 META_EOF", :eof_missing, nil],
133
+ ["wmf.eof.not_last", :eof_not_last, "MS-WMF 2.3.2.1 META_EOF", :eof_not_last, nil],
134
+ ["wmf.record.unknown_function", :unknown_function, "MS-WMF 2.1.1.1 RecordType Enumeration; " \
135
+ "2.3 WMF Records (unknown record types are ignored)", :unknown_function,
136
+ "unspecified functions are skipped by size, so the stream stays readable"],
137
+ ["wmf.eof.trailing_bytes", :trailing_bytes, "MS-WMF 2.3.2.2 META_HEADER (SizeLow, SizeHigh)", :trailing_bytes,
138
+ "bytes after the declared Size are ignored by readers"]
139
+ ].each do |id, method, citation, kind, reason, severity = :error|
140
+ registry.register(id: id, formats: WmfFramingRules::WMF, citation: citation, kind: kind, severity: severity,
141
+ tolerant_reason: reason) do |context|
142
+ WmfFramingRules.public_send(method, context)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WMF rules for the fixed-layout records whose only stated constraints are their exact layout and a
4
+ # reserved or enumerated field (MS-WMF 2.3.3 drawing, 2.3.5 state records). The parser keeps any words
5
+ # after the last field in `suffix`, so a record can pass decoding with a RecordSize larger than its
6
+ # layout. Records whose size is exact at decoding time (the rectangle, point and offset records of
7
+ # Binary::FIXED_RECORDS) are checked by wmf.record.declared_size instead.
8
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
9
+
10
+ module Emf
11
+ module Conformance
12
+ module WmfLayoutRules
13
+ WMF = %i[wmf].freeze
14
+ R = Emf::Model::Wmf::Records
15
+ KEPT = "the surplus words stay in the record's suffix, so nothing is dropped"
16
+ COLORREF_RESERVED_SHIFT = 24
17
+ COLORREF_CITATION = "MS-WMF 2.2.2.8 ColorRef Object (Reserved); 2.3.3.7 META_FLOODFILL, 2.3.3.19 META_SETPIXEL, " \
18
+ "2.3.5.14 META_SETBKCOLOR, 2.3.5.26 META_SETTEXTCOLOR"
19
+
20
+ # record class => [parameter bytes the layout defines, section]. Each section lists the record's fields; none lists more.
21
+ LAYOUTS = {
22
+ R::FloodFill => [8, "2.3.3.7 META_FLOODFILL"], R::SetPixel => [8, "2.3.3.19 META_SETPIXEL"],
23
+ R::PatBlt => [12, "2.3.3.12 META_PATBLT"], R::ExcludeClipRect => [8, "2.3.5.2 META_EXCLUDECLIPRECT"],
24
+ R::IntersectClipRect => [8, "2.3.5.3 META_INTERSECTCLIPRECT"], R::OffsetClipRgn => [4, "2.3.5.5 META_OFFSETCLIPRGN"],
25
+ R::RealizePalette => [0, "2.3.5.8 META_REALIZEPALETTE"], R::SetBkColor => [4, "2.3.5.14 META_SETBKCOLOR"],
26
+ R::SetMapperFlags => [4, "2.3.5.18 META_SETMAPPERFLAGS"], R::SetRelAbs => [0, "2.3.5.21 META_SETRELABS"],
27
+ R::SetTextCharExtra => [2, "2.3.5.25 META_SETTEXTCHAREXTRA"], R::SetTextColor => [4, "2.3.5.26 META_SETTEXTCOLOR"],
28
+ R::SetTextJustification => [4, "2.3.5.27 META_SETTEXTJUSTIFICATION"]
29
+ }.freeze
30
+ COLOR_FIELDS = { R::SetBkColor => :color_ref, R::SetTextColor => :color_ref, R::FloodFill => :color, R::SetPixel => :color }.freeze
31
+
32
+ module_function
33
+
34
+ def layout_size(context)
35
+ each_of(context, *LAYOUTS.keys) do |record|
36
+ surplus = record.suffix.to_s.bytesize
37
+ next if surplus.zero?
38
+
39
+ payload, section = LAYOUTS.fetch(record.class)
40
+ words = 3 + ((payload + surplus) / 2)
41
+ note = payload.zero? ? ", the record has no parameters" : ""
42
+ "#{section.split.last} RecordSize is #{words} words, the layout is #{3 + (payload / 2)} words " \
43
+ "(#{surplus} surplus bytes#{note})"
44
+ end
45
+ end
46
+
47
+ def raster_operation(context)
48
+ each_of(context, R::PatBlt) do |record|
49
+ value = record.raster_operation.to_i
50
+ if value >= (1 << COLORREF_RESERVED_SHIFT)
51
+ next format("PatBlt RasterOperation 0x%<value>08X has bits set above the 24-bit ternary code, " \
52
+ "so it is no TernaryRasterOperation value", value: value)
53
+ end
54
+
55
+ expected = TernaryRasterOperations.expected(value)
56
+ next if expected.nil? || expected == value
57
+
58
+ format("PatBlt RasterOperation 0x%<value>08X is not a TernaryRasterOperation value; " \
59
+ "operation 0x%<index>02X is 0x%<expected>08X", value: value, index: (value >> 16) & 0xFF, expected: expected)
60
+ end
61
+ end
62
+
63
+ PALETTE_RESERVED = [0x01, 0x02].freeze
64
+
65
+ def colorref_reserved(context)
66
+ colorref_findings(context) { |reserved| !PALETTE_RESERVED.include?(reserved) }
67
+ end
68
+
69
+ def colorref_palette(context)
70
+ colorref_findings(context) { |reserved| PALETTE_RESERVED.include?(reserved) }
71
+ end
72
+
73
+ def colorref_findings(context)
74
+ each_of(context, *COLOR_FIELDS.keys) do |record|
75
+ value = record.public_send(COLOR_FIELDS.fetch(record.class)).to_i
76
+ reserved = value >> COLORREF_RESERVED_SHIFT
77
+ next if reserved.zero? || !yield(reserved)
78
+
79
+ format("%<name>s ColorRef 0x%<value>08X has Reserved byte 0x%<reserved>02X, MUST be 0x00",
80
+ name: record.class.name.split("::").last, value: value, reserved: reserved)
81
+ end
82
+ end
83
+
84
+ def each_of(context, *classes)
85
+ context.records.each_with_index.filter_map do |record, index|
86
+ next unless classes.any? { |klass| record.instance_of?(klass) }
87
+
88
+ message = yield record
89
+ next unless message
90
+
91
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
92
+ end
93
+ end
94
+ end
95
+
96
+ [
97
+ ["wmf.record.layout_size", :layout_size, :record_layout, WmfLayoutRules::KEPT,
98
+ "MS-WMF 2.3.3.7, 2.3.3.12, 2.3.3.19, 2.3.5.2, 2.3.5.3, 2.3.5.5, 2.3.5.8, 2.3.5.14, 2.3.5.18, 2.3.5.21, 2.3.5.25, " \
99
+ "2.3.5.26, 2.3.5.27 (RecordSize; the record lists no field after its last parameter)"],
100
+ ["wmf.fixed.raster_operation", :raster_operation, :flag_mask, nil,
101
+ "MS-WMF 2.3.3.12 META_PATBLT (RasterOperation); 2.1.1.31 TernaryRasterOperation Enumeration"],
102
+ ["wmf.fixed.colorref_reserved", :colorref_reserved, :reserved_zero, nil, WmfLayoutRules::COLORREF_CITATION],
103
+ ["wmf.fixed.colorref_palette", :colorref_palette, :reserved_zero,
104
+ "GDI writes PALETTEINDEX and PALETTERGB colors with 0x01 or 0x02 in the Reserved byte, and readers resolve them",
105
+ WmfLayoutRules::COLORREF_CITATION]
106
+ ].each do |id, method, kind, reason, citation|
107
+ Registry.loading.register(id: id, formats: WmfLayoutRules::WMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
108
+ WmfLayoutRules.public_send(method, context)
109
+ end
110
+ end
111
+ end
112
+ end