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,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Native reports header truncation by byte position; the Ruby parser names the
6
+ # header's own nSize. When Ruby would reject the same input with a FormatError,
7
+ # report Ruby's wording, including an nSize too small for the fields it implies.
8
+ module HeaderErrors
9
+ module_function
10
+
11
+ def translate(error, input)
12
+ return error if input.bytesize < 8
13
+
14
+ size = input.unpack1("@4V")
15
+ return FormatError.new("EMF header nSize too small: #{size}") if size < 8
16
+ return FormatError.new("truncated EMF header (claimed #{size}, got #{input.bytesize})") if input.bytesize < size
17
+ return FormatError.new("EMF header nSize #{size} cannot hold its fields") unless ruby_header_fits?(input, size)
18
+
19
+ error
20
+ end
21
+
22
+ def ruby_header_fits?(input, size)
23
+ Emr::Binary::Header.read(input.byteslice(0, size))
24
+ true
25
+ rescue IOError
26
+ false
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "fields"
4
+ require_relative "font_fields"
5
+ require_relative "gradient_fields"
6
+ require_relative "bitmap_fields"
7
+ require_relative "small_text_fields"
8
+ require_relative "poly_text_fields"
9
+ require_relative "lazy_wire"
10
+ require_relative "diagnostics"
11
+ require_relative "emf_plus_source"
12
+
13
+ module Emf
14
+ module Native
15
+ class Hydrator
16
+ SUPPORTED_TYPES = [*(2..68), *(70..98), 114, 115, 116, 118, 120].freeze
17
+ OPAQUE_BODY_TYPES = [23, 96, 97, *(99..106), *(109..113), 119, 121, 122].freeze
18
+
19
+ def initialize(snapshot, input)
20
+ @snapshot = snapshot
21
+ @input = input
22
+ # Filled wires read their fields from this frozen view, as Ruby-parsed wires do.
23
+ @data = input.frozen? ? input : input.dup.freeze
24
+ end
25
+
26
+ def call
27
+ diagnostics = Diagnostics.new(@snapshot, @input)
28
+ plus_candidates = []
29
+ records = hydrate_records(diagnostics, plus_candidates)
30
+ emf_plus = emf_plus_stream(diagnostics, plus_candidates)
31
+ Model::Metafile.new(
32
+ format: emf_plus.empty? ? :emf : :emf_plus, header: Header.new.call(@snapshot.header, @snapshot.raw_header), records: records,
33
+ errors: diagnostics.errors, emf_plus: emf_plus.empty? ? nil : emf_plus,
34
+ trailing: @snapshot.trailing + @snapshot.unframed, emf_plus_source: (EmfPlusSource.call(@input) unless emf_plus.empty?)
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ # One pass over the snapshot table. Records the Ruby parser reads from
41
+ # bytes alone become lazy wires straight from the table's own type,
42
+ # family and span; the rest take the field path.
43
+ def hydrate_records(diagnostics, plus_candidates)
44
+ table = @snapshot.table
45
+ recovering = diagnostics.recovering?
46
+ limit = @input.bytesize
47
+ Array.new(@snapshot.record_count) do |index|
48
+ base = index * 5
49
+ offset = table[base + 1]
50
+ length = table[base + 2]
51
+ type_family = table[base + 3]
52
+ plus_candidates << index if (recovered = recovering && diagnostics.recovered?(offset)) || type_family >> 32 == 4
53
+ adapter = diagnostics.adapter_at(offset) if recovered
54
+ next adapter if adapter
55
+
56
+ raise FormatError, "invalid native record span" unless length >= 8 && offset >= 0 && offset <= limit - length
57
+
58
+ filled = FilledWire.for(type_family, offset, length, @data)
59
+ next Model::Emr::Records::WireAdapter.lazy(source: filled, offset: offset) if filled
60
+
61
+ hydrate_record(@snapshot.record(index), offset, length)
62
+ end
63
+ end
64
+
65
+ def hydrate_record(record, offset, length)
66
+ raw = @input.byteslice(offset, length)
67
+
68
+ if record.fetch(:family) == 90
69
+ wire = FontFields.new(record.fetch(:payload), raw).call
70
+ return Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset)
71
+ end
72
+
73
+ klass, fields = wire_fields(record, raw)
74
+ Model::Emr::Records::WireAdapter.lazy(source: LazyWire.new(klass, fields, raw, record), offset: offset)
75
+ end
76
+
77
+ def wire_fields(record, raw)
78
+ payload = record.fetch(:payload)
79
+ family = record.fetch(:family)
80
+ case family
81
+ when 93..103 then BitmapFields.call(payload, family, raw)
82
+ when 0 then raw_fields(record, raw)
83
+ when 1 then Fields.eof(payload, raw)
84
+ when 3, 45..53 then Fields.point_array(payload, record.fetch(:family))
85
+ when 54..57 then Fields.multi_point_array(payload, record.fetch(:family))
86
+ when 58, 59 then Fields.poly_draw(payload, record.fetch(:family))
87
+ when 4 then Fields.comment(payload, raw)
88
+ when 33
89
+ klass, fields = Fields.call(payload, 33)
90
+ [klass, fields.merge(trailing: raw.byteslice(12..))]
91
+ when 2, 5..44, 60..86, 91, 92, 104..106 then scalar_fields(record, raw)
92
+ else raise FormatError, "unsupported native snapshot record family #{record.fetch(:family)}"
93
+ end
94
+ end
95
+
96
+ def scalar_fields(record, raw)
97
+ family = record.fetch(:family)
98
+ return PolyTextFields.call(record.fetch(:payload), family, raw, record.fetch(:type)) if (104..106).cover?(family)
99
+
100
+ return PaletteFields.call(record.fetch(:payload), family, raw) if (82..86).cover?(family)
101
+
102
+ return Fields.extended_pen(record.fetch(:payload), raw) if family == 81
103
+
104
+ return Fields.region(record.fetch(:payload), family, raw) if (76..80).cover?(family)
105
+
106
+ return Fields.ext_text(record.fetch(:payload), family, raw) if [74, 75].include?(family)
107
+
108
+ if [91, 92].include?(family)
109
+ klass, fields = Fields.call(record.fetch(:payload), family)
110
+ fixed = family == 91 ? 24 : 16
111
+ raise FormatError, "invalid native state record length" if raw.bytesize < fixed
112
+
113
+ return [klass, fields.merge(trailing: raw.byteslice(fixed..))]
114
+ end
115
+
116
+ Fields.call(record.fetch(:payload), family)
117
+ end
118
+
119
+ def raw_fields(record, raw)
120
+ raise FormatError, "invalid native typed payload length" unless record.fetch(:payload).empty?
121
+
122
+ type = record.fetch(:type)
123
+ return SmallTextFields.call(raw) if type == 108
124
+
125
+ # Body-backed public classes also preserve malformed recognized records.
126
+ # Their family-0 snapshot carries no semantic fields; diagnostics report recovery.
127
+ return [Emr::Binary::Records.lookup(type), { body: raw.byteslice(8..) }] if OPAQUE_BODY_TYPES.include?(type)
128
+
129
+ if !SUPPORTED_TYPES.include?(type) && Emr::Binary::Records.lookup(type) != Emr::Binary::Records::Raw
130
+ raise FormatError, "native backend does not support typed EMR record #{type}"
131
+ end
132
+
133
+ [Emr::Binary::Records::Raw, { payload: raw.byteslice(8..) }]
134
+ end
135
+
136
+ # Only recovered records and COMMENTs can carry EMF+ bytes, in record order.
137
+ def emf_plus_stream(diagnostics, candidates)
138
+ table = @snapshot.table
139
+ candidates.filter_map do |index|
140
+ offset = table[(index * 5) + 1]
141
+ next diagnostics.emf_plus_at(offset) if diagnostics.recovered?(offset)
142
+ next unless table[(index * 5) + 3] >> 32 == 4
143
+
144
+ _klass, fields = Fields.comment(@snapshot.payload(index), @input.byteslice(offset, table[(index * 5) + 2]))
145
+ data = fields.fetch(:data)
146
+ data.byteslice(4..) if fields.fetch(:cb_data) >= 4 && data.start_with?("EMF+")
147
+ end.join.b
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Owns validated Ruby values, never a native document or its hydrator.
6
+ class LazyWire
7
+ XFORM_LEAVES = %i[m11 m12 m21 m22 dx dy].each_with_index.map { |name, index| [[:xform, name], 8 + (index * 4)] }.freeze
8
+ TEXT_SCALE_LEAVES = [[[:ex_scale], 28], [[:ey_scale], 32]].freeze
9
+
10
+ # Whole-record byte offsets of each float32 leaf, header included.
11
+ FLOAT_LEAVES = {
12
+ Emr::Binary::Records::SetWorldTransform => XFORM_LEAVES,
13
+ Emr::Binary::Records::ModifyWorldTransform => XFORM_LEAVES,
14
+ Emr::Binary::Records::AngleArc => [[[:start_angle], 20], [[:sweep_angle], 24]],
15
+ Emr::Binary::Records::SetMiterLimit => [[[:miter_limit], 8]],
16
+ Emr::Binary::Records::ExtTextOutA => TEXT_SCALE_LEAVES,
17
+ Emr::Binary::Records::ExtTextOutW => TEXT_SCALE_LEAVES,
18
+ Emr::Binary::Records::SmallTextOut => TEXT_SCALE_LEAVES
19
+ }.freeze
20
+ private_constant :XFORM_LEAVES, :TEXT_SCALE_LEAVES, :FLOAT_LEAVES
21
+
22
+ def initialize(klass, fields, bytes, record)
23
+ @klass = klass
24
+ @fields = fields.merge(i_type: record.fetch(:type), n_size: bytes.bytesize)
25
+ @bytes = bytes
26
+ end
27
+
28
+ def type_id
29
+ @fields.fetch(:i_type)
30
+ end
31
+
32
+ def to_binary_s
33
+ @bytes
34
+ end
35
+
36
+ def append_to(out)
37
+ out << to_binary_s
38
+ end
39
+
40
+ def wire_class
41
+ @klass
42
+ end
43
+
44
+ # No byte reader: these fields come from the hydrator, which may
45
+ # disagree with the raw bytes (recovered records).
46
+ def value_source
47
+ nil
48
+ end
49
+
50
+ def materialize
51
+ @klass.new(**@fields).tap { |wire| restore_float_bits(wire) }
52
+ end
53
+
54
+ def suffix(wire)
55
+ Model::Emr::Records::WireAdapter.suffix_of(@bytes, wire)
56
+ end
57
+
58
+ private
59
+
60
+ # The constructor only receives Floats, and Ruby has already canonicalised
61
+ # any NaN payload on the way in; the record bytes still hold the original.
62
+ def restore_float_bits(wire)
63
+ FLOAT_LEAVES.fetch(@klass, []).each do |path, offset|
64
+ path.inject(wire) { |node, name| node.public_send(name) }
65
+ .assign_bits(@bytes.byteslice(offset, 4).unpack1("V"))
66
+ end
67
+ end
68
+ end
69
+
70
+ # Same contract as LazyWire for a fixed-layout record whose bytes alone
71
+ # define the wire: no per-record field Hash is built at parse time, and
72
+ # materialising reads the record bytes with the same FastFill the Ruby
73
+ # backend parses with. Float leaves keep their original bits there.
74
+ class FilledWire < Emr::FilledRecord
75
+ # Native records have always answered a plain Integer type id.
76
+ attr_reader :type_id
77
+
78
+ # Snapshot families whose records the Ruby backend's fillers read from
79
+ # bytes alone; any other family keeps its own validation in the Hydrator.
80
+ FAMILIES = Array.new(87) { |family| [2, 3, *5..32, *35..57, *60..86].include?(family) }.freeze
81
+ # The filler of each EMR type, indexed by type; types past the table have none.
82
+ FILLERS = Array.new(128) { |type| Emr::Binary::FastFill.for(type) || Emr::Binary::FastFill.for_points(type) }.freeze
83
+ private_constant :FAMILIES, :FILLERS
84
+
85
+ # The source for the fixed-layout or point-array record the snapshot
86
+ # places at `offset` of the frozen `data`, or nil. `type_family` is the
87
+ # snapshot table word, type | family << 32. The snapshot takes the type
88
+ # and `length` from the record's own header, so they are not read again.
89
+ # Recovered (family 0, including a count that overruns the bytes)
90
+ # records keep the field path. The caller has bounds-checked the span.
91
+ def self.for(type_family, offset, length, data)
92
+ type = type_family & 0xFFFF_FFFF
93
+ filler = FILLERS[type]
94
+ return unless filler && FAMILIES[type_family >> 32] && length >= filler.size
95
+
96
+ new(filler, data, offset, length, type)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ module LegacyStateFields
6
+ def self.call(payload, family)
7
+ raise FormatError, "invalid native opaque payload length" if payload.bytesize < 8
8
+
9
+ count = payload.unpack1("Q<")
10
+ raise FormatError, "invalid native opaque payload length" unless payload.bytesize == 8 + count
11
+
12
+ body = payload.byteslice(8..)
13
+ semantic_family = family == 68 ? 91 : 92
14
+ length = semantic_family == 91 ? 16 : 8
15
+ return [Emr::Binary::Records::Raw, { payload: body }] if count < length
16
+
17
+ klass, fields = Fields.call(body.byteslice(0, length), semantic_family)
18
+ [klass, fields.merge(trailing: body.byteslice(length..))]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+
6
+ module Emf
7
+ module Native
8
+ class LibraryPath
9
+ def initialize(directory = __dir__)
10
+ @directory = directory
11
+ end
12
+
13
+ def call
14
+ override = ENV.fetch("EMF_NATIVE_LIBRARY", nil)
15
+ return override unless override.nil?
16
+
17
+ manifest_path = File.join(@directory, "platforms.json")
18
+ return unless File.exist?(manifest_path)
19
+
20
+ manifest = JSON.parse(File.read(manifest_path))
21
+ validate_manifest(manifest)
22
+ bundled_path(manifest)
23
+ rescue JSON::ParserError, SystemCallError => e
24
+ raise Unavailable, "native backend manifest could not load: #{e.message}"
25
+ end
26
+
27
+ private
28
+
29
+ def validate_manifest(manifest)
30
+ unless manifest.is_a?(Hash) && component?(manifest["platform"]) && component?(manifest["library"]) &&
31
+ manifest["sha256"].is_a?(String) && manifest["sha256"].match?(/\A[0-9a-f]{64}\z/)
32
+ raise Unavailable, "native backend manifest is invalid"
33
+ end
34
+ raise Unavailable, "native backend manifest platform mismatch" unless compatible_platform?(manifest["platform"])
35
+ return if manifest["abi_version"].is_a?(Integer) && manifest["abi_version"] == 1
36
+
37
+ raise Unavailable, "native backend manifest ABI version mismatch (expected 1)"
38
+ end
39
+
40
+ # The bundled library's platform is RubyGems' to match; without
41
+ # RubyGems (ruby --disable-gems) only EMF_NATIVE_LIBRARY can name one.
42
+ def compatible_platform?(platform)
43
+ raise Unavailable, "native backend needs RubyGems to match the bundled platform" unless defined?(::Gem)
44
+
45
+ require "rubygems/platform"
46
+ case Gem::Platform.local
47
+ when Gem::Platform.new(platform) then true
48
+ else false
49
+ end
50
+ end
51
+
52
+ def component?(value)
53
+ value.is_a?(String) && value.match?(/\A[a-zA-Z0-9_][a-zA-Z0-9_.-]*\z/)
54
+ end
55
+
56
+ def bundled_path(manifest)
57
+ root = File.realpath(@directory)
58
+ path = File.join(root, manifest.fetch("platform"), manifest.fetch("library"))
59
+ unless File.realpath(path).start_with?("#{root}#{File::SEPARATOR}")
60
+ raise Unavailable, "native backend library escapes its installation directory"
61
+ end
62
+ raise Unavailable, "native backend library checksum mismatch" unless Digest::SHA256.file(path).hexdigest == manifest.fetch("sha256")
63
+
64
+ path
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiddle"
4
+ require_relative "wmf_loading"
5
+ require_relative "resource_failure"
6
+
7
+ module Emf
8
+ module Native
9
+ class Loader
10
+ include WmfLoading
11
+ include ResourceFailure
12
+
13
+ POINTER = Fiddle::TYPE_VOIDP
14
+ UINT64 = Fiddle::TYPE_LONG_LONG
15
+ # Version 1 status values from emf-ffi/include/emf.h.
16
+ STATUS_ERRORS = { 2 => FormatError,
17
+ 4 => SerializeError }.freeze
18
+ SIGNATURES = {
19
+ emf_parse: [[POINTER, UINT64, POINTER, POINTER, POINTER], Fiddle::TYPE_INT],
20
+ emf_document_snapshot: [[POINTER, POINTER, POINTER], Fiddle::TYPE_INT],
21
+ emf_record_bytes: [[POINTER, UINT64, POINTER, POINTER], Fiddle::TYPE_INT],
22
+ emf_buffer_view: [[POINTER, POINTER, POINTER], Fiddle::TYPE_INT],
23
+ emf_error_view: [[POINTER, POINTER], Fiddle::TYPE_INT],
24
+ emf_document_free: [[POINTER], Fiddle::TYPE_VOID],
25
+ emf_buffer_free: [[POINTER], Fiddle::TYPE_VOID],
26
+ emf_error_free: [[POINTER], Fiddle::TYPE_VOID]
27
+ }.freeze
28
+ # Present in newer libraries only; an older one keeps the schema 2 snapshot.
29
+ SNAPSHOT_V3 = :emf_document_snapshot_v3
30
+
31
+ def initialize(path)
32
+ raise Unavailable, "native backend requires a 64-bit process" unless Fiddle::SIZEOF_VOIDP == 8
33
+
34
+ @library = Fiddle.dlopen(path)
35
+ @functions = { emf_abi_version: Fiddle::Function.new(@library["emf_abi_version"], [], Fiddle::TYPE_INT) }
36
+ raise Unavailable, "native backend ABI version mismatch (expected 1)" unless invoke(:emf_abi_version) == 1
37
+
38
+ SIGNATURES.each do |name, (arguments, result)|
39
+ @functions[name] = Fiddle::Function.new(@library[name.to_s], arguments, result)
40
+ end
41
+ optional(:emf_error_resource_view, [POINTER, POINTER])
42
+ @snapshot_function = optional(SNAPSHOT_V3, [POINTER, POINTER, POINTER]) ? SNAPSHOT_V3 : :emf_document_snapshot
43
+ rescue Fiddle::DLError => e
44
+ raise Unavailable, "native backend could not load: #{e.message}"
45
+ end
46
+
47
+ def document(bytes)
48
+ output = slot
49
+ checked(:emf_parse, bytes, bytes.bytesize, 0, output)
50
+ yield pointer(output)
51
+ ensure
52
+ invoke(:emf_document_free, pointer(output)) if output && pointer(output) != 0
53
+ end
54
+
55
+ def snapshot(document)
56
+ buffer(@snapshot_function, document)
57
+ end
58
+
59
+ def record_bytes(document, index)
60
+ buffer(:emf_record_bytes, document, index)
61
+ end
62
+
63
+ private
64
+
65
+ def optional(name, arguments)
66
+ @functions[name] = Fiddle::Function.new(@library[name.to_s], arguments, Fiddle::TYPE_INT)
67
+ rescue Fiddle::DLError
68
+ nil
69
+ end
70
+
71
+ def invoke(name, *arguments)
72
+ @functions.fetch(name).call(*arguments)
73
+ end
74
+
75
+ def slot
76
+ Fiddle::Pointer["\0" * 8]
77
+ end
78
+
79
+ def pointer(output)
80
+ output[0, 8].unpack1("J")
81
+ end
82
+
83
+ def checked(name, *arguments)
84
+ error = slot
85
+ status = invoke(name, *arguments, error)
86
+ return if status.zero?
87
+
88
+ raise resource_failure(pointer(error)) if status == 3
89
+
90
+ message = error_message(pointer(error), status)
91
+ raise STATUS_ERRORS.fetch(status, Error), message
92
+ ensure
93
+ invoke(:emf_error_free, pointer(error)) if error && pointer(error) != 0
94
+ end
95
+
96
+ def error_message(error, status)
97
+ return "native operation failed (status #{status})" if error.zero?
98
+
99
+ info = Fiddle::Pointer[[48, 1].pack("L2") + ("\0" * 40)]
100
+ return "native operation failed (status #{status})" unless invoke(:emf_error_view, error, info).zero?
101
+
102
+ address, length = info[32, 16].unpack("JQ")
103
+ Fiddle::Pointer.new(address)[0, length].force_encoding(Encoding::UTF_8)
104
+ end
105
+
106
+ def buffer(name, *arguments)
107
+ output = slot
108
+ checked(name, *arguments, output)
109
+ data = slot
110
+ length = slot
111
+ status = invoke(:emf_buffer_view, pointer(output), data, length)
112
+ raise STATUS_ERRORS.fetch(status, Error), "native buffer view failed (status #{status})" unless status.zero?
113
+
114
+ Fiddle::Pointer.new(pointer(data))[0, pointer(length)]
115
+ ensure
116
+ invoke(:emf_buffer_free, pointer(output)) if output && pointer(output) != 0
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ module PaletteFields
6
+ def self.call(payload, family, raw)
7
+ klass = [Emr::Binary::Records::SelectPalette, Emr::Binary::Records::CreatePalette,
8
+ Emr::Binary::Records::SetPaletteEntries, Emr::Binary::Records::ResizePalette,
9
+ Emr::Binary::Records::RealizePalette].fetch(family - 82)
10
+ return array_fields(payload, family, raw, klass) if [83, 84].include?(family)
11
+
12
+ fixed = { 82 => 4, 85 => 8, 86 => 0 }.fetch(family)
13
+ raise FormatError, "invalid native palette payload" unless payload.bytesize == fixed && raw.bytesize >= fixed + 8
14
+
15
+ fields = { trailing: raw.byteslice((fixed + 8)..) }
16
+ fields[:ih_pal] = payload.unpack1("V") if fixed.positive?
17
+ fields[:entry_count] = payload.byteslice(4, 4).unpack1("V") if family == 85
18
+ [klass, fields]
19
+ end
20
+
21
+ def self.palette_counts_valid?(family, scalar, count)
22
+ family != 83 || (scalar <= 0xFFFF && count <= 0xFFFF)
23
+ end
24
+
25
+ def self.array_fields(payload, family, raw, klass)
26
+ raise FormatError, "invalid native palette array payload" if payload.bytesize < 16
27
+
28
+ handle, scalar, count = payload.unpack("V2Q<")
29
+ fixed = family == 83 ? 16 : 20
30
+ valid = payload.bytesize == 16 + (count * 4) && raw.bytesize >= fixed + (count * 4)
31
+ valid &&= palette_counts_valid?(family, scalar, count)
32
+ raise FormatError, "invalid native palette array length" unless valid
33
+
34
+ ResourceLimits::EmfRecords.check_count(count) if family == 84
35
+
36
+ entries = payload.byteslice(16..).unpack("C*").each_slice(4).map do |red, green, blue, flags|
37
+ { red: red, green: green, blue: blue, flags: flags }
38
+ end
39
+ fields = { ih_pal: handle, entry_count: count, palette_entries: entries, trailing: raw.byteslice((fixed + (count * 4))..) }
40
+ fields[family == 83 ? :version : :start] = scalar
41
+ [klass, fields]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ # Snapshot families 104/105 encode a 36-byte header and 52-byte entry
6
+ # headers followed by text units and Dx words. Family 106 is twelve words.
7
+ # Source gaps and suffixes remain authoritative for the public body API.
8
+ module PolyTextFields
9
+ def self.call(payload, family, raw, type)
10
+ expected_type = { 104 => 96, 105 => 97, 106 => 23 }.fetch(family)
11
+ valid_header = type == expected_type && raw.bytesize >= 8 && raw.unpack1("V") == type
12
+ reject! unless valid_header && raw.byteslice(4, 4).unpack1("V") == raw.bytesize
13
+
14
+ if family == 106
15
+ reject! unless payload.bytesize == 24 && raw.bytesize >= 32 && payload == raw.byteslice(8, 24)
16
+ else
17
+ TextValidator.new(payload, raw, family == 104 ? 1 : 2).call
18
+ end
19
+ [Emr::Binary::Records.lookup(type), { body: raw.byteslice(8..) }]
20
+ end
21
+
22
+ def self.reject!
23
+ raise FormatError, "invalid native PolyTextOut/COLORADJUSTMENT payload"
24
+ end
25
+
26
+ # The source descriptor table is variable width. Validate every semantic
27
+ # field and detached span before constructing the public body-backed wire.
28
+ class TextValidator
29
+ def initialize(payload, raw, width)
30
+ @payload = payload
31
+ @raw = raw
32
+ @width = width
33
+ @cursor = 36
34
+ @descriptor = 40
35
+ @spans = []
36
+ end
37
+
38
+ def call
39
+ count.times { entry }
40
+ reject! unless @cursor == @payload.bytesize
41
+
42
+ finish = @descriptor
43
+ @spans.sort.each do |start, stop|
44
+ reject! if start < finish
45
+ finish = stop
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def count
52
+ reject! unless @payload.bytesize >= 36 && @raw.bytesize >= 40
53
+ reject! unless @payload.byteslice(0, 28) == @raw.byteslice(8, 28)
54
+
55
+ value = @payload.byteslice(28, 8).unpack1("Q<")
56
+ reject! unless value == @raw.byteslice(36, 4).unpack1("V")
57
+ reject! unless value <= (@payload.bytesize - 36) / 52 && value <= (@raw.bytesize - 40) / 24
58
+ value
59
+ end
60
+
61
+ def entry
62
+ reject! unless @cursor + 52 <= @payload.bytesize && @descriptor + 24 <= @raw.bytesize
63
+
64
+ options, has_rect = @payload.byteslice(@cursor + 8, 8).unpack("V2")
65
+ size = rectangle(options, has_rect)
66
+ reject! unless @payload.byteslice(@cursor, 8) == @raw.byteslice(@descriptor, 8)
67
+ reject! unless options == @raw.byteslice(@descriptor + 16, 4).unpack1("V")
68
+
69
+ chars, string = @raw.byteslice(@descriptor + 8, 8).unpack("V2")
70
+ dx = @raw.byteslice(@descriptor + size - 4, 4).unpack1("V")
71
+ dx_count = spacing_count(chars, dx, options)
72
+ @cursor += 52
73
+ buffer(string, chars * @width, @width, string.zero? && chars.zero?)
74
+ buffer(dx, dx_count * 4, 4, dx.zero?)
75
+ @descriptor += size
76
+ end
77
+
78
+ def rectangle(options, has_rect)
79
+ with_rect = options.nobits?(0x100)
80
+ size = with_rect ? 40 : 24
81
+ reject! unless has_rect == (with_rect ? 1 : 0) && @descriptor + size <= @raw.bytesize
82
+
83
+ bytes = with_rect ? @raw.byteslice(@descriptor + 20, 16) : "\0".b * 16
84
+ reject! unless @payload.byteslice(@cursor + 16, 16) == bytes
85
+ size
86
+ end
87
+
88
+ def spacing_count(chars, dx, options)
89
+ text_count, has_dx, dx_count = @payload.byteslice(@cursor + 32, 20).unpack("Q<VQ<")
90
+ reject! unless text_count == chars && has_dx == (dx.zero? ? 0 : 1)
91
+
92
+ multiplier = options.anybits?(0x2000) ? 2 : 1
93
+ expected = dx.zero? ? 0 : chars * multiplier
94
+ reject! unless dx_count == expected
95
+ dx_count
96
+ end
97
+
98
+ def buffer(offset, length, alignment, absent)
99
+ reject! unless length <= @payload.bytesize - @cursor
100
+ unless absent
101
+ reject! unless offset >= 40 && (offset % alignment).zero? && offset <= @raw.bytesize
102
+ reject! unless length <= @raw.bytesize - offset
103
+ reject! unless @payload.byteslice(@cursor, length) == @raw.byteslice(offset, length)
104
+
105
+ @spans << [offset, offset + length]
106
+ end
107
+ @cursor += length
108
+ end
109
+
110
+ def reject!
111
+ PolyTextFields.reject!
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emf
4
+ module Native
5
+ module ResourceFailure
6
+ RESOURCES = { 1 => :input_bytes, 2 => :record_bytes, 3 => :record_count,
7
+ 4 => :allocated_bytes, 5 => :work_units, 6 => :output_bytes }.freeze
8
+
9
+ private
10
+
11
+ def resource_failure(error)
12
+ # Older ABI-1 libraries expose only the original status and message.
13
+ return FormatError.new(error_message(error, 3)) unless @functions.key?(:emf_error_resource_view)
14
+ return Error.new("native resource failure has no error detail") if error.zero?
15
+
16
+ info = Fiddle::Pointer[[32, 1].pack("L2") + ("\0" * 24)]
17
+ return Error.new("native resource failure detail is unreadable") unless invoke(:emf_error_resource_view, error, info).zero?
18
+
19
+ code = info[8, 4].unpack1("L")
20
+ resource = RESOURCES[code]
21
+ return Error.new("native resource failure has unknown resource #{code}") unless resource
22
+
23
+ limit, actual = info[16, 16].unpack("Q2")
24
+ ResourceLimitError.new(resource: resource, limit: limit, actual: actual)
25
+ end
26
+ end
27
+ end
28
+ end