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,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ # State record rules (MS-EMF 2.3.11): fixed sizes, enumeration domains, nonzero
4
+ # divisors, RESTOREDC sign, and the length fields of the profile and UFI records.
5
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
6
+
7
+ module Emf
8
+ module Conformance
9
+ module StateRules
10
+ EMF = %i[emf emf_plus].freeze
11
+
12
+ # type => Size the record section states ("This value is 0x...").
13
+ STATED_SIZE = { 16 => 12, 23 => 32, 34 => 12, 57 => 12, 98 => 12, 115 => 12 }.freeze
14
+ # type => Size implied by a record that has fixed fields only (no Size value is stated).
15
+ LAYOUT_SIZE = { 9 => 16, 10 => 16, 11 => 16, 12 => 16, 13 => 16, 17 => 12, 18 => 12, 19 => 12, 20 => 12, 21 => 12,
16
+ 22 => 12, 24 => 12, 25 => 12, 27 => 16, 31 => 24, 32 => 24, 33 => 8, 52 => 8, 58 => 12, 104 => 48,
17
+ 109 => 16, 120 => 16 }.freeze
18
+ # Types whose Size BELOW the layout is the error emf.opaque.fixed_size (any record, decoded or Raw); this table
19
+ # then only speaks of a Size above it, as a warning.
20
+ SHORT_BY_OPAQUE = [104, 109].freeze
21
+ SECTION = { 9 => "2.3.11.30", 10 => "2.3.11.31", 11 => "2.3.11.28", 12 => "2.3.11.29", 13 => "2.3.11.12",
22
+ 16 => "2.3.11.20", 17 => "2.3.11.19", 18 => "2.3.11.11", 19 => "2.3.11.22", 20 => "2.3.11.23",
23
+ 21 => "2.3.11.24", 22 => "2.3.11.25", 23 => "2.3.11.13", 24 => "2.3.11.26", 25 => "2.3.11.10",
24
+ 27 => "2.3.11.4", 31 => "2.3.11.7", 32 => "2.3.11.8", 33 => "2.3.11", 34 => "2.3.11.6",
25
+ 52 => "2.3.11", 57 => "2.3.11.9", 58 => "2.3.11.21", 98 => "2.3.11.14", 104 => "2.3.11.5",
26
+ 109 => "2.3.11.2", 115 => "2.3.11.17", 120 => "2.3.11.27" }.freeze
27
+
28
+ TEXT_ALIGN_MASK = 0x11F # TA_UPDATECP, horizontal 0x6, vertical 0x18, TA_RTLREADING
29
+ HORIZONTAL_ALIGN = [0, 2, 6].freeze # TA_LEFT, TA_RIGHT, TA_CENTER
30
+ VERTICAL_ALIGN = [0, 8, 24].freeze # TA_TOP, TA_BOTTOM, TA_BASELINE
31
+ LAYOUT_BITS = 0x9 # LAYOUT_RTL, LAYOUT_BITMAPORIENTATIONPRESERVED (LAYOUT_LTR is 0)
32
+
33
+ TEXT_ALIGN_SWEEP = "spec/fixtures/emf test-039 and test-040 write every alignment value 0x0..0x16, including the " \
34
+ "undefined 0x4, 0x10 and combinations of them; each component is still read from its own bits"
35
+
36
+ # rule id => [type, wire field, valid?, enumeration label, record section, severity, tolerant reason]
37
+ ENUMS = {
38
+ "emf.state.map_mode" => [17, :map_mode, (1..8).method(:cover?), "MapMode (MM_TEXT..MM_ANISOTROPIC, 1..8)", "2.3.11.19", :error],
39
+ "emf.state.bk_mode" => [18, :bk_mode, (1..2).method(:cover?), "BackgroundMode (TRANSPARENT 1, OPAQUE 2)", "2.3.11.11", :error],
40
+ "emf.state.poly_fill_mode" => [19, :poly_fill_mode, (1..2).method(:cover?), "PolygonFillMode (ALTERNATE 1, WINDING 2)",
41
+ "2.3.11.22", :error],
42
+ "emf.state.rop2" => [20, :rop2, (1..16).method(:cover?), "Binary Raster Op (R2_BLACK..R2_WHITE, 1..16)", "2.3.11.23", :error],
43
+ "emf.state.stretch_mode" => [21, :stretch_mode, (1..4).method(:cover?), "StretchMode (1..4); the record says MAY", "2.3.11.24",
44
+ :warning],
45
+ "emf.state.arc_direction" => [57, :arc_direction, (1..2).method(:cover?), "ArcDirection (AD_COUNTERCLOCKWISE 1, AD_CLOCKWISE 2)",
46
+ "2.3.11.9", :error],
47
+ "emf.state.icm_mode" => [98, :mode, (1..4).method(:cover?), "ICMMode (ICM_OFF..ICM_DONE_OUTSIDEDC, 1..4)", "2.3.11.14", :error],
48
+ "emf.state.layout_mode" => [115, :mode, lambda { |v|
49
+ v.nobits?(~LAYOUT_BITS)
50
+ }, "LayoutMode flags (0x0, 0x1, 0x8)", "2.3.11.17", :error],
51
+ "emf.state.mapper_flags" => [16, :flags, [0, 1].method(:include?), "font mapper Flags (0 or 1)", "2.3.11.20", :warning],
52
+ "emf.state.text_align" => [22, :text_align_mode, lambda { |v|
53
+ v.nobits?(~TEXT_ALIGN_MASK) && HORIZONTAL_ALIGN.include?(v & 0x6) && VERTICAL_ALIGN.include?(v & 0x18)
54
+ }, "TextAlignmentMode flags (one horizontal and one vertical value, optional TA_UPDATECP and TA_RTLREADING)", "2.3.11.25", :error,
55
+ TEXT_ALIGN_SWEEP]
56
+ }.freeze
57
+
58
+ # rule id => [type, [wire fields], record section]
59
+ NONZERO = {
60
+ "emf.state.scale_viewport_ext" => [31, %i[x_num x_denom y_num y_denom], "2.3.11.7"],
61
+ "emf.state.scale_window_ext" => [32, %i[x_num x_denom y_num y_denom], "2.3.11.8"]
62
+ }.freeze
63
+
64
+ # type => [name field/prefix bytes, record section] for the cbName + cbData profile payload.
65
+ PROFILE = { 112 => [12, "2.3.11.15"], 113 => [12, "2.3.11.16"], 121 => [16, "2.3.11.1"] }.freeze
66
+
67
+ module_function
68
+
69
+ def each_wire(context, types)
70
+ context.records.each_with_index do |record, index|
71
+ next unless types.include?(context.record_type(record))
72
+
73
+ wire = record.wire
74
+ yield(record, index, wire) if wire.is_a?(Emf::Emr::Binary::Record)
75
+ end
76
+ end
77
+
78
+ def at(record, index, message)
79
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
80
+ end
81
+
82
+ def size_findings(context, table, label)
83
+ found = []
84
+ each_wire(context, table.keys) do |record, index, _wire|
85
+ type = context.record_type(record)
86
+ size = context.record_size(record)
87
+ next if size == table.fetch(type)
88
+ next if size < table.fetch(type) && table.equal?(LAYOUT_SIZE) && SHORT_BY_OPAQUE.include?(type)
89
+
90
+ found << at(record, index, "#{RecordTypes.name(type)} (#{SECTION.fetch(type)}) Size is #{size}, #{label} #{table.fetch(type)}")
91
+ end
92
+ found
93
+ end
94
+
95
+ def fixed_size(context) = size_findings(context, STATED_SIZE, "the record section states")
96
+
97
+ def layout_size(context) = size_findings(context, LAYOUT_SIZE, "its fixed fields occupy")
98
+
99
+ def enum_check(context, id)
100
+ type, field, valid, label, section, = ENUMS.fetch(id)
101
+ found = []
102
+ each_wire(context, [type]) do |record, index, wire|
103
+ value = wire.public_send(field).to_i
104
+ next if valid.call(value)
105
+
106
+ found << at(record, index, "#{RecordTypes.name(type)} (#{section}) #{field} 0x#{value.to_s(16)} is not a valid #{label}")
107
+ end
108
+ found
109
+ end
110
+
111
+ def nonzero(context, id)
112
+ type, fields, section = NONZERO.fetch(id)
113
+ found = []
114
+ each_wire(context, [type]) do |record, index, wire|
115
+ zero = fields.select { |field| wire.public_send(field).to_i.zero? }
116
+ next if zero.empty?
117
+
118
+ found << at(record, index, "#{RecordTypes.name(type)} (#{section}) #{zero.join(', ')} MUST NOT be zero")
119
+ end
120
+ found
121
+ end
122
+
123
+ def restoredc_negative(context)
124
+ found = []
125
+ each_wire(context, [34]) do |record, index, wire|
126
+ saved = wire.saved_dc.to_i
127
+ found << at(record, index, "EMR_RESTOREDC SavedDC is #{saved}, MUST be negative") unless saved.negative?
128
+ end
129
+ found
130
+ end
131
+
132
+ def color_adjustment(context)
133
+ found = []
134
+ each_wire(context, [23]) do |record, index, wire|
135
+ # A body under 24 bytes is a Size under 32: emf.state.fixed_size already reports that.
136
+ next if wire.body.to_s.bytesize < 24
137
+
138
+ size = wire.adjustment_size
139
+ found << at(record, index, "ColorAdjustment Size is #{size}, MUST be 0x0018") unless size == 24
140
+ index_value = wire.illuminant_index
141
+ if index_value > 8
142
+ found << at(record, index,
143
+ "ColorAdjustment IlluminantIndex #{index_value} is not in the Illuminant enumeration (0..8)")
144
+ end
145
+ end
146
+ found
147
+ end
148
+
149
+ def pixel_format_version(context)
150
+ found = []
151
+ each_wire(context, [104]) do |record, index, wire|
152
+ next if wire.body.to_s.bytesize < 40
153
+
154
+ version = wire.pfd_version
155
+ found << at(record, index, "PixelFormatDescriptor nVersion is #{version}, MUST be 1") unless version == 1
156
+ end
157
+ found
158
+ end
159
+
160
+ def color_match_target(context)
161
+ found = []
162
+ each_wire(context, [121]) do |record, index, wire|
163
+ next if wire.body.to_s.bytesize < 16
164
+
165
+ unless (1..3).cover?(wire.dw_action)
166
+ found << at(record, index,
167
+ "EMR_COLORMATCHTOTARGETW dwAction #{wire.dw_action} is not in the ColorSpace enumeration (1..3)")
168
+ end
169
+ unless (0..1).cover?(wire.dw_flags)
170
+ found << at(record, index,
171
+ "EMR_COLORMATCHTOTARGETW dwFlags #{wire.dw_flags} is not in the ColorMatchToTarget enumeration (0..1)")
172
+ end
173
+ end
174
+ found
175
+ end
176
+
177
+ def profile_bounds(context)
178
+ found = []
179
+ each_wire(context, PROFILE.keys) do |record, index, wire|
180
+ prefix, section = PROFILE.fetch(context.record_type(record))
181
+ body = wire.body.to_s.bytesize
182
+ name = RecordTypes.name(context.record_type(record))
183
+ if body < prefix
184
+ next found << at(record, index,
185
+ "#{name} (#{section}) holds #{body} bytes after Size, fewer than its #{prefix}-byte fixed part")
186
+ end
187
+
188
+ need = prefix + wire.cb_name + wire.cb_data
189
+ next unless need > body
190
+
191
+ found << at(record, index,
192
+ "#{name} (#{section}) cbName #{wire.cb_name} + cbData #{wire.cb_data} need #{need} bytes after Size, " \
193
+ "the record holds #{body}")
194
+ end
195
+ found
196
+ end
197
+
198
+ def linked_ufis_bounds(context)
199
+ found = []
200
+ each_wire(context, [119]) do |record, index, wire|
201
+ body = wire.body.to_s.bytesize
202
+ next found << at(record, index, "EMR_SETLINKEDUFIS holds #{body} bytes after Size, fewer than uNumLinkedUFI") if body < 4
203
+
204
+ count = wire.u_num_linked_ufi
205
+ need = 12 + (count * 8) # uNumLinkedUFI + ufis + 8 reserved bytes
206
+ if need > body
207
+ found << at(record, index,
208
+ "EMR_SETLINKEDUFIS (2.3.11.18) uNumLinkedUFI #{count} needs #{need} bytes after Size, the record holds #{body}")
209
+ end
210
+ end
211
+ found
212
+ end
213
+ end
214
+
215
+ registry = Registry.loading
216
+ state = StateRules::EMF
217
+
218
+ registry.register(id: "emf.state.fixed_size", formats: state, citation: "MS-EMF 2.3.11", kind: :fixed_size, severity: :error) do |c|
219
+ StateRules.fixed_size(c)
220
+ end
221
+ registry.register(id: "emf.state.layout_size", formats: state, citation: "MS-EMF 2.3.11", kind: :layout_size, severity: :warning) do |c|
222
+ StateRules.layout_size(c)
223
+ end
224
+ StateRules::ENUMS.each do |id, (_type, _field, _valid, _label, section, severity, reason)|
225
+ registry.register(id: id, formats: state, citation: "MS-EMF #{section}", kind: id.split(".").last.to_sym, severity: severity,
226
+ tolerant_reason: reason) do |c|
227
+ StateRules.enum_check(c, id)
228
+ end
229
+ end
230
+ StateRules::NONZERO.each do |id, (_type, _fields, section)|
231
+ registry.register(id: id, formats: state, citation: "MS-EMF #{section}", kind: :nonzero_divisor) { |c| StateRules.nonzero(c, id) }
232
+ end
233
+ {
234
+ "emf.state.restoredc_negative" => [:restoredc_negative, "MS-EMF 2.3.11.6", :saved_dc],
235
+ "emf.state.color_adjustment" => [:color_adjustment, "MS-EMF 2.2.2", :color_adjustment],
236
+ "emf.state.pixel_format_version" => [:pixel_format_version, "MS-EMF 2.2.22", :pixel_format_version],
237
+ "emf.state.color_match_target" => [:color_match_target, "MS-EMF 2.3.11.1", :color_match_target],
238
+ "emf.state.profile_bounds" => [:profile_bounds, "MS-EMF 2.3.11.15", :profile_bounds],
239
+ "emf.state.linked_ufis_bounds" => [:linked_ufis_bounds, "MS-EMF 2.3.11.18", :linked_ufis_bounds]
240
+ }.each do |id, (method, citation, kind)|
241
+ registry.register(id: id, formats: state, citation: citation, kind: kind) { |c| StateRules.public_send(method, c) }
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Text output rules (MS-EMF 2.2.5 EmrText, 2.3.5.7/.8 EXTTEXTOUT, 2.3.5.32/.33 POLYTEXTOUT,
4
+ # 2.3.5.37 SMALLTEXTOUT). Offsets, counts and option bits are read from the record's own
5
+ # bytes; the characters themselves are never decoded.
6
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
7
+
8
+ module Emf
9
+ module Conformance
10
+ module TextRules
11
+ EMF = %i[emf emf_plus].freeze
12
+ ETO_PDY = 0x2000
13
+ ETO_GLYPH_INDEX = 0x10
14
+ ETO_NO_RECT = 0x100
15
+ ETO_SMALL_CHARS = 0x200
16
+ # ExtTextOutOptions (MS-EMF 2.1.11).
17
+ DEFINED_OPTIONS = [0x2, 0x4, 0x10, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x10000].sum.freeze
18
+
19
+ # type => [name, wide characters?]
20
+ EXT = { 83 => ["EMR_EXTTEXTOUTA", false], 84 => ["EMR_EXTTEXTOUTW", true] }.freeze
21
+ POLY = { 96 => ["EMR_POLYTEXTOUTA", false], 97 => ["EMR_POLYTEXTOUTW", true] }.freeze
22
+ SMALL = 108
23
+ EXT_TEXT_AT = 36
24
+ POLY_TABLE_AT = 40
25
+ UNREADABLE = "the record Size still delimits the stream, so a string or Dx array that cannot be located is skipped by readers"
26
+
27
+ # id => [citation, kind, severity, tolerant_reason]
28
+ RULES = {
29
+ "emf.exttextout.string_bounds" => ["MS-EMF 2.2.5", :string_bounds, :error, UNREADABLE],
30
+ "emf.exttextout.string_offset_alignment" => ["MS-EMF 2.2.5", :string_alignment, :error, nil],
31
+ "emf.exttextout.dx_bounds" => ["MS-EMF 2.2.5", :dx_bounds, :error, UNREADABLE],
32
+ "emf.exttextout.dx_offset_alignment" => ["MS-EMF 2.2.5", :dx_alignment, :error, nil],
33
+ "emf.exttextout.options_defined" => ["MS-EMF 2.1.11", :exttextout_options, :warning, nil],
34
+ "emf.exttextout.string_encoding" => ["MS-EMF 2.2.5", :string_encoding, :error,
35
+ "readers decode a damaged UTF-16LE string character by character and still draw the rest"],
36
+ "emf.smalltextout.text_string" => ["MS-EMF 2.3.5.37", :small_text, :error, UNREADABLE],
37
+ "emf.smalltextout.options_defined" => ["MS-EMF 2.1.11", :smalltextout_options, :warning, nil],
38
+ "emf.polytextout.strings" => ["MS-EMF 2.3.5.32", :poly_strings, :error, UNREADABLE],
39
+ "emf.polytextout.dx" => ["MS-EMF 2.3.5.32", :poly_dx, :error, UNREADABLE],
40
+ "emf.polytextout.options_defined" => ["MS-EMF 2.1.11", :polytextout_options, :warning, nil],
41
+ "emf.opaque.text_arrays" => ["MS-EMF 2.3.5.32", :poly_table, :error, UNREADABLE]
42
+ }.freeze
43
+
44
+ # Findings of one metafile, grouped by rule id; computed once and shared by all rules.
45
+ CACHE = ObjectSpace::WeakMap.new
46
+
47
+ module_function
48
+
49
+ def findings(context, id)
50
+ # The WeakMap holds its values weakly: keep a strong reference until the lookup is done.
51
+ grouped = CACHE[context] || (CACHE[context] = analyze(context))
52
+ grouped.fetch(id, [])
53
+ end
54
+
55
+ def analyze(context)
56
+ out = Hash.new { |hash, id| hash[id] = [] }
57
+ context.records.each_with_index do |record, index|
58
+ type = context.record_type(record)
59
+ next unless EXT.key?(type) || POLY.key?(type) || type == SMALL
60
+
61
+ Scan.new(record, index, context.record_size(record), out).run(type)
62
+ end
63
+ out
64
+ end
65
+
66
+ # Option, string and Dx checks shared by the EXTTEXTOUT and POLYTEXTOUT descriptors; mixed into Scan.
67
+ module BufferChecks
68
+ private
69
+
70
+ def check_options(id, label, options)
71
+ undefined = options & ~DEFINED_OPTIONS
72
+ return if undefined.zero?
73
+
74
+ add(id,
75
+ format("%<label>s Options 0x%<options>X sets bits outside ExtTextOutOptions (0x%<bits>X)",
76
+ label: label, options: options, bits: undefined))
77
+ end
78
+
79
+ def check_string(label, entry, wide, fixed_end, bounds_id, alignment_id)
80
+ width = wide ? 2 : 1
81
+ length = entry[:chars] * width
82
+ return if entry[:chars].zero?
83
+
84
+ if entry[:off_string] < fixed_end || entry[:off_string] + length > @size
85
+ add(bounds_id,
86
+ "#{label} string of #{entry[:chars]} chars at #{entry[:off_string]} is outside the record body (#{fixed_end}..#{@size})")
87
+ elsif (entry[:off_string] % width).positive?
88
+ add(alignment_id, "#{label} offString #{entry[:off_string]} is not #{width * 8}-bit aligned")
89
+ elsif wide && entry[:options].nobits?(ETO_GLYPH_INDEX) # glyph indexes are arbitrary 16-bit codes, not UTF-16
90
+ check_encoding(label, entry, length)
91
+ end
92
+ end
93
+
94
+ # UTF16-LE: an unpaired surrogate is not a character (MS-EMF 2.2.5, OutputString).
95
+ def check_encoding(label, entry, length)
96
+ text = @bytes.byteslice(entry[:off_string], length).force_encoding(Encoding::UTF_16LE)
97
+ return if text.valid_encoding?
98
+
99
+ add("emf.exttextout.string_encoding",
100
+ "#{label} string at offset #{entry[:off_string]} is not valid UTF-16LE (unpaired surrogate)")
101
+ end
102
+
103
+ def check_dx(label, entry, fixed_end, bounds_id, alignment_id)
104
+ return if entry[:chars].zero? || entry[:off_dx].zero?
105
+
106
+ length = entry[:chars] * (entry[:options].anybits?(ETO_PDY) ? 2 : 1) * 4
107
+ if entry[:off_dx] < fixed_end || entry[:off_dx] + length > @size
108
+ add(bounds_id,
109
+ "#{label} Dx array of #{length / 4} values at #{entry[:off_dx]} is outside the record body (#{fixed_end}..#{@size})")
110
+ elsif (entry[:off_dx] % 4).positive?
111
+ add(alignment_id, "#{label} offDx #{entry[:off_dx]} is not 32-bit aligned")
112
+ end
113
+ end
114
+ end
115
+
116
+ # One record's checks; every method adds to `out[rule id]`.
117
+ class Scan
118
+ include BufferChecks
119
+
120
+ def initialize(record, index, size, out)
121
+ @record = record
122
+ @index = index
123
+ @out = out
124
+ @bytes = record.to_binary_s
125
+ @size = [size.to_i, @bytes.bytesize].min
126
+ end
127
+
128
+ def run(type)
129
+ if EXT.key?(type)
130
+ ext_text(*EXT.fetch(type))
131
+ elsif POLY.key?(type)
132
+ poly_text(*POLY.fetch(type))
133
+ else
134
+ small_text
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def add(id, message)
141
+ @out[id] << { message: message, offset: @record.offset, record_index: @index + 1, record_type: @record.type_id.to_i }
142
+ end
143
+
144
+ def u32(pos) = @bytes.unpack1("V", offset: pos)
145
+
146
+ # EmrText fixed part: Reference, Chars, offString, Options, [Rectangle], offDx.
147
+ # offDx is nil when the record ends before it; callers check `length` first.
148
+ def text_entry(start)
149
+ options = u32(start + 16)
150
+ length = options.anybits?(ETO_NO_RECT) ? 24 : 40
151
+ off_dx = u32(start + length - 4) if start + length <= @bytes.bytesize
152
+ { chars: u32(start + 8), off_string: u32(start + 12), options: options, off_dx: off_dx, length: length }
153
+ end
154
+
155
+ def ext_text(name, wide)
156
+ return unless @size >= EXT_TEXT_AT + 24
157
+
158
+ entry = text_entry(EXT_TEXT_AT)
159
+ if @size < EXT_TEXT_AT + entry[:length]
160
+ return add("emf.exttextout.string_bounds",
161
+ "#{name} is shorter than its #{EXT_TEXT_AT + entry[:length]}-byte EmrText fixed part")
162
+ end
163
+
164
+ check_options("emf.exttextout.options_defined", name, entry[:options])
165
+ check_string(name, entry, wide, EXT_TEXT_AT + entry[:length], "emf.exttextout.string_bounds",
166
+ "emf.exttextout.string_offset_alignment")
167
+ check_dx(name, entry, EXT_TEXT_AT + entry[:length], "emf.exttextout.dx_bounds", "emf.exttextout.dx_offset_alignment")
168
+ end
169
+
170
+ def poly_text(name, wide)
171
+ entries = poly_table(name) or return
172
+
173
+ entries.each_with_index do |entry, i|
174
+ label = "#{name} string #{i}"
175
+ check_options("emf.polytextout.options_defined", label, entry[:options])
176
+ check_string(label, entry, wide, @table_end, "emf.polytextout.strings", "emf.polytextout.strings")
177
+ check_dx(label, entry, @table_end, "emf.polytextout.dx", "emf.polytextout.dx")
178
+ end
179
+ end
180
+
181
+ # cStrings descriptors, each 24 bytes (ETO_NO_RECT) or 40, must fit in the record; nil when they do not.
182
+ def poly_table(name)
183
+ count = u32(POLY_TABLE_AT - 4) if @size >= POLY_TABLE_AT
184
+ unless count
185
+ add("emf.opaque.text_arrays", "#{name} Size #{@size} is smaller than its 40-byte fixed part")
186
+ return nil
187
+ end
188
+
189
+ @table_end = POLY_TABLE_AT
190
+ (0...count).map do |i|
191
+ length = @table_end + 24 <= @size && (u32(@table_end + 16).anybits?(ETO_NO_RECT) ? 24 : 40)
192
+ if !length || @table_end + length > @size
193
+ add("emf.opaque.text_arrays", "#{name} cStrings #{count} descriptors run past the record at descriptor #{i}")
194
+ return nil
195
+ end
196
+
197
+ text_entry(@table_end).tap { @table_end += length }
198
+ end
199
+ end
200
+
201
+ def small_text
202
+ return unless @size >= 36
203
+
204
+ options = u32(20)
205
+ check_options("emf.smalltextout.options_defined", "EMR_SMALLTEXTOUT", options)
206
+ start = options.anybits?(ETO_NO_RECT) ? 36 : 52
207
+ length = u32(16) * (options.anybits?(ETO_SMALL_CHARS) ? 1 : 2)
208
+ return unless start + length > @size
209
+
210
+ add("emf.smalltextout.text_string",
211
+ "EMR_SMALLTEXTOUT TextString of #{length} bytes at offset #{start} runs past the #{@size}-byte record")
212
+ end
213
+ end
214
+ end
215
+
216
+ TextRules::RULES.each do |id, (citation, kind, severity, reason)|
217
+ Registry.loading.register(id: id, formats: TextRules::EMF, citation: citation, kind: kind, severity: severity,
218
+ tolerant_reason: reason) { |context| TextRules.findings(context, id) }
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Transform record rules (MS-EMF 2.3.12, 2.2.28): fixed sizes, the
4
+ # ModifyWorldTransformMode enumeration and the XForm matrix values.
5
+ return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
6
+
7
+ module Emf
8
+ module Conformance
9
+ module TransformRules
10
+ EMF = %i[emf emf_plus].freeze
11
+ SET_WORLD = 35
12
+ MODIFY_WORLD = 36
13
+ STATED_SIZE = { SET_WORLD => 32, MODIFY_WORLD => 36 }.freeze
14
+ MATRIX_FIELDS = %i[m11 m12 m21 m22 dx dy].freeze
15
+ MWT_IDENTITY = 1
16
+
17
+ module_function
18
+
19
+ def each_wire(context)
20
+ context.records.each_with_index do |record, index|
21
+ next unless STATED_SIZE.key?(context.record_type(record))
22
+
23
+ wire = record.wire
24
+ yield(record, index, wire) if wire.is_a?(Emf::Emr::Binary::Record)
25
+ end
26
+ end
27
+
28
+ def at(record, index, message)
29
+ { message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
30
+ end
31
+
32
+ def fixed_size(context)
33
+ found = []
34
+ each_wire(context) do |record, index, _wire|
35
+ type = context.record_type(record)
36
+ size = context.record_size(record)
37
+ next if size == STATED_SIZE.fetch(type)
38
+
39
+ found << at(record, index, "#{RecordTypes.name(type)} Size is #{size}, the record section states #{STATED_SIZE.fetch(type)}")
40
+ end
41
+ found
42
+ end
43
+
44
+ def modify_mode(context)
45
+ found = []
46
+ each_wire(context) do |record, index, wire|
47
+ next unless context.record_type(record) == MODIFY_WORLD
48
+
49
+ mode = wire.modify_mode.to_i
50
+ next if (1..4).cover?(mode)
51
+
52
+ found << at(record, index,
53
+ "EMR_MODIFYWORLDTRANSFORM ModifyWorldTransformMode #{mode} is not in the enumeration (MWT_IDENTITY..MWT_SET, 1..4)")
54
+ end
55
+ found
56
+ end
57
+
58
+ # MWT_IDENTITY ignores the transform data, so its matrix is not inspected.
59
+ def finite_matrix(context)
60
+ found = []
61
+ each_wire(context) do |record, index, wire|
62
+ next if context.record_type(record) == MODIFY_WORLD && wire.modify_mode.to_i == MWT_IDENTITY
63
+
64
+ bad = MATRIX_FIELDS.reject { |field| wire.xform.public_send(field).to_f.finite? }
65
+ next if bad.empty?
66
+
67
+ found << at(record, index, "#{RecordTypes.name(context.record_type(record))} XForm #{bad.join(', ')} is not a finite number")
68
+ end
69
+ found
70
+ end
71
+ end
72
+
73
+ registry = Registry.loading
74
+ transform = TransformRules::EMF
75
+ registry.register(id: "emf.transform.fixed_size", formats: transform, citation: "MS-EMF 2.3.12", kind: :fixed_size) do |c|
76
+ TransformRules.fixed_size(c)
77
+ end
78
+ registry.register(id: "emf.transform.modify_mode", formats: transform, citation: "MS-EMF 2.3.12.1", kind: :modify_mode) do |c|
79
+ TransformRules.modify_mode(c)
80
+ end
81
+ # The XForm section defines six FLOAT values and states no finiteness requirement.
82
+ registry.register(id: "emf.transform.finite_matrix", formats: transform, citation: "MS-EMF 2.2.28", kind: :finite_matrix,
83
+ severity: :warning) { |c| TransformRules.finite_matrix(c) }
84
+ end
85
+ end