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,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require_relative "library_path"
5
+ require_relative "emf_plus_loader"
6
+ require_relative "emf_plus_snapshot"
7
+
8
+ module Emf
9
+ module Native
10
+ # An Emf::EmfPlus::Stream whose framing comes from the native EMF+ snapshot
11
+ # instead of the Ruby framer. The model is the Ruby one (Segment, typed
12
+ # Records, ObjectTable, editing, rebuild): each native frame's validated raw
13
+ # bytes are decoded by the same Records classes, so unedited output is
14
+ # byte-identical to the source. The native structured payloads are exposed
15
+ # beside it through #snapshot (frame family, normalized payload, and the
16
+ # object-reference diagnostics computed in Rust).
17
+ #
18
+ # Native framing follows the Ruby framer: each comment is framed on its own,
19
+ # a malformed header ends that comment's framing (the rest stays #unframed and
20
+ # the ParseError is collected), and an EMR_COMMENT whose cbData exceeds its
21
+ # record is clamped to the bytes it holds.
22
+ #
23
+ # .from_metafile returns nil whenever the native snapshot cannot stand in for
24
+ # the Ruby parse (library unavailable, a record whose typed layout the native
25
+ # decoder rejects where Ruby keeps an Unknown record, resource limits, or a
26
+ # segment layout that differs from Ruby's);
27
+ # the caller then uses Emf::EmfPlus::Stream.from_metafile, which keeps
28
+ # Ruby's error-collecting recovery. An edited stream (#with_record) keeps
29
+ # the Ruby model but carries no snapshot.
30
+ class EmfPlusStream < Emf::EmfPlus::Stream
31
+ SEGMENT = Emf::EmfPlus::Stream::Segment
32
+ LOADERS_LOCK = Mutex.new
33
+ private_constant :SEGMENT, :LOADERS_LOCK
34
+
35
+ attr_reader :snapshot
36
+
37
+ class << self
38
+ # `bytes` is the EMF file the metafile was parsed from; it is rebuilt from
39
+ # the metafile when omitted. `library` defaults to the configured native library.
40
+ def from_metafile(metafile, bytes: nil, library: LibraryPath.new.call)
41
+ return unless metafile.format == :emf_plus && library && !library.empty?
42
+
43
+ bytes ||= Emf::Emr::Serializer.call(metafile)
44
+ snapshot = EmfPlusSnapshot.new(loader(library).emf_plus_snapshot(bytes))
45
+ build(metafile, snapshot)
46
+ rescue Emf::Error
47
+ nil
48
+ end
49
+
50
+ private
51
+
52
+ def loader(library)
53
+ LOADERS_LOCK.synchronize { (@loaders ||= {})[library] ||= EmfPlusLoader.new(library) }
54
+ end
55
+
56
+ def build(metafile, snapshot)
57
+ comments = ruby_comment_segments(metafile)
58
+ return unless comments.map(&:first) == snapshot.segments.map(&:record_index)
59
+
60
+ errors = []
61
+ segments = comments.zip(snapshot.segments).map do |(index, payload_length), row|
62
+ segment(metafile, snapshot, index, row, payload_length, errors)
63
+ end
64
+ return if segments.any?(&:nil?)
65
+
66
+ new(source: metafile, segments: segments, errors: errors, digests: digests_of(metafile, segments), snapshot: snapshot)
67
+ end
68
+
69
+ # [comment record index, payload length] exactly as Segment.from_comment selects them.
70
+ def ruby_comment_segments(metafile)
71
+ metafile.records.each_with_index.filter_map do |record, index|
72
+ wire = record.wire
73
+ next unless wire.is_a?(Emf::Emr::Binary::Records::Comment) && wire.cb_data >= SEGMENT::IDENTIFIER.bytesize
74
+ next unless wire.data.b.start_with?(SEGMENT::IDENTIFIER)
75
+
76
+ [index, [wire.cb_data, wire.data.bytesize].min - SEGMENT::IDENTIFIER.bytesize]
77
+ end
78
+ end
79
+
80
+ def segment(metafile, snapshot, record_index, row, payload_length, errors)
81
+ return unless row.payload_length == payload_length && row.data_start == SEGMENT::IDENTIFIER.bytesize
82
+
83
+ data = metafile.records[record_index].wire.data.b
84
+ payload = data.byteslice(SEGMENT::IDENTIFIER.bytesize, payload_length)
85
+ frames = snapshot.frames.select { |frame| within?(frame, row) }
86
+ return unless frames.all? { |frame| spans_only?(frame, snapshot.segments, row) }
87
+
88
+ framed = frames.sum(&:frame_size)
89
+ return unless unframed_error(payload, framed, record_index, errors)
90
+
91
+ records = frames.map { |frame| decode(frame, row, record_index, errors) }
92
+ payload_end = SEGMENT::IDENTIFIER.bytesize + payload_length
93
+ SEGMENT.new(record_index: record_index, payload_range: SEGMENT::IDENTIFIER.bytesize...payload_end,
94
+ records: records, unframed: payload.byteslice(framed..), tail: data.byteslice(payload_end..))
95
+ end
96
+
97
+ # Native framing stops where the Ruby framer does; the unframed remainder
98
+ # must raise the same ParseError there (recorded in framing order, before
99
+ # record decode errors). A remainder Ruby would frame disagrees: no adoption.
100
+ def unframed_error(payload, framed, record_index, errors)
101
+ return true if framed == payload.bytesize
102
+
103
+ Emf::EmfPlus::Parser.read_frame(payload, framed, record_index)
104
+ false
105
+ rescue ParseError => e
106
+ errors << e
107
+ true
108
+ end
109
+
110
+ def within?(frame, row)
111
+ frame.offset >= row.logical_start && frame.offset < row.logical_start + row.payload_length
112
+ end
113
+
114
+ # A frame that crosses comments is malformed for Ruby (a record cannot span
115
+ # comments); only a frame wholly inside one segment is adopted.
116
+ def spans_only?(frame, segments, row)
117
+ segments[frame.first_segment].equal?(row) && frame.last_segment == frame.first_segment
118
+ end
119
+
120
+ def decode(frame, row, record_index, errors)
121
+ framed = Emf::EmfPlus::Frame.new(type: frame.type, flags: frame.flags, size: frame.frame_size, data_size: frame.data_size,
122
+ data: frame.raw.byteslice(Emf::EmfPlus::Frame::HEADER_BYTES..),
123
+ offset: frame.offset - row.logical_start, comment_index: record_index)
124
+ Emf::EmfPlus::Records.decode(framed)
125
+ rescue ParseError => e
126
+ errors << e
127
+ Emf::EmfPlus::Records::Unknown.new(framed)
128
+ end
129
+
130
+ def digests_of(metafile, segments)
131
+ segments.map { |segment| Digest::SHA256.digest(metafile.records[segment.record_index].to_binary_s) }
132
+ end
133
+ end
134
+
135
+ def initialize(snapshot: nil, **arguments)
136
+ super(**arguments)
137
+ @snapshot = snapshot
138
+ end
139
+
140
+ # The native family (EmfPlusSnapshot::FAMILIES) of the record at `handle`, or nil once edited.
141
+ def native_family(handle)
142
+ frame = snapshot&.frames&.at(handle.stream_index)
143
+ frame&.family
144
+ end
145
+
146
+ # Object-reference problems Rust found, resolved against the same table order as #resolve.
147
+ def reference_diagnostics = snapshot ? snapshot.diagnostics : []
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "palette_fields"
4
+ require_relative "legacy_state_fields"
5
+
6
+ module Emf
7
+ module Native
8
+ module Fields
9
+ LAYOUTS = {
10
+ 2 => [Emr::Binary::Records::SetMapMode, [:map_mode, nil, "V", 4]],
11
+ 5 => [Emr::Binary::Records::SetBkMode, [:bk_mode, nil, "V", 4]],
12
+ 6 => [Emr::Binary::Records::SetPolyFillMode, [:poly_fill_mode, nil, "V", 4]],
13
+ 7 => [Emr::Binary::Records::SetRop2, [:rop2, nil, "V", 4]],
14
+ 8 => [Emr::Binary::Records::SetStretchBltMode, [:stretch_mode, nil, "V", 4]],
15
+ 9 => [Emr::Binary::Records::SetWindowExtEx, [:extent, %i[cx cy], "l<2", 8]],
16
+ 10 => [Emr::Binary::Records::SetWindowOrgEx, [:origin, %i[x y], "l<2", 8]],
17
+ 11 => [Emr::Binary::Records::SetViewportExtEx, [:extent, %i[cx cy], "l<2", 8]],
18
+ 12 => [Emr::Binary::Records::SetViewportOrgEx, [:origin, %i[x y], "l<2", 8]],
19
+ 13 => [Emr::Binary::Records::SetBrushOrgEx, [:ptl_origin, %i[x y], "l<2", 8]],
20
+ 14 => [Emr::Binary::Records::OffsetClipRgn, [:ptl_offset, %i[x y], "l<2", 8]],
21
+ 15 => [Emr::Binary::Records::MoveToEx, [:origin, %i[x y], "l<2", 8]],
22
+ 16 => [Emr::Binary::Records::SetTextColor, [:color, %i[red green blue reserved], "C4", 4]],
23
+ 17 => [Emr::Binary::Records::SetBkColor, [:color, %i[red green blue reserved], "C4", 4]],
24
+ 18 => [Emr::Binary::Records::Ellipse, [:rcl_box, %i[left top right bottom], "l<4", 16]],
25
+ 19 => [Emr::Binary::Records::Rectangle, [:rcl_box, %i[left top right bottom], "l<4", 16]],
26
+ 20 => [Emr::Binary::Records::RoundRect, [:rcl_box, %i[left top right bottom], "l<4", 16], [:szl_corner, %i[cx cy], "l<2", 8]],
27
+ 21 => [Emr::Binary::Records::SetTextAlign, [:text_align_mode, nil, "V", 4]],
28
+ 22 => [Emr::Binary::Records::SaveDc],
29
+ 23 => [Emr::Binary::Records::RestoreDc, [:saved_dc, nil, "l<", 4]],
30
+ 24 => [Emr::Binary::Records::SelectObject, [:ih_object, nil, "V", 4]],
31
+ 25 => [Emr::Binary::Records::DeleteObject, [:ih_object, nil, "V", 4]],
32
+ 26 => [Emr::Binary::Records::SetArcDirection, [:arc_direction, nil, "V", 4]],
33
+ 27 => [Emr::Binary::Records::BeginPath],
34
+ 28 => [Emr::Binary::Records::EndPath],
35
+ 29 => [Emr::Binary::Records::CloseFigure],
36
+ 30 => [Emr::Binary::Records::FillPath, [:rcl_bounds, %i[left top right bottom], "l<4", 16]],
37
+ 31 => [Emr::Binary::Records::StrokeAndFillPath, [:rcl_bounds, %i[left top right bottom], "l<4", 16]],
38
+ 32 => [Emr::Binary::Records::StrokePath, [:rcl_bounds, %i[left top right bottom], "l<4", 16]],
39
+ 33 => [Emr::Binary::Records::SelectClipPath, [:region_mode, nil, "V", 4]],
40
+ 34 => [Emr::Binary::Records::AbortPath],
41
+ 35 => [Emr::Binary::Records::SetPixelV, [:ptl_pixel, %i[x y], "l<2", 8], [:cr_color, %i[red green blue reserved], "C4", 4]],
42
+ 36 => [Emr::Binary::Records::ExcludeClipRect, [:rcl_clip, %i[left top right bottom], "l<4", 16]],
43
+ 37 => [Emr::Binary::Records::IntersectClipRect, [:rcl_clip, %i[left top right bottom], "l<4", 16]],
44
+ 38 => [Emr::Binary::Records::CreatePen, [:ih_pen, nil, "V", 4], [:pen_style, nil, "V", 4],
45
+ [:width, %i[x y], "l<2", 8], [:color, %i[red green blue reserved], "C4", 4]],
46
+ 39 => [Emr::Binary::Records::CreateBrushIndirect, [:ih_brush, nil, "V", 4], [:brush_style, nil, "V", 4],
47
+ [:color, %i[red green blue reserved], "C4", 4], [:brush_hatch, nil, "V", 4]],
48
+ 40 => [Emr::Binary::Records::Arc, [:rcl_box, %i[left top right bottom], "l<4", 16],
49
+ [:ptl_start, %i[x y], "l<2", 8], [:ptl_end, %i[x y], "l<2", 8]],
50
+ 41 => [Emr::Binary::Records::Chord, [:rcl_box, %i[left top right bottom], "l<4", 16],
51
+ [:ptl_start, %i[x y], "l<2", 8], [:ptl_end, %i[x y], "l<2", 8]],
52
+ 42 => [Emr::Binary::Records::Pie, [:rcl_box, %i[left top right bottom], "l<4", 16],
53
+ [:ptl_start, %i[x y], "l<2", 8], [:ptl_end, %i[x y], "l<2", 8]],
54
+ 43 => [Emr::Binary::Records::LineTo, [:origin, %i[x y], "l<2", 8]],
55
+ 44 => [Emr::Binary::Records::ArcTo, [:rcl_box, %i[left top right bottom], "l<4", 16],
56
+ [:ptl_start, %i[x y], "l<2", 8], [:ptl_end, %i[x y], "l<2", 8]],
57
+ 60 => [Emr::Binary::Records::ScaleViewportExtEx, [:x_num, nil, "l<", 4], [:x_denom, nil, "l<", 4],
58
+ [:y_num, nil, "l<", 4], [:y_denom, nil, "l<", 4]],
59
+ 61 => [Emr::Binary::Records::ScaleWindowExtEx, [:x_num, nil, "l<", 4], [:x_denom, nil, "l<", 4],
60
+ [:y_num, nil, "l<", 4], [:y_denom, nil, "l<", 4]],
61
+ 62 => [Emr::Binary::Records::SetWorldTransform, [:xform, %i[m11 m12 m21 m22 dx dy], "e6", 24]],
62
+ 63 => [Emr::Binary::Records::ModifyWorldTransform, [:xform, %i[m11 m12 m21 m22 dx dy], "e6", 24], [:modify_mode, nil, "V", 4]],
63
+ 64 => [Emr::Binary::Records::AngleArc, [:ptl_center, %i[x y], "l<2", 8], [:n_radius, nil, "V", 4],
64
+ [:start_angle, nil, "e", 4], [:sweep_angle, nil, "e", 4]],
65
+ 65 => [Emr::Binary::Records::SetMiterLimit, [:miter_limit, nil, "e", 4]],
66
+ 66 => [Emr::Binary::Records::SetMapperFlags, [:flags, nil, "V", 4]],
67
+ 67 => [Emr::Binary::Records::SetMetArgn],
68
+ 69 => [Emr::Binary::Records::FlattenPath],
69
+ 70 => [Emr::Binary::Records::WidenPath],
70
+ 71 => [Emr::Binary::Records::SetIcmMode, [:mode, nil, "V", 4]],
71
+ 72 => [Emr::Binary::Records::SetLayout, [:mode, nil, "V", 4]],
72
+ 91 => [Emr::Binary::Records::ExtFloodFill, [:start, %i[x y], "l<2", 8],
73
+ [:color, %i[red green blue reserved], "C4", 4], [:flood_fill_mode, nil, "V", 4]],
74
+ 92 => [Emr::Binary::Records::SetTextJustification, [:n_break_extra, nil, "l<", 4], [:n_break_count, nil, "l<", 4]]
75
+ }.freeze
76
+
77
+ POINT_ARRAYS = {
78
+ 3 => [Emr::Binary::Records::Polyline, 8, :cptl, :aptl],
79
+ 45 => [Emr::Binary::Records::PolyBezier, 8, :cptl, :aptl],
80
+ 46 => [Emr::Binary::Records::Polygon, 8, :cptl, :aptl],
81
+ 47 => [Emr::Binary::Records::PolyBezierTo, 8, :cptl, :aptl],
82
+ 48 => [Emr::Binary::Records::PolylineTo, 8, :cptl, :aptl],
83
+ 49 => [Emr::Binary::Records::PolyBezier16, 4, :cpts, :apts],
84
+ 50 => [Emr::Binary::Records::Polygon16, 4, :cpts, :apts],
85
+ 51 => [Emr::Binary::Records::Polyline16, 4, :cpts, :apts],
86
+ 52 => [Emr::Binary::Records::PolyBezierTo16, 4, :cpts, :apts],
87
+ 53 => [Emr::Binary::Records::PolylineTo16, 4, :cpts, :apts]
88
+ }.freeze
89
+
90
+ def self.comment(payload, raw)
91
+ raise FormatError, "invalid native COMMENT payload" if payload.bytesize < 8
92
+
93
+ count = payload.unpack1("Q<")
94
+ raise FormatError, "invalid native typed payload length" unless payload.bytesize == 8 + count
95
+
96
+ data = payload.byteslice(8..) + (raw.byteslice((12 + count)..) || "".b)
97
+ [Emr::Binary::Records::Comment, { cb_data: count, data: data }]
98
+ end
99
+
100
+ def self.eof(payload, raw)
101
+ raise FormatError, "invalid native typed payload length" unless payload.bytesize == 12 && raw.bytesize >= 20
102
+
103
+ # The public Ruby wire keeps the fixed word and opaque remainder even
104
+ # when the native EOF model reads nSizeLast from the record's final word.
105
+ entries, offset, last = raw.byteslice(8, 12).unpack("V3")
106
+ [Emr::Binary::Records::Eof, { n_pal_entries: entries, off_pal_entries: offset, n_size_last: last, body: raw.byteslice(20..) }]
107
+ end
108
+
109
+ def self.point_array(payload, family)
110
+ klass, width, count_field, points_field = POINT_ARRAYS.fetch(family)
111
+ raise FormatError, "invalid native point array payload" if payload.bytesize < 24
112
+
113
+ count = payload.byteslice(16, 8).unpack1("Q<")
114
+ raise FormatError, "invalid native typed payload length" unless payload.bytesize == 24 + (count * width)
115
+
116
+ ResourceLimits::EmfRecords.check_count(count)
117
+
118
+ bounds = %i[left top right bottom].zip(payload.unpack("l<4")).to_h
119
+ format = width == 8 ? "l<*" : "s<*"
120
+ points = payload.byteslice(24..).unpack(format).each_slice(2).map { |x, y| { x: x, y: y } }
121
+ [klass, { rcl_bounds: bounds, count_field => count, points_field => points }]
122
+ end
123
+
124
+ MULTI_POINT_ARRAYS = {
125
+ 54 => [Emr::Binary::Records::PolyPolyline, 8, :c_ptl_total, :aptl],
126
+ 55 => [Emr::Binary::Records::PolyPolygon, 8, :c_ptl_total, :aptl],
127
+ 56 => [Emr::Binary::Records::PolyPolyline16, 4, :c_pts_total, :apts],
128
+ 57 => [Emr::Binary::Records::PolyPolygon16, 4, :c_pts_total, :apts]
129
+ }.freeze
130
+
131
+ def self.multi_point_array(payload, family)
132
+ klass, width, total_field, points_field = MULTI_POINT_ARRAYS.fetch(family)
133
+ raise FormatError, "invalid native multi array payload" if payload.bytesize < 32
134
+
135
+ poly_count, point_count = payload.byteslice(16, 16).unpack("Q<2")
136
+ counts_size = poly_count * 4
137
+ raise FormatError, "invalid native typed payload length" unless payload.bytesize == 32 + counts_size + (point_count * width)
138
+
139
+ ResourceLimits::EmfRecords.check_count(poly_count)
140
+ ResourceLimits::EmfRecords.check_count(point_count)
141
+
142
+ bounds = %i[left top right bottom].zip(payload.unpack("l<4")).to_h
143
+ counts = payload.byteslice(32, counts_size).unpack("V*")
144
+ format = width == 8 ? "l<*" : "s<*"
145
+ points = payload.byteslice((32 + counts_size)..).unpack(format).each_slice(2).map { |x, y| { x: x, y: y } }
146
+ [klass, { rcl_bounds: bounds, n_polys: poly_count, a_poly_counts: counts,
147
+ total_field => point_count, points_field => points }]
148
+ end
149
+
150
+ POLY_DRAW = {
151
+ 58 => [Emr::Binary::Records::PolyDraw, 8, :cptl, :aptl],
152
+ 59 => [Emr::Binary::Records::PolyDraw16, 4, :cpts, :apts]
153
+ }.freeze
154
+
155
+ def self.poly_draw(payload, family)
156
+ klass, width, count_field, points_field = POLY_DRAW.fetch(family)
157
+ raise FormatError, "invalid native PolyDraw payload" if payload.bytesize < 24
158
+
159
+ count = payload.byteslice(16, 8).unpack1("Q<")
160
+ raise FormatError, "invalid native typed payload length" unless payload.bytesize == 24 + (count * (width + 1))
161
+
162
+ ResourceLimits::EmfRecords.check_count(count)
163
+
164
+ bounds = %i[left top right bottom].zip(payload.unpack("l<4")).to_h
165
+ format = width == 8 ? "l<*" : "s<*"
166
+ points = payload.byteslice(24, count * width).unpack(format).each_slice(2).map { |x, y| { x: x, y: y } }
167
+ types = payload.byteslice((24 + (count * width))..).unpack("C*")
168
+ [klass, { rcl_bounds: bounds, count_field => count, points_field => points, ab_types: types }]
169
+ end
170
+
171
+ def self.ext_text(payload, family, raw)
172
+ raise FormatError, "invalid native text payload length" if payload.bytesize < 104
173
+
174
+ count, dx_present, dx_count, dx_first, string_present, string_length, dx_length = payload.byteslice(60, 44).unpack("Q<VQ<V2Q<2")
175
+ width = family == 74 ? 1 : 2
176
+ expected = 104 + (count * width) + (dx_count * 4) + string_length + dx_length
177
+ raise FormatError, "invalid native text payload length" unless payload.bytesize == expected
178
+
179
+ options, rectangle_present = payload.byteslice(36, 8).unpack("V2")
180
+ text_layout!(options, rectangle_present, count, dx_present, dx_count, dx_first, string_present, string_length, dx_length)
181
+
182
+ buffers = text_parts(payload, count, width, dx_count, string_length, dx_length, string_present, dx_present)
183
+ buffers.reverse! if dx_first == 1
184
+ offsets, trailing = text_buffers(buffers, rectangle_present == 1 ? 76 : 60, raw)
185
+ fields = text_scalars(payload)
186
+ fields.merge!(n_chars: count, off_string: offsets.fetch(false, 0), f_options: options,
187
+ off_dx: offsets.fetch(true, 0), trailing: trailing)
188
+ fields[:rcl] = %i[left top right bottom].zip(payload.byteslice(44, 16).unpack("l<4")).to_h if rectangle_present == 1
189
+ klass = family == 74 ? Emr::Binary::Records::ExtTextOutA : Emr::Binary::Records::ExtTextOutW
190
+ [klass, fields]
191
+ end
192
+
193
+ def self.text_scalars(payload)
194
+ {
195
+ rcl_bounds: %i[left top right bottom].zip(payload.unpack("l<4")).to_h,
196
+ i_graphics_mode: payload.byteslice(16, 4).unpack1("V"),
197
+ ex_scale: payload.byteslice(20, 4).unpack1("e"), ey_scale: payload.byteslice(24, 4).unpack1("e"),
198
+ ptl_reference: %i[x y].zip(payload.byteslice(28, 8).unpack("l<2")).to_h
199
+ }
200
+ end
201
+
202
+ def self.text_layout!(options, rectangle, count, dx_present, dx_count, dx_first, string_present, string_length, dx_length)
203
+ flags = [rectangle, dx_present, dx_first, string_present]
204
+ valid = flags.all? { |flag| (0..1).cover?(flag) } && rectangle == (options.nobits?(0x100) ? 1 : 0)
205
+ expected_dx = count * (options.nobits?(0x2000) ? 1 : 2)
206
+ valid &&= text_dx_valid?(dx_present, dx_count, expected_dx, dx_length, dx_first)
207
+ valid &&= string_present == 1 || (count.zero? && string_length.zero?)
208
+ raise FormatError, "invalid native text layout" unless valid
209
+ end
210
+
211
+ def self.text_dx_valid?(present, count, expected, padding, first)
212
+ present == 1 ? count == expected : [count, padding, first].all?(&:zero?)
213
+ end
214
+
215
+ def self.text_parts(payload, count, width, dx_count, string_length, dx_length, string_present, dx_present)
216
+ text_end = 104 + (count * width)
217
+ dx_end = text_end + (dx_count * 4)
218
+ [[false, string_present, payload.byteslice(104...text_end), payload.byteslice(dx_end, string_length), width],
219
+ [true, dx_present, payload.byteslice(text_end...dx_end), payload.byteslice(dx_end + string_length, dx_length), 4]]
220
+ end
221
+
222
+ def self.text_buffers(buffers, fixed, raw)
223
+ offsets = {}
224
+ trailing = "".b
225
+ buffers.each do |is_dx, present, bytes, padding, alignment|
226
+ next if present.zero?
227
+
228
+ trailing << padding
229
+ offset = fixed + trailing.bytesize
230
+ raise FormatError, "invalid native text buffer alignment" unless (offset % alignment).zero?
231
+
232
+ offsets[is_dx] = offset
233
+ trailing << bytes
234
+ end
235
+ consumed = fixed + trailing.bytesize
236
+ raise FormatError, "invalid native text record span" if consumed > raw.bytesize
237
+
238
+ [offsets, trailing + raw.byteslice(consumed..)]
239
+ end
240
+
241
+ def self.extended_pen(payload, raw)
242
+ raise FormatError, "invalid native extended pen payload" if payload.bytesize < 76
243
+
244
+ count = payload.byteslice(24, 8).unpack1("Q<")
245
+ info, bits, first = payload.byteslice(32, 12).unpack("V3")
246
+ lengths = payload.byteslice(44, 32).unpack("Q<4")
247
+ raise FormatError, "invalid native extended pen length" unless payload.bytesize == 76 + (count * 4) + lengths.sum
248
+
249
+ pen_layout!([info, bits, first], lengths)
250
+ ResourceLimits::EmfRecords.check_count(count, record_bytes: payload.bytesize)
251
+
252
+ cursor = 76 + (count * 4)
253
+ parts = lengths.map do |length|
254
+ bytes = payload.byteslice(cursor, length)
255
+ cursor += length
256
+ bytes
257
+ end
258
+ offsets, buffers = pen_buffers(parts, [info, bits], first, 52 + (count * 4), raw)
259
+ values = payload.unpack("V6")
260
+ [Emr::Binary::Records::ExtCreatePen,
261
+ { ih_pen: values[0], pen_style: values[1], width: values[2], brush_style: values[3],
262
+ color: %i[red green blue reserved].zip(payload.byteslice(16, 4).unpack("C4")).to_h,
263
+ brush_hatch: values[5], num_style_entries: count, style_entries: payload.byteslice(76, count * 4).unpack("V*"),
264
+ off_bmi: offsets[0], cb_bmi: lengths[0], off_bits: offsets[1], cb_bits: lengths[1], buffers: buffers }]
265
+ end
266
+
267
+ def self.pen_layout!(flags, lengths)
268
+ valid = flags.all? { |flag| (0..1).cover?(flag) }
269
+ valid &&= flags[0] == 1 || [lengths[0], lengths[2]].all?(&:zero?)
270
+ valid &&= flags[1] == 1 || [lengths[1], lengths[3], flags[2]].all?(&:zero?)
271
+ raise FormatError, "invalid native extended pen layout" unless valid
272
+ end
273
+
274
+ def self.pen_buffers(parts, presence, first, fixed, raw)
275
+ offsets = [0, 0]
276
+ buffers = {}
277
+ cursor = fixed
278
+ (first == 1 || presence == [0, 1] ? [1, 0] : [0, 1]).each_with_index do |kind, index|
279
+ prefix = index.zero? ? "first" : "second"
280
+ padding, bytes = parts.values_at(kind + 2, kind)
281
+ cursor += padding.bytesize
282
+ if presence[kind] == 1
283
+ raise FormatError, "invalid native extended pen alignment" unless (cursor % 4).zero?
284
+
285
+ # Empty buffers can retain offsets into fixed fields without consuming bytes.
286
+ original = raw.byteslice(12 + (kind * 8), 4).unpack1("V")
287
+ offsets[kind] = bytes.empty? && (8...fixed).cover?(original) ? original : cursor
288
+ end
289
+ buffers[:"#{prefix}_padding"] = padding
290
+ buffers[:"#{prefix}_bitmap"] = bytes
291
+ cursor += bytes.bytesize
292
+ end
293
+ raise FormatError, "invalid native extended pen span" if cursor > raw.bytesize
294
+
295
+ buffers[:trailing] = raw.byteslice(cursor..)
296
+ [offsets, buffers]
297
+ end
298
+
299
+ def self.region(payload, family, raw)
300
+ klass = [Emr::Binary::Records::FillRgn, Emr::Binary::Records::FrameRgn,
301
+ Emr::Binary::Records::InvertRgn, Emr::Binary::Records::PaintRgn,
302
+ Emr::Binary::Records::ExtSelectClipRgn].fetch(family - 76)
303
+ fixed = { 76 => 20, 77 => 28, 78 => 16, 79 => 16, 80 => 8 }.fetch(family)
304
+ raise FormatError, "invalid native region payload" if payload.bytesize < fixed
305
+
306
+ fields = region_scalars(payload, family)
307
+ data = optional_region_data(payload, family, fixed)
308
+ count = data ? 32 + (data.fetch(:count_rects) * 16) + data.fetch(:trailing).bytesize : 0
309
+ wire_fixed = family == 80 ? 16 : fixed + 12
310
+ raise FormatError, "invalid native region span" if raw.bytesize < wire_fixed + count
311
+
312
+ fields[:cb_rgn_data] = count
313
+ fields[:body] = raw.byteslice((wire_fixed + count)..)
314
+ fields[:rgn_data] = data if data
315
+ [klass, fields]
316
+ end
317
+
318
+ def self.optional_region_data(payload, family, fixed)
319
+ present = family == 80 ? payload.byteslice(4, 4).unpack1("V") : 1
320
+ raise FormatError, "invalid native region presence" unless (0..1).cover?(present)
321
+ return region_data(payload.byteslice(fixed..)) if present == 1
322
+
323
+ raise FormatError, "invalid native clip reset" unless payload.bytesize == fixed
324
+
325
+ nil
326
+ end
327
+
328
+ def self.region_scalars(payload, family)
329
+ return { dw_mode: payload.unpack1("V") } if family == 80
330
+
331
+ fields = { rcl_bounds: %i[left top right bottom].zip(payload.unpack("l<4")).to_h }
332
+ fields[:ih_brush] = payload.byteslice(16, 4).unpack1("V") if [76, 77].include?(family)
333
+ fields[:szl_stroke] = %i[cx cy].zip(payload.byteslice(20, 8).unpack("l<2")).to_h if family == 77
334
+ fields
335
+ end
336
+
337
+ def self.region_data(payload)
338
+ raise FormatError, "invalid native region data" if payload.bytesize < 36
339
+
340
+ count, extra = payload.byteslice(20, 16).unpack("Q<2")
341
+ expected = 36 + (count * 16) + extra
342
+ raise FormatError, "invalid native region data length" unless payload.bytesize == expected
343
+
344
+ ResourceLimits::EmfRecords.check_count(count, record_bytes: 32 + (count * 16) + extra)
345
+
346
+ axes = %i[left top right bottom]
347
+ rectangles = payload.byteslice(36, count * 16).unpack("l<*").each_slice(4).map { |values| axes.zip(values).to_h }
348
+ { header_size: 32, region_type: payload.unpack1("V"), count_rects: count, rgn_size: count * 16,
349
+ bounds: axes.zip(payload.byteslice(4, 16).unpack("l<4")).to_h,
350
+ rectangles: rectangles, trailing: payload.byteslice(36 + (count * 16), extra) }
351
+ end
352
+
353
+ def self.call(payload, family)
354
+ return LegacyStateFields.call(payload, family) if [68, 73].include?(family)
355
+
356
+ klass, *layouts = LAYOUTS.fetch(family)
357
+ raise FormatError, "invalid native typed payload length" unless payload.bytesize == layouts.sum { |_, _, _, length| length }
358
+
359
+ offset = 0
360
+ fields = layouts.to_h do |field, axes, format, length|
361
+ values = payload.byteslice(offset, length).unpack(format)
362
+ offset += length
363
+ [field, axes ? axes.zip(values).to_h : values.first]
364
+ end
365
+ [klass, fields]
366
+ end
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "emf/emr/binary/font"
4
+
5
+ module Emf
6
+ module Native
7
+ # Converts family 90 semantic fields directly into editable font objects.
8
+ class FontFields
9
+ LOG_FIELDS = %i[height width escapement orientation weight italic underline strike_out char_set
10
+ out_precision clip_precision quality pitch_and_family].freeze
11
+ PANOSE_FIELDS = %i[family_type serif_style weight proportion contrast stroke_variation arm_style
12
+ letterform midline x_height].freeze
13
+
14
+ def initialize(payload, raw)
15
+ @payload = payload
16
+ @raw = raw
17
+ @position = 0
18
+ end
19
+
20
+ def call
21
+ handle, kind, encoded_size = take(16).unpack("V2Q<")
22
+ font = case kind
23
+ when 0 then Emr::Binary::LogFont.new(log_font)
24
+ when 1 then panose
25
+ when 2 then extended
26
+ else raise FormatError, "invalid native font variant"
27
+ end
28
+ validate_length(kind, encoded_size)
29
+ wire = Emr::Binary::Records::CreateFontIndirectW.new(i_type: 82, n_size: @raw.bytesize, ih_object: handle)
30
+ wire.body.font = font
31
+ wire
32
+ end
33
+
34
+ private
35
+
36
+ def take(length)
37
+ raise FormatError, "invalid native font payload length" if length > @payload.bytesize - @position
38
+
39
+ value = @payload.byteslice(@position, length)
40
+ @position += length
41
+ value
42
+ end
43
+
44
+ def log_font
45
+ LOG_FIELDS.zip(take(28).unpack("l<5C8")).to_h.merge(facename: take(64).unpack("v32"))
46
+ end
47
+
48
+ def names
49
+ { log_font: log_font, fullname: take(128).unpack("v64"), style: take(64).unpack("v32") }
50
+ end
51
+
52
+ def panose
53
+ fields = names.merge(%i[version style_size match reserved].zip(take(16).unpack("V4")).to_h)
54
+ fields.merge!(vendor_id: take(4).bytes, culture: take(4).unpack1("V"),
55
+ panose: PANOSE_FIELDS.zip(take(10).bytes).to_h, padding: take(2).bytes)
56
+ Emr::Binary::LogFontPanose.new(fields)
57
+ end
58
+
59
+ def extended
60
+ fields = names.merge(script: take(64).unpack("v32"))
61
+ signature, count = take(8).unpack("V2")
62
+ raise FormatError, "invalid native font design vector" unless signature == 0x08007664 && count <= 16
63
+
64
+ fields[:design_vector] = { signature: signature, num_axes: count, axis_values: take(count * 4).unpack("l<*") }
65
+ fields[:trailing] = @raw.byteslice((12 + @position - 16)..) || "".b
66
+ Emr::Binary::LogFontExDv.new(fields)
67
+ end
68
+
69
+ def validate_length(kind, encoded_size)
70
+ valid = valid_size?(kind, encoded_size)
71
+ raise FormatError, "invalid native font payload length" unless valid && @position == @payload.bytesize
72
+ return unless kind == 1
73
+
74
+ omitted = @payload.byteslice((16 + encoded_size)..)
75
+ raise FormatError, "invalid native partial font fields" unless omitted.bytes.all?(&:zero?)
76
+ end
77
+
78
+ def valid_size?(kind, encoded_size)
79
+ case kind
80
+ when 0 then encoded_size == 92 && @raw.bytesize == 104
81
+ when 1 then (93..320).cover?(encoded_size) && @raw.bytesize == encoded_size + 12
82
+ when 2 then encoded_size == @position - 16 && @raw.bytesize >= encoded_size + 12
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ module GradientFields
6
+ # The snapshot carries bounds, ulMode and the widened counts. Elements are
7
+ # not sized here: Ruby keeps everything after the fixed prefix as body.
8
+ def self.call(payload, raw)
9
+ raise FormatError, "invalid native gradient payload" if payload.bytesize < 36 || raw.bytesize < 36
10
+
11
+ mode, vertices, meshes = payload.byteslice(16, 20).unpack("VQ<2")
12
+ unless raw.byteslice(8, 16) == payload.byteslice(0, 16) && raw.byteslice(24, 8).unpack("V2") == [vertices, meshes] &&
13
+ raw.byteslice(32, 4).unpack1("V") == mode
14
+ raise FormatError, "invalid native gradient fields"
15
+ end
16
+
17
+ [Emr::Binary::Records::GradientFill,
18
+ { rcl_bounds: %i[left top right bottom].zip(payload.unpack("l<4")).to_h,
19
+ n_ver: vertices, n_tri: meshes, ul_mode: mode, body: raw.byteslice(36..) }]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ class Header
6
+ def signed(values)
7
+ values.pack("V*").unpack("l<*")
8
+ end
9
+
10
+ def rect(values)
11
+ Model::Geometry::Rect.new(**%i[left top right bottom].zip(signed(values)).to_h)
12
+ end
13
+
14
+ def size(values)
15
+ Model::Geometry::Size.new(**%i[cx cy].zip(signed(values)).to_h)
16
+ end
17
+
18
+ def call(fields, raw_header)
19
+ extension = if fields[22].anybits?(2)
20
+ 108
21
+ else
22
+ (fields[22].anybits?(1) ? 100 : 88)
23
+ end
24
+ Model::Emr::Header.new(
25
+ n_size: fields[0], bounds: rect(fields[1, 4]), frame: rect(fields[5, 4]),
26
+ signature: fields[9], version: fields[10], n_bytes: fields[11], n_records: fields[12],
27
+ n_handles: fields[13], s_reserved: fields[14], n_description: fields[15], off_description: fields[16],
28
+ n_pal_entries: fields[17], device_pixels: size(fields[18, 2]), device_mm: size(fields[20, 2]),
29
+ cb_pixel_format: fields[23], off_pixel_format: fields[24], b_open_gl: fields[25],
30
+ szl_micrometers: size(fields[26, 2]), trailing: raw_header.byteslice(extension..) || "".b
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end