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.
- checksums.yaml +4 -4
- data/README.adoc +224 -19
- data/exe/emf +12 -0
- data/lib/emf/backend.rb +60 -0
- data/lib/emf/binary/class_field_accessors.rb +49 -0
- data/lib/emf/binary/float32_le.rb +54 -0
- data/lib/emf/binary/types.rb +40 -6
- data/lib/emf/conformance/context.rb +36 -0
- data/lib/emf/conformance/diagnostic.rb +18 -0
- data/lib/emf/conformance/manifest/emf_bitmap_misc.yml +622 -0
- data/lib/emf/conformance/manifest/emf_control_buffers.yml +61 -0
- data/lib/emf/conformance/manifest/emf_core.yml +117 -0
- data/lib/emf/conformance/manifest/emf_corpus_rules.yml +21 -0
- data/lib/emf/conformance/manifest/emf_objects_geometry.yml +229 -0
- data/lib/emf/conformance/manifest/emf_plus.yml +357 -0
- data/lib/emf/conformance/manifest/emf_plus_core.yml +33 -0
- data/lib/emf/conformance/manifest/emf_state_path.yml +403 -0
- data/lib/emf/conformance/manifest/emf_text.yml +97 -0
- data/lib/emf/conformance/manifest/wmf.yml +874 -0
- data/lib/emf/conformance/manifest.rb +79 -0
- data/lib/emf/conformance/profile.rb +35 -0
- data/lib/emf/conformance/record_types.rb +24 -0
- data/lib/emf/conformance/registry.rb +76 -0
- data/lib/emf/conformance/report.rb +83 -0
- data/lib/emf/conformance/rule.rb +16 -0
- data/lib/emf/conformance/rules/emf_bitmap.rb +357 -0
- data/lib/emf/conformance/rules/emf_comment_escape.rb +309 -0
- data/lib/emf/conformance/rules/emf_control_buffers.rb +137 -0
- data/lib/emf/conformance/rules/emf_corpus_named.rb +83 -0
- data/lib/emf/conformance/rules/emf_framing.rb +94 -0
- data/lib/emf/conformance/rules/emf_geometry.rb +282 -0
- data/lib/emf/conformance/rules/emf_header.rb +114 -0
- data/lib/emf/conformance/rules/emf_objects.rb +291 -0
- data/lib/emf/conformance/rules/emf_parse_recovery.rb +97 -0
- data/lib/emf/conformance/rules/emf_path_clip.rb +165 -0
- data/lib/emf/conformance/rules/emf_plus.rb +75 -0
- data/lib/emf/conformance/rules/emf_plus_common.rb +58 -0
- data/lib/emf/conformance/rules/emf_plus_drawing.rb +78 -0
- data/lib/emf/conformance/rules/emf_plus_effects.rb +104 -0
- data/lib/emf/conformance/rules/emf_plus_fields.rb +169 -0
- data/lib/emf/conformance/rules/emf_plus_fields_b.rb +102 -0
- data/lib/emf/conformance/rules/emf_plus_framing.rb +172 -0
- data/lib/emf/conformance/rules/emf_plus_objects.rb +97 -0
- data/lib/emf/conformance/rules/emf_plus_payloads.rb +320 -0
- data/lib/emf/conformance/rules/emf_plus_properties.rb +96 -0
- data/lib/emf/conformance/rules/emf_plus_state.rb +77 -0
- data/lib/emf/conformance/rules/emf_state.rb +244 -0
- data/lib/emf/conformance/rules/emf_text.rb +221 -0
- data/lib/emf/conformance/rules/emf_transform.rb +85 -0
- data/lib/emf/conformance/rules/wmf_buffers.rb +259 -0
- data/lib/emf/conformance/rules/wmf_framing.rb +146 -0
- data/lib/emf/conformance/rules/wmf_layout_records.rb +112 -0
- data/lib/emf/conformance/rules/wmf_objects.rb +168 -0
- data/lib/emf/conformance/rules/wmf_records.rb +191 -0
- data/lib/emf/conformance/rules/wmf_rejections.rb +184 -0
- data/lib/emf/conformance/runner.rb +61 -0
- data/lib/emf/conformance/ternary_raster_operations.rb +52 -0
- data/lib/emf/conformance.rb +31 -0
- data/lib/emf/emf_plus/effects/blur.rb +24 -0
- data/lib/emf/emf_plus/effects/brightness_contrast.rb +22 -0
- data/lib/emf/emf_plus/effects/color_balance.rb +24 -0
- data/lib/emf/emf_plus/effects/color_curve.rb +24 -0
- data/lib/emf/emf_plus/effects/color_lookup_table.rb +26 -0
- data/lib/emf/emf_plus/effects/color_matrix.rb +23 -0
- data/lib/emf/emf_plus/effects/effect.rb +50 -0
- data/lib/emf/emf_plus/effects/hue_saturation_lightness.rb +24 -0
- data/lib/emf/emf_plus/effects/levels.rb +24 -0
- data/lib/emf/emf_plus/effects/opaque.rb +19 -0
- data/lib/emf/emf_plus/effects/red_eye_correction.rb +35 -0
- data/lib/emf/emf_plus/effects/sharpen.rb +24 -0
- data/lib/emf/emf_plus/effects/tint.rb +22 -0
- data/lib/emf/emf_plus/effects.rb +45 -0
- data/lib/emf/emf_plus/frame.rb +41 -0
- data/lib/emf/emf_plus/links/effect_link.rb +16 -0
- data/lib/emf/emf_plus/links/get_dc_span.rb +18 -0
- data/lib/emf/emf_plus/links/link_error.rb +15 -0
- data/lib/emf/emf_plus/links.rb +77 -0
- data/lib/emf/emf_plus/object_chain.rb +108 -0
- data/lib/emf/emf_plus/object_table.rb +182 -0
- data/lib/emf/emf_plus/objects/brush.rb +296 -0
- data/lib/emf/emf_plus/objects/custom_line_cap.rb +106 -0
- data/lib/emf/emf_plus/objects/font.rb +47 -0
- data/lib/emf/emf_plus/objects/image.rb +68 -0
- data/lib/emf/emf_plus/objects/image_attributes.rb +28 -0
- data/lib/emf/emf_plus/objects/opaque.rb +19 -0
- data/lib/emf/emf_plus/objects/parts.rb +103 -0
- data/lib/emf/emf_plus/objects/path.rb +21 -0
- data/lib/emf/emf_plus/objects/pen.rb +132 -0
- data/lib/emf/emf_plus/objects/region.rb +39 -0
- data/lib/emf/emf_plus/objects/string_format.rb +55 -0
- data/lib/emf/emf_plus/objects/typed.rb +50 -0
- data/lib/emf/emf_plus/objects.rb +46 -0
- data/lib/emf/emf_plus/parser.rb +64 -2
- data/lib/emf/emf_plus/records/base.rb +107 -0
- data/lib/emf/emf_plus/records/begin_container.rb +31 -0
- data/lib/emf/emf_plus/records/begin_container_no_params.rb +20 -0
- data/lib/emf/emf_plus/records/body.rb +16 -0
- data/lib/emf/emf_plus/records/clear.rb +22 -0
- data/lib/emf/emf_plus/records/clip_transform_flags.rb +26 -0
- data/lib/emf/emf_plus/records/comment.rb +21 -0
- data/lib/emf/emf_plus/records/draw_arc.rb +33 -0
- data/lib/emf/emf_plus/records/draw_beziers.rb +31 -0
- data/lib/emf/emf_plus/records/draw_closed_curve.rb +34 -0
- data/lib/emf/emf_plus/records/draw_curve.rb +38 -0
- data/lib/emf/emf_plus/records/draw_driver_string.rb +61 -0
- data/lib/emf/emf_plus/records/draw_ellipse.rb +28 -0
- data/lib/emf/emf_plus/records/draw_image.rb +42 -0
- data/lib/emf/emf_plus/records/draw_image_points.rb +62 -0
- data/lib/emf/emf_plus/records/draw_lines.rb +34 -0
- data/lib/emf/emf_plus/records/draw_path.rb +23 -0
- data/lib/emf/emf_plus/records/draw_pie.rb +33 -0
- data/lib/emf/emf_plus/records/draw_rects.rb +30 -0
- data/lib/emf/emf_plus/records/draw_string.rb +41 -0
- data/lib/emf/emf_plus/records/end_container.rb +20 -0
- data/lib/emf/emf_plus/records/end_of_file.rb +17 -0
- data/lib/emf/emf_plus/records/fill_brush.rb +21 -0
- data/lib/emf/emf_plus/records/fill_closed_curve.rb +38 -0
- data/lib/emf/emf_plus/records/fill_ellipse.rb +29 -0
- data/lib/emf/emf_plus/records/fill_path.rb +23 -0
- data/lib/emf/emf_plus/records/fill_pie.rb +34 -0
- data/lib/emf/emf_plus/records/fill_polygon.rb +32 -0
- data/lib/emf/emf_plus/records/fill_rects.rb +31 -0
- data/lib/emf/emf_plus/records/fill_region.rb +23 -0
- data/lib/emf/emf_plus/records/get_dc.rb +18 -0
- data/lib/emf/emf_plus/records/header.rb +35 -0
- data/lib/emf/emf_plus/records/multiply_world_transform.rb +24 -0
- data/lib/emf/emf_plus/records/object.rb +32 -0
- data/lib/emf/emf_plus/records/offset_clip.rb +24 -0
- data/lib/emf/emf_plus/records/primitives/argb.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/bitmap.rb +32 -0
- data/lib/emf/emf_plus/records/primitives/bitmap_data.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/blend_colors.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/blend_factors.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/bounded_array.rb +38 -0
- data/lib/emf/emf_plus/records/primitives/byte_reader.rb +25 -0
- data/lib/emf/emf_plus/records/primitives/character_range.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/compressed_image.rb +17 -0
- data/lib/emf/emf_plus/records/primitives/focus_scale_data.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/graphics_version.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/language_identifier.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/metafile.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/palette.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/path.rb +89 -0
- data/lib/emf/emf_plus/records/primitives/path_point_type.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/path_point_type_rle.rb +23 -0
- data/lib/emf/emf_plus/records/primitives/point.rb +18 -0
- data/lib/emf/emf_plus/records/primitives/point_array.rb +42 -0
- data/lib/emf/emf_plus/records/primitives/point_f.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/point_r.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/rect.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/rect_array.rb +24 -0
- data/lib/emf/emf_plus/records/primitives/rect_f.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/region_node.rb +68 -0
- data/lib/emf/emf_plus/records/primitives/region_node_child_nodes.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/region_node_path.rb +37 -0
- data/lib/emf/emf_plus/records/primitives/relative_coordinate.rb +32 -0
- data/lib/emf/emf_plus/records/primitives/string_format_data.rb +23 -0
- data/lib/emf/emf_plus/records/primitives/transform_matrix.rb +23 -0
- data/lib/emf/emf_plus/records/primitives.rb +41 -0
- data/lib/emf/emf_plus/records/reset_clip.rb +17 -0
- data/lib/emf/emf_plus/records/reset_world_transform.rb +17 -0
- data/lib/emf/emf_plus/records/restore.rb +20 -0
- data/lib/emf/emf_plus/records/rotate_world_transform.rb +24 -0
- data/lib/emf/emf_plus/records/save.rb +20 -0
- data/lib/emf/emf_plus/records/scale_world_transform.rb +26 -0
- data/lib/emf/emf_plus/records/serializable_object.rb +32 -0
- data/lib/emf/emf_plus/records/set_anti_alias_mode.rb +25 -0
- data/lib/emf/emf_plus/records/set_clip_path.rb +20 -0
- data/lib/emf/emf_plus/records/set_clip_rect.rb +24 -0
- data/lib/emf/emf_plus/records/set_clip_region.rb +20 -0
- data/lib/emf/emf_plus/records/set_compositing_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_compositing_quality.rb +21 -0
- data/lib/emf/emf_plus/records/set_interpolation_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_page_transform.rb +25 -0
- data/lib/emf/emf_plus/records/set_pixel_offset_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_rendering_origin.rb +22 -0
- data/lib/emf/emf_plus/records/set_text_contrast.rb +24 -0
- data/lib/emf/emf_plus/records/set_text_rendering_hint.rb +21 -0
- data/lib/emf/emf_plus/records/set_ts_clip.rb +40 -0
- data/lib/emf/emf_plus/records/set_ts_graphics.rb +88 -0
- data/lib/emf/emf_plus/records/set_world_transform.rb +22 -0
- data/lib/emf/emf_plus/records/shape_flags.rb +23 -0
- data/lib/emf/emf_plus/records/translate_world_transform.rb +26 -0
- data/lib/emf/emf_plus/records/unknown.rb +20 -0
- data/lib/emf/emf_plus/records.rb +107 -0
- data/lib/emf/emf_plus/serializer.rb +9 -2
- data/lib/emf/emf_plus/stream/conformance.rb +68 -0
- data/lib/emf/emf_plus/stream/editor.rb +21 -0
- data/lib/emf/emf_plus/stream/emf_gap.rb +20 -0
- data/lib/emf/emf_plus/stream/link_index.rb +30 -0
- data/lib/emf/emf_plus/stream/rebuild.rb +47 -0
- data/lib/emf/emf_plus/stream/record_handle.rb +26 -0
- data/lib/emf/emf_plus/stream/segment.rb +70 -0
- data/lib/emf/emf_plus/stream.rb +153 -0
- data/lib/emf/emf_plus.rb +9 -0
- data/lib/emf/emr/binary/dib_header.rb +142 -0
- data/lib/emf/emr/binary/ext_text_out_fields.rb +43 -0
- data/lib/emf/emr/binary/extended_pen_buffers.rb +23 -0
- data/lib/emf/emr/binary/fast_fill.rb +1164 -0
- data/lib/emf/emr/binary/font.rb +159 -0
- data/lib/emf/emr/binary/lazy_utf16_array.rb +82 -0
- data/lib/emf/emr/binary/pack_plan.rb +144 -0
- data/lib/emf/emr/binary/pack_plan_check.rb +75 -0
- data/lib/emf/emr/binary/pack_plan_exact.rb +130 -0
- data/lib/emf/emr/binary/pack_plan_generate.rb +116 -0
- data/lib/emf/emr/binary/pack_plan_values.rb +92 -0
- data/lib/emf/emr/binary/palette_body.rb +19 -0
- data/lib/emf/emr/binary/palette_entry.rb +18 -0
- data/lib/emf/emr/binary/record.rb +33 -0
- data/lib/emf/emr/binary/records/alpha_blend.rb +43 -0
- data/lib/emf/emr/binary/records/angle_arc.rb +2 -2
- data/lib/emf/emr/binary/records/bit_blt.rb +14 -0
- data/lib/emf/emr/binary/records/color_correct_palette.rb +55 -1
- data/lib/emf/emr/binary/records/colormatch_to_target_w.rb +76 -1
- data/lib/emf/emr/binary/records/create_colorspace.rb +67 -1
- data/lib/emf/emr/binary/records/create_colorspace_w.rb +90 -1
- data/lib/emf/emr/binary/records/create_dib_pattern_brush_pt.rb +14 -0
- data/lib/emf/emr/binary/records/create_font_indirect_w.rb +6 -2
- data/lib/emf/emr/binary/records/create_mono_brush.rb +14 -0
- data/lib/emf/emr/binary/records/create_palette.rb +19 -1
- data/lib/emf/emr/binary/records/delete_colorspace.rb +22 -1
- data/lib/emf/emr/binary/records/draw_escape.rb +45 -1
- data/lib/emf/emr/binary/records/ellipse.rb +1 -1
- data/lib/emf/emr/binary/records/eof.rb +9 -0
- data/lib/emf/emr/binary/records/ext_create_pen.rb +80 -1
- data/lib/emf/emr/binary/records/ext_escape.rb +45 -1
- data/lib/emf/emr/binary/records/ext_flood_fill.rb +9 -2
- data/lib/emf/emr/binary/records/ext_select_clip_rgn.rb +9 -1
- data/lib/emf/emr/binary/records/ext_text_out_a.rb +16 -3
- data/lib/emf/emr/binary/records/ext_text_out_w.rb +14 -3
- data/lib/emf/emr/binary/records/fill_rgn.rb +11 -2
- data/lib/emf/emr/binary/records/force_ufi_mapping.rb +38 -1
- data/lib/emf/emr/binary/records/frame_rgn.rb +12 -2
- data/lib/emf/emr/binary/records/gls_bounded_record.rb +61 -1
- data/lib/emf/emr/binary/records/gls_record.rb +42 -1
- data/lib/emf/emr/binary/records/gradient_fill.rb +7 -0
- data/lib/emf/emr/binary/records/invert_rgn.rb +11 -3
- data/lib/emf/emr/binary/records/mask_blt.rb +66 -0
- data/lib/emf/emr/binary/records/named_escape.rb +78 -1
- data/lib/emf/emr/binary/records/paint_rgn.rb +11 -3
- data/lib/emf/emr/binary/records/pixel_format.rb +76 -1
- data/lib/emf/emr/binary/records/plg_blt.rb +66 -0
- data/lib/emf/emr/binary/records/poly_textout_a.rb +128 -1
- data/lib/emf/emr/binary/records/poly_textout_w.rb +128 -1
- data/lib/emf/emr/binary/records/raw.rb +3 -0
- data/lib/emf/emr/binary/records/realize_palette.rb +1 -0
- data/lib/emf/emr/binary/records/rectangle.rb +1 -1
- data/lib/emf/emr/binary/records/resize_palette.rb +16 -2
- data/lib/emf/emr/binary/records/round_rect.rb +2 -2
- data/lib/emf/emr/binary/records/select_palette.rb +1 -0
- data/lib/emf/emr/binary/records/set_color_adjustment.rb +136 -0
- data/lib/emf/emr/binary/records/set_colorspace.rb +22 -1
- data/lib/emf/emr/binary/records/set_dibits_to_device.rb +43 -0
- data/lib/emf/emr/binary/records/set_icm_profile_a.rb +71 -1
- data/lib/emf/emr/binary/records/set_icm_profile_w.rb +71 -1
- data/lib/emf/emr/binary/records/set_linked_ufis.rb +49 -1
- data/lib/emf/emr/binary/records/set_miter_limit.rb +1 -1
- data/lib/emf/emr/binary/records/set_palette_entries.rb +20 -2
- data/lib/emf/emr/binary/records/set_text_justification.rb +8 -2
- data/lib/emf/emr/binary/records/small_text_out.rb +55 -4
- data/lib/emf/emr/binary/records/stretch_blt.rb +14 -0
- data/lib/emf/emr/binary/records/stretch_dibits.rb +14 -0
- data/lib/emf/emr/binary/records/transparent_blt.rb +43 -0
- data/lib/emf/emr/binary/records.rb +4 -2
- data/lib/emf/emr/binary/region_data.rb +33 -0
- data/lib/emf/emr/binary/state_record_body.rb +33 -0
- data/lib/emf/emr/binary/thin_shell.rb +226 -0
- data/lib/emf/emr/binary/value_view/layout.rb +101 -0
- data/lib/emf/emr/binary/value_view/reader_source.rb +88 -0
- data/lib/emf/emr/binary/value_view.rb +173 -0
- data/lib/emf/emr/binary/variable_buffers.rb +87 -0
- data/lib/emf/emr/binary.rb +4 -0
- data/lib/emf/emr/filled_record.rb +77 -0
- data/lib/emf/emr/parser.rb +239 -50
- data/lib/emf/emr/serializer.rb +4 -5
- data/lib/emf/emr.rb +1 -0
- data/lib/emf/error.rb +24 -1
- data/lib/emf/model/diagnostic.rb +41 -0
- data/lib/emf/model/emr/header.rb +40 -1
- data/lib/emf/model/emr/records.rb +142 -6
- data/lib/emf/model/emr/wire_values.rb +43 -0
- data/lib/emf/model/emr.rb +1 -0
- data/lib/emf/model/geometry/color.rb +19 -1
- data/lib/emf/model/metafile.rb +22 -2
- data/lib/emf/model/wmf/bitmap_records.rb +155 -0
- data/lib/emf/model/wmf/brush_records.rb +45 -0
- data/lib/emf/model/wmf/dc_escape_records.rb +67 -0
- data/lib/emf/model/wmf/font_record.rb +40 -0
- data/lib/emf/model/wmf/geometry_records.rb +107 -0
- data/lib/emf/model/wmf/header.rb +36 -0
- data/lib/emf/model/wmf/object_records.rb +71 -0
- data/lib/emf/model/wmf/palette_records.rb +68 -0
- data/lib/emf/model/wmf/records.rb +247 -0
- data/lib/emf/model/wmf/region_records.rb +112 -0
- data/lib/emf/model/wmf/state_records.rb +188 -0
- data/lib/emf/model/wmf/text_records.rb +36 -0
- data/lib/emf/model/wmf.rb +11 -0
- data/lib/emf/model.rb +2 -0
- data/lib/emf/native/bitmap_fields.rb +58 -0
- data/lib/emf/native/diagnostic_contract.rb +39 -0
- data/lib/emf/native/diagnostics.rb +81 -0
- data/lib/emf/native/emf_plus_loader.rb +35 -0
- data/lib/emf/native/emf_plus_snapshot.rb +96 -0
- data/lib/emf/native/emf_plus_source.rb +20 -0
- data/lib/emf/native/emf_plus_stream.rb +150 -0
- data/lib/emf/native/fields.rb +369 -0
- data/lib/emf/native/font_fields.rb +87 -0
- data/lib/emf/native/gradient_fields.rb +23 -0
- data/lib/emf/native/header.rb +35 -0
- data/lib/emf/native/header_errors.rb +30 -0
- data/lib/emf/native/hydrator.rb +151 -0
- data/lib/emf/native/lazy_wire.rb +100 -0
- data/lib/emf/native/legacy_state_fields.rb +22 -0
- data/lib/emf/native/library_path.rb +68 -0
- data/lib/emf/native/loader.rb +120 -0
- data/lib/emf/native/palette_fields.rb +45 -0
- data/lib/emf/native/poly_text_fields.rb +116 -0
- data/lib/emf/native/resource_failure.rb +28 -0
- data/lib/emf/native/small_text_fields.rb +27 -0
- data/lib/emf/native/snapshot.rb +102 -0
- data/lib/emf/native/wmf_bitmap_fields.rb +58 -0
- data/lib/emf/native/wmf_brush_fields.rb +62 -0
- data/lib/emf/native/wmf_fields.rb +117 -0
- data/lib/emf/native/wmf_hydrator.rb +143 -0
- data/lib/emf/native/wmf_loading.rb +49 -0
- data/lib/emf/native/wmf_palette_fields.rb +60 -0
- data/lib/emf/native/wmf_region_fields.rb +92 -0
- data/lib/emf/native/wmf_rejection.rb +84 -0
- data/lib/emf/native/wmf_remaining_fields.rb +70 -0
- data/lib/emf/native/wmf_snapshot.rb +51 -0
- data/lib/emf/native/wmf_text_fields.rb +56 -0
- data/lib/emf/native.rb +61 -0
- data/lib/emf/parse_evidence.rb +57 -0
- data/lib/emf/resource_budget/bitmap.rb +66 -0
- data/lib/emf/resource_budget/gradient.rb +30 -0
- data/lib/emf/resource_budget/poly_text.rb +85 -0
- data/lib/emf/resource_budget.rb +382 -0
- data/lib/emf/resource_limits/emf_records.rb +130 -0
- data/lib/emf/resource_limits.rb +58 -0
- data/lib/emf/version.rb +1 -1
- data/lib/emf/visitors/dump.rb +4 -0
- data/lib/emf/visitors/stats.rb +6 -5
- data/lib/emf/wmf/binary.rb +150 -1
- data/lib/emf/wmf/bitmap_codec.rb +118 -0
- data/lib/emf/wmf/brush_codec.rb +102 -0
- data/lib/emf/wmf/dc_escape_codec.rb +75 -0
- data/lib/emf/wmf/dib.rb +187 -0
- data/lib/emf/wmf/embedded_emf.rb +137 -0
- data/lib/emf/wmf/escape_views.rb +89 -0
- data/lib/emf/wmf/evidence.rb +255 -0
- data/lib/emf/wmf/geometry_codec.rb +49 -0
- data/lib/emf/wmf/palette_codec.rb +85 -0
- data/lib/emf/wmf/parser.rb +147 -2
- data/lib/emf/wmf/recovery.rb +95 -0
- data/lib/emf/wmf/region_codec.rb +151 -0
- data/lib/emf/wmf/serializer.rb +164 -2
- data/lib/emf/wmf/text_codec.rb +123 -0
- data/lib/emf/wmf.rb +7 -0
- data/lib/emf.rb +13 -9
- metadata +287 -10
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
# Hydrates the fixed EMRSMALLTEXTOUT prefix and leaves option-dependent
|
|
6
|
+
# text bytes opaque for the Ruby visitor.
|
|
7
|
+
module SmallTextFields
|
|
8
|
+
FIXED_SIZE = 36
|
|
9
|
+
|
|
10
|
+
def self.call(raw)
|
|
11
|
+
return [Emr::Binary::Records::Raw, { payload: raw.byteslice(8..) }] if raw.bytesize < FIXED_SIZE
|
|
12
|
+
|
|
13
|
+
[Emr::Binary::Records::SmallTextOut,
|
|
14
|
+
{
|
|
15
|
+
x_dest: raw.byteslice(8, 4).unpack1("l<"),
|
|
16
|
+
y_dest: raw.byteslice(12, 4).unpack1("l<"),
|
|
17
|
+
c_chars: raw.byteslice(16, 4).unpack1("V"),
|
|
18
|
+
fu_options: raw.byteslice(20, 4).unpack1("V"),
|
|
19
|
+
i_graphics_mode: raw.byteslice(24, 4).unpack1("V"),
|
|
20
|
+
ex_scale: raw.byteslice(28, 4).unpack1("e"),
|
|
21
|
+
ey_scale: raw.byteslice(32, 4).unpack1("e"),
|
|
22
|
+
body: raw.byteslice(FIXED_SIZE..)
|
|
23
|
+
}]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
# Decodes the versioned transport, never the original EMF wire format.
|
|
6
|
+
class Snapshot
|
|
7
|
+
# Five integers per record, in record order: index, offset, length,
|
|
8
|
+
# type | family << 32 and payload length (the 40-byte entry as u64 words).
|
|
9
|
+
attr_reader :header, :raw_header, :table, :record_count, :diagnostics, :trailing, :unframed
|
|
10
|
+
|
|
11
|
+
TYPE_MASK = 0xFFFF_FFFF
|
|
12
|
+
|
|
13
|
+
def initialize(bytes)
|
|
14
|
+
@bytes = bytes
|
|
15
|
+
@position = 0
|
|
16
|
+
raise FormatError, "invalid native snapshot schema" unless take(4) == "EMFS"
|
|
17
|
+
|
|
18
|
+
schema = u32
|
|
19
|
+
raise FormatError, "invalid native snapshot schema" unless [2, 3].include?(schema)
|
|
20
|
+
raise FormatError, "invalid native snapshot length" unless u64 == bytes.bytesize
|
|
21
|
+
|
|
22
|
+
header_length, record_count, diagnostic_count, trailing_length, unframed_length = Array.new(5) { u64 }
|
|
23
|
+
raise FormatError, "invalid native snapshot counts" if record_count > bytes.bytesize / 40 || diagnostic_count > bytes.bytesize / 40
|
|
24
|
+
|
|
25
|
+
@header = Array.new(28) { u32 }
|
|
26
|
+
@raw_header = take(header_length)
|
|
27
|
+
@record_count = record_count
|
|
28
|
+
schema == 3 ? read_table : read_entries
|
|
29
|
+
@diagnostics = Array.new(diagnostic_count) { take(40).unpack("V4Q<3") }
|
|
30
|
+
@trailing = take(trailing_length)
|
|
31
|
+
@unframed = take(unframed_length)
|
|
32
|
+
raise FormatError, "unconsumed native snapshot bytes" unless @position == bytes.bytesize
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def take(length)
|
|
36
|
+
raise FormatError, "truncated native snapshot" if length > @bytes.bytesize - @position
|
|
37
|
+
|
|
38
|
+
result = @bytes.byteslice(@position, length)
|
|
39
|
+
@position += length
|
|
40
|
+
result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def u32
|
|
44
|
+
take(4).unpack1("V")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def u64
|
|
48
|
+
take(8).unpack1("Q<")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The record at `index` as a Hash with its typed payload.
|
|
52
|
+
def record(index)
|
|
53
|
+
base = index * 5
|
|
54
|
+
type_family = @table[base + 3]
|
|
55
|
+
{ index: @table[base], offset: @table[base + 1], length: @table[base + 2], type: type_family & TYPE_MASK,
|
|
56
|
+
family: type_family >> 32, payload: payload(index) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def records
|
|
60
|
+
@records ||= Array.new(@record_count) { |index| record(index) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def payload(index)
|
|
64
|
+
length = @table[(index * 5) + 4]
|
|
65
|
+
length.zero? ? EMPTY_PAYLOAD : @bytes.byteslice(@payload_at[index], length)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
EMPTY_PAYLOAD = "".b.freeze
|
|
71
|
+
private_constant :EMPTY_PAYLOAD
|
|
72
|
+
|
|
73
|
+
# Schema 3: every entry in one fixed-stride table, then the payloads.
|
|
74
|
+
def read_table
|
|
75
|
+
raise FormatError, "truncated native snapshot" if @bytes.bytesize - @position < @record_count * 40
|
|
76
|
+
|
|
77
|
+
@table = @bytes.unpack("Q<#{@record_count * 5}", offset: @position)
|
|
78
|
+
@position += @record_count * 40
|
|
79
|
+
@payload_at = Array.new(@record_count) do |index|
|
|
80
|
+
at = @position
|
|
81
|
+
@position += @table[(index * 5) + 4]
|
|
82
|
+
at
|
|
83
|
+
end
|
|
84
|
+
raise FormatError, "truncated native snapshot" if @position > @bytes.bytesize
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Schema 2: each entry is followed by its payload.
|
|
88
|
+
def read_entries
|
|
89
|
+
@table = []
|
|
90
|
+
@payload_at = Array.new(@record_count) do
|
|
91
|
+
raise FormatError, "truncated native snapshot" if @bytes.bytesize - @position < 40
|
|
92
|
+
|
|
93
|
+
@table.concat(@bytes.unpack("Q<5", offset: @position))
|
|
94
|
+
@position += 40
|
|
95
|
+
at = @position
|
|
96
|
+
take(@table.last)
|
|
97
|
+
at
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class WmfBitmapFields
|
|
6
|
+
def initialize(record, raw)
|
|
7
|
+
@record = record
|
|
8
|
+
@raw = raw
|
|
9
|
+
@type = record.fetch(:type)
|
|
10
|
+
@offset = record.fetch(:offset)
|
|
11
|
+
@payload = record.fetch(:payload)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
validate(@record.fetch(:family) == 11 && @raw.bytesize >= 6 && @raw.bytesize.even? &&
|
|
16
|
+
@payload.bytesize <= Wmf::Binary::MAX_INPUT_BYTES)
|
|
17
|
+
validate(@raw.unpack("Vv") == [@raw.bytesize / 2, @type])
|
|
18
|
+
kind = @type & 0xFF
|
|
19
|
+
fields = decode_fields(kind)
|
|
20
|
+
body = Wmf::BitmapCodec.encode_fields(fields, @type)
|
|
21
|
+
validate(@raw.bytesize == body.bytesize + 6 && @raw.byteslice(6..) == body)
|
|
22
|
+
Wmf::BitmapCodec::RECORDS.fetch(kind).new(**fields, type_id: @type, offset: @offset)
|
|
23
|
+
rescue SerializeError
|
|
24
|
+
validate(false)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def decode_fields(kind)
|
|
30
|
+
names = Wmf::BitmapCodec::FIELDS.fetch(kind)
|
|
31
|
+
fixed = names.length * 4
|
|
32
|
+
validate(@payload.bytesize >= fixed + 8)
|
|
33
|
+
values = names.each_with_index.map do |field, index|
|
|
34
|
+
format = Wmf::BitmapCodec.field_type(kind, field) == :int16 ? "l<" : "V"
|
|
35
|
+
@payload.byteslice(index * 4, 4).unpack1(format)
|
|
36
|
+
end
|
|
37
|
+
names.zip(values).to_h.merge(image_fields(kind, fixed))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def image_fields(kind, fixed)
|
|
41
|
+
presence, length = @payload.byteslice(fixed, 8).unpack("V2")
|
|
42
|
+
validate([0, 1].include?(presence) && @payload.bytesize == fixed + 8 + length)
|
|
43
|
+
absent = presence.zero?
|
|
44
|
+
validate(absent ? Wmf::BitmapCodec.optional?(kind) && length.zero? : length.positive? && length.even?)
|
|
45
|
+
prefix = Wmf::BitmapCodec.wire_fields(kind, absent).sum { |field| field == :raster_operation ? 4 : 2 }
|
|
46
|
+
validate(@raw.bytesize >= 6 + prefix)
|
|
47
|
+
{ bitmap: absent ? nil : @payload.byteslice(fixed + 8, length),
|
|
48
|
+
suffix: absent ? @raw.byteslice((6 + prefix)..) : "".b }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate(condition)
|
|
52
|
+
return if condition
|
|
53
|
+
|
|
54
|
+
raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF bitmap record at offset #{@offset}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class WmfBrushFields
|
|
6
|
+
def initialize(record, raw)
|
|
7
|
+
@record = record
|
|
8
|
+
@raw = raw
|
|
9
|
+
@type = record.fetch(:type)
|
|
10
|
+
@offset = record.fetch(:offset)
|
|
11
|
+
@payload = record.fetch(:payload)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
validate(@record.fetch(:family) == 10 && @raw.bytesize >= 6 && @raw.bytesize.even? &&
|
|
16
|
+
@payload.bytesize <= Wmf::Binary::MAX_INPUT_BYTES)
|
|
17
|
+
fields = @type & 0xFF == 0xF9 ? pattern : dib
|
|
18
|
+
record = Wmf::BrushCodec::RECORDS.fetch(@type & 0xFF).new(**fields, type_id: @type, offset: @offset)
|
|
19
|
+
prefix = Wmf::BrushCodec.encode_payload(record)
|
|
20
|
+
validate(@raw.bytesize >= prefix.bytesize + 6 && @raw.byteslice(6, prefix.bytesize) == prefix)
|
|
21
|
+
if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
|
|
22
|
+
record.suffix = @raw.byteslice((6 + prefix.bytesize)..)
|
|
23
|
+
else
|
|
24
|
+
validate(@raw.bytesize == prefix.bytesize + 6)
|
|
25
|
+
end
|
|
26
|
+
record
|
|
27
|
+
rescue SerializeError
|
|
28
|
+
validate(false)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def pattern
|
|
34
|
+
validate(@payload.bytesize >= 50)
|
|
35
|
+
signed = @payload.unpack("l<4")
|
|
36
|
+
planes, bits_pixel = @payload.byteslice(16, 8).unpack("V2")
|
|
37
|
+
length = @payload.byteslice(46, 4).unpack1("V")
|
|
38
|
+
validate(signed.all? { |value| value.between?(-0x8000, 0x7FFF) } && planes <= 0xFF && bits_pixel <= 0xFF)
|
|
39
|
+
expected = Wmf::BrushCodec.pattern_length(signed[1], signed[2], planes, bits_pixel)
|
|
40
|
+
validate(expected && expected == length && @payload.bytesize == 50 + length && @raw.bytesize >= 38 + length)
|
|
41
|
+
Wmf::BrushCodec::SIGNED_FIELDS.zip(signed).to_h.merge(
|
|
42
|
+
planes: planes, bits_pixel: bits_pixel, bits: @payload.byteslice(24, 4), reserved: @payload.byteslice(28, 18),
|
|
43
|
+
pattern: @payload.byteslice(50, length)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def dib
|
|
48
|
+
validate(@payload.bytesize >= 14)
|
|
49
|
+
style, usage, length = @payload.unpack("V3")
|
|
50
|
+
validate(style <= 0xFFFF && usage <= 0xFFFF && (style == 3 || usage <= 2) && length.positive? && length.even? &&
|
|
51
|
+
@payload.bytesize == 12 + length && @raw.bytesize == 10 + length)
|
|
52
|
+
{ style: style, color_usage: usage, target: @payload.byteslice(12, length) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate(condition)
|
|
56
|
+
return if condition
|
|
57
|
+
|
|
58
|
+
raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF brush record at offset #{@offset}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "wmf_text_fields"
|
|
4
|
+
require_relative "wmf_palette_fields"
|
|
5
|
+
require_relative "wmf_region_fields"
|
|
6
|
+
require_relative "wmf_brush_fields"
|
|
7
|
+
require_relative "wmf_bitmap_fields"
|
|
8
|
+
require_relative "wmf_remaining_fields"
|
|
9
|
+
|
|
10
|
+
module Emf
|
|
11
|
+
module Native
|
|
12
|
+
class WmfFields
|
|
13
|
+
def initialize(record, raw)
|
|
14
|
+
@record = record
|
|
15
|
+
@raw = raw
|
|
16
|
+
@type = record.fetch(:type)
|
|
17
|
+
@offset = record.fetch(:offset)
|
|
18
|
+
@payload = record.fetch(:payload)
|
|
19
|
+
@function = @type & 0xFF
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
if @type.zero?
|
|
24
|
+
validate(@record.fetch(:family) == 1 && @payload.empty? && @raw.bytesize == 6)
|
|
25
|
+
return Model::Wmf::Records::Eof.new(offset: @offset)
|
|
26
|
+
end
|
|
27
|
+
return prefix_record if Wmf::Binary::PREFIX_FUNCTIONS.include?(@function)
|
|
28
|
+
return fixed(Wmf::Binary::OBJECT_RECORDS.fetch(@function), 4) if Wmf::Binary::OBJECT_RECORDS.key?(@function)
|
|
29
|
+
return WmfTextFields.new(@record, @raw).call if Wmf::Binary::TEXT_FUNCTIONS.include?(@function)
|
|
30
|
+
return points(Wmf::Binary::POINT_ARRAY_RECORDS.fetch(@function)) if Wmf::Binary::POINT_ARRAY_RECORDS.key?(@function)
|
|
31
|
+
return fixed(Wmf::Binary::FIXED_RECORDS.fetch(@function), 2) if Wmf::Binary::FIXED_RECORDS.key?(@function)
|
|
32
|
+
|
|
33
|
+
Model::Wmf::Records::UnknownRecord.new(type_id: @type, raw_bytes: @raw, offset: @offset)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def prefix_record
|
|
39
|
+
if Wmf::GeometryCodec::RECORDS.key?(@function) || Wmf::DcEscapeCodec::RECORDS.key?(@function)
|
|
40
|
+
return WmfRemainingFields.new(@record, @raw).call
|
|
41
|
+
end
|
|
42
|
+
return WmfBitmapFields.new(@record, @raw).call if Wmf::BitmapCodec::RECORDS.key?(@function)
|
|
43
|
+
|
|
44
|
+
return WmfBrushFields.new(@record, @raw).call if Wmf::BrushCodec::RECORDS.key?(@function)
|
|
45
|
+
return font if @function == 0xFB
|
|
46
|
+
return state(Wmf::Binary::STATE_RECORDS.fetch(@function)) if Wmf::Binary::STATE_RECORDS.key?(@function)
|
|
47
|
+
|
|
48
|
+
if Wmf::RegionCodec::RECORDS.key?(@function)
|
|
49
|
+
WmfRegionFields.new(@record, @raw).call
|
|
50
|
+
else
|
|
51
|
+
WmfPaletteFields.new(@record, @raw).call
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def font
|
|
56
|
+
klass = Model::Wmf::Records::CreateFontIndirect
|
|
57
|
+
prefix_size = 6 + klass::PAYLOAD_BYTES
|
|
58
|
+
validate(@record.fetch(:family) == 7 && @payload.bytesize == 84 &&
|
|
59
|
+
@raw.bytesize >= prefix_size && @raw.bytesize.even?)
|
|
60
|
+
signed = @payload.byteslice(0, 20).unpack("l<5")
|
|
61
|
+
bytes = @payload.byteslice(20, 32).unpack("V8")
|
|
62
|
+
validate(signed.all? { |value| value.between?(-0x8000, 0x7FFF) } && bytes.all? { |value| value <= 0xFF })
|
|
63
|
+
values = signed + bytes + [@payload.byteslice(52, 32)]
|
|
64
|
+
fields = klass::WIRE_FIELDS.zip(values).to_h
|
|
65
|
+
klass.new(**fields, type_id: @type, offset: @offset, suffix: @raw.byteslice(prefix_size..))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def state(klass)
|
|
69
|
+
fields = klass::WIRE_FIELDS
|
|
70
|
+
prefix_size = 6 + klass::PAYLOAD_BYTES
|
|
71
|
+
validate(@record.fetch(:family) == 6 && @payload.bytesize == fields.length * 4 &&
|
|
72
|
+
@raw.bytesize >= prefix_size && @raw.bytesize.even?)
|
|
73
|
+
values = @payload.unpack("V*")
|
|
74
|
+
values.zip(klass::WIRE_TYPES).each do |value, type|
|
|
75
|
+
validate(type != :uint16 || value <= 0xFFFF)
|
|
76
|
+
end
|
|
77
|
+
klass.new(**fields.zip(values).to_h, type_id: @type, offset: @offset, suffix: @raw.byteslice(prefix_size..))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def fixed(klass, family)
|
|
81
|
+
fields = klass::WIRE_FIELDS
|
|
82
|
+
wire_size = family == 4 ? klass::PAYLOAD_BYTES : fields.length * 2
|
|
83
|
+
validate(@record.fetch(:family) == family && @payload.bytesize == fields.length * 4 && @raw.bytesize == wire_size + 6)
|
|
84
|
+
values = @payload.unpack(family == 4 ? "V*" : "l<*")
|
|
85
|
+
if family == 4
|
|
86
|
+
values = values.zip(klass::WIRE_TYPES).map { |value, type| type == :int16 && value >= 0x80000000 ? value - 0x100000000 : value }
|
|
87
|
+
end
|
|
88
|
+
klass.new(**fields.zip(values).to_h, type_id: @type, offset: @offset)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def points(klass)
|
|
92
|
+
validate(@record.fetch(:family) == 3 && @payload.bytesize >= 16)
|
|
93
|
+
count, polygons = @payload.unpack("Q<2")
|
|
94
|
+
validate(@payload.bytesize == 16 + (polygons * 4) + (count * 8))
|
|
95
|
+
counts = @payload.byteslice(16, polygons * 4).unpack("V*")
|
|
96
|
+
multi = klass == Model::Wmf::Records::PolyPolygon
|
|
97
|
+
if multi
|
|
98
|
+
validate(@raw.bytesize == 8 + (polygons * 2) + (count * 4) && counts.sum == count)
|
|
99
|
+
else
|
|
100
|
+
minimum = klass == Model::Wmf::Records::Polygon ? 2 : 0
|
|
101
|
+
validate(polygons.zero? && count.between?(minimum, 0x7FFF) && @raw.bytesize == 8 + (count * 4))
|
|
102
|
+
end
|
|
103
|
+
coordinates = @payload.byteslice((16 + (polygons * 4))..).unpack("l<*")
|
|
104
|
+
points = coordinates.each_slice(2).map { |x, y| Model::Wmf::Records::PointS.new(x: x, y: y) }
|
|
105
|
+
fields = { points: points, type_id: @type, offset: @offset }
|
|
106
|
+
fields[:points_per_polygon] = counts if multi
|
|
107
|
+
klass.new(**fields)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def validate(condition)
|
|
111
|
+
return if condition
|
|
112
|
+
|
|
113
|
+
raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF typed record at offset #{@offset}")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "wmf_fields"
|
|
4
|
+
require_relative "wmf_snapshot"
|
|
5
|
+
require_relative "wmf_rejection"
|
|
6
|
+
|
|
7
|
+
module Emf
|
|
8
|
+
module Native
|
|
9
|
+
class WmfHydrator
|
|
10
|
+
# Native parsing is authoritative for accepted input. A rejection, from the core or the hydrator, is reported
|
|
11
|
+
# with the native first-rejection detail mapped to the contract's error; when the contract would accept the
|
|
12
|
+
# input the native rejection is raised as is.
|
|
13
|
+
def self.parse(loader, input)
|
|
14
|
+
loader.wmf_document(input) do |document|
|
|
15
|
+
new(WmfSnapshot.new(loader.wmf_snapshot(document)), input).call
|
|
16
|
+
end
|
|
17
|
+
rescue Emf::Error => e
|
|
18
|
+
found = WmfRejection.read(loader.wmf_rejection(input, Wmf::Binary::MAX_INPUT_BYTES, Wmf::Binary::MAX_RECORDS))
|
|
19
|
+
raise WmfRejection.error(found, input) if found
|
|
20
|
+
|
|
21
|
+
raise e
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(snapshot, input)
|
|
25
|
+
@snapshot = snapshot
|
|
26
|
+
@input = input
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def call
|
|
30
|
+
@errors = []
|
|
31
|
+
header = hydrate_header
|
|
32
|
+
records = hydrate_records
|
|
33
|
+
recovered = Wmf::Evidence.reference_recovery(@input, @errors)
|
|
34
|
+
Wmf::Binary.validate_objects(records, header.number_of_objects, recovered: recovered) do |record, message|
|
|
35
|
+
fail_record(record.offset, record.type_id, message)
|
|
36
|
+
end
|
|
37
|
+
Model::Metafile.new(format: :wmf, header: header, records: records,
|
|
38
|
+
errors: Wmf::Evidence.ordered(@errors + Wmf::Evidence.drift_errors(@input)),
|
|
39
|
+
trailing: @input.byteslice(@limit..).to_s)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def hydrate_header
|
|
45
|
+
type, size, version, words, objects, maximum, members, placeable, eof, reserved = @snapshot.header
|
|
46
|
+
validate_header(type, size, version, placeable, eof, reserved)
|
|
47
|
+
@start = placeable == 1 ? 22 : 0
|
|
48
|
+
# The record stream ends after the first EOF record, whatever its length or the declared Size (both are
|
|
49
|
+
# recovered); the core keeps reading past an EOF it does not accept as final, so the stream is cut here.
|
|
50
|
+
@records = framed_records
|
|
51
|
+
|
|
52
|
+
placeable_header = hydrate_placeable(type) if placeable == 1
|
|
53
|
+
raise_framing_error if @records.empty?
|
|
54
|
+
@limit = @records.last.fetch(:offset) + @records.last.fetch(:length)
|
|
55
|
+
|
|
56
|
+
Model::Wmf::Header.new(type: type, version: version, size_words: words, number_of_objects: objects,
|
|
57
|
+
max_record_words: maximum, number_of_members: members, placeable: placeable_header)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate_header(type, size, version, placeable, eof, reserved)
|
|
61
|
+
valid = [1, 2].include?(type) && size == 9 && [0x100, 0x300].include?(version) &&
|
|
62
|
+
[0, 1].include?(placeable) && [0, 1].include?(eof) && reserved.zero?
|
|
63
|
+
raise FormatError, "invalid native WMF header" unless valid
|
|
64
|
+
raise FormatError, "invalid native WMF source length" unless @snapshot.source_length == @input.bytesize
|
|
65
|
+
raise FormatError, "WMF input too large" if @input.bytesize > Wmf::Binary::MAX_INPUT_BYTES
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The snapshot's records up to and including the first EOF record; empty when the core found none.
|
|
69
|
+
def framed_records
|
|
70
|
+
index = @snapshot.records.index { |record| record.fetch(:type).zero? }
|
|
71
|
+
index ? @snapshot.records.first(index + 1) : []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Core diagnostics 6-10 are record framing failures; Ruby raises ParseError for them.
|
|
75
|
+
FRAMING_DIAGNOSTICS = {
|
|
76
|
+
6 => "invalid record length", 7 => "truncated record", 8 => "invalid EOF record",
|
|
77
|
+
9 => "missing EOF record", 10 => "malformed record payload"
|
|
78
|
+
}.freeze
|
|
79
|
+
private_constant :FRAMING_DIAGNOSTICS
|
|
80
|
+
|
|
81
|
+
def raise_framing_error
|
|
82
|
+
kind, has_function, function, _detail, offset = @snapshot.diagnostics.find { |entry| FRAMING_DIAGNOSTICS.key?(entry.first) }
|
|
83
|
+
raise FormatError, "invalid native WMF header" unless kind
|
|
84
|
+
|
|
85
|
+
raise ParseError.new(offset: offset, record_code: has_function == 1 ? function : nil,
|
|
86
|
+
message: "WMF #{FRAMING_DIAGNOSTICS.fetch(kind)} at offset #{offset}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def hydrate_placeable(type)
|
|
90
|
+
handle, left, top, right, bottom, inch, reserved, checksum = @snapshot.placeable
|
|
91
|
+
raise FormatError, "invalid native WMF placeable header" unless reserved.zero? && (type != 2 || handle.zero?)
|
|
92
|
+
|
|
93
|
+
@errors << Wmf::Evidence.recovered_error(203, @input, nil) unless checksum == Wmf::Binary.checksum(@input.byteslice(0, 20))
|
|
94
|
+
|
|
95
|
+
Model::Wmf::PlaceableHeader.new(handle: handle, left: left, top: top, right: right, bottom: bottom,
|
|
96
|
+
inch: inch, checksum: checksum)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def hydrate_records
|
|
100
|
+
if @records.length > Wmf::Binary::MAX_RECORDS
|
|
101
|
+
raise ResourceLimitError.new(resource: :record_count, limit: Wmf::Binary::MAX_RECORDS,
|
|
102
|
+
actual: Wmf::Binary::MAX_RECORDS + 1)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
position = @start + 18
|
|
106
|
+
records = @records.each_with_index.map do |record, index|
|
|
107
|
+
length = record.fetch(:length)
|
|
108
|
+
offset = record.fetch(:offset)
|
|
109
|
+
validate_span(record, index, position)
|
|
110
|
+
position += length
|
|
111
|
+
hydrate_record(record, @input.byteslice(offset, length)).declare_size(length / 2)
|
|
112
|
+
end
|
|
113
|
+
unless position == @limit && records.last.is_a?(Model::Wmf::Records::Eof) && records.one?(Model::Wmf::Records::Eof)
|
|
114
|
+
raise FormatError, "WMF requires one final EOF record"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
records
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# A recoverable shape is decoded by the Ruby contract itself (Wmf::Recovery) on the record's own bytes, so both
|
|
121
|
+
# backends build the same record and error; every other record comes from the native typed fields.
|
|
122
|
+
def hydrate_record(record, raw)
|
|
123
|
+
recovered, reason = Wmf::Recovery.record(raw, record.fetch(:type), record.fetch(:offset))
|
|
124
|
+
return WmfFields.new(record, raw).call unless reason
|
|
125
|
+
|
|
126
|
+
@errors << Wmf::Evidence.recovered_error(reason, @input, record.fetch(:offset), record.fetch(:type))
|
|
127
|
+
recovered
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def validate_span(record, index, position)
|
|
131
|
+
length = record.fetch(:length)
|
|
132
|
+
offset = record.fetch(:offset)
|
|
133
|
+
return if record.fetch(:index) == index && offset == position && length >= 6 && length.even? && length <= @limit - offset
|
|
134
|
+
|
|
135
|
+
raise FormatError, "invalid native WMF record span"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def fail_record(offset, type, message)
|
|
139
|
+
raise ParseError.new(offset: offset, record_code: type, message: "WMF #{message} at offset #{offset}")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
# The WMF entry points of the native library, loaded on first use. Mixed into Loader, which owns the
|
|
6
|
+
# buffer, status and pointer helpers these call.
|
|
7
|
+
module WmfLoading
|
|
8
|
+
def wmf_document(bytes)
|
|
9
|
+
load_wmf_functions
|
|
10
|
+
output = slot
|
|
11
|
+
checked(:emf_wmf_parse, bytes, bytes.bytesize, 0, output)
|
|
12
|
+
yield pointer(output)
|
|
13
|
+
ensure
|
|
14
|
+
invoke(:emf_wmf_document_free, pointer(output)) if output && pointer(output) != 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def wmf_snapshot(document)
|
|
18
|
+
buffer(:emf_wmf_document_snapshot, document)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The first phase of the Ruby WMF contract that rejects `bytes`, under the caller's limits (WMFR v1).
|
|
22
|
+
def wmf_rejection(bytes, max_input_bytes, max_records)
|
|
23
|
+
load_wmf_functions
|
|
24
|
+
buffer(:emf_wmf_rejection, bytes, bytes.bytesize, max_input_bytes, max_records)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def load_wmf_functions
|
|
30
|
+
return if @functions.key?(:emf_wmf_parse)
|
|
31
|
+
|
|
32
|
+
pointer = Loader::POINTER
|
|
33
|
+
uint64 = Loader::UINT64
|
|
34
|
+
signatures = {
|
|
35
|
+
emf_wmf_parse: [[pointer, uint64, pointer, pointer, pointer], Fiddle::TYPE_INT],
|
|
36
|
+
emf_wmf_document_snapshot: [[pointer, pointer, pointer], Fiddle::TYPE_INT],
|
|
37
|
+
emf_wmf_rejection: [[pointer, uint64, uint64, uint64, pointer, pointer], Fiddle::TYPE_INT],
|
|
38
|
+
emf_wmf_document_free: [[pointer], Fiddle::TYPE_VOID]
|
|
39
|
+
}
|
|
40
|
+
functions = signatures.to_h do |name, (arguments, result)|
|
|
41
|
+
[name, Fiddle::Function.new(@library[name.to_s], arguments, result)]
|
|
42
|
+
end
|
|
43
|
+
@functions.merge!(functions)
|
|
44
|
+
rescue Fiddle::DLError => e
|
|
45
|
+
raise Unavailable, "native WMF backend could not load: #{e.message}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class WmfPaletteFields
|
|
6
|
+
def initialize(record, raw)
|
|
7
|
+
@record = record
|
|
8
|
+
@raw = raw
|
|
9
|
+
@type = record.fetch(:type)
|
|
10
|
+
@offset = record.fetch(:offset)
|
|
11
|
+
@payload = record.fetch(:payload)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
klass = Wmf::PaletteCodec::RECORDS.fetch(@type & 0xFF)
|
|
16
|
+
validate(@record.fetch(:family) == 8 && @raw.bytesize.even?)
|
|
17
|
+
fields, prefix = if klass <= Model::Wmf::Records::PaletteRecord
|
|
18
|
+
entries
|
|
19
|
+
elsif klass == Model::Wmf::Records::RealizePalette
|
|
20
|
+
validate(@payload.empty?)
|
|
21
|
+
[{}, 6]
|
|
22
|
+
else
|
|
23
|
+
scalar(klass)
|
|
24
|
+
end
|
|
25
|
+
validate(@raw.bytesize >= prefix)
|
|
26
|
+
klass.new(**fields, type_id: @type, offset: @offset, suffix: @raw.byteslice(prefix..))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def entries
|
|
32
|
+
validate(@payload.bytesize >= 8)
|
|
33
|
+
start, count = @payload.unpack("V2")
|
|
34
|
+
validate(start <= 0xFFFF && count <= 0xFFFF && @payload.bytesize == 8 + (count * 16))
|
|
35
|
+
prefix = 10 + (count * 4)
|
|
36
|
+
validate(@raw.bytesize >= prefix)
|
|
37
|
+
values = @payload.byteslice(8..).unpack("V*")
|
|
38
|
+
validate(values.all? { |value| value <= 0xFF })
|
|
39
|
+
palette = values.each_slice(4).map do |entry|
|
|
40
|
+
Model::Wmf::Records::PaletteEntry.new(**Wmf::PaletteCodec::ENTRY_FIELDS.zip(entry).to_h)
|
|
41
|
+
end
|
|
42
|
+
[{ start: start, entries: palette }, prefix]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def scalar(klass)
|
|
46
|
+
validate(@payload.bytesize == 4)
|
|
47
|
+
value = @payload.unpack1("V")
|
|
48
|
+
validate(value <= 0xFFFF)
|
|
49
|
+
field = klass == Model::Wmf::Records::SelectPalette ? :object_index : :entry_count
|
|
50
|
+
[{ field => value }, 8]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate(condition)
|
|
54
|
+
return if condition
|
|
55
|
+
|
|
56
|
+
raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF palette record at offset #{@offset}")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|