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,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
4
+
5
+ require_relative "emf_plus_common"
6
+
7
+ # Per-field enumeration and range rules for the decoded EMF+ graphics objects
8
+ # (MS-EMFPLUS 2.2.1). CHECKS is a table of object type => check lambda; each
9
+ # lambda gets the decoded object and returns the problems it finds. Only a
10
+ # clause that says a value "MUST" be, or "is defined in", an enumeration is an
11
+ # error here; the bounds of the payload are emfplus.object.payload's.
12
+ module Emf
13
+ module Conformance
14
+ module EmfPlusFields
15
+ Objects = Emf::EmfPlus::Objects
16
+ Support = EmfPlusSupport
17
+
18
+ # Enumerations, MS-EMFPLUS 2.1.1. LineCapType lists AnchorMask (0xF0) among its values, and a real producer writes it
19
+ # as an EndCap (LibreOffice emfio TestEmfPlusDrawLineWithCaps).
20
+ BRUSH_TYPE = (0..4)
21
+ HATCH_STYLE = (0..0x34)
22
+ WRAP_MODE = (0..4)
23
+ UNIT_TYPE = (0..6)
24
+ LINE_CAP = [0, 1, 2, 3, 0x10, 0x11, 0x12, 0x13, 0x14, 0xF0, 0xFF].freeze
25
+ LINE_JOIN = (0..3)
26
+ LINE_STYLE = (0..5)
27
+ DASHED_LINE_CAP = [0, 2, 3].freeze # DashedLineCapType has no value 1
28
+ PEN_ALIGNMENT = (0..4)
29
+ CUSTOM_LINE_CAP_TYPE = (0..1)
30
+ # Flags, MS-EMFPLUS 2.1.2: every defined bit.
31
+ BRUSH_DATA_FLAGS = 0x1DF # BrushData 0x01..0x10, 0x40, 0x80, 0x100 (0x20 is undefined)
32
+ PEN_DATA_FLAGS = 0x1FFF
33
+ CUSTOM_LINE_CAP_DATA_FLAGS = 0x3
34
+
35
+ module_function
36
+
37
+ def hex(value) = format("0x%X", value)
38
+
39
+ def in_domain(label, value, domain, name)
40
+ return if value.nil?
41
+
42
+ ok = domain.is_a?(Array) ? domain.include?(value) : domain.cover?(value)
43
+ return if ok
44
+
45
+ shown = domain.is_a?(Array) ? domain.map { |v| hex(v) }.join(", ") : "#{hex(domain.first)}..#{hex(domain.last)}"
46
+ "#{label} #{value} is not a #{name} value (#{shown})"
47
+ end
48
+
49
+ def flags_within(label, value, mask, name)
50
+ return if value.nil? || value.nobits?(~mask)
51
+
52
+ "#{label} #{hex(value)} sets bits outside the defined #{name} flags (#{hex(mask)})"
53
+ end
54
+
55
+ def zero_point(label, point)
56
+ return if point.nil? || (point.x.to_f.zero? && point.y.to_f.zero?)
57
+
58
+ "#{label} is {#{point.x}, #{point.y}}, MUST be {0.0, 0.0}"
59
+ end
60
+
61
+ # --- Brush, Pen, CustomLineCap ------------------------------------------
62
+
63
+ def brush(object)
64
+ type = object.brush_type
65
+ problems = [in_domain("EmfPlusBrush Type", type, BRUSH_TYPE, "BrushType")]
66
+ data = object.data
67
+ case type
68
+ when Objects::Brush::HATCH
69
+ problems << in_domain("EmfPlusHatchBrushData HatchStyle", data.hatch_style, HATCH_STYLE, "HatchStyle")
70
+ when Objects::Brush::TEXTURE, Objects::Brush::PATH_GRADIENT, Objects::Brush::LINEAR_GRADIENT
71
+ problems << flags_within("BrushDataFlags", data.brush_data_flags, BRUSH_DATA_FLAGS, "BrushData")
72
+ problems << in_domain("WrapMode", data.wrap_mode, WRAP_MODE, "WrapMode")
73
+ end
74
+ problems
75
+ end
76
+
77
+ def pen(object)
78
+ flags = object.pen_data_flags
79
+ [("EmfPlusPen Type is #{object.pen_type}, MUST be 0" unless object.pen_type.zero?),
80
+ flags_within("PenDataFlags", flags, PEN_DATA_FLAGS, "PenData"),
81
+ in_domain("PenUnit", object.pen_unit, UNIT_TYPE, "UnitType"),
82
+ in_domain("StartCap", object.start_cap, LINE_CAP, "LineCapType"),
83
+ in_domain("EndCap", object.end_cap, LINE_CAP, "LineCapType"),
84
+ in_domain("PenAlignment", object.pen_alignment, PEN_ALIGNMENT, "PenAlignment"),
85
+ *[object.custom_start_cap, object.custom_end_cap].compact.flat_map { |cap| custom_line_cap(cap) }]
86
+ end
87
+
88
+ # Join, LineStyle and DashedLineCapType: corrupt-by-design LibreOffice fuzz regressions write garbage here and
89
+ # are expected to stay readable, so this group alone is waived in tolerant (see PEN_LINE_WAIVER).
90
+ def pen_line(object)
91
+ [in_domain("Join", object.join, LINE_JOIN, "LineJoinType"),
92
+ in_domain("LineStyle", object.line_style, LINE_STYLE, "LineStyle"),
93
+ in_domain("DashedLineCapType", object.dashed_line_cap, DASHED_LINE_CAP, "DashedLineCapType")]
94
+ end
95
+
96
+ def custom_line_cap(object)
97
+ data = object.data
98
+ problems = [in_domain("EmfPlusCustomLineCap Type", object.cap_type, CUSTOM_LINE_CAP_TYPE, "CustomLineCapDataType")]
99
+ case object.cap_type
100
+ when Objects::CustomLineCap::DEFAULT
101
+ problems.concat(custom_line_cap_default(data))
102
+ when Objects::CustomLineCap::ADJUSTABLE_ARROW
103
+ problems.concat(custom_line_cap_arrow(data))
104
+ end
105
+ problems
106
+ end
107
+
108
+ def custom_line_cap_default(data)
109
+ [flags_within("CustomLineCapDataFlags", data.custom_line_cap_data_flags, CUSTOM_LINE_CAP_DATA_FLAGS, "CustomLineCapData"),
110
+ in_domain("BaseCap", data.base_cap, LINE_CAP, "LineCapType"),
111
+ in_domain("StrokeStartCap", data.stroke_start_cap, LINE_CAP, "LineCapType"),
112
+ in_domain("StrokeEndCap", data.stroke_end_cap, LINE_CAP, "LineCapType"),
113
+ in_domain("StrokeJoin", data.stroke_join, LINE_JOIN, "LineJoinType"),
114
+ zero_point("FillHotSpot", data.fill_hot_spot), zero_point("StrokeHotSpot", data.stroke_hot_spot)]
115
+ end
116
+
117
+ def custom_line_cap_arrow(data)
118
+ [in_domain("LineStartCap", data.line_start_cap, LINE_CAP, "LineCapType"),
119
+ in_domain("LineEndCap", data.line_end_cap, LINE_CAP, "LineCapType"),
120
+ in_domain("LineJoin", data.line_join, LINE_JOIN, "LineJoinType"),
121
+ zero_point("FillHotSpot", data.fill_hot_spot), zero_point("LineHotSpot", data.line_hot_spot)]
122
+ end
123
+
124
+ # Object type => check. Each group below owns its own entries.
125
+ CHECKS = {
126
+ # Brush, Pen, CustomLineCap
127
+ Objects::BRUSH => method(:brush),
128
+ Objects::PEN => method(:pen),
129
+ Objects::CUSTOM_LINE_CAP => method(:custom_line_cap),
130
+ # Path, Region, Font, StringFormat, ImageAttributes
131
+ **[Objects::PATH, Objects::REGION, Objects::FONT, Objects::STRING_FORMAT, Objects::IMAGE_ATTRIBUTES]
132
+ .to_h { |type| [type, ->(object) { EmfPlusFieldsB.findings_for(object) }] }
133
+ }.freeze
134
+
135
+ # Same shape as CHECKS, for the fields a real corpus file violates.
136
+ WAIVED_CHECKS = {
137
+ # Brush, Pen, CustomLineCap
138
+ Objects::PEN => method(:pen_line)
139
+ }.freeze
140
+
141
+ PEN_LINE_WAIVER = "fuzz-regression files LibreOffice ships as readable inputs write out-of-domain pen Join / " \
142
+ "DashedLineCapType values (vcl graphicfilter emf_fail_crash-2, emf_pass_crash-1, emf_fail_hang-1, " \
143
+ "emf_fail_hang-2); importers ignore them and keep drawing"
144
+
145
+ def object_fields(stream, located, table = CHECKS)
146
+ logical = EmfPlusPayloads.completed_object(stream, located)
147
+ check = logical && table[logical.object_type]
148
+ return unless check
149
+
150
+ label = "EmfPlusObject (object type #{logical.object_type}, ID #{logical.emf_object_id})"
151
+ check.call(logical.body).compact.map { |problem| "#{label} #{problem}" }
152
+ rescue Emf::ParseError
153
+ nil # reported by emfplus.object.payload
154
+ end
155
+ end
156
+
157
+ Registry.loading.register(id: "emfplus.object.fields", formats: [:emf_plus], citation: "MS-EMFPLUS 2.2.1",
158
+ kind: :object_fields, tolerant_reason: nil) do |context|
159
+ EmfPlusPayloads.per_object(context) { |stream, located| EmfPlusFields.object_fields(stream, located) }
160
+ end
161
+
162
+ Registry.loading.register(id: "emfplus.object.pen_line_fields", formats: [:emf_plus], citation: "MS-EMFPLUS 2.2.2.34",
163
+ kind: :object_pen_line, tolerant_reason: EmfPlusFields::PEN_LINE_WAIVER) do |context|
164
+ EmfPlusPayloads.per_object(context) do |stream, located|
165
+ EmfPlusFields.object_fields(stream, located, EmfPlusFields::WAIVED_CHECKS)
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
4
+
5
+ # Field-domain checks of the Path, Region, Font, StringFormat and ImageAttributes graphics objects
6
+ # (MS-EMFPLUS 2.2.1.3, 2.2.1.5, 2.2.1.6, 2.2.1.8, 2.2.1.9). No rule is registered here: the shared
7
+ # "emfplus.object.fields" rule (emf_plus_fields.rb) calls findings_for(object) for each completed object.
8
+ # Only MUST-level domains are reported. SHOULD-level text (HotkeyPrefix/StringFormat flag descriptions,
9
+ # "Windows never uses UnitTypeDisplay" in Font SizeUnit) is not an error and is not reported.
10
+ module Emf
11
+ module Conformance
12
+ module EmfPlusFieldsB
13
+ Objects = Emf::EmfPlus::Objects
14
+
15
+ UNIT_TYPE = (0..6) # 2.1.1.32; 2.2.1.3 SizeUnit "is in the UnitType enumeration"
16
+ FONT_STYLE_MASK = 0xF # 2.1.2.4: Bold, Italic, Underline, Strikeout
17
+ PATH_POINT_TYPES = [0x0, 0x1, 0x3].freeze # 2.1.1.22: Start, Line, Bezier
18
+ PATH_POINT_FLAGS_MASK = 0x0B # 2.1.2.6 (high nibble): DashMode 0x1, PathMarker 0x2, CloseSubpath 0x8
19
+ WRAP_MODE = (0..4) # 2.1.1.33
20
+ OBJECT_CLAMP = (0..1) # 2.2.1.5 "MUST be one of": RectClamp, BitmapClamp
21
+ STRING_ALIGNMENT = (0..2) # 2.1.1.28
22
+ DIGIT_SUBSTITUTION = (0..3) # 2.1.1.29
23
+ HOTKEY_PREFIX = (0..2) # 2.1.1.14
24
+ STRING_TRIMMING = (0..5) # 2.1.1.30
25
+ # 2.1.2.8: DirectionRightToLeft, DirectionVertical, NoFitBlackBox, DisplayFormatControl, NoFontFallback,
26
+ # MeasureTrailingSpaces, NoWrap, LineLimit, NoClip, BypassGDI.
27
+ STRING_FORMAT_FLAGS_MASK = 0x1 | 0x2 | 0x4 | 0x20 | 0x400 | 0x800 | 0x1000 | 0x2000 | 0x4000 | 0x80000000
28
+
29
+ module_function
30
+
31
+ # `object` is a completed logical object (object_type, body) or an already decoded Objects::Typed.
32
+ def findings_for(object)
33
+ typed = object.is_a?(Objects::Typed) ? object : object.body
34
+ case typed
35
+ when Objects::Path then path(typed)
36
+ when Objects::Region then region(typed)
37
+ when Objects::Font then font(typed)
38
+ when Objects::StringFormat then string_format(typed)
39
+ when Objects::ImageAttributes then image_attributes(typed)
40
+ else []
41
+ end
42
+ end
43
+
44
+ def domain(label, value, allowed)
45
+ return [] if allowed.cover?(value)
46
+
47
+ ["#{label} #{value} is outside #{allowed.first}..#{allowed.last}"]
48
+ end
49
+
50
+ def path(path)
51
+ types = path.point_types.flat_map { |entry| entry.respond_to?(:point_type) ? [entry.point_type] : [entry] }
52
+ types.flat_map { |type| path_point_type(type) }.uniq.map { |message| "EmfPlusPath #{message}" }
53
+ end
54
+
55
+ def path_point_type(type)
56
+ messages = []
57
+ messages << "PathPointType #{type.type} is not Start (0), Line (1) or Bezier (3)" unless PATH_POINT_TYPES.include?(type.type)
58
+ unless type.flags.nobits?(~PATH_POINT_FLAGS_MASK)
59
+ messages << "PathPointType flags 0x#{format('%X', type.flags)} hold bits other than DashMode, PathMarker and CloseSubpath"
60
+ end
61
+ messages
62
+ end
63
+
64
+ # 2.2.1.8: RegionNode is an array of RegionNodeCount + 1 nodes; the tree read from the data must hold exactly that many.
65
+ def region(region)
66
+ expected = region.node_count + 1
67
+ actual = region.root.node_count
68
+ return [] if actual == expected
69
+
70
+ ["EmfPlusRegion RegionNodeCount #{region.node_count} declares #{expected} nodes but the node tree holds #{actual}"]
71
+ end
72
+
73
+ def font(font)
74
+ messages = domain("EmfPlusFont SizeUnit", font.size_unit, UNIT_TYPE)
75
+ unless font.style_flags.nobits?(~FONT_STYLE_MASK)
76
+ messages << "EmfPlusFont FontStyleFlags 0x#{format('%X',
77
+ font.style_flags & 0xFFFFFFFF)} sets undefined bits"
78
+ end
79
+ messages
80
+ end
81
+
82
+ def string_format(object)
83
+ body = object.body
84
+ messages = []
85
+ messages.concat(domain("EmfPlusStringFormat StringAlignment", body.string_alignment, STRING_ALIGNMENT))
86
+ messages.concat(domain("EmfPlusStringFormat LineAlign", body.line_align, STRING_ALIGNMENT))
87
+ messages.concat(domain("EmfPlusStringFormat DigitSubstitution", body.digit_substitution, DIGIT_SUBSTITUTION))
88
+ messages.concat(domain("EmfPlusStringFormat HotkeyPrefix", body.hotkey_prefix, HOTKEY_PREFIX))
89
+ messages.concat(domain("EmfPlusStringFormat Trimming", body.trimming, STRING_TRIMMING))
90
+ unless object.flags.nobits?(~STRING_FORMAT_FLAGS_MASK)
91
+ messages << "EmfPlusStringFormat StringFormatFlags 0x#{format('%X', object.flags)} sets bits that are not StringFormat flags"
92
+ end
93
+ messages
94
+ end
95
+
96
+ def image_attributes(attributes)
97
+ domain("EmfPlusImageAttributes WrapMode", attributes.wrap_mode, WRAP_MODE) +
98
+ domain("EmfPlusImageAttributes ObjectClamp", attributes.object_clamp, OBJECT_CLAMP)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,172 @@
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+ framing and control rules beyond emf_plus.rb: fixed Size/DataSize values,
8
+ # DataSize consistency, undecodable bodies, the Header signature and
9
+ # EndOfFile placement.
10
+ module Emf
11
+ module Conformance
12
+ module EmfPlusFraming
13
+ Records = Emf::EmfPlus::Records
14
+ Support = EmfPlusSupport
15
+
16
+ # DataSize each record type MUST have (Size is then 12 + DataSize), as stated in MS-EMFPLUS 2.3.x.
17
+ FIXED_DATA_SIZE = {
18
+ Records::OffsetClip => 8, Records::ResetClip => 0, Records::SetClipPath => 0, Records::SetClipRect => 16,
19
+ Records::SetClipRegion => 0, Records::EndOfFile => 0, Records::GetDC => 0, Records::Header => 16,
20
+ Records::Clear => 4, Records::DrawPath => 4, Records::FillPath => 4, Records::FillRegion => 4,
21
+ Records::SetAntiAliasMode => 0, Records::SetCompositingMode => 0, Records::SetCompositingQuality => 0,
22
+ Records::SetInterpolationMode => 0, Records::SetPixelOffsetMode => 0, Records::SetRenderingOrigin => 8,
23
+ Records::SetTextContrast => 0, Records::SetTextRenderingHint => 0, Records::BeginContainer => 36,
24
+ Records::BeginContainerNoParams => 4, Records::EndContainer => 4, Records::Restore => 4, Records::Save => 4,
25
+ Records::MultiplyWorldTransform => 24, Records::ResetWorldTransform => 0, Records::RotateWorldTransform => 4,
26
+ Records::ScaleWorldTransform => 8, Records::SetPageTransform => 4, Records::SetWorldTransform => 24,
27
+ Records::TranslateWorldTransform => 8
28
+ }.transform_keys { |klass| klass::TYPE }.freeze
29
+ # The spec gives SetAntiAliasMode a DataSize only, not a Size.
30
+ SIZE_UNSPECIFIED = [Records::SetAntiAliasMode::TYPE].freeze
31
+
32
+ # Records whose Size and DataSize a formula rule states exactly: type => [rule module, check method]. Where that
33
+ # check names a DataSize problem, the generic emfplus.record.data_size and emfplus.record.body_bounds stay silent.
34
+ FORMULA_OWNERS = {
35
+ Records::DrawEllipse::TYPE => %i[EmfPlusDrawing ellipse_size], Records::FillEllipse::TYPE => %i[EmfPlusDrawing ellipse_size],
36
+ Records::DrawDriverString::TYPE => %i[EmfPlusPayloads driver_string_size],
37
+ Records::DrawString::TYPE => %i[EmfPlusPayloads draw_string_size],
38
+ Records::DrawImage::TYPE => %i[EmfPlusPayloads draw_image_size],
39
+ Records::DrawImagePoints::TYPE => %i[EmfPlusPayloads draw_image_size],
40
+ Records::SerializableObject::TYPE => %i[EmfPlusPayloads serializable_size],
41
+ Records::SetTSClip::TYPE => %i[EmfPlusPayloads ts_clip_size], Records::SetTSGraphics::TYPE => %i[EmfPlusPayloads ts_graphics_size]
42
+ }.freeze
43
+
44
+ module_function
45
+
46
+ def formula_owned?(located)
47
+ owner, check = FORMULA_OWNERS[located.record.type]
48
+ owner && Conformance.const_get(owner).public_send(check, located).to_s.include?("DataSize")
49
+ end
50
+
51
+ def fixed_size(located)
52
+ record = located.record
53
+ expected = FIXED_DATA_SIZE[record.type]
54
+ return unless expected
55
+
56
+ return if page_transform_bodyless?(record)
57
+
58
+ size = Support.size_of(record)
59
+ data_size = Support.data_size_of(record)
60
+ problems = []
61
+ problems << "DataSize is #{data_size}, MUST be #{expected}" unless data_size == expected
62
+ problems << "Size is #{size}, MUST be #{expected + 12}" unless size == expected + 12 || SIZE_UNSPECIFIED.include?(record.type)
63
+ "#{Support.type_name(record)} #{problems.join(' and ')}" unless problems.empty?
64
+ end
65
+
66
+ # The exact body-less SetPageTransform: Size 12, DataSize 0 (the spec says Size 16, DataSize 4).
67
+ def page_transform_bodyless?(record)
68
+ record.type == Records::SetPageTransform::TYPE && Support.data_size_of(record).zero? && Support.size_of(record) == 12
69
+ end
70
+
71
+ def page_transform_bodyless(located)
72
+ record = located.record
73
+ return unless page_transform_bodyless?(record)
74
+
75
+ "#{Support.type_name(record)} has Size 12 and DataSize 0, MUST be Size 16 and DataSize 4 (its PageScale is missing)"
76
+ end
77
+
78
+ # A defined record whose fields (padded to 4) decode to fewer bytes than DataSize leaves bytes unaccounted for.
79
+ # DataSize is 32-bit aligned (MS-EMFPLUS 2.3.3), so padding after an odd-length field is not a surplus.
80
+ def data_size_mismatch(located)
81
+ record = located.record
82
+ return unless Support.typed?(record) && !FIXED_DATA_SIZE.key?(record.type)
83
+
84
+ used = record.body.num_bytes
85
+ # An unaligned DataSize is emfplus.record.data_size_alignment's; compare what it rounds up to.
86
+ return if ((record.data_size + 3) & ~3) == ((used + 3) & ~3) || formula_owned?(located)
87
+
88
+ "#{Support.type_name(record)} DataSize is #{record.data_size} but its fields occupy #{used} bytes"
89
+ end
90
+
91
+ def data_size_alignment(located)
92
+ data_size = Support.data_size_of(located.record)
93
+ return if (data_size % 4).zero?
94
+
95
+ "#{Support.type_name(located.record)} DataSize #{data_size} is not a multiple of 4"
96
+ end
97
+
98
+ def undecodable(located)
99
+ record = located.record
100
+ return if Support.typed?(record) || !Records.registry.key?(record.type) || formula_owned?(located)
101
+
102
+ "#{Support.type_name(record)} body does not fit in its DataSize #{record.frame.data_size} " \
103
+ "(a count or length field runs past the record)"
104
+ end
105
+
106
+ def header_signature(located)
107
+ record = located.record
108
+ return unless record.is_a?(Records::Header) && !record.valid_signature?
109
+
110
+ format("EmfPlusHeader MetafileSignature is 0x%05X, MUST be 0xDBC01", record.graphics_signature)
111
+ end
112
+
113
+ def eof_findings(context)
114
+ located = Support.records(context)
115
+ return [] if located.empty?
116
+
117
+ eof_at = located.rindex { |item| item.record.is_a?(Records::EndOfFile) }
118
+ return [missing_eof(located.last)] unless eof_at
119
+
120
+ eof = located.fetch(eof_at)
121
+ problems = []
122
+ problems << "records follow it in the stream" if eof_at < located.size - 1
123
+ problems << "its EMR_COMMENT is not the record immediately before EMR_EOF" unless last_comment?(context, eof)
124
+ if problems.empty?
125
+ []
126
+ else
127
+ [Support.finding(eof,
128
+ "EmfPlusEndOfFile MUST be the last EMF+ record, in the EMF record before EMR_EOF; #{problems.join(' and ')}")]
129
+ end
130
+ end
131
+
132
+ def missing_eof(last)
133
+ Support.finding(last, "no EmfPlusEndOfFile: the last EMF+ record MUST be EmfPlusEndOfFile")
134
+ end
135
+
136
+ def last_comment?(context, eof)
137
+ context.records.size >= 2 && eof.comment_index == context.records.size - 2
138
+ end
139
+ end
140
+
141
+ registry = Registry.loading
142
+ EmfPlusSupport.per_record(id: "emfplus.record.fixed_size", citation: "MS-EMFPLUS 2.3", kind: :record_fixed_size) do |located|
143
+ EmfPlusFraming.fixed_size(located)
144
+ end
145
+ EmfPlusSupport.per_record(id: "emfplus.record.page_transform_bodyless", citation: "MS-EMFPLUS 2.3",
146
+ kind: :record_page_transform_bodyless,
147
+ tolerant_reason: "LibreOffice's TestAlignRtlReading and tdf93750 carry a body-less SetPageTransform and " \
148
+ "import; the record is kept with its bytes intact") do |located|
149
+ EmfPlusFraming.page_transform_bodyless(located)
150
+ end
151
+ EmfPlusSupport.per_record(id: "emfplus.record.data_size", citation: "MS-EMFPLUS 2.3", kind: :record_data_size,
152
+ tolerant_reason: "the surplus bytes stay in the record's trailing data, so nothing is dropped") do |located|
153
+ EmfPlusFraming.data_size_mismatch(located)
154
+ end
155
+ EmfPlusSupport.per_record(id: "emfplus.record.data_size_alignment", citation: "MS-EMFPLUS 2.3.3",
156
+ kind: :record_data_size_alignment,
157
+ tolerant_reason: "DataSize is informational once Size is aligned; the record frames and decodes") do |located|
158
+ EmfPlusFraming.data_size_alignment(located)
159
+ end
160
+ EmfPlusSupport.per_record(id: "emfplus.record.body_bounds", citation: "MS-EMFPLUS 2.3", kind: :record_body_bounds,
161
+ tolerant_reason: "the record is kept as an unknown record with its bytes intact") do |located|
162
+ EmfPlusFraming.undecodable(located)
163
+ end
164
+ EmfPlusSupport.per_record(id: "emfplus.header.signature", citation: "MS-EMFPLUS 2.2.2.19", kind: :header_signature) do |located|
165
+ EmfPlusFraming.header_signature(located)
166
+ end
167
+ registry.register(id: "emfplus.eof.placement", formats: [:emf_plus], citation: "MS-EMFPLUS 1.3", kind: :eof_placement,
168
+ tolerant_reason: "records after the EMF+ end of file stay readable and are kept") do |context|
169
+ EmfPlusFraming.eof_findings(context)
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
4
+
5
+ require_relative "emf_plus_common"
6
+
7
+ # Turns the public object-resolution results (Stream#resolve -> ObjectTable::Resolution)
8
+ # into diagnostics at the referring record. The resolver itself is not copied.
9
+ # Object-definition problems (incomplete chains, definition IDs out of range) are
10
+ # ObjectTable#errors, which emf_plus.rb already reports as emfplus.stream.framing.
11
+ module Emf
12
+ module Conformance
13
+ module EmfPlusObjects
14
+ Support = EmfPlusSupport
15
+
16
+ OPTIONAL_REFERENCE = "the referenced object (StringFormatId, ImageAttributesID, a clip Region) is optional or absent and " \
17
+ "producers write 0xFFFFFFFF, 0 or a never-defined slot for none; importers skip it and keep drawing " \
18
+ "(corpus: LibreOffice emfio TestDrawStringTransparent, TestDrawImagePointsTypeBitmap, " \
19
+ "TestDrawStringWithBrush, cppcanvas fdo77229, graphicfilter pass_fdo38580-3)"
20
+
21
+ KINDS = {
22
+ empty_slot: ["emfplus.object.empty_slot", "refers to an object slot with no object defined before this record"],
23
+ wrong_type: ["emfplus.object.wrong_type", "refers to a slot holding an object of a different type"],
24
+ object_id_out_of_range: ["emfplus.object.id_range", "has an object ID outside 0..63"]
25
+ }.freeze
26
+
27
+ # Optional references (MS-EMFPLUS: StringFormatId, ImageAttributesID; the clip Region of SetClipRegion as
28
+ # written by the corpus producers). Every other role (pen, brush, path, font, image, fill region) is mandatory.
29
+ OPTIONAL_ROLES = %i[string_format image_attributes].freeze
30
+
31
+ OPTIONAL_KINDS = {
32
+ empty_slot: ["emfplus.object.optional_empty_slot",
33
+ "refers to an optional object slot with no object defined before this record"],
34
+ object_id_out_of_range: ["emfplus.object.optional_id_range",
35
+ "has an optional object reference with an ID outside 0..63"]
36
+ }.freeze
37
+
38
+ module_function
39
+
40
+ # A SetClipRegion naming a never-defined slot is a measured producer pattern (LibreOffice fdo38580-3); an
41
+ # out-of-range id is not, and MS-EMFPLUS 2.3.1.5 limits the Region ObjectID to 0..63, so it stays mandatory.
42
+ def optional?(located, resolution)
43
+ return true if OPTIONAL_ROLES.include?(resolution.role)
44
+
45
+ located.record.is_a?(Emf::EmfPlus::Records::SetClipRegion) && resolution.diagnostic&.kind == :empty_slot
46
+ end
47
+
48
+ def findings(context, kind, optional: false)
49
+ stream = context.metafile.emf_plus_stream
50
+ return [] unless stream
51
+
52
+ Support.records(context).flat_map do |located|
53
+ stream.resolve(located.handle).filter_map do |resolution|
54
+ next unless resolution.diagnostic&.kind == kind
55
+ next unless kind == :wrong_type || optional?(located, resolution) == optional
56
+
57
+ Support.finding(located, "#{Support.type_name(located.record)} #{resolution.role} reference " \
58
+ "#{resolution.raw_id} #{(optional ? OPTIONAL_KINDS : KINDS).fetch(kind).last}")
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # MS-EMFPLUS 2.3.4.9: an E-flag DrawImagePoints needs an Effect specified in an earlier
65
+ # SerializableObject. Stream#effect_for is the public link (the immediately preceding record).
66
+ Registry.loading.register(id: "emfplus.object.effect_missing", formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.4.9",
67
+ kind: :effect_link, tolerant_reason: nil) do |context|
68
+ stream = context.metafile.emf_plus_stream
69
+ next [] unless stream
70
+
71
+ located_records = EmfPlusSupport.records(context)
72
+ located_records.each_with_index.filter_map do |located, index|
73
+ # Type and flags come from the record frame, so an E-flag draw whose body does not decode is still checked.
74
+ record = located.record
75
+ draw = Emf::EmfPlus::Records::DrawImagePoints
76
+ next unless record.type == draw::TYPE && record.flags.anybits?(draw::EFFECT_FLAG)
77
+ next if stream.effect_for(located.handle)
78
+ # The clause says "an earlier" SerializableObject, not the immediately preceding one.
79
+ next if located_records.first(index).any? { |earlier| earlier.record.type == Emf::EmfPlus::Records::SerializableObject::TYPE }
80
+
81
+ EmfPlusSupport.finding(located, "DrawImagePoints has the effect flag but no SerializableObject record precedes it")
82
+ end
83
+ end
84
+
85
+ EmfPlusObjects::KINDS.each do |kind, (id, _)|
86
+ Registry.loading.register(id: id, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.4", kind: kind,
87
+ tolerant_reason: nil) { |context| EmfPlusObjects.findings(context, kind) }
88
+ end
89
+
90
+ EmfPlusObjects::OPTIONAL_KINDS.each do |kind, (id, _)|
91
+ Registry.loading.register(id: id, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.4", kind: :"optional_#{kind}",
92
+ tolerant_reason: EmfPlusObjects::OPTIONAL_REFERENCE) do |context|
93
+ EmfPlusObjects.findings(context, kind, optional: true)
94
+ end
95
+ end
96
+ end
97
+ end