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,320 @@
1
+ # frozen_string_literal: true
2
+
3
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
4
+
5
+ require_relative "emf_plus_common"
6
+
7
+ # EMF+ payload rules over the public decoded records and objects: the Size and
8
+ # DataSize formulas of the text, image, serializable-object and terminal-server
9
+ # records, the fields those records fix, and the bounds of the object payloads
10
+ # the public model decodes (EmfPlusImage, EmfPlusFont, EmfPlusStringFormat, ...).
11
+ # Findings land on the record that carries the defect; undecodable records are
12
+ # read from their raw payload, so a count that runs past the record still gets a
13
+ # precise message next to emfplus.record.body_bounds.
14
+ module Emf
15
+ module Conformance
16
+ module EmfPlusPayloads
17
+ Records = Emf::EmfPlus::Records
18
+ Objects = Emf::EmfPlus::Objects
19
+ Support = EmfPlusSupport
20
+
21
+ HEADER = 12
22
+ ALIGN = 3 # a record may pad its data to 4 bytes (MS-EMFPLUS 2.3.3)
23
+ DRIVER_STRING_OPTIONS = 0x0000000F
24
+ OBJECT_TYPES = (1..9)
25
+ IMAGE_DATA_TYPES = (0..2)
26
+ BITMAP_DATA_TYPES = (0..1)
27
+ METAFILE_DATA_TYPES = (1..5)
28
+ TS_ENUMS = {
29
+ "AntiAliasMode (SmoothingMode)" => [:anti_alias_mode, (0..5)],
30
+ "TextRenderHint (TextRenderingHint)" => [:text_render_hint, (0..5)],
31
+ "CompositingMode" => [:compositing_mode, (0..1)],
32
+ "CompositingQuality" => [:compositing_quality, (1..5)],
33
+ "TextContrast" => [:text_contrast, (0..12)],
34
+ "FilterType" => [:filter_type, [0, 1, 2, 3, 4, 6, 7]],
35
+ "PixelOffset (PixelOffsetMode)" => [:pixel_offset, (0..4)]
36
+ }.freeze
37
+
38
+ module_function
39
+
40
+ # The record-specific bytes after the 12-byte header, for typed and unknown records alike.
41
+ def payload(record) = record.to_binary.byteslice(HEADER..).to_s
42
+
43
+ def u32(payload, offset) = payload.byteslice(offset, 4)&.unpack1("V")
44
+
45
+ def name(record) = Support.type_name(record)
46
+
47
+ # Size and DataSize each in `minimum..minimum + 3` (the data may carry up to 3 padding bytes), or nil.
48
+ def padded_sizes(record, data_minimum)
49
+ size = Support.size_of(record)
50
+ data_size = Support.data_size_of(record)
51
+ problems = []
52
+ problems << "DataSize is #{data_size}, MUST be #{data_minimum} plus up to 3 padding bytes" unless
53
+ (data_minimum..(data_minimum + ALIGN)).cover?(data_size)
54
+ problems << "Size is #{size}, MUST be #{data_minimum + HEADER} plus up to 3 padding bytes" unless
55
+ ((data_minimum + HEADER)..(data_minimum + HEADER + ALIGN)).cover?(size)
56
+ problems
57
+ end
58
+
59
+ def exact_sizes(record, data_expected)
60
+ size = Support.size_of(record)
61
+ data_size = Support.data_size_of(record)
62
+ problems = []
63
+ problems << "DataSize is #{data_size}, MUST be #{data_expected}" unless data_size == data_expected
64
+ problems << "Size is #{size}, MUST be #{data_expected + HEADER}" unless size == data_expected + HEADER
65
+ problems
66
+ end
67
+
68
+ def sized(located, problems, formula)
69
+ return if problems.empty?
70
+
71
+ "#{name(located.record)} #{problems.join(' and ')} (#{formula})"
72
+ end
73
+
74
+ # --- text -----------------------------------------------------------
75
+
76
+ def driver_string_size(located)
77
+ record = located.record
78
+ return unless record.type == Records::DrawDriverString::TYPE
79
+
80
+ data = payload(record)
81
+ count = u32(data, 12)
82
+ return unless count
83
+
84
+ matrix = u32(data, 8) == 1
85
+ minimum = (count * 10) + 16 + (matrix ? 24 : 0)
86
+ sized(located, padded_sizes(record, minimum),
87
+ "GlyphCount #{count}: DataSize = GlyphCount * 10 + #{matrix ? 40 : 16}")
88
+ end
89
+
90
+ def driver_string_matrix(located)
91
+ return unless located.record.type == Records::DrawDriverString::TYPE
92
+
93
+ value = u32(payload(located.record), 8)
94
+ return if value.nil? || [0, 1].include?(value)
95
+
96
+ "DrawDriverString MatrixPresent is #{value}, MUST be 0 or 1"
97
+ end
98
+
99
+ def driver_string_options(located)
100
+ return unless located.record.type == Records::DrawDriverString::TYPE
101
+
102
+ value = u32(payload(located.record), 4)
103
+ return if value.nil? || value.nobits?(~DRIVER_STRING_OPTIONS)
104
+
105
+ format("DrawDriverString DriverStringOptionsFlags 0x%<value>08X sets bits outside the four DriverStringOptions flags",
106
+ value: value)
107
+ end
108
+
109
+ def draw_string_size(located)
110
+ record = located.record
111
+ return unless record.type == Records::DrawString::TYPE
112
+
113
+ length = u32(payload(record), 8)
114
+ return unless length
115
+
116
+ sized(located, padded_sizes(record, (length * 2) + 28), "Length #{length}: DataSize = Length * 2 + 28")
117
+ end
118
+
119
+ # --- images ---------------------------------------------------------
120
+
121
+ def draw_image_size(located)
122
+ record = located.record
123
+ compressed = record.flags.anybits?(0x4000)
124
+ case record.type
125
+ when Records::DrawImage::TYPE
126
+ sized(located, exact_sizes(record, compressed ? 0x20 : 0x28), "the C flag selects EmfPlusRect or EmfPlusRectF")
127
+ when Records::DrawImagePoints::TYPE
128
+ sized(located, image_points_sizes(record, compressed), "the P and C flags select the point format")
129
+ end
130
+ end
131
+
132
+ # EmfPlusPointR elements are 1 or 2 bytes per coordinate, so for P the text's 0x24 is the writer's figure;
133
+ # a DataSize that matches the decoded points (padded or not) is accepted too.
134
+ def image_points_sizes(record, compressed)
135
+ return exact_sizes(record, compressed ? 0x28 : 0x34) unless record.flags.anybits?(0x0800)
136
+ return exact_sizes(record, 0x24) if Support.data_size_of(record) == 0x24 || !Support.typed?(record)
137
+
138
+ padded_sizes(record, record.body.num_bytes)
139
+ end
140
+
141
+ # --- serializable objects -------------------------------------------
142
+
143
+ def serializable_size(located)
144
+ record = located.record
145
+ return unless record.is_a?(Records::SerializableObject)
146
+
147
+ problems = exact_sizes(record, record.buffer_size + 0x14)
148
+ problems << "BufferSize #{record.buffer_size} is not a multiple of 4" unless (record.buffer_size % 4).zero?
149
+ sized(located, problems, "DataSize = BufferSize + 0x14, Size = BufferSize + 0x20")
150
+ end
151
+
152
+ def serializable_guid(located)
153
+ record = located.record
154
+ return unless record.is_a?(Records::SerializableObject)
155
+ return if Emf::EmfPlus::Effects.lookup(record.object_guid)
156
+
157
+ "SerializableObject ObjectGUID #{record.object_guid} is not an image effect identifier"
158
+ end
159
+
160
+ def serializable_buffer(located)
161
+ record = located.record
162
+ return unless record.is_a?(Records::SerializableObject) && Emf::EmfPlus::Effects.lookup(record.object_guid)
163
+
164
+ effect = record.effect
165
+ return unless effect.respond_to?(:trailing) && !effect.trailing.empty?
166
+
167
+ "SerializableObject Buffer holds #{effect.trailing.bytesize} bytes beyond its #{record.object_guid} effect parameter block"
168
+ rescue Emf::ParseError => e
169
+ "SerializableObject Buffer does not hold its effect parameter block: #{e.message}"
170
+ end
171
+
172
+ # --- terminal server ------------------------------------------------
173
+
174
+ def ts_clip_size(located)
175
+ record = located.record
176
+ return unless record.is_a?(Records::SetTSClip)
177
+
178
+ count = record.rect_count
179
+ if record.compressed?
180
+ # The text fixes 4 bytes per rectangle and also describes 1-2 bytes per coordinate; only the shared lower bound holds.
181
+ return if Support.data_size_of(record) >= count * 4 && Support.size_of(record) >= (count * 4) + HEADER
182
+
183
+ "SetTSClip DataSize #{Support.data_size_of(record)} is below #{count * 4} (NumRects #{count} at 4 bytes each, C set)"
184
+ else
185
+ sized(located, exact_sizes(record, count * 8), "NumRects #{count}, 8 bytes per rectangle (C clear)")
186
+ end
187
+ end
188
+
189
+ def ts_graphics_size(located)
190
+ record = located.record
191
+ return unless record.is_a?(Records::SetTSGraphics)
192
+
193
+ data_size = Support.data_size_of(record)
194
+ problems = exact_sizes(record, record.palette? ? data_size : 36)
195
+ problems.reject! { |problem| problem.start_with?("DataSize") } if record.palette?
196
+ sized(located, problems, record.palette? ? "T set: Size = 0x30 + the Palette size" : "T clear: DataSize 0x24, Size 0x30")
197
+ end
198
+
199
+ def ts_graphics_palette(located)
200
+ record = located.record
201
+ return unless record.is_a?(Records::SetTSGraphics) && record.palette?
202
+ return if exact_palette?(record.palette) && record.decoded_palette
203
+
204
+ "SetTSGraphics Palette of #{record.palette.bytesize} bytes is not exactly one EmfPlusPalette (8 + PaletteCount * 4 bytes)"
205
+ end
206
+
207
+ # PaletteCount sized the whole palette; check it before decoding so a huge count never reaches the array reader.
208
+ def exact_palette?(bytes) = bytes.bytesize >= 8 && bytes.bytesize == 8 + (bytes.unpack1("@4V") * 4)
209
+
210
+ def ts_graphics_enums(located)
211
+ record = located.record
212
+ return unless record.is_a?(Records::SetTSGraphics)
213
+
214
+ TS_ENUMS.filter_map do |label, (reader, domain)|
215
+ value = record.public_send(reader)
216
+ next if domain.include?(value)
217
+
218
+ "SetTSGraphics #{label} value #{value} is outside #{domain_text(domain)}"
219
+ end
220
+ end
221
+
222
+ def domain_text(domain) = domain.is_a?(Range) ? "#{domain.first}..#{domain.last}" : domain.join(", ")
223
+
224
+ # --- object payloads --------------------------------------------------
225
+
226
+ def object_type(located)
227
+ record = located.record
228
+ return unless record.is_a?(Records::Object) && !OBJECT_TYPES.cover?(record.object_type)
229
+
230
+ "EmfPlusObject ObjectType is #{record.object_type}, MUST be 1..9 (Brush..CustomLineCap)"
231
+ end
232
+
233
+ # The object this record completes (a chain's last chunk, or a single record), or nil.
234
+ def completed_object(stream, located)
235
+ record = located.record
236
+ return unless record.is_a?(Records::Object) && record.emf_object_id < Emf::EmfPlus::ObjectTable::SLOTS
237
+
238
+ index = located.handle.stream_index
239
+ object = stream.objects.lookup(record.emf_object_id, at: index + 1)
240
+ object unless object.nil? || object.equal?(stream.objects.lookup(record.emf_object_id, at: index))
241
+ end
242
+
243
+ def object_payload(stream, located)
244
+ logical = completed_object(stream, located)
245
+ return unless logical
246
+
247
+ logical.body
248
+ nil
249
+ rescue Emf::ParseError => e
250
+ "EmfPlusObject (object type #{logical.object_type}, ID #{logical.emf_object_id}) payload does not fit: #{e.message}"
251
+ end
252
+
253
+ def image_fields(stream, located)
254
+ logical = completed_object(stream, located)
255
+ return unless logical&.object_type == Objects::IMAGE
256
+
257
+ body = logical.body
258
+ problems = image_problems(body)
259
+ problems.map { |problem| "EmfPlusImage (object ID #{logical.emf_object_id}) #{problem}" }
260
+ rescue Emf::ParseError
261
+ nil # reported by emfplus.object.payload
262
+ end
263
+
264
+ def image_problems(image)
265
+ problems = []
266
+ problems << "Type #{image.image_type} is not an ImageDataType (0..2)" unless IMAGE_DATA_TYPES.cover?(image.image_type)
267
+ bitmap = image.bitmap
268
+ problems.concat(bitmap_problems(bitmap)) if bitmap
269
+ metafile = image.metafile
270
+ if metafile && !METAFILE_DATA_TYPES.cover?(metafile.metafile_type)
271
+ problems << "metafile Type #{metafile.metafile_type} is not a MetafileDataType (1..5)"
272
+ end
273
+ problems
274
+ end
275
+
276
+ def bitmap_problems(bitmap)
277
+ problems = []
278
+ problems << "bitmap Type #{bitmap.bitmap_type} is not a BitmapDataType (0..1)" unless BITMAP_DATA_TYPES.cover?(bitmap.bitmap_type)
279
+ problems << "bitmap Stride #{bitmap.stride} is not a multiple of 4" if bitmap.bitmap_type.zero? && !(bitmap.stride % 4).zero?
280
+ problems
281
+ end
282
+
283
+ def per_object(context, &)
284
+ stream = context.metafile.emf_plus_stream
285
+ return [] unless stream
286
+
287
+ Support.records(context).flat_map do |located|
288
+ Array(yield(stream, located)).map { |message| Support.finding(located, message) }
289
+ end
290
+ end
291
+ end
292
+
293
+ {
294
+ "emfplus.draw.driver_string" => ["MS-EMFPLUS 2.3.4.6", :driver_string_size, :driver_string_size],
295
+ "emfplus.draw.driver_string_matrix" => ["MS-EMFPLUS 2.3.4.6", :driver_string_matrix_present, :driver_string_matrix],
296
+ "emfplus.draw.driver_string_options" => ["MS-EMFPLUS 2.3.4.6", :driver_string_options, :driver_string_options],
297
+ "emfplus.draw.string" => ["MS-EMFPLUS 2.3.4.14", :draw_string_size, :draw_string_size],
298
+ "emfplus.draw.image" => ["MS-EMFPLUS 2.3.4", :draw_image_size, :draw_image_size],
299
+ "emfplus.object.serializable" => ["MS-EMFPLUS 2.3.5.2", :serializable_size, :serializable_size],
300
+ "emfplus.object.serializable_guid" => ["MS-EMFPLUS 2.3.5.2", :serializable_guid, :serializable_guid],
301
+ "emfplus.object.serializable_buffer" => ["MS-EMFPLUS 2.3.5.2", :serializable_buffer, :serializable_buffer],
302
+ "emfplus.terminal.clip" => ["MS-EMFPLUS 2.3.8.1", :ts_clip_size, :ts_clip_size],
303
+ "emfplus.terminal.graphics" => ["MS-EMFPLUS 2.3.8.2", :ts_graphics_size, :ts_graphics_size],
304
+ "emfplus.terminal.graphics_palette" => ["MS-EMFPLUS 2.3.8.2", :ts_graphics_palette, :ts_graphics_palette],
305
+ "emfplus.terminal.graphics_enums" => ["MS-EMFPLUS 2.3.8.2", :ts_graphics_enums, :ts_graphics_enums],
306
+ "emfplus.object.type" => ["MS-EMFPLUS 2.3.5.1", :object_type, :object_type]
307
+ }.each do |id, (citation, kind, check)|
308
+ EmfPlusSupport.per_record(id: id, citation: citation, kind: kind) { |located| EmfPlusPayloads.public_send(check, located) }
309
+ end
310
+
311
+ {
312
+ "emfplus.object.payload" => ["MS-EMFPLUS 2.3.5.1", :object_payload, :object_payload],
313
+ "emfplus.image.fields" => ["MS-EMFPLUS 2.2.1.4", :image_fields, :image_fields]
314
+ }.each do |id, (citation, kind, check)|
315
+ Registry.loading.register(id: id, formats: [:emf_plus], citation: citation, kind: kind, tolerant_reason: nil) do |context|
316
+ EmfPlusPayloads.per_object(context) { |stream, located| EmfPlusPayloads.public_send(check, stream, located) }
317
+ end
318
+ end
319
+ end
320
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
4
+
5
+ require_relative "emf_plus_common"
6
+
7
+ # EMF+ property, clip combine-mode and page-unit rules: enum domains and ranges
8
+ # of values the public records decode from Flags.
9
+ module Emf
10
+ module Conformance
11
+ module EmfPlusProperties
12
+ Records = Emf::EmfPlus::Records
13
+ Support = EmfPlusSupport
14
+
15
+ # record class => [decoded reader, allowed values, enumeration name] (MS-EMFPLUS 2.1.1 enumerations).
16
+ ENUMS = {
17
+ Records::SetAntiAliasMode => [:smoothing_mode, (0..5), "SmoothingMode"],
18
+ Records::SetTextRenderingHint => [:text_rendering_hint, (0..5), "TextRenderingHint"],
19
+ Records::SetInterpolationMode => [:interpolation_mode, (0..7), "InterpolationMode"],
20
+ Records::SetPixelOffsetMode => [:pixel_offset_mode, (0..4), "PixelOffsetMode"],
21
+ Records::SetCompositingMode => [:compositing_mode, (0..1), "CompositingMode"],
22
+ Records::SetClipRect => [:combine_mode, (0..5), "CombineMode"],
23
+ Records::SetClipPath => [:combine_mode, (0..5), "CombineMode"],
24
+ Records::SetClipRegion => [:combine_mode, (0..5), "CombineMode"]
25
+ }.freeze
26
+ QUALITY = [:compositing_quality, (1..5), "CompositingQuality"].freeze
27
+ UNIT_TYPE = (0..6)
28
+ UNIT_WORLD_OR_DISPLAY = (0..1)
29
+ CONTRAST = (1000..2200)
30
+
31
+ module_function
32
+
33
+ def enum_domain(located, table = ENUMS)
34
+ record = located.record
35
+ reader, domain, name = table.find { |klass, _| record.is_a?(klass) }&.last
36
+ return unless reader
37
+
38
+ value = record.public_send(reader)
39
+ return if domain.cover?(value)
40
+
41
+ "#{Support.type_name(record)} #{name} value #{value} is outside #{domain.first}..#{domain.last}"
42
+ end
43
+
44
+ def compositing_quality(located) = enum_domain(located, { Records::SetCompositingQuality => QUALITY })
45
+
46
+ def text_contrast(located)
47
+ record = located.record
48
+ return unless record.is_a?(Records::SetTextContrast) && !CONTRAST.cover?(record.text_contrast)
49
+
50
+ "EmfPlusSetTextContrast TextContrast #{record.text_contrast} is outside 1000..2200"
51
+ end
52
+
53
+ def page_unit_value(record)
54
+ case record
55
+ when Records::SetPageTransform, Records::BeginContainer then record.page_unit
56
+ end
57
+ end
58
+
59
+ def page_unit_domain(located)
60
+ unit = page_unit_value(located.record)
61
+ return if unit.nil? || UNIT_TYPE.cover?(unit)
62
+
63
+ "#{Support.type_name(located.record)} PageUnit #{unit} is not a UnitType value (0..6)"
64
+ end
65
+
66
+ def page_unit_discouraged(located)
67
+ unit = page_unit_value(located.record)
68
+ return unless unit && UNIT_WORLD_OR_DISPLAY.cover?(unit)
69
+
70
+ "#{Support.type_name(located.record)} PageUnit #{unit} (UnitType#{unit.zero? ? 'World' : 'Display'}) SHOULD NOT be used"
71
+ end
72
+ end
73
+
74
+ EmfPlusSupport.per_record(id: "emfplus.property.enum", citation: "MS-EMFPLUS 2.3.6", kind: :property_enum) do |located|
75
+ EmfPlusProperties.enum_domain(located)
76
+ end
77
+ # Only CompositingQuality is waived: real producers write 0 (spec/fixtures/emf/test-000.emf) and Windows treats any
78
+ # invalid value as CompositingQualityDefault (MS-EMFPLUS product behavior note 3).
79
+ EmfPlusSupport.per_record(id: "emfplus.property.compositing_quality", citation: "MS-EMFPLUS 2.3.6.4",
80
+ kind: :property_enum,
81
+ tolerant_reason: "a real producer writes CompositingQuality 0 (spec/fixtures/emf/test-000.emf) " \
82
+ "and Windows treats an invalid value as CompositingQualityDefault") do |located|
83
+ EmfPlusProperties.compositing_quality(located)
84
+ end
85
+ EmfPlusSupport.per_record(id: "emfplus.property.text_contrast", citation: "MS-EMFPLUS 2.3.6.7", kind: :text_contrast) do |located|
86
+ EmfPlusProperties.text_contrast(located)
87
+ end
88
+ EmfPlusSupport.per_record(id: "emfplus.transform.page_unit", citation: "MS-EMFPLUS 2.3.9", kind: :page_unit) do |located|
89
+ EmfPlusProperties.page_unit_domain(located)
90
+ end
91
+ EmfPlusSupport.per_record(id: "emfplus.transform.page_unit_discouraged", citation: "MS-EMFPLUS 2.3.9",
92
+ kind: :page_unit_discouraged, severity: :warning) do |located|
93
+ EmfPlusProperties.page_unit_discouraged(located)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
4
+
5
+ require_relative "emf_plus_common"
6
+
7
+ # Save/Restore and BeginContainer/EndContainer matching over the record sequence.
8
+ # Restore and EndContainer name a StackIndex a previous Save or Begin record
9
+ # assigned (MS-EMFPLUS 2.3.7); the two are separate stacks in the spec's wording.
10
+ module Emf
11
+ module Conformance
12
+ module EmfPlusState
13
+ Records = Emf::EmfPlus::Records
14
+ Support = EmfPlusSupport
15
+
16
+ # id => [opener classes, closer class, closer name, opener name, which defects the rule reports].
17
+ # :unknown is a StackIndex no earlier opener ever assigned; :stale is one that was assigned and is already
18
+ # popped (or skipped over). Only the Restore rule splits them; EndContainer reports both.
19
+ PAIRS = {
20
+ "emfplus.state.restore_unmatched" =>
21
+ [[Records::Save], Records::Restore, "EmfPlusRestore", "EmfPlusSave", :stale],
22
+ "emfplus.state.end_container_unmatched" =>
23
+ [[Records::BeginContainer, Records::BeginContainerNoParams], Records::EndContainer, "EmfPlusEndContainer",
24
+ "a begin-container record", :any]
25
+ }.freeze
26
+
27
+ # LibreOffice's TestEmfPlusSave.emf restores StackIndex 0 before any EmfPlusSave. GDI+ fails such a restore and
28
+ # draws on, so the tolerant profile warns; a restore of a state that was saved and already popped stays an error.
29
+ UNKNOWN_RESTORE = "emfplus.state.restore_unknown_state"
30
+ UNKNOWN_RESTORE_REASON = "a Restore naming a StackIndex no earlier EmfPlusSave assigned fails in GDI+ and is " \
31
+ "ignored; the rest of the stream still renders"
32
+
33
+ module_function
34
+
35
+ # Restoring (or ending) index N pops every level pushed after N's push, like GDI+.
36
+ def unmatched(context, opens, closer, closer_name, opener_name, report = :any)
37
+ stack = []
38
+ seen = {}
39
+ open_count = Hash.new(0)
40
+ Support.records(context).filter_map do |located|
41
+ record = located.record
42
+ if opens.any? { |klass| record.is_a?(klass) }
43
+ stack.push(record.stack_index)
44
+ open_count[record.stack_index] += 1
45
+ seen[record.stack_index] = true
46
+ next
47
+ end
48
+ next unless record.is_a?(closer)
49
+
50
+ # open_count keeps an unmatched index O(1) instead of a full-stack scan.
51
+ if open_count[record.stack_index].positive?
52
+ pop_to(stack, open_count, record.stack_index)
53
+ next
54
+ end
55
+ unknown = !seen.key?(record.stack_index)
56
+ next if (report == :unknown && !unknown) || (report == :stale && unknown)
57
+
58
+ Support.finding(located, "#{closer_name} StackIndex #{record.stack_index} matches no open #{opener_name}")
59
+ end
60
+ end
61
+
62
+ def pop_to(stack, open_count, index)
63
+ stack.slice!(stack.rindex(index)..).each { |popped| open_count[popped] -= 1 }
64
+ end
65
+ end
66
+
67
+ EmfPlusState::PAIRS.each do |id, args|
68
+ Registry.loading.register(id: id, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.7", kind: :state_matching,
69
+ tolerant_reason: nil) { |context| EmfPlusState.unmatched(context, *args) }
70
+ end
71
+
72
+ Registry.loading.register(id: EmfPlusState::UNKNOWN_RESTORE, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.7",
73
+ kind: :state_matching, tolerant_reason: EmfPlusState::UNKNOWN_RESTORE_REASON) do |context|
74
+ EmfPlusState.unmatched(context, *EmfPlusState::PAIRS.fetch("emfplus.state.restore_unmatched")[0..3], :unknown)
75
+ end
76
+ end
77
+ end