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,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Emr
|
|
5
|
+
module Binary
|
|
6
|
+
# The lazy and thin shells of a Record (see FastFill). Included by
|
|
7
|
+
# Record, so every method here is BinData's with its @field_objs
|
|
8
|
+
# settled first.
|
|
9
|
+
module ThinShell
|
|
10
|
+
# A record `FastFill` returns is a thin shell: the record's bytes and
|
|
11
|
+
# offset, no @field_objs. Its fields are copied from the filler's
|
|
12
|
+
# template, with the unpacked value assigned, the first time anything
|
|
13
|
+
# asks for them (every BinData path to a field goes through
|
|
14
|
+
# instantiate_obj_at); that first ask also makes @field_objs, in
|
|
15
|
+
# place, so the record keeps its identity and every field object
|
|
16
|
+
# made afterwards is the one every later read answers. Nothing read
|
|
17
|
+
# straight from @field_objs may see an unmade field as a default, so
|
|
18
|
+
# clear and clear? settle the shell first.
|
|
19
|
+
LAZY_IVARS = %i[@lazy_filler @lazy_data @lazy_base @lazy_built @lazy_patched].freeze
|
|
20
|
+
|
|
21
|
+
def initialize_instance
|
|
22
|
+
@lazy_filler = @lazy_data = @lazy_base = @lazy_built = @lazy_patched = nil
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear
|
|
27
|
+
hydrate
|
|
28
|
+
@lazy_filler = nil
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def clear?
|
|
33
|
+
instantiate_all_objs if @lazy_filler
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# A frozen record cannot make @field_objs later, so it is made now;
|
|
38
|
+
# its fields still fill in lazily, as a BinData record's do.
|
|
39
|
+
def freeze
|
|
40
|
+
hydrate if @lazy_filler
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# BinData's dup is shallow: a copy shares @field_objs, so a field
|
|
45
|
+
# either record makes or edits is the other's too. A thin source has
|
|
46
|
+
# no @field_objs yet, so it is made first and shared. The two also
|
|
47
|
+
# share their bytes, so neither is untouched any more.
|
|
48
|
+
def initialize_copy(source)
|
|
49
|
+
super
|
|
50
|
+
return unless @lazy_filler
|
|
51
|
+
|
|
52
|
+
source.send(:hydrate)
|
|
53
|
+
@field_objs = source.instance_variable_get(:@field_objs)
|
|
54
|
+
@lazy_patched = true
|
|
55
|
+
source.instance_variable_set(:@lazy_patched, true) unless source.frozen?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The filler is generated code Marshal cannot dump, so a record is
|
|
59
|
+
# dumped as the plain BinData record it reads as: every field made,
|
|
60
|
+
# no lazy source.
|
|
61
|
+
def marshal_dump
|
|
62
|
+
instantiate_all_objs if @lazy_filler
|
|
63
|
+
@field_objs&.each { |field| field.settle_points if field.is_a?(FastFill::FlatPoints) }
|
|
64
|
+
(instance_variables - LAZY_IVARS).to_h { |name| [name, instance_variable_get(name)] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def marshal_load(ivars)
|
|
68
|
+
ivars.each { |name, value| instance_variable_set(name, value) }
|
|
69
|
+
LAZY_IVARS.each { |name| instance_variable_set(name, nil) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Fixed-layout records serialize with one Array#pack, as WireAdapter
|
|
73
|
+
# already does; a block or a layout with floats is BinData's writer.
|
|
74
|
+
def to_binary_s(&block)
|
|
75
|
+
packed = built_binary_s unless block
|
|
76
|
+
return packed if packed
|
|
77
|
+
|
|
78
|
+
return thin_binary_s if !block && thin_parsed?
|
|
79
|
+
|
|
80
|
+
packed = FastFill.for_class(self.class)&.pack(self) unless block
|
|
81
|
+
packed || super
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# [data, offset, untouched] of the bytes a shell is filled from, nil
|
|
85
|
+
# for any other record. `untouched` is true while no field has been
|
|
86
|
+
# made: a field not made is still its bytes, a field made may have
|
|
87
|
+
# been edited and is read from the wire.
|
|
88
|
+
def lazy_source
|
|
89
|
+
return unless @lazy_filler
|
|
90
|
+
|
|
91
|
+
[@lazy_data, @lazy_base, !@lazy_patched && (@field_objs.nil? || @field_objs.all?(&:nil?))]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Makes a fresh record the thin shell of the record at `base` of
|
|
95
|
+
# `data`: no field made and no @field_objs. `params` and `names` are
|
|
96
|
+
# the template's, shared as a copy of it would share them. A built
|
|
97
|
+
# shell's data is the whole record, packed from the attributes.
|
|
98
|
+
def thin(data, base, filler, params, names, built)
|
|
99
|
+
@params = params
|
|
100
|
+
@field_names = names
|
|
101
|
+
@lazy_data = data
|
|
102
|
+
@lazy_base = base
|
|
103
|
+
@lazy_filler = filler
|
|
104
|
+
@lazy_built = built
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The size of a built shell no field of which, bar n_size, has been made
|
|
109
|
+
# (fixed layouts never change size, a point array's size is its bytes').
|
|
110
|
+
def num_bytes
|
|
111
|
+
return @lazy_data.bytesize if built_clean?
|
|
112
|
+
return @lazy_filler.record_size(@lazy_data, @lazy_base) if thin_parsed?
|
|
113
|
+
|
|
114
|
+
super
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The record's bytes while a built shell still is them (n_size patched
|
|
118
|
+
# in), else nil.
|
|
119
|
+
def built_binary_s
|
|
120
|
+
return unless built_clean?
|
|
121
|
+
|
|
122
|
+
bytes = @lazy_data.dup
|
|
123
|
+
size = patched_size
|
|
124
|
+
bytes[4, 4] = [size].pack("V") if size
|
|
125
|
+
bytes
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Appends `built_binary_s` to `out` and answers true, without the copy
|
|
129
|
+
# when n_size needs no patch; false (nothing appended) for any other
|
|
130
|
+
# record.
|
|
131
|
+
def append_built_to?(out)
|
|
132
|
+
return false unless built_clean?
|
|
133
|
+
|
|
134
|
+
out << (patched_size ? built_binary_s : @lazy_data)
|
|
135
|
+
true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Appends the record's bytes to `out`: the built fast path, else
|
|
139
|
+
# `to_binary_s`.
|
|
140
|
+
def append_to(out)
|
|
141
|
+
return out if append_built_to?(out)
|
|
142
|
+
|
|
143
|
+
out << to_binary_s
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
# `n_size=` on a built shell whose n_size is not made: the new value
|
|
149
|
+
# goes into the shell's own bytes (only n_size is there) and the
|
|
150
|
+
# field is made, from them, if it is asked for. A value the bytes
|
|
151
|
+
# already hold writes nothing: a write would copy the buffer a
|
|
152
|
+
# `to_binary_s` dup still shares. Anything that is not
|
|
153
|
+
# a uint32 Integer is BinData's to assign (or to refuse); nil then,
|
|
154
|
+
# the assigned value otherwise.
|
|
155
|
+
def packed_assign(index, value)
|
|
156
|
+
return unless index == N_SIZE_INDEX && @lazy_built && @lazy_filler &&
|
|
157
|
+
value.instance_of?(Integer) && value >= 0 && value <= 0xFFFF_FFFF
|
|
158
|
+
|
|
159
|
+
@lazy_patched = true
|
|
160
|
+
data = @lazy_data
|
|
161
|
+
low = value & 0xFFFF
|
|
162
|
+
high = value >> 16
|
|
163
|
+
return value if data.getbyte(4) | (data.getbyte(5) << 8) == low &&
|
|
164
|
+
data.getbyte(6) | (data.getbyte(7) << 8) == high
|
|
165
|
+
|
|
166
|
+
data.setbyte(4, low & 0xFF)
|
|
167
|
+
data.setbyte(5, low >> 8)
|
|
168
|
+
data.setbyte(6, high & 0xFF)
|
|
169
|
+
data.setbyte(7, high >> 8)
|
|
170
|
+
value
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
N_SIZE_INDEX = 1
|
|
174
|
+
private_constant :N_SIZE_INDEX
|
|
175
|
+
|
|
176
|
+
# The n_size to patch into a built shell's bytes, nil when the bytes
|
|
177
|
+
# already hold it.
|
|
178
|
+
def patched_size
|
|
179
|
+
size = @field_objs&.[](N_SIZE_INDEX)&.value
|
|
180
|
+
size if size && size != @lazy_data.unpack1("V", offset: 4)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# A built shell whose bytes are still the record: only n_size, which
|
|
184
|
+
# is patched into them, may have been made.
|
|
185
|
+
def built_clean?
|
|
186
|
+
return false unless @lazy_built && @lazy_filler
|
|
187
|
+
return true if @field_objs.nil?
|
|
188
|
+
|
|
189
|
+
made = @field_objs.size - @field_objs.count(nil)
|
|
190
|
+
return true if made.zero?
|
|
191
|
+
|
|
192
|
+
# A value that is not an Integer is BinData's to write (or refuse).
|
|
193
|
+
made == 1 && @field_objs[N_SIZE_INDEX]&.value.instance_of?(Integer)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# A thin shell of a parsed record: its bytes are in the input.
|
|
197
|
+
def thin_parsed?
|
|
198
|
+
@field_objs.nil? && @lazy_filler && !@lazy_built
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def thin_binary_s
|
|
202
|
+
@lazy_data.byteslice(@lazy_base, @lazy_filler.record_size(@lazy_data, @lazy_base))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Makes @field_objs of a thin shell: every slot unmade.
|
|
206
|
+
def hydrate
|
|
207
|
+
@field_objs = Array.new(@lazy_filler.field_count) if @field_objs.nil?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def find_index_of(obj)
|
|
211
|
+
hydrate
|
|
212
|
+
super
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def instantiate_obj_at(index)
|
|
216
|
+
return super unless @lazy_filler
|
|
217
|
+
|
|
218
|
+
hydrate
|
|
219
|
+
return unless @field_objs[index].nil?
|
|
220
|
+
|
|
221
|
+
@field_objs[index] = @lazy_filler.lazy_field(index, self, @lazy_data, @lazy_base)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Emr
|
|
5
|
+
module Binary
|
|
6
|
+
class ValueView
|
|
7
|
+
# The leaf and array layout of one wire class, from a fresh instance.
|
|
8
|
+
class Layout
|
|
9
|
+
# Unpack directive and width of each leaf class.
|
|
10
|
+
LEAVES = {
|
|
11
|
+
"BinData::Uint32le" => ["V", 4],
|
|
12
|
+
"BinData::Int32le" => ["l<", 4],
|
|
13
|
+
"BinData::Uint8" => ["C", 1],
|
|
14
|
+
"Emf::Binary::Float32Le" => ["e", 4]
|
|
15
|
+
}.freeze
|
|
16
|
+
# Unpack directive and element width of each point coordinate class.
|
|
17
|
+
POINTS = {
|
|
18
|
+
"BinData::Int32le" => ["l<", 8],
|
|
19
|
+
"BinData::Int16le" => ["s<", 4]
|
|
20
|
+
}.freeze
|
|
21
|
+
FLOAT = "Emf::Binary::Float32Le"
|
|
22
|
+
private_constant :LEAVES, :POINTS, :FLOAT
|
|
23
|
+
|
|
24
|
+
# tree: [value class, [[name, node], ...]]; a leaf node is [:leaf, index], the array [:array].
|
|
25
|
+
# leaves: { directive:, at:, float:, klass: } in wire order.
|
|
26
|
+
# array: { name:, directive:, bytes:, count:, klass: } or nil.
|
|
27
|
+
attr_reader :tree, :leaves, :array, :size, :classes
|
|
28
|
+
|
|
29
|
+
def initialize(wire_class)
|
|
30
|
+
@wire_class = wire_class
|
|
31
|
+
@leaves = []
|
|
32
|
+
@classes = []
|
|
33
|
+
@array = nil
|
|
34
|
+
@size = 0
|
|
35
|
+
sample = wire_class.new
|
|
36
|
+
@tree = struct_of(sample)
|
|
37
|
+
raise Unsupported, "layout does not tile" unless sample.num_bytes == @size
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def leaf_paths(node = @tree, prefix = [])
|
|
41
|
+
return [prefix] if node.first == :leaf
|
|
42
|
+
return [] if node.first == :array
|
|
43
|
+
|
|
44
|
+
node.last.flat_map { |name, child| leaf_paths(child, prefix + [name]) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Index of the leaf holding the point count.
|
|
48
|
+
def count_index
|
|
49
|
+
slot = @tree.last.assoc(@array[:count])
|
|
50
|
+
raise Unsupported, "count field" unless slot && slot.last.first == :leaf
|
|
51
|
+
|
|
52
|
+
slot.last.last
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def struct_of(struct)
|
|
58
|
+
children = struct.field_names.map { |name| [name, child_of(struct.public_send(name), name, struct)] }
|
|
59
|
+
klass = ValueView.value_class(struct.class, children.map(&:first))
|
|
60
|
+
@classes << klass
|
|
61
|
+
[klass, children]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def child_of(field, name, parent)
|
|
65
|
+
if field.respond_to?(:field_names)
|
|
66
|
+
struct_of(field)
|
|
67
|
+
elsif field.is_a?(BinData::Array)
|
|
68
|
+
array_of(field, name, parent)
|
|
69
|
+
else
|
|
70
|
+
leaf_of(field)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def leaf_of(field)
|
|
75
|
+
name = field.class.name
|
|
76
|
+
directive, width = LEAVES.fetch(name) { raise Unsupported, "leaf #{field.class}" }
|
|
77
|
+
@leaves << { directive: directive, at: @size, float: FLOAT.eql?(name), klass: name }
|
|
78
|
+
@size += width
|
|
79
|
+
[:leaf, @leaves.size - 1]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def array_of(field, name, parent)
|
|
83
|
+
raise Unsupported, "second array" if @array || !parent.instance_of?(@wire_class)
|
|
84
|
+
raise Unsupported, "array is not last" unless parent.field_names.last == name
|
|
85
|
+
|
|
86
|
+
point = field.send(:new_element)
|
|
87
|
+
directive, bytes = POINTS.fetch(point.x.class.name) { raise Unsupported, "point #{point.x.class}" }
|
|
88
|
+
raise Unsupported, "point members" unless point.field_names == %i[x y] && point.x.instance_of?(point.y.class)
|
|
89
|
+
|
|
90
|
+
count = field.send(:get_parameter, :initial_length)
|
|
91
|
+
raise Unsupported, "array count" unless count.is_a?(Symbol)
|
|
92
|
+
|
|
93
|
+
@array = { name: name, directive: directive, bytes: bytes, count: count,
|
|
94
|
+
klass: ValueView.value_class(point.class, %i[x y]) }
|
|
95
|
+
[:array]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Emr
|
|
5
|
+
module Binary
|
|
6
|
+
class ValueView
|
|
7
|
+
# Source of the readers of a view: `read_bytes(data, base)`,
|
|
8
|
+
# `read_wire(wire)` and `read_mixed(data, base, wire)`.
|
|
9
|
+
class ReaderSource
|
|
10
|
+
def initialize(layout)
|
|
11
|
+
@layout = layout
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Source of `read_bytes(data, base)`, `read_wire(wire)` and
|
|
15
|
+
# `read_mixed(data, base, wire)`; each builds the value object from
|
|
16
|
+
# the locals `v0..vN` (unpacked leaves) and `points`, or from the
|
|
17
|
+
# wire's accessors.
|
|
18
|
+
def to_s
|
|
19
|
+
unpack = "#{locals} = data.unpack(#{@layout.leaves.map { |leaf| leaf[:directive] }.join.inspect}, offset: base)"
|
|
20
|
+
<<~RUBY
|
|
21
|
+
def read_bytes(data, base)
|
|
22
|
+
#{unpack}
|
|
23
|
+
#{array_source}
|
|
24
|
+
#{construct(@layout.tree, :bytes, 'wire')}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read_wire(wire)
|
|
28
|
+
#{construct(@layout.tree, :wire, 'wire')}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def read_mixed(data, base, wire)
|
|
32
|
+
#{unpack}
|
|
33
|
+
#{array_source}
|
|
34
|
+
objs = wire.instance_variable_get(:@field_objs)
|
|
35
|
+
#{mixed_construct}
|
|
36
|
+
end
|
|
37
|
+
RUBY
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def locals
|
|
43
|
+
@layout.leaves.each_index.map { |index| "v#{index}" }.join(", ")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Source for the local `points` of an array record (nothing for
|
|
47
|
+
# others); the reader returns nil when the count overruns the record.
|
|
48
|
+
def array_source
|
|
49
|
+
array = @layout.array
|
|
50
|
+
return "" unless array
|
|
51
|
+
|
|
52
|
+
count = "v#{@layout.count_index}"
|
|
53
|
+
size = @layout.size
|
|
54
|
+
<<~RUBY
|
|
55
|
+
return if #{size} + (#{count} * #{array[:bytes]}) > data.unpack1("V", offset: base + 4) ||
|
|
56
|
+
base + #{size} + (#{count} * #{array[:bytes]}) > data.bytesize
|
|
57
|
+
|
|
58
|
+
flat = data.unpack(#{array[:directive].inspect} + (#{count} * 2).to_s, offset: base + #{size})
|
|
59
|
+
points = Array.new(#{count}) { |i| POINT.new(flat[i * 2], flat[(i * 2) + 1]) }.freeze
|
|
60
|
+
RUBY
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def construct(node, mode, chain)
|
|
64
|
+
return (mode == :bytes ? "v#{node.last}" : "#{chain}.value") if node.first == :leaf
|
|
65
|
+
if node.first == :array
|
|
66
|
+
return (mode == :bytes ? "points" : "#{chain}.map { |point| POINT.new(point.x.value, point.y.value) }.freeze")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
klass, children = node
|
|
70
|
+
args = children.map { |name, child| construct(child, mode, "#{chain}.#{name}") }
|
|
71
|
+
"CLASSES[#{@layout.classes.index(klass)}].new(#{args.join(', ')})"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# `construct` for the top struct with each field taken from the wire
|
|
75
|
+
# when the shell has made it (its slot in @field_objs is set; a thin shell
|
|
76
|
+
# has no @field_objs and has made none).
|
|
77
|
+
def mixed_construct
|
|
78
|
+
klass, children = @layout.tree
|
|
79
|
+
args = children.each_with_index.map do |(name, child), slot|
|
|
80
|
+
"(objs&.[](#{slot}) ? #{construct(child, :wire, "wire.#{name}")} : #{construct(child, :bytes, '')})"
|
|
81
|
+
end
|
|
82
|
+
"CLASSES[#{@layout.classes.index(klass)}].new(#{args.join(', ')})"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Emr
|
|
5
|
+
module Binary
|
|
6
|
+
# Field values of a fixed-layout or point-array wire record as plain,
|
|
7
|
+
# frozen Ruby objects, with no BinData field object made.
|
|
8
|
+
#
|
|
9
|
+
# A view is built once per wire class from the class's own field list
|
|
10
|
+
# and proved against BinData on a record with a distinct value in every
|
|
11
|
+
# leaf (the bytes reader, the wire reader and BinData's own read must
|
|
12
|
+
# agree, signedness and float values included). A class that cannot be
|
|
13
|
+
# proved has no view: `ValueView.for_class` returns nil.
|
|
14
|
+
#
|
|
15
|
+
# The value object mirrors the wire: one `Data` member per field name,
|
|
16
|
+
# nested structs (`rcl_bounds`, `xform`, `color`) as nested `Data`
|
|
17
|
+
# objects with the wire's member names, and a counted point array as a
|
|
18
|
+
# frozen Array of `Data` points (`x`, `y`). Integers are Integers
|
|
19
|
+
# (signed or unsigned as the wire declares); float32 leaves are Floats,
|
|
20
|
+
# NaN payloads included (the same float32 widening BinData applies).
|
|
21
|
+
#
|
|
22
|
+
# Three readers produce equal objects: `from_bytes` unpacks a record's
|
|
23
|
+
# bytes with one String#unpack, `from_wire` reads the wire's values, and
|
|
24
|
+
# `from_mixed` reads a lazy shell, taking each field the shell has made
|
|
25
|
+
# from the wire and the others from the bytes.
|
|
26
|
+
class ValueView
|
|
27
|
+
# Namespace of the generated value classes.
|
|
28
|
+
module Values; end
|
|
29
|
+
|
|
30
|
+
# A layout the view does not support.
|
|
31
|
+
class Unsupported < StandardError; end
|
|
32
|
+
|
|
33
|
+
autoload :Layout, "emf/emr/binary/value_view/layout"
|
|
34
|
+
autoload :ReaderSource, "emf/emr/binary/value_view/reader_source"
|
|
35
|
+
|
|
36
|
+
BINDATA_WRITER = BinData::Base.instance_method(:to_binary_s)
|
|
37
|
+
private_constant :BINDATA_WRITER
|
|
38
|
+
|
|
39
|
+
@views = {}.compare_by_identity
|
|
40
|
+
# Marks every value class, whichever base class it is built on.
|
|
41
|
+
module Value; end
|
|
42
|
+
|
|
43
|
+
@value_classes = {}.compare_by_identity
|
|
44
|
+
@lock = Mutex.new
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
# The view of a wire class, or nil when the class has none.
|
|
48
|
+
def for_class(wire_class)
|
|
49
|
+
@views.fetch(wire_class) { @lock.synchronize { @views[wire_class] = build(wire_class) } }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The shared value class of a BinData struct class with these members.
|
|
53
|
+
def value_class(struct_class, members)
|
|
54
|
+
@value_classes[struct_class] ||= define_value_class(members).tap do |klass|
|
|
55
|
+
name = struct_class.name&.split("::")&.last
|
|
56
|
+
Values.const_set(name, klass) if name&.match?(/\A[A-Z]\w*\z/) && !Values.const_defined?(name, false)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# Data (Ruby 3.2+) where it exists; on Ruby 3.1 a Struct whose
|
|
63
|
+
# instances freeze themselves, so values stay frozen everywhere.
|
|
64
|
+
def define_value_class(members)
|
|
65
|
+
klass = defined?(::Data.define) ? ::Data.define(*members) : frozen_struct(members)
|
|
66
|
+
klass.include(Value)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def frozen_struct(members)
|
|
70
|
+
Struct.new(*members) do
|
|
71
|
+
def initialize(*)
|
|
72
|
+
super
|
|
73
|
+
freeze
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def build(wire_class)
|
|
79
|
+
filler = FastFill.for_class(wire_class)
|
|
80
|
+
return unless filler && !filler.is_a?(FastFill::TextFill)
|
|
81
|
+
|
|
82
|
+
new(wire_class).tap(&:prove)
|
|
83
|
+
rescue StandardError
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
attr_reader :wire_class, :value_class
|
|
89
|
+
|
|
90
|
+
def initialize(wire_class)
|
|
91
|
+
@wire_class = wire_class
|
|
92
|
+
@layout = Layout.new(wire_class)
|
|
93
|
+
@value_class = @layout.tree.first
|
|
94
|
+
generate
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The values of the record at `base` of the binary `data`; nil when
|
|
98
|
+
# its point count overruns the record.
|
|
99
|
+
def from_bytes(data, base) = read_bytes(data, base)
|
|
100
|
+
|
|
101
|
+
# The values of a lazy shell over the record at `base` of `data`:
|
|
102
|
+
# each top-level field the shell has made from the wire, the others
|
|
103
|
+
# from the bytes. Nil when the point count overruns the record.
|
|
104
|
+
def from_mixed(data, base, wire) = read_mixed(data, base, wire)
|
|
105
|
+
|
|
106
|
+
# The values the wire holds now.
|
|
107
|
+
def from_wire(wire) = read_wire(wire)
|
|
108
|
+
|
|
109
|
+
# Raises unless the readers agree with BinData on a distinct-valued record.
|
|
110
|
+
def prove
|
|
111
|
+
sample = distinct_sample
|
|
112
|
+
bytes = BINDATA_WRITER.bind_call(sample)
|
|
113
|
+
values = read_bytes("\xAA\xBB".b + bytes + "\xCC".b, 2)
|
|
114
|
+
reference = read_wire(@wire_class.read(bytes))
|
|
115
|
+
raise Unsupported, "bytes and wire differ" unless values == read_wire(sample) && values == reference
|
|
116
|
+
raise Unsupported, "a count past the record was read" unless overrun_rejected?(bytes)
|
|
117
|
+
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def generate
|
|
124
|
+
mod = Module.new
|
|
125
|
+
mod.const_set(:CLASSES, @layout.classes.freeze)
|
|
126
|
+
mod.const_set(:POINT, @layout.array[:klass]) if @layout.array
|
|
127
|
+
mod.module_eval(ReaderSource.new(@layout).to_s, __FILE__, __LINE__)
|
|
128
|
+
extend(mod)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# A record with a distinct value in every leaf: negative signed, high
|
|
132
|
+
# unsigned, fractional floats.
|
|
133
|
+
def distinct_sample
|
|
134
|
+
wire = @wire_class.new
|
|
135
|
+
@layout.leaf_paths.each_with_index do |path, index|
|
|
136
|
+
target = path.inject(wire) { |obj, name| obj.public_send(name) }
|
|
137
|
+
target.assign(distinct(@layout.leaves.fetch(index), index))
|
|
138
|
+
end
|
|
139
|
+
fill_array(wire)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def distinct(leaf, index)
|
|
143
|
+
return index + 1.5 if leaf[:float]
|
|
144
|
+
|
|
145
|
+
case leaf[:klass]
|
|
146
|
+
when "BinData::Int32le" then -(index + 1) * 1000
|
|
147
|
+
when "BinData::Uint32le" then 0xFFFF_FF00 - index
|
|
148
|
+
else 0x80 + index
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def fill_array(wire)
|
|
153
|
+
array = @layout.array
|
|
154
|
+
return wire unless array
|
|
155
|
+
|
|
156
|
+
limit = array[:bytes] == 8 ? 2**31 : 2**15
|
|
157
|
+
wire.public_send(array[:count]).assign(3)
|
|
158
|
+
wire.public_send(array[:name]).assign([{ x: -1, y: limit - 1 }, { x: 5, y: -limit }, { x: 0, y: 7 }])
|
|
159
|
+
wire
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# A count claiming more points than the record holds reads as nil.
|
|
163
|
+
def overrun_rejected?(bytes)
|
|
164
|
+
return true unless @layout.array
|
|
165
|
+
|
|
166
|
+
bad = bytes.dup
|
|
167
|
+
bad[@layout.leaves.fetch(@layout.count_index)[:at], 4] = [1_000_000].pack("V")
|
|
168
|
+
read_bytes(bad, 0).nil?
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Emr
|
|
5
|
+
module Binary
|
|
6
|
+
# Reads the offset/size-addressed variable parts of a record straight from
|
|
7
|
+
# its bytes, so they are visible even when a wire `body` or `trailing`
|
|
8
|
+
# field absorbed them. Offsets are from record start. A part that is
|
|
9
|
+
# absent, empty, starts inside the fixed layout or leaves the record is
|
|
10
|
+
# nil, never an error; nothing is computed at parse time.
|
|
11
|
+
#
|
|
12
|
+
# fixed_layout 100 # bytes of the fixed layout
|
|
13
|
+
# variable_buffer :bmi_bytes, offset: :off_bmi_src, length: :cb_bmi_src
|
|
14
|
+
#
|
|
15
|
+
# offset, length and the fixed size are an Integer, a Symbol naming a
|
|
16
|
+
# reader on the record, or a lambda run on the record.
|
|
17
|
+
module VariableBuffers
|
|
18
|
+
def self.included(base)
|
|
19
|
+
base.extend(ClassMethods)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Class macros.
|
|
23
|
+
module ClassMethods
|
|
24
|
+
attr_reader :fixed_layout_size
|
|
25
|
+
|
|
26
|
+
def fixed_layout(size)
|
|
27
|
+
@fixed_layout_size = size
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def buffer_specs
|
|
31
|
+
@buffer_specs ||= {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Declare a part and, unless reader is false, a reader named for it.
|
|
35
|
+
def variable_buffer(name, offset:, length:, reader: true)
|
|
36
|
+
buffer_specs[name] = [offset, length]
|
|
37
|
+
define_method(name) { buffer_range(name)&.then { |range| record_bytes.byteslice(range) } } if reader
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The bytes inside nSize after the fixed layout and after every declared
|
|
42
|
+
# part that lies inside the record, or "" when there are none. nil when
|
|
43
|
+
# nSize is smaller than the fixed layout or larger than the record.
|
|
44
|
+
def layout_surplus_bytes
|
|
45
|
+
bytes = record_bytes
|
|
46
|
+
size = n_size
|
|
47
|
+
fixed = resolve(self.class.fixed_layout_size)
|
|
48
|
+
return unless fixed && size >= fixed && size <= bytes.bytesize
|
|
49
|
+
|
|
50
|
+
finish = self.class.buffer_specs.each_key.filter_map { |name| buffer_range(name)&.end }.max
|
|
51
|
+
start = [fixed, finish.to_i].max
|
|
52
|
+
start >= size ? "".b : bytes.byteslice(start, size - start)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def record_bytes
|
|
58
|
+
to_binary_s
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def buffer_range(name)
|
|
62
|
+
offset, length = self.class.buffer_specs.fetch(name).map { |part| resolve(part) }
|
|
63
|
+
return unless offset && length&.positive?
|
|
64
|
+
return if offset < resolve(self.class.fixed_layout_size)
|
|
65
|
+
|
|
66
|
+
finish = offset + length
|
|
67
|
+
(offset...finish) if finish <= record_bytes.bytesize
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolve(part)
|
|
71
|
+
case part
|
|
72
|
+
when Integer then part
|
|
73
|
+
when Symbol then public_send(part)
|
|
74
|
+
else instance_exec(&part)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Unsigned 32-bit value at a record offset, 0 past the end (so a part
|
|
79
|
+
# sized by it is empty).
|
|
80
|
+
def u32_at(offset)
|
|
81
|
+
raw = record_bytes.byteslice(offset, 4)
|
|
82
|
+
raw&.bytesize == 4 ? raw.unpack1("V") : 0
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
data/lib/emf/emr/binary.rb
CHANGED
|
@@ -7,6 +7,10 @@ module Emf
|
|
|
7
7
|
autoload :Record, "emf/emr/binary/record"
|
|
8
8
|
autoload :WithBounds, "emf/emr/binary/with_bounds"
|
|
9
9
|
autoload :TypeCodes, "emf/emr/binary/type_codes"
|
|
10
|
+
autoload :FastFill, "emf/emr/binary/fast_fill"
|
|
11
|
+
autoload :PackPlan, "emf/emr/binary/pack_plan"
|
|
12
|
+
autoload :WireCopy, "emf/emr/binary/fast_fill"
|
|
13
|
+
autoload :ValueView, "emf/emr/binary/value_view"
|
|
10
14
|
autoload :Records, "emf/emr/binary/records"
|
|
11
15
|
end
|
|
12
16
|
end
|