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,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drawing geometry rules: point arrays, PolyDraw, GradientFill and region data
4
+ # (MS-EMF 2.3.5, 2.2.7, 2.2.8, 2.2.24-2.2.26, 2.1.15, 2.1.26).
5
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
6
+
7
+ module Emf
8
+ module Conformance
9
+ module GeometryRules
10
+ EMF = %i[emf emf_plus].freeze
11
+ # type => [count field, bytes per point]
12
+ POINT_ARRAYS = { 2 => [:cptl, 8], 3 => [:cptl, 8], 4 => [:cptl, 8], 5 => [:cptl, 8], 6 => [:cptl, 8],
13
+ 85 => [:cpts, 4], 86 => [:cpts, 4], 87 => [:cpts, 4], 88 => [:cpts, 4], 89 => [:cpts, 4] }.freeze
14
+ POLY_POLY = { 7 => [:c_ptl_total, 8], 8 => [:c_ptl_total, 8], 90 => [:c_pts_total, 4], 91 => [:c_pts_total, 4] }.freeze
15
+ POLY_DRAW = { 56 => [:cptl, 8], 92 => [:cpts, 4] }.freeze
16
+ POLYLINES = [7, 90].freeze
17
+ BEZIER = 4
18
+ POINT_TYPES = [0x02, 0x03, 0x04, 0x05, 0x06].freeze # LINETO, BEZIERTO and MOVETO, optionally with CLOSEFIGURE (not on MOVETO)
19
+ GRADIENT_FIXED = 36
20
+ GRADIENT_MODES = [0, 1, 2].freeze
21
+ GRADIENT_TRIANGLE = 2
22
+ REGION_RECORDS = { 71 => 32, 72 => 40, 73 => 28, 74 => 28, 75 => 16 }.freeze # fixed part before RgnData
23
+ BEZIER_COUNT = { 0 => "a multiple of 3", 1 => "one more than a multiple of 3" }.freeze
24
+ COUNT_WINS = "readers take Count points and ignore the rest of the record"
25
+ EXTRA_POINTS = "the spec says extra points MUST be ignored, so a reader still draws the curve or polygon"
26
+
27
+ module_function
28
+
29
+ def field(record, name)
30
+ wire = record.wire
31
+ wire.respond_to?(name) ? wire.public_send(name).to_i : nil
32
+ end
33
+
34
+ def at(record, index, message)
35
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
36
+ end
37
+
38
+ def each_of(context, types)
39
+ context.records.each_with_index do |record, index|
40
+ type = context.record_type(record)
41
+ yield record, index, type if types.include?(type)
42
+ end
43
+ end
44
+
45
+ def padded(bytes) = (bytes + 3) & ~3
46
+
47
+ def size_finding(context, record, index, type, expected, what)
48
+ size = context.record_size(record)
49
+ return if size == expected
50
+
51
+ at(record, index, "#{RecordTypes.name(type)} #{what}, so Size MUST be #{expected}, not #{size}")
52
+ end
53
+
54
+ def point_array_size(context)
55
+ found = []
56
+ each_of(context, POINT_ARRAYS.keys) do |record, index, type|
57
+ name, width = POINT_ARRAYS.fetch(type)
58
+ count = field(record, name)
59
+ next if count.nil?
60
+
61
+ found << size_finding(context, record, index, type, 28 + padded(count * width), "holds #{count} points")
62
+ end
63
+ found.compact
64
+ end
65
+
66
+ def bezier_count(context)
67
+ found = []
68
+ each_of(context, [2, 85, 5, 88]) do |record, index, type|
69
+ count = field(record, POINT_ARRAYS.fetch(type).first)
70
+ next if count.nil?
71
+
72
+ expected = [2, 85].include?(type) ? 1 : 0
73
+ next if (count % 3) == expected
74
+
75
+ found << at(record, index, "#{RecordTypes.name(type)} Count #{count} MUST be #{BEZIER_COUNT.fetch(expected)}")
76
+ end
77
+ found
78
+ end
79
+
80
+ def poly_poly_totals(record, type)
81
+ total_name = POLY_POLY.fetch(type).first
82
+ counts = record.wire.a_poly_counts.to_a.map(&:to_i)
83
+ [counts, field(record, total_name)]
84
+ end
85
+
86
+ def poly_poly_size(context)
87
+ found = []
88
+ each_of(context, POLY_POLY.keys) do |record, index, type|
89
+ polys = field(record, :n_polys)
90
+ total = field(record, POLY_POLY.fetch(type).first)
91
+ next if polys.nil? || total.nil?
92
+
93
+ found << size_finding(context, record, index, type, 32 + (4 * polys) + padded(total * POLY_POLY.fetch(type).last),
94
+ "holds #{polys} polygons and #{total} points")
95
+ end
96
+ found.compact
97
+ end
98
+
99
+ def poly_poly_counts(context)
100
+ found = []
101
+ each_of(context, POLY_POLY.keys) do |record, index, type|
102
+ next unless record.wire.respond_to?(:a_poly_counts)
103
+
104
+ counts, total = poly_poly_totals(record, type)
105
+ sum = counts.sum
106
+ found << at(record, index, "#{RecordTypes.name(type)} per-figure counts sum to #{sum} but Count says #{total}") if sum != total
107
+ short = counts.count { |c| c < 2 }
108
+ if POLYLINES.include?(type) && short.positive?
109
+ found << at(record, index,
110
+ "#{RecordTypes.name(type)} has #{short} polyline(s) with fewer than 2 points, each count MUST be >= 2")
111
+ end
112
+ end
113
+ found
114
+ end
115
+
116
+ def poly_draw_size(context)
117
+ found = []
118
+ each_of(context, POLY_DRAW.keys) do |record, index, type|
119
+ name, width = POLY_DRAW.fetch(type)
120
+ count = field(record, name)
121
+ next if count.nil?
122
+
123
+ found << size_finding(context, record, index, type, 28 + padded((count * width) + count), "holds #{count} points and types")
124
+ end
125
+ found.compact
126
+ end
127
+
128
+ def bezier_runs(types)
129
+ types.chunk_while { |a, b| (a & ~1) == BEZIER && (b & ~1) == BEZIER }
130
+ .select { |run| (run.first & ~1) == BEZIER }.map(&:size)
131
+ end
132
+
133
+ def poly_draw_types(context)
134
+ found = []
135
+ each_of(context, POLY_DRAW.keys) do |record, index, type|
136
+ next unless record.wire.respond_to?(:ab_types)
137
+
138
+ types = record.wire.ab_types.to_a.map(&:to_i)
139
+ bad = types.reject { |t| POINT_TYPES.include?(t) }.uniq
140
+ unless bad.empty?
141
+ found << at(record, index, "#{RecordTypes.name(type)} point types #{bad.map do |t|
142
+ format('0x%02X', t)
143
+ end.join(', ')} are not in the Point enumeration")
144
+ end
145
+ odd = bezier_runs(types - bad).reject { |run| (run % 3).zero? }
146
+ unless odd.empty?
147
+ found << at(record, index,
148
+ "#{RecordTypes.name(type)} has PT_BEZIERTO runs of #{odd.join(', ')} points, they MUST come in sets of three")
149
+ end
150
+ end
151
+ found
152
+ end
153
+
154
+ # GradientFill: header, nVer TriVertex (16 bytes), then nTri index objects (12 bytes each,
155
+ # a rectangle's 8 bytes plus its mandatory 4 bytes of padding).
156
+ def gradient_fields(record)
157
+ %i[n_ver n_tri ul_mode].map { |name| field(record, name) }
158
+ end
159
+
160
+ def gradient_mode(context)
161
+ found = []
162
+ each_of(context, [118]) do |record, index, _type|
163
+ mode = field(record, :ul_mode)
164
+ next if mode.nil? || GRADIENT_MODES.include?(mode)
165
+
166
+ found << at(record, index, "GRADIENTFILL ulMode #{mode} is not GRADIENT_FILL_RECT_H, RECT_V or TRIANGLE")
167
+ end
168
+ found
169
+ end
170
+
171
+ def gradient_size(context)
172
+ found = []
173
+ each_of(context, [118]) do |record, index, type|
174
+ n_ver, n_tri, = gradient_fields(record)
175
+ next if n_ver.nil?
176
+
177
+ found << size_finding(context, record, index, type, GRADIENT_FIXED + (16 * n_ver) + (12 * n_tri),
178
+ "holds #{n_ver} vertexes and #{n_tri} index objects")
179
+ end
180
+ found.compact
181
+ end
182
+
183
+ def gradient_index(context)
184
+ found = []
185
+ each_of(context, [118]) do |record, index, _type|
186
+ n_ver, n_tri, mode = gradient_fields(record)
187
+ next if n_ver.nil?
188
+
189
+ bad = out_of_range_indexes(record.wire.body.to_s.b, n_ver, n_tri, mode)
190
+ found << at(record, index, "GRADIENTFILL vertex index #{bad.first} is not below nVer (#{n_ver})") if bad.any?
191
+ end
192
+ found
193
+ end
194
+
195
+ def out_of_range_indexes(body, n_ver, n_tri, mode)
196
+ per = mode == GRADIENT_TRIANGLE ? 3 : 2
197
+ start = 16 * n_ver
198
+ available = [(body.bytesize - start) / 12, n_tri].min
199
+ return [] if available <= 0
200
+
201
+ (0...available).flat_map { |i| body.byteslice(start + (12 * i), 4 * per).unpack("V#{per}") }.reject { |v| v < n_ver }
202
+ end
203
+
204
+ def region_data(record)
205
+ wire = record.wire
206
+ return unless wire.respond_to?(:rgn_data) && field(record, :cb_rgn_data).to_i.positive?
207
+
208
+ wire.rgn_data
209
+ end
210
+
211
+ def region_header(context)
212
+ found = []
213
+ each_of(context, REGION_RECORDS.keys) do |record, index, type|
214
+ data = region_data(record)
215
+ next unless data
216
+
217
+ unless data.header_size.to_i == 32
218
+ found << at(record, index,
219
+ "#{RecordTypes.name(type)} RegionDataHeader Size is #{data.header_size.to_i}, MUST be 32")
220
+ end
221
+ unless data.region_type.to_i == 1
222
+ found << at(record, index,
223
+ "#{RecordTypes.name(type)} RegionDataHeader Type is #{data.region_type.to_i}, MUST be 1")
224
+ end
225
+ end
226
+ found
227
+ end
228
+
229
+ def region_rects(context)
230
+ found = []
231
+ each_of(context, REGION_RECORDS.keys) do |record, index, type|
232
+ data = region_data(record)
233
+ next unless data
234
+
235
+ count = data.count_rects.to_i
236
+ size = data.rgn_size.to_i
237
+ cb = field(record, :cb_rgn_data)
238
+ if size != count * 16
239
+ found << at(record, index,
240
+ "#{RecordTypes.name(type)} CountRects #{count} needs #{count * 16} bytes but RgnSize is #{size}")
241
+ end
242
+ if cb != 32 + size
243
+ found << at(record, index,
244
+ "#{RecordTypes.name(type)} RgnDataSize #{cb} is not the 32-byte header plus RgnSize #{size}")
245
+ end
246
+ end
247
+ found
248
+ end
249
+
250
+ def region_size(context)
251
+ found = []
252
+ each_of(context, REGION_RECORDS.keys) do |record, index, type|
253
+ cb = field(record, :cb_rgn_data)
254
+ next if cb.nil?
255
+
256
+ found << size_finding(context, record, index, type, REGION_RECORDS.fetch(type) + cb, "carries #{cb} bytes of region data")
257
+ end
258
+ found.compact
259
+ end
260
+ end
261
+
262
+ registry = Registry.loading
263
+ {
264
+ "emf.geometry.point_array_size" => [:point_array_size, "MS-EMF 2.3.5", :point_array_size, GeometryRules::COUNT_WINS],
265
+ "emf.geometry.bezier_count" => [:bezier_count, "MS-EMF 2.3.5.16", :bezier_count, GeometryRules::EXTRA_POINTS],
266
+ "emf.geometry.poly_poly_size" => [:poly_poly_size, "MS-EMF 2.3.5.28", :poly_poly_size, nil],
267
+ "emf.geometry.poly_poly_counts" => [:poly_poly_counts, "MS-EMF 2.3.5.28", :poly_poly_counts, GeometryRules::EXTRA_POINTS],
268
+ "emf.geometry.poly_draw_size" => [:poly_draw_size, "MS-EMF 2.3.5.20", :poly_draw_size, nil],
269
+ "emf.geometry.poly_draw_types" => [:poly_draw_types, "MS-EMF 2.1.26", :poly_draw_types, nil],
270
+ "emf.geometry.gradient_mode" => [:gradient_mode, "MS-EMF 2.1.15", :gradient_mode, nil],
271
+ "emf.geometry.gradient_size" => [:gradient_size, "MS-EMF 2.3.5.12", :gradient_size, nil],
272
+ "emf.geometry.gradient_index" => [:gradient_index, "MS-EMF 2.2.7", :gradient_index, nil],
273
+ "emf.region.header" => [:region_header, "MS-EMF 2.2.25", :region_header, nil],
274
+ "emf.region.rects" => [:region_rects, "MS-EMF 2.2.25", :region_rects, nil],
275
+ "emf.region.size" => [:region_size, "MS-EMF 2.3.5", :region_size, nil]
276
+ }.each do |id, (method, citation, kind, reason)|
277
+ registry.register(id: id, formats: GeometryRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
278
+ GeometryRules.public_send(method, context)
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ # EMR_HEADER / Header object rules (MS-EMF 2.2.9, 2.3.4.2).
4
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
5
+
6
+ module Emf
7
+ module Conformance
8
+ module HeaderRules
9
+ EMF = %i[emf emf_plus].freeze
10
+ SIGNATURE = 0x464D4520
11
+ FIXED_HEADER_SIZE = 88 # EmfMetafileHeader fixed part (2.3.4.2)
12
+ CREATE_OBJECT_TYPES = [38, 39, 49, 82, 93, 94, 95, 99, 122].freeze
13
+ DRIFT = "the numeric header fields are advisory for renderers; every corpus file with a drifted value still renders"
14
+
15
+ module_function
16
+
17
+ def finding(message) = { message: message, offset: 0, record_index: 0, record_type: RecordTypes::HEADER }
18
+
19
+ def signature(context)
20
+ sig = context.header.signature.to_i
21
+ return [] if sig == SIGNATURE
22
+
23
+ [finding(format("RecordSignature is 0x%08X, MUST be ENHMETA_SIGNATURE 0x464D4520", sig))]
24
+ end
25
+
26
+ def size_bounds(context)
27
+ size = context.header.n_size.to_i
28
+ problems = []
29
+ problems << "header Size #{size} is below the #{FIXED_HEADER_SIZE}-byte fixed part" if size < FIXED_HEADER_SIZE
30
+ problems << "header Size #{size} is not a multiple of 4" unless (size % 4).zero?
31
+ problems.map { |message| finding(message) }
32
+ end
33
+
34
+ def bytes_mismatch(context)
35
+ actual = context.byte_length
36
+ declared = context.header.n_bytes.to_i
37
+ return [] if actual.nil? || declared == actual
38
+
39
+ [finding("header Bytes is #{declared} but the metafile is #{actual} bytes")]
40
+ end
41
+
42
+ def records_mismatch(context)
43
+ declared = context.header.n_records.to_i
44
+ return [] if declared == context.record_count
45
+
46
+ [finding("header Records is #{declared} but the metafile holds #{context.record_count} records")]
47
+ end
48
+
49
+ def handle_count(context)
50
+ handles = context.header.n_handles.to_i
51
+ context.records.each_with_index.filter_map do |record, index|
52
+ type = context.record_type(record)
53
+ next unless CREATE_OBJECT_TYPES.include?(type)
54
+
55
+ handle = record.to_binary_s.unpack1("V", offset: 8)
56
+ next if handle.nil? || handle < handles
57
+
58
+ { message: "object handle index #{handle} created at record #{index + 1} is not below header Handles (#{handles})",
59
+ offset: record.offset, record_index: index + 1, record_type: type }
60
+ end
61
+ end
62
+
63
+ def description_bounds(context)
64
+ header = context.header
65
+ chars = header.n_description.to_i
66
+ return [] if chars.zero? && header.off_description.to_i.zero?
67
+
68
+ offset = header.off_description.to_i
69
+ problems = []
70
+ problems << "description offset #{offset} is inside the #{FIXED_HEADER_SIZE}-byte fixed part" if offset < FIXED_HEADER_SIZE
71
+ if offset + (2 * chars) > header.n_size.to_i
72
+ problems << "description (#{chars} UTF-16 chars at offset #{offset}) ends past header Size #{header.n_size}"
73
+ end
74
+ problems.map { |message| finding(message) }
75
+ end
76
+
77
+ # HeaderExtension1 (and with it the pixel format fields) exists only when
78
+ # the variable-length data starts at byte 100 or later (MS-EMF 2.3.4.2,
79
+ # HeaderSize rule); a description at byte 88 means the original 88-byte header.
80
+ def extension1?(header)
81
+ return header.n_size.to_i >= 100 if header.n_description.to_i.zero?
82
+
83
+ header.off_description.to_i >= 100
84
+ end
85
+
86
+ def pixel_format_bounds(context)
87
+ header = context.header
88
+ return [] unless extension1?(header)
89
+
90
+ size = header.cb_pixel_format.to_i
91
+ offset = header.off_pixel_format.to_i
92
+ return [] if size.zero? && offset.zero?
93
+ return [] if offset >= FIXED_HEADER_SIZE && offset + size <= header.n_size.to_i
94
+
95
+ [finding("pixel format (#{size} bytes at offset #{offset}) lies outside the header record (Size #{header.n_size})")]
96
+ end
97
+ end
98
+
99
+ registry = Registry.loading
100
+ {
101
+ "emf.header.signature" => [:signature, "MS-EMF 2.2.9", :signature, nil],
102
+ "emf.header.size_bounds" => [:size_bounds, "MS-EMF 2.3.4.2", :header_size, nil],
103
+ "emf.header.bytes" => [:bytes_mismatch, "MS-EMF 2.2.9", :bytes_mismatch, HeaderRules::DRIFT],
104
+ "emf.header.records" => [:records_mismatch, "MS-EMF 2.2.9", :records_mismatch, HeaderRules::DRIFT],
105
+ "emf.header.handles" => [:handle_count, "MS-EMF 2.2.9", :handle_count, HeaderRules::DRIFT],
106
+ "emf.header.description_bounds" => [:description_bounds, "MS-EMF 2.2.9", :description_bounds, HeaderRules::DRIFT],
107
+ "emf.header.pixel_format_bounds" => [:pixel_format_bounds, "MS-EMF 2.2.10", :pixel_format_bounds, HeaderRules::DRIFT]
108
+ }.each do |id, (method, citation, kind, reason)|
109
+ registry.register(id: id, formats: HeaderRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
110
+ HeaderRules.public_send(method, context)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Object creation and manipulation rules (MS-EMF 2.3.7, 2.3.8, 2.1.31, 2.2.17-2.2.20).
4
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
5
+
6
+ module Emf
7
+ module Conformance
8
+ module ObjectRules
9
+ EMF = %i[emf emf_plus].freeze
10
+ DEFAULT_PALETTE = 0x8000000F
11
+ STOCK = ((0x80000000..0x80000008).to_a + (0x8000000A..0x80000013).to_a).freeze # MS-EMF 2.1.31
12
+ # Record types that put a new handle in the object table, and the wire field that holds it.
13
+ CREATE = { 38 => :ih_pen, 39 => :ih_brush, 49 => :ih_pal, 82 => :ih_object, 93 => :ih_brush,
14
+ 94 => :ih_brush, 95 => :ih_pen, 99 => :ih_cs, 122 => :ih_cs }.freeze
15
+ # Records that name an existing handle: type => wire field.
16
+ SELECT = { 37 => :ih_object, 48 => :ih_pal, 100 => :ih_cs }.freeze
17
+ DELETE = { 40 => :ih_object, 101 => :ih_cs }.freeze
18
+ USE = { 50 => :ih_pal, 51 => :ih_pal }.freeze
19
+ REFERENCES = SELECT.merge(DELETE).merge(USE).freeze
20
+ FIXED_SIZES = { 38 => 28, 39 => 24, 48 => 12, 100 => 12, 111 => 24 }.freeze
21
+ PS_GEOMETRIC = 0x10000
22
+ PS_NULL = 5
23
+ PS_ALTERNATE = 8
24
+ BS_SOLID = 0
25
+ BS_NULL = 1
26
+ BS_HATCHED = 2
27
+ MAX_PALETTE = 0x400
28
+ MIN_FONT_RECORD = 12 + 320 # fixed part plus a LogFontPanose (2.3.7.8)
29
+ COSMETIC_WIDTH = "GDI writes PS_SOLID pens wider than 1 and renders them at that width; 165 corpus files do this"
30
+ PEN_BRUSH = "the same section's brush table lists BS_PATTERN and BS_DIBPATTERN as supported for the pen; GDI writes " \
31
+ "DIB-pattern geometric pens (5 corpus files)"
32
+ FONT_SHORT = "the same section says a LogFont MAY be present instead of a LogFontExDv, so a shorter elw is not uniformly invalid"
33
+ HANDLE_RANGE = "vectory's svg2emf, ps2emf and eps2emf output declares Handles=1 while selecting object indices 1 and 2; " \
34
+ "renderers resolve handles from the live table, not the header count, and display the file"
35
+ UNKNOWN_HANDLE = "readers skip a SELECTOBJECT or DELETEOBJECT whose handle is not in the table and keep drawing"
36
+
37
+ module_function
38
+
39
+ # nil when the record has no such field, or its body is too short to decode it.
40
+ def field(record, name)
41
+ wire = record.wire
42
+ wire.respond_to?(name) ? wire.public_send(name).to_i : nil
43
+ rescue ArgumentError
44
+ nil
45
+ end
46
+
47
+ def at(record, index, message)
48
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
49
+ end
50
+
51
+ def each_of(context, types)
52
+ context.records.each_with_index do |record, index|
53
+ type = context.record_type(record)
54
+ yield record, index, type if types.include?(type)
55
+ end
56
+ end
57
+
58
+ def hex(value) = format("0x%08X", value)
59
+
60
+ def fixed_size(context)
61
+ found = []
62
+ each_of(context, FIXED_SIZES.keys) do |record, index, type|
63
+ size = context.record_size(record)
64
+ next if size == FIXED_SIZES.fetch(type)
65
+
66
+ found << at(record, index, "#{RecordTypes.name(type)} Size is #{size}, MUST be #{FIXED_SIZES.fetch(type)}")
67
+ end
68
+ found
69
+ end
70
+
71
+ # PenStyle (2.1.25): line style 0-8, cap 0/0x100/0x200, join 0/0x1000/0x2000, type 0/PS_GEOMETRIC.
72
+ def font_size(context)
73
+ found = []
74
+ each_of(context, [82]) do |record, index, _type|
75
+ size = context.record_size(record)
76
+ next if size >= MIN_FONT_RECORD
77
+
78
+ found << at(record, index, "EXTCREATEFONTINDIRECTW Size #{size} leaves #{size - 12} bytes for elw, " \
79
+ "less than a LogFontPanose (#{MIN_FONT_RECORD - 12})")
80
+ end
81
+ found
82
+ end
83
+
84
+ def pen_style_problem(style)
85
+ return "line style #{style & 0xFF} is outside PS_SOLID..PS_ALTERNATE" if (style & 0xFF) > PS_ALTERNATE
86
+ return "end cap #{hex(style & 0xF00)} is not round, square or flat" unless [0, 0x100, 0x200].include?(style & 0xF00)
87
+ return "line join #{hex(style & 0xF000)} is not round, bevel or miter" unless [0, 0x1000, 0x2000].include?(style & 0xF000)
88
+ return "pen type #{hex(style & 0xF0000)} is not PS_COSMETIC or PS_GEOMETRIC" unless [0, PS_GEOMETRIC].include?(style & 0xF0000)
89
+ return "bits #{hex(style & 0xFFF00000)} are outside the PenStyle enumeration" unless style.nobits?(0xFFF00000)
90
+
91
+ "PS_ALTERNATE is applicable only to PS_COSMETIC pens" if (style & 0xFF) == PS_ALTERNATE && style.anybits?(PS_GEOMETRIC)
92
+ end
93
+
94
+ def pen_style(context)
95
+ found = []
96
+ each_of(context, [38, 95]) do |record, index, type|
97
+ style = field(record, :pen_style)
98
+ problem = style && pen_style_problem(style)
99
+ found << at(record, index, "#{RecordTypes.name(type)} PenStyle #{hex(style)}: #{problem}") if problem
100
+ end
101
+ found
102
+ end
103
+
104
+ def pen_width(record, type)
105
+ return record.wire.width.x.to_i if type == 38 && record.wire.respond_to?(:width)
106
+
107
+ field(record, :width)
108
+ end
109
+
110
+ def cosmetic_width(context)
111
+ found = []
112
+ each_of(context, [38, 95]) do |record, index, type|
113
+ style = field(record, :pen_style)
114
+ next if style.nil? || style.anybits?(PS_GEOMETRIC)
115
+
116
+ width = pen_width(record, type)
117
+ next if width.nil? || width == 1
118
+
119
+ found << at(record, index, "#{RecordTypes.name(type)} is a PS_COSMETIC pen with width #{width}, MUST be 1")
120
+ end
121
+ found
122
+ end
123
+
124
+ def brush_style(context)
125
+ found = []
126
+ each_of(context, [39]) do |record, index, _type|
127
+ style = field(record, :brush_style)
128
+ next if style.nil? || [BS_SOLID, BS_NULL, BS_HATCHED].include?(style)
129
+
130
+ found << at(record, index, "CREATEBRUSHINDIRECT BrushStyle #{style} MUST be BS_SOLID, BS_HATCHED or BS_NULL")
131
+ end
132
+ found
133
+ end
134
+
135
+ def extpen_brush_problem(pen_style, brush, hatch)
136
+ if pen_style.anybits?(PS_GEOMETRIC)
137
+ "a PS_GEOMETRIC pen MUST use BS_SOLID or BS_HATCHED" unless [BS_SOLID, BS_HATCHED].include?(brush) ||
138
+ (brush == BS_NULL && (pen_style & 0xFF) == PS_NULL)
139
+ elsif brush == BS_HATCHED && ![0x8, 0xA].include?(hatch)
140
+ "a cosmetic BS_HATCHED pen MUST use HS_SOLIDTEXTCLR or HS_SOLIDBKCLR, not #{hatch}"
141
+ end
142
+ end
143
+
144
+ def extpen_brush(context)
145
+ found = []
146
+ each_of(context, [95]) do |record, index, _type|
147
+ style = field(record, :pen_style)
148
+ brush = field(record, :brush_style)
149
+ next if style.nil? || brush.nil?
150
+
151
+ problem = extpen_brush_problem(style, brush, field(record, :brush_hatch))
152
+ found << at(record, index, "EXTCREATEPEN brush style #{brush}: #{problem}") if problem
153
+ end
154
+ found
155
+ end
156
+
157
+ def palette_version(context)
158
+ found = []
159
+ each_of(context, [49]) do |record, index, _type|
160
+ version = field(record, :version)
161
+ next if version.nil? || version == 0x300
162
+
163
+ found << at(record, index, "CREATEPALETTE LogPalette Version is #{format('0x%04X', version)}, MUST be 0x0300")
164
+ end
165
+ found
166
+ end
167
+
168
+ def palette_entries(context)
169
+ found = []
170
+ each_of(context, [49, 51]) do |record, index, type|
171
+ count = field(record, :entry_count)
172
+ next if count.nil?
173
+ next if count.positive? && (type == 49 || count <= MAX_PALETTE)
174
+
175
+ found << at(record, index,
176
+ "#{RecordTypes.name(type)} NumberOfEntries is #{count}, MUST be #{type == 49 ? '> 0' : "> 0 and <= #{MAX_PALETTE}"}")
177
+ end
178
+ found
179
+ end
180
+
181
+ # Palette records whose entry array is sized by a count: the record Size must match the layout.
182
+ PALETTE_FIXED = { 49 => 16, 50 => 20 }.freeze
183
+
184
+ def palette_size(context)
185
+ found = []
186
+ each_of(context, PALETTE_FIXED.keys) do |record, index, type|
187
+ count = field(record, :entry_count)
188
+ next if count.nil?
189
+
190
+ expected = PALETTE_FIXED.fetch(type) + (4 * count)
191
+ size = context.record_size(record)
192
+ next if size == expected
193
+
194
+ found << at(record, index, "#{RecordTypes.name(type)} holds #{count} palette entries, so Size MUST be #{expected}, not #{size}")
195
+ end
196
+ found
197
+ end
198
+
199
+ def index_zero(context)
200
+ found = []
201
+ each_of(context, [37, 40, 48]) do |record, index, type|
202
+ next unless field(record, REFERENCES.fetch(type))&.zero?
203
+
204
+ found << at(record, index, "#{RecordTypes.name(type)} object index 0 is reserved for the metafile itself, MUST NOT be used")
205
+ end
206
+ found
207
+ end
208
+
209
+ def delete_stock(context)
210
+ found = []
211
+ each_of(context, [40]) do |record, index, _type|
212
+ handle = field(record, :ih_object)
213
+ next unless STOCK.include?(handle)
214
+
215
+ found << at(record, index, "DELETEOBJECT names the stock object #{hex(handle)}, which cannot be deleted")
216
+ end
217
+ found
218
+ end
219
+
220
+ # A stock index is a valid operand of SELECTOBJECT; SELECTPALETTE takes only DEFAULT_PALETTE.
221
+ def stock_ok?(type, handle)
222
+ return handle == DEFAULT_PALETTE if type == 48
223
+
224
+ type == 37 && STOCK.include?(handle)
225
+ end
226
+
227
+ def handle_range(context)
228
+ limit = context.header.n_handles.to_i
229
+ found = []
230
+ each_of(context, REFERENCES.keys) do |record, index, type|
231
+ handle = field(record, REFERENCES.fetch(type))
232
+ next if handle.nil? || handle.zero? || handle < limit || stock_ok?(type, handle)
233
+ next if type == 40 && STOCK.include?(handle) # emf.object.delete_stock reports it
234
+
235
+ found << at(record, index,
236
+ "#{RecordTypes.name(type)} names object index #{hex(handle)}, " \
237
+ "not below header Handles (#{limit}) and not a stock object")
238
+ end
239
+ found
240
+ end
241
+
242
+ # Walks the stream in order, keeping the live handle table.
243
+ def lifecycle(context)
244
+ live = {}
245
+ found = []
246
+ context.records.each_with_index do |record, index|
247
+ type = context.record_type(record)
248
+ name = CREATE[type] || REFERENCES[type]
249
+ handle = name && field(record, name)
250
+ next if handle.nil?
251
+
252
+ if CREATE.key?(type)
253
+ live[handle] = index + 1
254
+ else
255
+ problem = missing_handle(live, type, handle)
256
+ found << at(record, index, "#{RecordTypes.name(type)} #{problem}") if problem
257
+ live.delete(handle) if DELETE.key?(type)
258
+ end
259
+ end
260
+ found
261
+ end
262
+
263
+ def missing_handle(live, _type, handle)
264
+ return if handle.zero? || handle >= 0x80000000 || live.key?(handle) # zero and stock are other rules' business
265
+
266
+ "uses object index #{handle}, which the stream never created or already deleted"
267
+ end
268
+ end
269
+
270
+ registry = Registry.loading
271
+ {
272
+ "emf.object.fixed_size" => [:fixed_size, "MS-EMF 2.3.7", :object_fixed_size, nil],
273
+ "emf.object.font_size" => [:font_size, "MS-EMF 2.3.7.8", :font_size, ObjectRules::FONT_SHORT],
274
+ "emf.object.pen_style" => [:pen_style, "MS-EMF 2.1.25", :pen_style, nil],
275
+ "emf.object.pen_cosmetic_width" => [:cosmetic_width, "MS-EMF 2.2.19", :pen_cosmetic_width, ObjectRules::COSMETIC_WIDTH],
276
+ "emf.object.brush_style" => [:brush_style, "MS-EMF 2.3.7.1", :brush_style, nil],
277
+ "emf.object.extpen_brush" => [:extpen_brush, "MS-EMF 2.2.20", :extpen_brush, ObjectRules::PEN_BRUSH],
278
+ "emf.object.palette_version" => [:palette_version, "MS-EMF 2.3.7.6", :palette_version, nil],
279
+ "emf.object.palette_entries" => [:palette_entries, "MS-EMF 2.3.8.4", :palette_entries, nil],
280
+ "emf.object.palette_size" => [:palette_size, "MS-EMF 2.3.7.6", :palette_size, nil],
281
+ "emf.object.index_zero" => [:index_zero, "MS-EMF 2.3.8.5", :index_zero, nil],
282
+ "emf.object.delete_stock" => [:delete_stock, "MS-EMF 2.3.8.3", :delete_stock, nil],
283
+ "emf.object.handle_range" => [:handle_range, "MS-EMF 2.3.8", :handle_range, ObjectRules::HANDLE_RANGE],
284
+ "emf.object.lifecycle" => [:lifecycle, "MS-EMF 2.3.8", :object_lifecycle, ObjectRules::UNKNOWN_HANDLE]
285
+ }.each do |id, (method, citation, kind, reason)|
286
+ registry.register(id: id, formats: ObjectRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
287
+ ObjectRules.public_send(method, context)
288
+ end
289
+ end
290
+ end
291
+ end