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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5ee6e6c5a49eabf172b92810e3ec3ab0591122a97ac4960a9f4af32efe6e79f
|
|
4
|
+
data.tar.gz: 033ccee0b06ca850f57aca486f0d07692cd4e5ac52bfcbb907f1889fd9fd9b5f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9746ee5767b814b7da75ee2ee6116a9963dbd260eebf8539cfce50d4ad91631b88339a373c47a7acfd8ee1beaab11bbfd20849db2630d31e7502f1b61e427b99
|
|
7
|
+
data.tar.gz: caae6ff8f26b3536b55486bca2c95a3bc5d42196cbaff9a4f1e0ea7cd18e03009e1b3447b162139a5f667c2e1755f06b19d32beec1c07b4d75d4722dcb105ff5
|
data/README.adoc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
= emf:
|
|
1
|
+
= emf: parser for WMF, EMF, and EMF+ metafiles
|
|
2
2
|
|
|
3
|
-
`emf` is a clean-room
|
|
3
|
+
`emf` is a clean-room Ruby parser with an optional Rust native backend for the three Microsoft Windows
|
|
4
4
|
metafile formats:
|
|
5
5
|
|
|
6
6
|
* **WMF** — Windows Metafile (see MS-WMF, kaitai `wmf.ksy`)
|
|
@@ -15,13 +15,17 @@ wrapper around the GPLv2 `libemf2svg` C library.
|
|
|
15
15
|
|
|
16
16
|
== Status
|
|
17
17
|
|
|
18
|
-
* **EMF**:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* **
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
* **EMF**: parsing, mutable wire records and binary serialization are implemented.
|
|
19
|
+
* **EMF+**: raw carrier payloads (`Metafile#emf_plus`) and a typed stream view
|
|
20
|
+
(`Metafile#emf_plus_stream`) are implemented.
|
|
21
|
+
* **WMF**: standard and placeable headers, records and binary serialization
|
|
22
|
+
are implemented.
|
|
23
|
+
* **Native backend**: Rust decoding and a Ruby adapter are implemented and match the
|
|
24
|
+
Ruby backend on the valid and corrupt fixture corpora, every EMF record type and
|
|
25
|
+
mutated WMF input; installed platform qualification remains open.
|
|
26
|
+
* **Qualification**: implementation does not imply complete format coverage or
|
|
27
|
+
release readiness. See link:TODO.plan/10-progress.md[delivery progress] and
|
|
28
|
+
link:TODO.plan/09-format-extension.md[format extension acceptance].
|
|
25
29
|
* **License**: BSD-2-Clause. No GPL obligation.
|
|
26
30
|
|
|
27
31
|
== Installation
|
|
@@ -36,6 +40,45 @@ Or install directly:
|
|
|
36
40
|
|
|
37
41
|
Requires Ruby >= 3.1.
|
|
38
42
|
|
|
43
|
+
== Backend selection
|
|
44
|
+
|
|
45
|
+
The public `Emf.parse`, `Emf.parse_file` and `Emf.serialize` APIs use
|
|
46
|
+
`EMF_BACKEND` at call time:
|
|
47
|
+
|
|
48
|
+
[cols="1,3",options="header"]
|
|
49
|
+
|===
|
|
50
|
+
|Value |Behavior
|
|
51
|
+
|`auto` (default) |Use the native backend when available; fall back to Ruby if native setup raises `Emf::Native::Unavailable`.
|
|
52
|
+
|`ruby` |Use Ruby directly, without loading the native library.
|
|
53
|
+
|`native` |Require the native backend; unavailable setup raises `Emf::Native::Unavailable`.
|
|
54
|
+
|===
|
|
55
|
+
|
|
56
|
+
`Emf.backend` resolves the backend at call time and returns `:ruby` or `:native`.
|
|
57
|
+
It uses the same selection as parsing and serialization, including the `auto`
|
|
58
|
+
availability fallback. It returns a symbol rather than a backend adapter.
|
|
59
|
+
Explicit `native` selection raises `Emf::Native::Unavailable` if unavailable;
|
|
60
|
+
unexpected setup errors propagate. This query may load the native library, and
|
|
61
|
+
its result describes the current settings rather than pinning future operations.
|
|
62
|
+
|
|
63
|
+
Other values raise `ArgumentError`. A parsing or serialization error after
|
|
64
|
+
backend selection propagates to the caller; it is not retried using Ruby.
|
|
65
|
+
Both backends return the existing Ruby model and use the Ruby binary writers
|
|
66
|
+
for serialization, including mutable wire records.
|
|
67
|
+
|
|
68
|
+
For example, to explicitly use Ruby:
|
|
69
|
+
|
|
70
|
+
EMF_BACKEND=ruby ruby -remf -e 'p Emf.parse_file(ARGV.fetch(0)).format' image.emf
|
|
71
|
+
|
|
72
|
+
Native loading uses a packaged platform manifest when present. For development
|
|
73
|
+
with an existing compatible library, `EMF_NATIVE_LIBRARY` overrides discovery:
|
|
74
|
+
|
|
75
|
+
EMF_BACKEND=native EMF_NATIVE_LIBRARY=/absolute/path/to/libemf_ffi.dylib ruby -remf -e 'p Emf.parse_file(ARGV.fetch(0)).format' image.emf
|
|
76
|
+
|
|
77
|
+
Use the appropriate shared-library filename for the platform. An empty override
|
|
78
|
+
makes native loading unavailable. Native installation and platform support must
|
|
79
|
+
be qualified using link:TODO.plan/08-platform-delivery.md[the delivery checks];
|
|
80
|
+
the source implementation alone does not establish an available platform gem.
|
|
81
|
+
|
|
39
82
|
== Synopsis
|
|
40
83
|
|
|
41
84
|
require "emf"
|
|
@@ -50,9 +93,23 @@ Requires Ruby >= 3.1.
|
|
|
50
93
|
stats = Emf::Visitors::Stats.new.visit_all(metafile)
|
|
51
94
|
puts stats.to_s
|
|
52
95
|
|
|
53
|
-
# Serialise back to binary
|
|
96
|
+
# Serialise the model back to binary
|
|
54
97
|
bytes = Emf.serialize(metafile)
|
|
55
98
|
|
|
99
|
+
=== Reading field values without BinData
|
|
100
|
+
|
|
101
|
+
`record.values` returns a record's field values as a frozen, plain Ruby object
|
|
102
|
+
(a `Data` with the wire's member names; nested structs as nested `Data`; a counted
|
|
103
|
+
point array as an Array of `Data` points with `x` and `y`) without making
|
|
104
|
+
BinData field objects. It is `nil` for records without a fixed layout or a
|
|
105
|
+
counted point array (for example ExtTextOut, bitmaps, fonts, Comment, Raw); use
|
|
106
|
+
`record.wire` for those. The values are equal on the Ruby and native backends and
|
|
107
|
+
always reflect edits made through `record.wire`.
|
|
108
|
+
|
|
109
|
+
record = metafile.records.find { |r| r.type_id == 86 } # POLYGON16
|
|
110
|
+
record.values.rcl_bounds.left
|
|
111
|
+
record.values.apts.map { |point| [point.x, point.y] }
|
|
112
|
+
|
|
56
113
|
== Command-line tool
|
|
57
114
|
|
|
58
115
|
emf version Print version
|
|
@@ -62,6 +119,145 @@ Requires Ruby >= 3.1.
|
|
|
62
119
|
emf validate FILE Parse + report errors (exit 0 ok, 1 invalid)
|
|
63
120
|
emf round-trip FILE [--out] Re-serialize after parse, compare or write
|
|
64
121
|
|
|
122
|
+
== Conformance lens
|
|
123
|
+
|
|
124
|
+
`Emf::Conformance` checks a metafile against the Microsoft specifications
|
|
125
|
+
(MS-EMF, MS-EMFPLUS and MS-WMF) and reports what it finds. It does not repair
|
|
126
|
+
files and it does not change what the parser returns. The report uses the key
|
|
127
|
+
layout of `svg_conform`, so tools that read one can read the other.
|
|
128
|
+
|
|
129
|
+
require "emf"
|
|
130
|
+
|
|
131
|
+
report = Emf::Conformance.check("image.emf", profile: :tolerant)
|
|
132
|
+
report.valid? # => true when there are no errors (warnings do not count)
|
|
133
|
+
report.to_h # => Hash with string keys
|
|
134
|
+
report.to_yaml
|
|
135
|
+
|
|
136
|
+
The lens reads only the public model, so the Ruby and the native backend give
|
|
137
|
+
the same report. It adds no runtime dependency: `bindata` is still the only one.
|
|
138
|
+
|
|
139
|
+
=== Input
|
|
140
|
+
|
|
141
|
+
`check` accepts four forms:
|
|
142
|
+
|
|
143
|
+
[cols="1,3",options="header"]
|
|
144
|
+
|===
|
|
145
|
+
|Input |Behavior
|
|
146
|
+
|path `String` |Read with `File.binread`. The path becomes the report's `filename`.
|
|
147
|
+
|`Pathname` |Same as a path `String`.
|
|
148
|
+
|bytes `String` |Parsed once. `filename` is `nil`. A `String` is taken as a path only when it names an existing file, has no NUL byte and is under 4096 bytes long.
|
|
149
|
+
|`Emf::Model::Metafile` |Evaluated without parsing again. `filename` is `nil`.
|
|
150
|
+
|===
|
|
151
|
+
|
|
152
|
+
A parsed `Metafile` carries no input bytes. Rules that compare a declared
|
|
153
|
+
length with the real input length are skipped for it. One example is
|
|
154
|
+
`emf.header.bytes`. The WMF size check against the input is another. Pass the
|
|
155
|
+
path or the bytes when you want those rules to run.
|
|
156
|
+
|
|
157
|
+
=== Profiles
|
|
158
|
+
|
|
159
|
+
`profile:` is `:strict` (the default) or `:tolerant`. Any other value raises
|
|
160
|
+
`ArgumentError`.
|
|
161
|
+
|
|
162
|
+
* `:strict` reports every spec violation as an error.
|
|
163
|
+
* `:tolerant` accepts what real producers write. A rule that carries a
|
|
164
|
+
`tolerant_reason` becomes a warning instead of an error. The warning keeps
|
|
165
|
+
its `requirement_id`, `kind`, citation and offset. The reason appears as
|
|
166
|
+
`context.waiver`.
|
|
167
|
+
|
|
168
|
+
A waiver belongs to the rule, not to a file. Every input that triggers that
|
|
169
|
+
rule gets the downgrade. There is no per-file allowlist. A rule without a
|
|
170
|
+
`tolerant_reason` is an error under both profiles.
|
|
171
|
+
|
|
172
|
+
Two files in `spec/fixtures/emf-corrupted/` cannot be parsed into a model. One
|
|
173
|
+
has a truncated header and one has an unrecognised first word. They report
|
|
174
|
+
`valid: false` under both profiles, with the error `emf.parse.unparseable`.
|
|
175
|
+
The other 19 corrupted fixtures are `valid: true` under `:tolerant`.
|
|
176
|
+
|
|
177
|
+
=== The report
|
|
178
|
+
|
|
179
|
+
`Report#to_h` has these keys: `filename`, `profile`, `tool`, `version`,
|
|
180
|
+
`timestamp`, `valid`, `errors` and `warnings`. `errors` and `warnings` each hold
|
|
181
|
+
`total_count`, `by_requirement` and the ordered `issues`. Issues are ordered by
|
|
182
|
+
offset, then requirement id. Each issue has `type`, `requirement_id`,
|
|
183
|
+
`message`, `element`, `attribute`, `value`, `offset`, `severity`, `kind`,
|
|
184
|
+
`category`, `spec_citation`, `remediation_available` and `context`.
|
|
185
|
+
`offset` is the absolute byte offset in the file. `kind` is a stable
|
|
186
|
+
machine-readable name. `category` repeats `kind` and `severity` repeats `type`,
|
|
187
|
+
for `svg_conform` compatibility.
|
|
188
|
+
|
|
189
|
+
This is the strict report for a repo fixture. It comes from
|
|
190
|
+
`Emf::Conformance.check("spec/fixtures/emf/test-000.emf").to_h(timestamp: false).to_yaml`.
|
|
191
|
+
The `timestamp` line is left out by `timestamp: false`.
|
|
192
|
+
|
|
193
|
+
filename: spec/fixtures/emf/test-000.emf
|
|
194
|
+
profile: strict
|
|
195
|
+
tool: emf
|
|
196
|
+
version: 0.1.2
|
|
197
|
+
valid: false
|
|
198
|
+
errors:
|
|
199
|
+
total_count: 1
|
|
200
|
+
by_requirement:
|
|
201
|
+
emfplus.property.enum: 1
|
|
202
|
+
issues:
|
|
203
|
+
- type: error
|
|
204
|
+
requirement_id: emfplus.property.enum
|
|
205
|
+
message: SetCompositingQuality CompositingQuality value 0 is outside 1..5 (EMF+
|
|
206
|
+
record 0x4024 at offset 0 of EMR_COMMENT_EMFPLUS record 50)
|
|
207
|
+
element: emf_plus:0x4024
|
|
208
|
+
attribute:
|
|
209
|
+
value:
|
|
210
|
+
offset: 52152
|
|
211
|
+
severity: error
|
|
212
|
+
kind: property_enum
|
|
213
|
+
category: property_enum
|
|
214
|
+
spec_citation: MS-EMFPLUS 2.3.6
|
|
215
|
+
remediation_available: false
|
|
216
|
+
context:
|
|
217
|
+
spec_citation: MS-EMFPLUS 2.3.6
|
|
218
|
+
format: emf_plus
|
|
219
|
+
record_index: 50
|
|
220
|
+
record_type: 16420
|
|
221
|
+
warnings:
|
|
222
|
+
total_count: 0
|
|
223
|
+
by_requirement: {}
|
|
224
|
+
issues: []
|
|
225
|
+
|
|
226
|
+
With `profile: :tolerant` the same file is `valid: true`. The finding moves to
|
|
227
|
+
`warnings`, and `context.waiver` carries the written reason.
|
|
228
|
+
|
|
229
|
+
=== Comparing backends
|
|
230
|
+
|
|
231
|
+
The report holds a creation `timestamp`, so two runs never compare equal as they
|
|
232
|
+
are. To compare two reports, drop the timestamp or pin it:
|
|
233
|
+
|
|
234
|
+
Emf::Conformance.check(path).to_h(timestamp: false)
|
|
235
|
+
Emf::Conformance.check(path, now: Time.utc(2026, 1, 1)).to_h # "timestamp" => "2026-01-01T00:00:00Z"
|
|
236
|
+
|
|
237
|
+
Run once with `EMF_BACKEND=ruby` and once with `EMF_BACKEND=native` (see
|
|
238
|
+
<<Backend selection>>) and compare the two results. They must be equal. The
|
|
239
|
+
`native` value raises `Emf::Native::Unavailable` when no native library loads.
|
|
240
|
+
|
|
241
|
+
=== The rule manifest
|
|
242
|
+
|
|
243
|
+
Every rule the lens implements, and every check it does not yet make, is listed
|
|
244
|
+
in `lib/emf/conformance/manifest/*.yml`. A row has an `id`, a `format`, the
|
|
245
|
+
`record_types` it covers, a `citation`, a `status` and a `reason`. The reason is
|
|
246
|
+
required for every status except `implemented`.
|
|
247
|
+
|
|
248
|
+
[cols="1,3",options="header"]
|
|
249
|
+
|===
|
|
250
|
+
|Status |Meaning
|
|
251
|
+
|`implemented` |A registered rule with this id exists.
|
|
252
|
+
|`deferred` |The data is readable from the public model today, but no rule is written yet.
|
|
253
|
+
|`blocked:model` |The check needs a public model field that does not exist yet. The reason names the field.
|
|
254
|
+
|`not_checkable` |No input can violate it, or the bytes are ignored on receipt.
|
|
255
|
+
|===
|
|
256
|
+
|
|
257
|
+
Read the manifest to see what a clean report does not prove. A `deferred` or
|
|
258
|
+
`blocked:model` row is a check the lens does not make. Every record type of the
|
|
259
|
+
three specifications appears in at least one row.
|
|
260
|
+
|
|
65
261
|
== Architecture
|
|
66
262
|
|
|
67
263
|
Three layers, kept strictly separate (MECE):
|
|
@@ -95,7 +291,7 @@ Three layers, kept strictly separate (MECE):
|
|
|
95
291
|
* No AI attribution in any commit. All changes go through PRs.
|
|
96
292
|
|
|
97
293
|
See `docs/architecture.adoc` and `docs/format_notes.adoc` for the deep
|
|
98
|
-
dive, and
|
|
294
|
+
dive, and link:TODO.plan/10-progress.md[delivery progress] for implementation status.
|
|
99
295
|
|
|
100
296
|
=== Reference documentation
|
|
101
297
|
|
|
@@ -110,7 +306,7 @@ revision bumps.
|
|
|
110
306
|
git clone https://github.com/claricle/emf
|
|
111
307
|
cd emf
|
|
112
308
|
bundle install
|
|
113
|
-
bundle exec rspec #
|
|
309
|
+
bundle exec rspec # fixture and unit checks
|
|
114
310
|
bundle exec rubocop # clean
|
|
115
311
|
|
|
116
312
|
The round-trip harness in `spec/emf/round_trip_spec.rb` is the strongest
|
|
@@ -119,13 +315,22 @@ byte-identical parse -> serialise -> parse.
|
|
|
119
315
|
|
|
120
316
|
== Roadmap
|
|
121
317
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
* TODO
|
|
127
|
-
|
|
128
|
-
* TODO
|
|
318
|
+
The execution plan for issue #15 starts at
|
|
319
|
+
link:TODO.plan/00-overview.md[the shared roadmap]. Implementation is in progress; remaining acceptance and
|
|
320
|
+
qualification work is tracked in the linked sections.
|
|
321
|
+
|
|
322
|
+
* link:TODO.plan/05-ruby-execution.md[Ruby implementation]: existing API dispatch,
|
|
323
|
+
model compatibility, mutable wire serialization, fallback and CLI behavior.
|
|
324
|
+
* link:TODO.plan/03-rust-core.md[Rust core] and
|
|
325
|
+
link:TODO.plan/04-c-abi.md[C ABI]: typed decoding, serialization and native ownership.
|
|
326
|
+
* link:TODO.plan/09-format-extension.md[Format extensions]: WMF and structured EMF+.
|
|
327
|
+
* link:TODO.plan/06-resilience.md[Resilience],
|
|
328
|
+
link:TODO.plan/07-performance.md[performance] and
|
|
329
|
+
link:TODO.plan/08-platform-delivery.md[platform delivery].
|
|
330
|
+
|
|
331
|
+
Other roadmap items include semantic domain records beyond `WireAdapter` and
|
|
332
|
+
two-pass path association (`BEGINPATH` with `FILLPATH`/`STROKEPATH`). These remain
|
|
333
|
+
separate from the issue #15 delivery criteria.
|
|
129
334
|
|
|
130
335
|
== License
|
|
131
336
|
|
data/exe/emf
CHANGED
|
@@ -45,12 +45,24 @@ module Emf
|
|
|
45
45
|
return unless mf.header
|
|
46
46
|
|
|
47
47
|
h = mf.header
|
|
48
|
+
return wmf_info(h) if mf.format == :wmf
|
|
49
|
+
|
|
48
50
|
puts "Bounds: left=#{h.bounds.left} top=#{h.bounds.top} right=#{h.bounds.right} bottom=#{h.bounds.bottom}"
|
|
49
51
|
puts "Frame: left=#{h.frame.left} top=#{h.frame.top} right=#{h.frame.right} bottom=#{h.frame.bottom}"
|
|
50
52
|
puts "Device: #{h.device_pixels.cx}x#{h.device_pixels.cy} px (#{h.device_mm.cx / 100.0}x#{h.device_mm.cy / 100.0} mm)"
|
|
51
53
|
puts "nRecords: #{h.n_records} nHandles: #{h.n_handles} nBytes: #{h.n_bytes}"
|
|
52
54
|
end
|
|
53
55
|
|
|
56
|
+
def wmf_info(header)
|
|
57
|
+
puts "Type: #{header.type} Version: #{header.version} Size: #{header.size_words} words"
|
|
58
|
+
puts "Objects: #{header.number_of_objects} MaxRecord: #{header.max_record_words} words Members: #{header.number_of_members}"
|
|
59
|
+
return unless header.placeable
|
|
60
|
+
|
|
61
|
+
placeable = header.placeable
|
|
62
|
+
puts "Bounds: left=#{placeable.left} top=#{placeable.top} right=#{placeable.right} bottom=#{placeable.bottom}"
|
|
63
|
+
puts "Units: #{placeable.inch} per inch"
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
def dump(argv)
|
|
55
67
|
path = parse_path(argv)
|
|
56
68
|
mf = Emf.parse_file(path)
|
data/lib/emf/backend.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Backend
|
|
5
|
+
module Ruby
|
|
6
|
+
class << self
|
|
7
|
+
def parse(bytes)
|
|
8
|
+
deferred = ResourceLimits.preflight(bytes, defer: true)
|
|
9
|
+
case Detector.call(bytes)
|
|
10
|
+
when :wmf_apm, :wmf_standard then Wmf::Parser.call(bytes)
|
|
11
|
+
when :emf then Emr::Parser.call(bytes, deferred_admission: deferred)
|
|
12
|
+
end
|
|
13
|
+
rescue FormatError, ParseError => e
|
|
14
|
+
raise ParseEvidenceBuilder.attach(e, bytes)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def serialize(metafile)
|
|
18
|
+
case metafile.format
|
|
19
|
+
when :wmf then Wmf::Serializer.call(metafile)
|
|
20
|
+
when :emf, :emf_plus then Emr::Serializer.call(metafile)
|
|
21
|
+
else raise SerializeError, "unknown format: #{metafile.format.inspect}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
private_constant :Ruby
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
def resolved
|
|
30
|
+
selected.equal?(Ruby) ? :ruby : :native
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parse(bytes)
|
|
34
|
+
selected.parse(bytes)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serialize(metafile)
|
|
38
|
+
selected.serialize(metafile)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def selected
|
|
44
|
+
override = ENV.fetch("EMF_BACKEND", "auto")
|
|
45
|
+
case override
|
|
46
|
+
when "ruby" then Ruby
|
|
47
|
+
when "auto" then native_or_ruby
|
|
48
|
+
when "native" then Native.backend
|
|
49
|
+
else raise ArgumentError, "invalid EMF_BACKEND: #{override.inspect} (expected auto, ruby, or native)"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def native_or_ruby
|
|
54
|
+
Native.backend
|
|
55
|
+
rescue Native::Unavailable
|
|
56
|
+
Ruby
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Binary
|
|
5
|
+
# BinData defines three singleton accessors per field on every instance,
|
|
6
|
+
# which dominates allocation when a metafile holds tens of thousands of
|
|
7
|
+
# records. The accessors depend only on the field's index, so they are
|
|
8
|
+
# defined once per class on first instantiation and shared afterwards.
|
|
9
|
+
module ClassFieldAccessors
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def define_field_accessors
|
|
13
|
+
klass = self.class
|
|
14
|
+
return if klass.instance_variable_get(:@field_accessors_defined)
|
|
15
|
+
|
|
16
|
+
get_parameter(:fields).each_with_index do |field, index|
|
|
17
|
+
name = field.name_as_sym
|
|
18
|
+
define_class_accessors(klass, name, index) if name
|
|
19
|
+
end
|
|
20
|
+
klass.instance_variable_set(:@field_accessors_defined, true)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def define_setter(klass, name, index)
|
|
24
|
+
klass.define_method("#{name}=") { |val| assign_field_at(index, val) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The generic assignment; a class that can take one into the bytes it
|
|
28
|
+
# is still made from overrides it.
|
|
29
|
+
def assign_field_at(index, val)
|
|
30
|
+
instantiate_obj_at(index) if @field_objs.nil? || @field_objs[index].nil?
|
|
31
|
+
@field_objs[index].assign(val)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def define_class_accessors(klass, name, index)
|
|
35
|
+
klass.define_method(name) do
|
|
36
|
+
@field_objs&.at(index) || begin
|
|
37
|
+
instantiate_obj_at(index)
|
|
38
|
+
@field_objs[index]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
define_setter(klass, name, index)
|
|
42
|
+
klass.define_method("#{name}?") do
|
|
43
|
+
instantiate_obj_at(index) if @field_objs.nil? || @field_objs[index].nil?
|
|
44
|
+
include_obj?(@field_objs[index])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bindata"
|
|
4
|
+
|
|
5
|
+
module Emf
|
|
6
|
+
module Binary
|
|
7
|
+
# Little-endian IEEE 754 single that writes back the exact bits it read.
|
|
8
|
+
# Ruby widens a float32 to a double on read but `pack("e")` canonicalises
|
|
9
|
+
# every NaN to 0x7fc00000, so NaN payloads (and signalling NaNs) would
|
|
10
|
+
# otherwise change on a parse -> serialize round trip. Only NaNs carry
|
|
11
|
+
# their bits; every other value re-encodes exactly through the Float.
|
|
12
|
+
# Assigning a value drops the retained bits.
|
|
13
|
+
class Float32Le < BinData::BasePrimitive
|
|
14
|
+
def initialize_instance
|
|
15
|
+
super
|
|
16
|
+
@nan_bits = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def assign(val)
|
|
20
|
+
@nan_bits = nil
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def assign_bits(bits)
|
|
25
|
+
@value = [bits].pack("V").unpack1("e")
|
|
26
|
+
@nan_bits = @value.nan? ? bits : nil
|
|
27
|
+
bits
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def do_num_bytes
|
|
31
|
+
4
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def sensible_default
|
|
37
|
+
0.0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def read_and_return_value(io)
|
|
41
|
+
raw = io.readbytes(4)
|
|
42
|
+
value = raw.unpack1("e")
|
|
43
|
+
@nan_bits = value.nan? ? raw.unpack1("V") : nil
|
|
44
|
+
value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def value_to_binary_string(val)
|
|
48
|
+
return [@nan_bits].pack("V") if @nan_bits
|
|
49
|
+
|
|
50
|
+
[val].pack("e")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/emf/binary/types.rb
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bindata"
|
|
4
|
+
require "emf/binary/class_field_accessors"
|
|
5
|
+
require "emf/binary/float32_le"
|
|
4
6
|
|
|
5
7
|
module Emf
|
|
6
8
|
module Binary
|
|
9
|
+
# `Type.new(x: 1, y: 2)` for an all-integer layout copies one instantiated
|
|
10
|
+
# sample and assigns the given leaves, as BinData's constructor does
|
|
11
|
+
# field by field; any other call shape, or an unknown key, is BinData's.
|
|
12
|
+
module KeywordNew
|
|
13
|
+
def new(*args, **attrs)
|
|
14
|
+
if args.empty? && !attrs.empty?
|
|
15
|
+
template, slots = (@keyword_template ||= keyword_template)
|
|
16
|
+
wire = Emf::Emr::Binary::WireCopy.build(template, slots, attrs)
|
|
17
|
+
return wire if wire
|
|
18
|
+
end
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def keyword_template
|
|
25
|
+
sample = allocate_sample
|
|
26
|
+
[sample, Emf::Emr::Binary::WireCopy.slots(sample)].freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def allocate_sample
|
|
30
|
+
sample = method(:new).super_method.call
|
|
31
|
+
sample.field_names.each { |name| sample.public_send(name) }
|
|
32
|
+
sample
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
7
36
|
# Bindata primitives shared by all three formats (WMF, EMF, EMF+).
|
|
8
37
|
# Each record type is declared once and used as a `type:` argument in
|
|
9
38
|
# larger records. See TODO.impl/03-binary-primitives.md.
|
|
@@ -58,12 +87,12 @@ module Emf
|
|
|
58
87
|
|
|
59
88
|
class XForm < BinData::Record
|
|
60
89
|
endian :little
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
float32_le :m11
|
|
91
|
+
float32_le :m12
|
|
92
|
+
float32_le :m21
|
|
93
|
+
float32_le :m22
|
|
94
|
+
float32_le :dx
|
|
95
|
+
float32_le :dy
|
|
67
96
|
end
|
|
68
97
|
|
|
69
98
|
class PaletteEntry < BinData::Record
|
|
@@ -86,12 +115,17 @@ module Emf
|
|
|
86
115
|
uint32 :dw_low_date_time
|
|
87
116
|
uint32 :dw_high_date_time
|
|
88
117
|
end
|
|
118
|
+
|
|
119
|
+
constants.each { |name| const_get(name).prepend ClassFieldAccessors }
|
|
120
|
+
|
|
121
|
+
[PointL, PointS, SizeL, RectL, RectS, ColorRef].each { |type| type.singleton_class.prepend(Emf::Binary::KeywordNew) }
|
|
89
122
|
end
|
|
90
123
|
end
|
|
91
124
|
end
|
|
92
125
|
|
|
93
126
|
# Register the primitives so they can be referenced by name (e.g. `rectl :bounds`)
|
|
94
127
|
# inside other BinData records. Bindata resolves types lazily through this registry.
|
|
128
|
+
BinData::RegisteredClasses.register("float32_le", Emf::Binary::Float32Le)
|
|
95
129
|
BinData::RegisteredClasses.register("point_l", Emf::Binary::Types::PointL)
|
|
96
130
|
BinData::RegisteredClasses.register("point_s", Emf::Binary::Types::PointS)
|
|
97
131
|
BinData::RegisteredClasses.register("point_d", Emf::Binary::Types::PointD)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Conformance
|
|
5
|
+
# What a rule sees: the parsed model plus the input length. `format` is the
|
|
6
|
+
# Metafile format, or :unparseable when the parser rejected the input
|
|
7
|
+
# (then `metafile` is nil and `parse_failure` holds the exception).
|
|
8
|
+
class Context
|
|
9
|
+
attr_reader :metafile, :bytes, :parse_failure
|
|
10
|
+
|
|
11
|
+
def initialize(metafile: nil, bytes: nil, parse_failure: nil)
|
|
12
|
+
@metafile = metafile
|
|
13
|
+
@bytes = bytes
|
|
14
|
+
@parse_failure = parse_failure
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def format = metafile ? metafile.format : :unparseable
|
|
18
|
+
|
|
19
|
+
def header = metafile.header
|
|
20
|
+
|
|
21
|
+
def records = metafile.records
|
|
22
|
+
|
|
23
|
+
# Input length in bytes, or nil when the metafile was passed in without its bytes.
|
|
24
|
+
def byte_length = bytes&.bytesize
|
|
25
|
+
|
|
26
|
+
# Declared EMR size of a record (its nSize field).
|
|
27
|
+
# EMF record size in bytes; nil for records without an EMF wire (WMF).
|
|
28
|
+
def record_size(record) = (record.wire.n_size.to_i if record.respond_to?(:wire))
|
|
29
|
+
|
|
30
|
+
def record_type(record) = record.type_id.to_i
|
|
31
|
+
|
|
32
|
+
# Total number of EMF records the header counts, header included.
|
|
33
|
+
def record_count = records.size + 1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Conformance
|
|
5
|
+
# One finding. `severity` is already resolved by the profile; `waiver` holds
|
|
6
|
+
# the written reason when the tolerant profile downgraded an error.
|
|
7
|
+
Diagnostic = Struct.new(:requirement_id, :spec_citation, :severity, :kind, :format, :offset,
|
|
8
|
+
:record_index, :record_type, :message, :waiver, keyword_init: true) do
|
|
9
|
+
def initialize(**)
|
|
10
|
+
super
|
|
11
|
+
freeze
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def error? = severity == :error
|
|
15
|
+
def warning? = severity == :warning
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|