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
@@ -1,9 +1,158 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Placeholder until TODO.impl/07-wmf-wire-domain-parser.md.
3
+ require "emf/wmf/text_codec"
4
+ require "emf/wmf/palette_codec"
5
+ require "emf/wmf/region_codec"
6
+ require "emf/wmf/brush_codec"
7
+ require "emf/wmf/bitmap_codec"
8
+ require "emf/wmf/geometry_codec"
9
+ require "emf/wmf/dc_escape_codec"
10
+
4
11
  module Emf
5
12
  module Wmf
6
13
  module Binary
14
+ PLACEABLE_KEY = 0x9AC6CDD7
15
+ HEADER_BYTES = 18
16
+ PLACEABLE_BYTES = 22
17
+ MAX_INPUT_BYTES = 200 * 1024 * 1024
18
+ MAX_RECORDS = 1_000_000
19
+
20
+ FIXED_RECORDS = {
21
+ 0x14 => Model::Wmf::Records::MoveTo,
22
+ 0x13 => Model::Wmf::Records::LineTo,
23
+ 0x0B => Model::Wmf::Records::SetWindowOrg,
24
+ 0x0C => Model::Wmf::Records::SetWindowExt,
25
+ 0x0D => Model::Wmf::Records::SetViewportOrg,
26
+ 0x0E => Model::Wmf::Records::SetViewportExt,
27
+ 0x0F => Model::Wmf::Records::OffsetWindowOrg,
28
+ 0x11 => Model::Wmf::Records::OffsetViewportOrg,
29
+ 0x17 => Model::Wmf::Records::Arc,
30
+ 0x18 => Model::Wmf::Records::Ellipse,
31
+ 0x1A => Model::Wmf::Records::Pie,
32
+ 0x1B => Model::Wmf::Records::Rectangle,
33
+ 0x1C => Model::Wmf::Records::RoundRect,
34
+ 0x30 => Model::Wmf::Records::Chord
35
+ }.freeze
36
+ FIXED_FUNCTIONS = FIXED_RECORDS.invert.freeze
37
+
38
+ POINT_ARRAY_RECORDS = {
39
+ 0x24 => Model::Wmf::Records::Polygon,
40
+ 0x25 => Model::Wmf::Records::Polyline,
41
+ 0x38 => Model::Wmf::Records::PolyPolygon
42
+ }.freeze
43
+ POINT_ARRAY_FUNCTIONS = POINT_ARRAY_RECORDS.invert.freeze
44
+
45
+ STATE_RECORDS = {
46
+ 0x01 => Model::Wmf::Records::SetBkColor,
47
+ 0x02 => Model::Wmf::Records::SetBkMode,
48
+ 0x03 => Model::Wmf::Records::SetMapMode,
49
+ 0x31 => Model::Wmf::Records::SetMapperFlags,
50
+ 0x06 => Model::Wmf::Records::SetPolyFillMode,
51
+ 0x04 => Model::Wmf::Records::SetRop2,
52
+ 0x07 => Model::Wmf::Records::SetStretchBltMode,
53
+ 0x2E => Model::Wmf::Records::SetTextAlign,
54
+ 0x08 => Model::Wmf::Records::SetTextCharExtra,
55
+ 0x09 => Model::Wmf::Records::SetTextColor,
56
+ 0x0A => Model::Wmf::Records::SetTextJustification,
57
+ 0x49 => Model::Wmf::Records::SetLayout
58
+ }.freeze
59
+ STATE_FUNCTIONS = STATE_RECORDS.invert.freeze
60
+ PREFIX_FUNCTIONS = ([0xFB] + STATE_RECORDS.keys + PaletteCodec::RECORDS.keys + RegionCodec::RECORDS.keys +
61
+ BrushCodec::RECORDS.keys + BitmapCodec::RECORDS.keys + GeometryCodec::RECORDS.keys +
62
+ DcEscapeCodec::RECORDS.keys).freeze
63
+
64
+ OBJECT_RECORDS = {
65
+ 0xFA => Model::Wmf::Records::CreatePenIndirect,
66
+ 0xFC => Model::Wmf::Records::CreateBrushIndirect,
67
+ 0x2D => Model::Wmf::Records::SelectObject,
68
+ 0xF0 => Model::Wmf::Records::DeleteObject
69
+ }.freeze
70
+ OBJECT_FUNCTIONS = OBJECT_RECORDS.invert.freeze
71
+ # Opaque creators still consume the lowest available object-table slot.
72
+ OBJECT_CREATORS = [0xFA, 0xFC, 0xFB, 0xF7, 0xF9, 0xFF, 0x42].freeze
73
+ TEXT_FUNCTIONS = [0x21, 0x32].freeze
74
+ TYPED_FUNCTIONS = (FIXED_RECORDS.keys + POINT_ARRAY_RECORDS.keys + OBJECT_RECORDS.keys + STATE_RECORDS.keys +
75
+ TEXT_FUNCTIONS + PaletteCodec::RECORDS.keys + RegionCodec::RECORDS.keys + BrushCodec::RECORDS.keys +
76
+ BitmapCodec::RECORDS.keys + GeometryCodec::RECORDS.keys + DcEscapeCodec::RECORDS.keys + [0xFB]).freeze
77
+
78
+ module_function
79
+
80
+ def prefix_codec(record)
81
+ [PaletteCodec, RegionCodec, BrushCodec, BitmapCodec, GeometryCodec, DcEscapeCodec]
82
+ .find { |codec| codec::FUNCTIONS.key?(record.class) }
83
+ end
84
+
85
+ def checksum(bytes)
86
+ bytes.unpack("v10").reduce(0, :^)
87
+ end
88
+
89
+ def integer(value, range, name)
90
+ return value if value.is_a?(Integer) && range.cover?(value)
91
+
92
+ raise SerializeError, "WMF #{name} out of range: #{value.inspect}"
93
+ end
94
+
95
+ def uint8(value, name)
96
+ integer(value, 0..0xFF, name)
97
+ end
98
+
99
+ def uint16(value, name)
100
+ integer(value, 0..0xFFFF, name)
101
+ end
102
+
103
+ def uint32(value, name)
104
+ integer(value, 0..0xFFFFFFFF, name)
105
+ end
106
+
107
+ # Yields (record, message) for a table violation the parser cannot recover from. A reference to no live object
108
+ # (1702) or, for a clip, paint or invert record, to a non-region (1704) is recovered: the reference is ignored,
109
+ # the record kept, and `recovered` (if given) is called with (record, reason, index).
110
+ def validate_objects(records, capacity, recovered: nil)
111
+ slots = Array.new(capacity, false)
112
+ records.each do |record|
113
+ function = record.type_id & 0xFF
114
+ if OBJECT_CREATORS.include?(function)
115
+ index = slots.index(false)
116
+ yield record, "object table capacity exceeded" unless index
117
+ slots[index] = function
118
+ elsif record.is_a?(Model::Wmf::Records::FillRegion)
119
+ validate_region_references(record, slots) { |message| yield record, message }
120
+ elsif record.is_a?(Model::Wmf::Records::ObjectReference)
121
+ validate_object_reference(record, capacity, slots, recovered && lambda { |reason, index|
122
+ recovered.call(record, reason, index)
123
+ }) do |message|
124
+ yield record, message
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def validate_object_reference(record, capacity, slots, recovered)
131
+ index = record.object_index
132
+ # Preserve high-bit stock selectors without interpreting their numeric ID.
133
+ return if record.is_a?(Model::Wmf::Records::SelectObject) && index.anybits?(0x8000)
134
+ return recovered&.call(1702, index) unless index < capacity && slots[index]
135
+
136
+ yield "object reference #{index} is not a palette" if record.is_a?(Model::Wmf::Records::SelectPalette) && slots[index] != 0xF7
137
+ return recovered&.call(1704, index) if not_region?(record, slots[index])
138
+
139
+ slots[index] = false if record.is_a?(Model::Wmf::Records::DeleteObject)
140
+ end
141
+
142
+ def not_region?(record, slot) = record.is_a?(Model::Wmf::Records::RegionReference) && slot != 0xFF
143
+
144
+ def validate_region_references(record, slots)
145
+ region = record.region_index
146
+ brush = record.brush_index
147
+ yield "invalid region reference #{region}" unless region < slots.length && slots[region]
148
+ yield "object reference #{region} is not a region" unless slots[region] == 0xFF
149
+ yield "invalid brush reference #{brush}" unless brush < slots.length && slots[brush]
150
+ yield "object reference #{brush} is not a brush" unless [0xFC, 0xF9, 0x42].include?(slots[brush])
151
+ end
152
+
153
+ def int16(value, name)
154
+ integer(value, -0x8000..0x7FFF, name)
155
+ end
7
156
  end
8
157
  end
9
158
  end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Wmf
5
+ module BitmapCodec
6
+ RECORDS = { 0x22 => Model::Wmf::Records::BitBlt, 0x40 => Model::Wmf::Records::DibBitBlt,
7
+ 0x23 => Model::Wmf::Records::StretchBlt, 0x41 => Model::Wmf::Records::DibStretchBlt,
8
+ 0x33 => Model::Wmf::Records::SetDibToDev, 0x43 => Model::Wmf::Records::StretchDib }.freeze
9
+ FUNCTIONS = RECORDS.invert.freeze
10
+ BLT_FIELDS = %i[raster_operation source_y source_x height width destination_y destination_x reserved].freeze
11
+ STRETCH_FIELDS = %i[raster_operation source_height source_width source_y source_x destination_height destination_width
12
+ destination_y destination_x reserved].freeze
13
+ SET_FIELDS = %i[color_usage scan_count start_scan source_y source_x height width destination_y destination_x].freeze
14
+ DIB_FIELDS = (STRETCH_FIELDS[0, 1] + [:color_usage] + STRETCH_FIELDS[1, 8]).freeze
15
+ FIELDS = { 0x22 => BLT_FIELDS, 0x40 => BLT_FIELDS, 0x23 => STRETCH_FIELDS, 0x41 => STRETCH_FIELDS,
16
+ 0x33 => SET_FIELDS, 0x43 => DIB_FIELDS }.freeze
17
+
18
+ FORMATS = { uint32: "V", uint16: "v", int16: "s<" }.freeze
19
+
20
+ module_function
21
+
22
+ def optional?(kind)
23
+ [0x22, 0x40, 0x23, 0x41].include?(kind)
24
+ end
25
+
26
+ def field_type(kind, field)
27
+ return :uint32 if field == :raster_operation
28
+ return :uint16 if kind == 0x33 || %i[color_usage reserved].include?(field)
29
+
30
+ :int16
31
+ end
32
+
33
+ def wire_fields(kind, absent)
34
+ fields = FIELDS.fetch(kind).reject { |field| field == :reserved }
35
+ fields.insert(fields.index([0x22, 0x40].include?(kind) ? :height : :destination_height), :reserved) if absent
36
+ fields
37
+ end
38
+
39
+ def format_for(kind, fields)
40
+ fields.map { |field| FORMATS.fetch(field_type(kind, field)) }.join
41
+ end
42
+
43
+ def decode(raw, function, offset)
44
+ kind = function & 0xFF
45
+ absent = optional?(kind) && (raw.bytesize - 6) / 2 == function >> 8
46
+ names = wire_fields(kind, absent)
47
+ format = format_for(kind, names)
48
+ length = names.sum { |field| field == :raster_operation ? 4 : 2 }
49
+ yield "truncated bitmap transfer prefix" if raw.bytesize < 6 + length
50
+ fields = names.zip(raw.byteslice(6, length).unpack(format)).to_h
51
+ fields[:reserved] ||= 0 if optional?(kind)
52
+ if absent
53
+ fields[:bitmap] = nil
54
+ fields[:suffix] = raw.byteslice((6 + length)..)
55
+ else
56
+ yield "empty bitmap transfer source" unless raw.bytesize > 6 + length
57
+ fields[:bitmap] = raw.byteslice((6 + length)..)
58
+ fields[:suffix] = "".b
59
+ end
60
+ RECORDS.fetch(kind).new(**fields, type_id: function, offset: offset)
61
+ end
62
+
63
+ def byte_size(record)
64
+ kind = FUNCTIONS.fetch(record.class)
65
+ prefix = wire_fields(kind, optional?(kind) && record.bitmap.nil?).sum { |field| field == :raster_operation ? 4 : 2 }
66
+ 6 + prefix + (record.bitmap&.bytesize || 0) + record.suffix.bytesize
67
+ end
68
+
69
+ def encode(record)
70
+ function = Binary.uint16(record.type_id, "RecordFunction")
71
+ kind = FUNCTIONS.fetch(record.class)
72
+ raise SerializeError, "WMF bitmap RecordFunction mismatch" unless function & 0xFF == kind
73
+
74
+ fields = FIELDS.fetch(kind).to_h { |field| [field, record.public_send(field)] }
75
+ fields[:bitmap] = record.bitmap
76
+ fields[:suffix] = record.suffix
77
+ body = encode_fields(fields, function)
78
+ [3 + (body.bytesize / 2), function].pack("Vv") + body
79
+ end
80
+
81
+ def encode_fields(fields, function)
82
+ kind = function & 0xFF
83
+ FIELDS.fetch(kind).each { |field| Binary.public_send(field_type(kind, field), fields.fetch(field), field) }
84
+ absent = optional?(kind) && fields[:bitmap].nil?
85
+ image, suffix = source_bytes(fields, kind, absent)
86
+ names = wire_fields(kind, absent)
87
+ size = names.sum { |field| field == :raster_operation ? 4 : 2 } + image.bytesize + suffix.bytesize
88
+ validate_layout(kind, absent, size, function)
89
+
90
+ names.map { |field| fields.fetch(field) }.pack(format_for(kind, names)) + image.b + suffix.b
91
+ end
92
+
93
+ def source_bytes(fields, kind, absent)
94
+ suffix = fields.fetch(:suffix, "".b)
95
+ validate_bytes(suffix)
96
+ raise SerializeError, "WMF bitmap suffix requires absent source" unless absent || suffix.empty?
97
+ raise SerializeError, "WMF present bitmap reserved word must be zero" if optional?(kind) && !absent && fields[:reserved] != 0
98
+
99
+ image = absent ? "".b : fields[:bitmap]
100
+ validate_bytes(image)
101
+ raise SerializeError, "empty WMF bitmap source" if !absent && image.empty?
102
+
103
+ [image, suffix]
104
+ end
105
+
106
+ def validate_layout(kind, absent, size, function)
107
+ raise SerializeError, "WMF bitmap too large" if size > Binary::MAX_INPUT_BYTES - 6
108
+ raise SerializeError, "ambiguous WMF bitmap source layout" if optional?(kind) && absent != (size / 2 == function >> 8)
109
+ end
110
+
111
+ def validate_bytes(value)
112
+ return if value.is_a?(String) && value.bytesize.even? && value.bytesize <= Binary::MAX_INPUT_BYTES - 6
113
+
114
+ raise SerializeError, "invalid WMF bitmap bytes"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Wmf
5
+ module BrushCodec
6
+ RECORDS = { 0xF9 => Model::Wmf::Records::CreatePatternBrush, 0x42 => Model::Wmf::Records::DibCreatePatternBrush }.freeze
7
+ FUNCTIONS = RECORDS.invert.freeze
8
+ SIGNED_FIELDS = %i[bitmap_type width height width_bytes].freeze
9
+ BYTE_FIELDS = %i[planes bits_pixel].freeze
10
+
11
+ module_function
12
+
13
+ def decode(raw, function, offset)
14
+ fields = { type_id: function, offset: offset }
15
+ if function & 0xFF == 0xF9
16
+ yield "truncated pattern brush prefix" if raw.bytesize < 38
17
+ values = raw.byteslice(6, 10).unpack("s<4C2")
18
+ fields.merge!((SIGNED_FIELDS + BYTE_FIELDS).zip(values).to_h)
19
+ length = pattern_length(fields[:width], fields[:height], fields[:planes], fields[:bits_pixel])
20
+ yield "invalid pattern brush extent" unless length && length <= raw.bytesize - 38
21
+ fields.merge!(bits: raw.byteslice(16, 4), reserved: raw.byteslice(20, 18),
22
+ pattern: raw.byteslice(38, length), suffix: raw.byteslice((38 + length)..))
23
+ else
24
+ yield "truncated DIB brush prefix" if raw.bytesize < 12
25
+ style, usage = raw.byteslice(6, 4).unpack("v2")
26
+ yield "invalid DIB brush color usage" unless style == 3 || usage <= 2
27
+ fields.merge!(style: style, color_usage: usage, target: raw.byteslice(10..))
28
+ end
29
+ RECORDS.fetch(function & 0xFF).new(**fields)
30
+ end
31
+
32
+ def pattern_length(width, height, planes, bits_pixel)
33
+ return unless width.positive? && height.positive? && planes == 1 && bits_pixel.positive?
34
+
35
+ (((width * bits_pixel) + 15) / 16) * 2 * height
36
+ end
37
+
38
+ def byte_size(record)
39
+ if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
40
+ 38 + record.pattern.bytesize + record.suffix.bytesize
41
+ else
42
+ 10 + record.target.bytesize
43
+ end
44
+ end
45
+
46
+ def encode(record)
47
+ function = Binary.uint16(record.type_id, "RecordFunction")
48
+ raise SerializeError, "WMF brush RecordFunction mismatch" unless function & 0xFF == FUNCTIONS[record.class]
49
+
50
+ payload = encode_payload(record)
51
+ if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
52
+ validate_string(record.suffix, "suffix", even: true)
53
+ validate_size(payload.bytesize + record.suffix.bytesize)
54
+ payload += record.suffix.b
55
+ end
56
+ validate_size(payload.bytesize)
57
+
58
+ [3 + (payload.bytesize / 2), function].pack("Vv") + payload
59
+ end
60
+
61
+ def encode_payload(record)
62
+ if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
63
+ pattern_payload(record)
64
+ else
65
+ style = Binary.uint16(record.style, "style")
66
+ usage = Binary.uint16(record.color_usage, "color_usage")
67
+ raise SerializeError, "invalid WMF DIB brush color usage" unless style == 3 || usage <= 2
68
+
69
+ validate_string(record.target, "target", even: true)
70
+ raise SerializeError, "empty WMF DIB brush target" if record.target.empty?
71
+
72
+ validate_size(4 + record.target.bytesize)
73
+ [style, usage].pack("v2") + record.target.b
74
+ end
75
+ end
76
+
77
+ def pattern_payload(record)
78
+ signed = SIGNED_FIELDS.map { |field| Binary.int16(record.public_send(field), field) }
79
+ bytes = BYTE_FIELDS.map { |field| Binary.uint8(record.public_send(field), field) }
80
+ length = pattern_length(record.width, record.height, record.planes, record.bits_pixel)
81
+ validate_string(record.bits, "bits", length: 4)
82
+ validate_string(record.reserved, "reserved", length: 18)
83
+ validate_string(record.pattern, "pattern", even: true)
84
+ raise SerializeError, "invalid WMF pattern brush extent" unless length && length == record.pattern.bytesize
85
+
86
+ validate_size(32 + record.pattern.bytesize)
87
+ [*signed, *bytes].pack("s<4C2") + record.bits.b + record.reserved.b + record.pattern.b
88
+ end
89
+
90
+ def validate_size(size)
91
+ raise SerializeError, "WMF brush too large" if size > Binary::MAX_INPUT_BYTES - 6
92
+ end
93
+
94
+ def validate_string(value, name, length: nil, even: false)
95
+ return if value.is_a?(String) && (!length || value.bytesize == length) && (!even || value.bytesize.even?) &&
96
+ value.bytesize <= Binary::MAX_INPUT_BYTES - 6
97
+
98
+ raise SerializeError, "invalid WMF brush #{name} bytes"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Wmf
5
+ module DcEscapeCodec
6
+ RECORDS = {
7
+ 0x1E => Model::Wmf::Records::SaveDc,
8
+ 0x27 => Model::Wmf::Records::RestoreDc,
9
+ 0x05 => Model::Wmf::Records::SetRelAbs,
10
+ 0x26 => Model::Wmf::Records::Escape
11
+ }.freeze
12
+ FUNCTIONS = RECORDS.invert.freeze
13
+
14
+ module_function
15
+
16
+ def decode(raw, function, offset)
17
+ klass = RECORDS.fetch(function & 0xFF)
18
+ fields = { type_id: function, offset: offset }
19
+ prefix = 6
20
+ if klass == Model::Wmf::Records::RestoreDc
21
+ prefix = 8
22
+ yield "truncated RestoreDc prefix" if raw.bytesize < prefix
23
+ fields[:saved_dc] = raw.byteslice(6, 2).unpack1("s<")
24
+ elsif klass == Model::Wmf::Records::Escape
25
+ yield "truncated Escape prefix" if raw.bytesize < 10
26
+ fields[:escape_function], count = raw.byteslice(6, 4).unpack("v2")
27
+ prefix = 10 + count + (count % 2)
28
+ yield "truncated Escape data or padding" if raw.bytesize < prefix
29
+ fields[:data] = raw.byteslice(10, count)
30
+ fields[:padding] = raw.getbyte(10 + count) if count.odd?
31
+ end
32
+ klass.new(**fields, suffix: raw.byteslice(prefix..))
33
+ end
34
+
35
+ def byte_size(record)
36
+ prefix = case record
37
+ when Model::Wmf::Records::RestoreDc then 8
38
+ when Model::Wmf::Records::Escape then 10 + record.data.bytesize + (record.data.bytesize % 2)
39
+ else 6
40
+ end
41
+ prefix + record.suffix.bytesize
42
+ end
43
+
44
+ def encode(record)
45
+ function = Binary.uint16(record.type_id, "RecordFunction")
46
+ raise SerializeError, "WMF DC/Escape RecordFunction mismatch" unless function & 0xFF == FUNCTIONS[record.class]
47
+ unless record.suffix.is_a?(String) && record.suffix.bytesize.even?
48
+ raise SerializeError, "WMF DC/Escape suffix must be a String with even length"
49
+ end
50
+
51
+ payload = case record
52
+ when Model::Wmf::Records::RestoreDc then [Binary.int16(record.saved_dc, "saved_dc")].pack("s<")
53
+ when Model::Wmf::Records::Escape then encode_escape(record)
54
+ else "".b
55
+ end
56
+ payload += record.suffix.b
57
+ [3 + (payload.bytesize / 2), function].pack("Vv") + payload
58
+ end
59
+
60
+ def encode_escape(record)
61
+ raise SerializeError, "WMF Escape data must be a byte string" unless record.data.is_a?(String)
62
+
63
+ count = Binary.uint16(record.data.bytesize, "Escape ByteCount")
64
+ function = Binary.uint16(record.escape_function, "EscapeFunction")
65
+ padding = "".b
66
+ if count.odd?
67
+ padding = [Binary.uint8(record.padding, "Escape padding")].pack("C")
68
+ elsif !record.padding.nil?
69
+ raise SerializeError, "WMF even Escape data must not have padding"
70
+ end
71
+ [function, count].pack("v2") + record.data.b + padding
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Wmf
5
+ # Read-only views of the packed bitmap interiors retained by WMF records.
6
+ # The raw bytes on the record stay authoritative; compressed pixels, header
7
+ # extensions and padding are kept verbatim and nothing is decoded or converted.
8
+ # Malformed or unsupported layouts return nil, as in the Rust views.
9
+ module Dib
10
+ CoreHeader = Struct.new(:width, :height, :planes, :bit_count, keyword_init: true)
11
+ InfoHeader = Struct.new(:width, :height, :planes, :bit_count, :compression, :image_size, :x_pels_per_meter,
12
+ :y_pels_per_meter, :colors_used, :colors_important, :extension, keyword_init: true)
13
+ # kind is :none, :rgb_triples, :rgb_quads or :palette_indices.
14
+ ColorTable = Struct.new(:kind, :rows, keyword_init: true)
15
+ Image = Struct.new(:header, :bitfield_masks, :colors, :pixels, :trailing, keyword_init: true)
16
+ Layout = Struct.new(:header, :masks, :color_count, :entry_size, :pixel_start, :pixel_size, keyword_init: true)
17
+
18
+ COMPRESSIONS = [0, 1, 2, 3, 4, 5, 11, 12, 13].freeze
19
+ BIT_COUNTS = [0, 1, 4, 8, 16, 24, 32].freeze
20
+ RLE_BITS = { 1 => 8, 12 => 8, 2 => 4, 13 => 4 }.freeze
21
+ PROFILE_SIGNATURES = [0x4C494E4B, 0x4D424544].freeze
22
+
23
+ module_function
24
+
25
+ def parse(bytes, color_usage = 0)
26
+ bytes = bytes.b
27
+ layout = layout(bytes, color_usage) or return
28
+ header = layout.header
29
+ Image.new(header: header_view(bytes, header),
30
+ bitfield_masks: layout.masks.positive? ? bytes.byteslice(header, 12).unpack("V3") : nil,
31
+ colors: color_table(bytes, layout, color_usage),
32
+ pixels: bytes.byteslice(layout.pixel_start, layout.pixel_size),
33
+ trailing: bytes.byteslice((layout.pixel_start + layout.pixel_size)..))
34
+ end
35
+
36
+ def header_view(bytes, size)
37
+ if size == 12
38
+ width, height, planes, bits = bytes.unpack("@4v4")
39
+ return CoreHeader.new(width: width, height: height, planes: planes, bit_count: bits)
40
+ end
41
+ width, height, planes, bits, compression, image_size, x_pels, y_pels, used, important = bytes.unpack("@4l<2v2V2l<2V2")
42
+ InfoHeader.new(width: width, height: height, planes: planes, bit_count: bits, compression: compression,
43
+ image_size: image_size, x_pels_per_meter: x_pels, y_pels_per_meter: y_pels, colors_used: used,
44
+ colors_important: important, extension: bytes.byteslice(40, size - 40))
45
+ end
46
+
47
+ def color_table(bytes, layout, color_usage)
48
+ return ColorTable.new(kind: :none, rows: []) if color_usage == 2
49
+
50
+ raw = bytes.byteslice(layout.header + layout.masks, layout.pixel_start - layout.header - layout.masks)
51
+ case layout.entry_size
52
+ when 2 then ColorTable.new(kind: :palette_indices, rows: raw.unpack("v*"))
53
+ when 3 then ColorTable.new(kind: :rgb_triples, rows: raw.bytes.each_slice(3).to_a)
54
+ else ColorTable.new(kind: :rgb_quads, rows: raw.bytes.each_slice(4).to_a)
55
+ end
56
+ end
57
+
58
+ def layout(bytes, usage)
59
+ header = header_size(bytes, usage) or return
60
+
61
+ width, height, planes, bits, compression, image_size, used = header_fields(bytes, header)
62
+ return unless valid_shape?(width, height, planes, bits) && valid_compression?(compression, bits, height)
63
+
64
+ masks = mask_bytes(compression, header)
65
+ colors = color_count(bits, used, usage)
66
+ size = pixel_size(compression, width, bits, height, image_size)
67
+ return unless masks && colors && size
68
+
69
+ build_layout(bytes, header, masks, colors, usage, size)
70
+ end
71
+
72
+ def header_size(bytes, usage)
73
+ return unless bytes.bytesize >= 4 && usage.between?(0, 2)
74
+
75
+ size = bytes.unpack1("V")
76
+ size unless (size != 12 && size < 40) || size > bytes.bytesize
77
+ end
78
+
79
+ def build_layout(bytes, header, masks, colors, usage, size)
80
+ entry_size = color_entry_size(header, usage)
81
+ start = header + masks + (colors * entry_size)
82
+ return unless start + size <= bytes.bytesize && valid_profile?(bytes, header, start + size)
83
+
84
+ Layout.new(header: header, masks: masks, color_count: colors, entry_size: entry_size, pixel_start: start, pixel_size: size)
85
+ end
86
+
87
+ def color_entry_size(header, usage)
88
+ return 2 if usage == 1
89
+
90
+ header == 12 ? 3 : 4
91
+ end
92
+
93
+ def header_fields(bytes, header)
94
+ return bytes.unpack("@4v4") + [0, 0, 0] if header == 12
95
+
96
+ width, height, planes, bits, compression, image_size = bytes.unpack("@4l<2v2V2")
97
+ [width, height, planes, bits, compression, image_size, bytes.unpack1("@32V")]
98
+ end
99
+
100
+ def valid_shape?(width, height, planes, bits)
101
+ width.positive? && !height.zero? && planes == 1 && BIT_COUNTS.include?(bits)
102
+ end
103
+
104
+ def valid_compression?(compression, bits, height)
105
+ return false unless COMPRESSIONS.include?(compression)
106
+ return false if height.negative? && ![0, 3].include?(compression)
107
+ return false if bits.zero? && ![4, 5].include?(compression)
108
+
109
+ RLE_BITS.fetch(compression, bits) == bits && (compression != 3 || [16, 32].include?(bits))
110
+ end
111
+
112
+ # Embedded masks occupy the extension in V4/V5 and later headers.
113
+ def mask_bytes(compression, header)
114
+ return 0 unless compression == 3
115
+ return 12 if header == 40
116
+
117
+ 0 if header >= 52
118
+ end
119
+
120
+ def color_count(bits, used, usage)
121
+ max_colors = bits.between?(1, 8) ? 1 << bits : 0
122
+ return 0 if usage == 2
123
+ return max_colors if used.zero?
124
+ return if max_colors.positive? && used > max_colors
125
+
126
+ used
127
+ end
128
+
129
+ def pixel_size(compression, width, bits, height, image_size)
130
+ return (((width * bits) + 31) / 32 * 4) * height.abs if [0, 3, 11].include?(compression)
131
+
132
+ image_size unless image_size.zero?
133
+ end
134
+
135
+ # A packed V5 profile may follow pixels, but may not overlap header/table/pixels.
136
+ def valid_profile?(bytes, header, pixel_end)
137
+ return true unless header >= 124 && PROFILE_SIGNATURES.include?(bytes.unpack1("@56V"))
138
+
139
+ profile, profile_size = bytes.unpack("@112V2")
140
+ profile_size.zero? || (profile >= pixel_end && profile + profile_size <= bytes.bytesize)
141
+ end
142
+ end
143
+
144
+ module Bitmap16
145
+ Image = Struct.new(:bitmap_type, :width, :height, :width_bytes, :planes, :bits_pixel, :pixels, :trailing,
146
+ keyword_init: true)
147
+
148
+ module_function
149
+
150
+ # The extent derives from Width and BitsPixel, independent of WidthBytes, which is
151
+ # preserved verbatim even when inconsistent with that stride.
152
+ def parse(bytes)
153
+ bytes = bytes.b
154
+ return unless bytes.bytesize >= 10
155
+
156
+ type, width, height, width_bytes, planes, bits = bytes.unpack("s<4C2")
157
+ return unless width.positive? && height.positive? && planes == 1 && bits.positive?
158
+
159
+ size = ((((width * bits) + 15) / 16) * 2) * height
160
+ return unless 10 + size <= bytes.bytesize
161
+
162
+ Image.new(bitmap_type: type, width: width, height: height, width_bytes: width_bytes, planes: planes,
163
+ bits_pixel: bits, pixels: bytes.byteslice(10, size), trailing: bytes.byteslice((10 + size)..))
164
+ end
165
+ end
166
+
167
+ # Typed interior of a bitmap transfer record: a Bitmap16::Image, a Dib::Image,
168
+ # ABSENT for an optional source that is not present, or nil when malformed.
169
+ module BitmapImage
170
+ Absent = Class.new { def inspect = "#<Emf::Wmf::BitmapImage::ABSENT>" }
171
+ ABSENT = Absent.new.freeze
172
+
173
+ module_function
174
+
175
+ def of(record)
176
+ bitmap = record.bitmap
177
+ return ABSENT if bitmap.nil?
178
+
179
+ case record
180
+ when Model::Wmf::Records::BitBlt, Model::Wmf::Records::StretchBlt then Bitmap16.parse(bitmap)
181
+ when Model::Wmf::Records::DibBitBlt, Model::Wmf::Records::DibStretchBlt then Dib.parse(bitmap, 0)
182
+ else Dib.parse(bitmap, record.color_usage)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end