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,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WMF object-table lifecycle and playback-DC depth rules over the public record
|
|
4
|
+
# sequence (MS-WMF 3.1.4.1 WMF Object Table, 2.3.4 object records, 2.3.5.10
|
|
5
|
+
# META_RESTOREDC). One walk replays the table exactly as the spec describes it:
|
|
6
|
+
# a created object takes the lowest free index below header NumberOfObjects,
|
|
7
|
+
# META_DELETEOBJECT frees it, and later records must refer to live indexes of the
|
|
8
|
+
# right object type. Records the model keeps opaque (UnknownRecord) are skipped.
|
|
9
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
10
|
+
|
|
11
|
+
module Emf
|
|
12
|
+
module Conformance
|
|
13
|
+
module WmfObjectRules
|
|
14
|
+
WMF = %i[wmf].freeze
|
|
15
|
+
R = Emf::Model::Wmf::Records
|
|
16
|
+
STOCK_FLAG = 0x8000
|
|
17
|
+
GDI_IGNORES = "GDI skips a record that names an unusable object instead of rejecting the metafile"
|
|
18
|
+
|
|
19
|
+
CREATORS = {
|
|
20
|
+
R::CreatePenIndirect => :pen, R::CreateBrushIndirect => :brush, R::CreatePatternBrush => :brush,
|
|
21
|
+
R::DibCreatePatternBrush => :brush, R::CreateFontIndirect => :font, R::CreatePalette => :palette,
|
|
22
|
+
R::CreateRegion => :region
|
|
23
|
+
}.freeze
|
|
24
|
+
REGION_USERS = [R::FillRegion, R::FrameRegion, R::InvertRegion, R::PaintRegion, R::SelectClipRegion].freeze
|
|
25
|
+
BRUSH_USERS = [R::FillRegion, R::FrameRegion].freeze
|
|
26
|
+
|
|
27
|
+
# id => [citation, tolerant_reason, kind]
|
|
28
|
+
RULES = {
|
|
29
|
+
"wmf.object.capacity" => ["MS-WMF 3.1.4.1 WMF Object Table, 2.3.2.2 META_HEADER (NumberOfObjects)", nil, :object_capacity],
|
|
30
|
+
"wmf.object.select_reference" => ["MS-WMF 2.3.4.10 META_SELECTOBJECT (ObjectIndex), 3.1.4.1; stock-object selectors " \
|
|
31
|
+
"(high bit 0x8000) are an implementation rule, MS-WMF defines no selector encoding",
|
|
32
|
+
GDI_IGNORES, :object_reference],
|
|
33
|
+
"wmf.object.delete_reference" => ["MS-WMF 2.3.4.7 META_DELETEOBJECT (ObjectIndex), 3.1.4.1; stock objects are never " \
|
|
34
|
+
"deleted (1.1 Glossary), the 0x8000 selector bit is an implementation rule",
|
|
35
|
+
GDI_IGNORES, :object_reference],
|
|
36
|
+
"wmf.object.palette_reference" => ["MS-WMF 2.3.4.11 META_SELECTPALETTE (Palette), 3.1.4.1", GDI_IGNORES, :object_type],
|
|
37
|
+
"wmf.object.region_reference" => [
|
|
38
|
+
"MS-WMF 2.3.3.6 META_FILLREGION, 2.3.3.8 META_FRAMEREGION, 2.3.3.9 META_INVERTREGION, " \
|
|
39
|
+
"2.3.3.11 META_PAINTREGION, 2.3.4.9 META_SELECTCLIPREGION (Region), 3.1.4.1", GDI_IGNORES, :object_type
|
|
40
|
+
],
|
|
41
|
+
"wmf.object.brush_reference" => [
|
|
42
|
+
"MS-WMF 2.3.3.6 META_FILLREGION, 2.3.3.8 META_FRAMEREGION (Brush), 3.1.4.1", GDI_IGNORES, :object_type
|
|
43
|
+
],
|
|
44
|
+
"wmf.dc.restore_depth" => ["MS-WMF 2.3.5.10 META_RESTOREDC (nSavedDC), 2.3.5.11 META_SAVEDC, 3.1.5; the outcome " \
|
|
45
|
+
"of an nSavedDC that names no saved context is an implementation rule, MS-WMF defines " \
|
|
46
|
+
"none", GDI_IGNORES, :dc_stack]
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
module_function
|
|
50
|
+
|
|
51
|
+
# Replays the record sequence and returns [rule_id, record, index, message] findings.
|
|
52
|
+
def walk(context)
|
|
53
|
+
table = Table.new(context.header.number_of_objects.to_i)
|
|
54
|
+
found = []
|
|
55
|
+
context.records.each_with_index do |record, index|
|
|
56
|
+
table.step(record) { |id, message| found << [id, record, index + 1, message] }
|
|
57
|
+
end
|
|
58
|
+
found
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def findings(context, id)
|
|
62
|
+
walk(context).filter_map do |rule_id, record, index, message|
|
|
63
|
+
next unless rule_id == id
|
|
64
|
+
|
|
65
|
+
{ message: message, offset: record.offset, record_index: index, record_type: record.type_id.to_i }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The conceptual WMF Object Table of MS-WMF 3.1.4.1 plus the SaveDC depth.
|
|
70
|
+
class Table
|
|
71
|
+
def initialize(capacity)
|
|
72
|
+
@capacity = capacity
|
|
73
|
+
@slots = []
|
|
74
|
+
@next_free = 0
|
|
75
|
+
@saved = 0
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def step(record, &)
|
|
79
|
+
kind = CREATORS.find { |klass, _| record.is_a?(klass) }&.last
|
|
80
|
+
return create(kind, &) if kind
|
|
81
|
+
|
|
82
|
+
case record
|
|
83
|
+
when R::SelectObject then select_object(record, &)
|
|
84
|
+
when R::DeleteObject then delete_object(record, &)
|
|
85
|
+
when R::SelectPalette then expect(record.object_index, :palette, "palette", "wmf.object.palette_reference", &)
|
|
86
|
+
when R::FillRegion, R::FrameRegion then fill_region(record, &)
|
|
87
|
+
when *REGION_USERS then expect(record.object_index, :region, "region", "wmf.object.region_reference", &)
|
|
88
|
+
when R::SaveDc, R::RestoreDc then device_context(record, &)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def live(index) = index.negative? ? nil : @slots[index]
|
|
95
|
+
|
|
96
|
+
def create(kind)
|
|
97
|
+
@next_free += 1 while @next_free < @capacity && @slots[@next_free]
|
|
98
|
+
if @next_free < @capacity
|
|
99
|
+
@slots[@next_free] = kind
|
|
100
|
+
@next_free += 1
|
|
101
|
+
else
|
|
102
|
+
yield "wmf.object.capacity", "#{kind} object has no free WMF Object Table index; " \
|
|
103
|
+
"NumberOfObjects is #{@capacity} and every index is in use"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def select_object(record)
|
|
108
|
+
index = record.object_index.to_i
|
|
109
|
+
return if index.anybits?(STOCK_FLAG) # stock objects live outside the metafile table
|
|
110
|
+
return if live(index)
|
|
111
|
+
|
|
112
|
+
yield "wmf.object.select_reference", "SelectObject index #{index} names no live object in the WMF Object Table"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def delete_object(record)
|
|
116
|
+
index = record.object_index.to_i
|
|
117
|
+
if index.anybits?(STOCK_FLAG)
|
|
118
|
+
yield "wmf.object.delete_reference", "DeleteObject index 0x#{index.to_s(16).upcase} is a stock object, which cannot be deleted"
|
|
119
|
+
elsif live(index)
|
|
120
|
+
@slots[index] = nil
|
|
121
|
+
@next_free = index if index < @next_free
|
|
122
|
+
else
|
|
123
|
+
yield "wmf.object.delete_reference", "DeleteObject index #{index} names no live object in the WMF Object Table"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def fill_region(record, &)
|
|
128
|
+
expect(record.region_index, :region, "region", "wmf.object.region_reference", &)
|
|
129
|
+
expect(record.brush_index, :brush, "brush", "wmf.object.brush_reference", &)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def expect(index, kind, label, id)
|
|
133
|
+
index = index.to_i
|
|
134
|
+
found = live(index)
|
|
135
|
+
if found.nil?
|
|
136
|
+
yield id, "#{label} index #{index} names no live object in the WMF Object Table"
|
|
137
|
+
elsif found != kind
|
|
138
|
+
yield id, "#{label} index #{index} refers to a #{found} object, MUST refer to a #{kind} object"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def device_context(record, &)
|
|
143
|
+
return @saved += 1 if record.is_a?(R::SaveDc)
|
|
144
|
+
|
|
145
|
+
restore(record.saved_dc.to_i, &)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def restore(saved_dc)
|
|
149
|
+
depth = if saved_dc.negative? then @saved + saved_dc
|
|
150
|
+
elsif saved_dc.positive? && saved_dc <= @saved then saved_dc - 1
|
|
151
|
+
end
|
|
152
|
+
if depth&.>=(0)
|
|
153
|
+
@saved = depth
|
|
154
|
+
else
|
|
155
|
+
yield "wmf.dc.restore_depth", "RestoreDc nSavedDC #{saved_dc} names no saved device context (#{@saved} saved)"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
registry = Registry.loading
|
|
162
|
+
WmfObjectRules::RULES.each do |id, (citation, reason, kind)|
|
|
163
|
+
registry.register(id: id, formats: WmfObjectRules::WMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
164
|
+
WmfObjectRules.findings(context, id)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WMF fixed-field, enum and point-array rules over typed public records
|
|
4
|
+
# (MS-WMF 2.3.3 drawing, 2.3.4 object, 2.3.5 state records). Records the model
|
|
5
|
+
# keeps opaque (UnknownRecord) are never judged here.
|
|
6
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
7
|
+
|
|
8
|
+
module Emf
|
|
9
|
+
module Conformance
|
|
10
|
+
module WmfRecordRules
|
|
11
|
+
WMF = %i[wmf].freeze
|
|
12
|
+
R = Emf::Model::Wmf::Records
|
|
13
|
+
IGNORED = "GDI ignores an out-of-range value instead of rejecting the record"
|
|
14
|
+
|
|
15
|
+
# id => [record class, field, valid values (Range/Array), citation, name of the enumeration]
|
|
16
|
+
ENUMS = {
|
|
17
|
+
"wmf.enum.bk_mode" => [R::SetBkMode, :mode, [1, 2], "MS-WMF 2.1.1.20 MixMode Enumeration; " \
|
|
18
|
+
"2.3.5.15 META_SETBKMODE (BkMode)", "MixMode"],
|
|
19
|
+
"wmf.enum.map_mode" => [R::SetMapMode, :mode, (1..8), "MS-WMF 2.1.1.16 MapMode Enumeration; " \
|
|
20
|
+
"2.3.5.17 META_SETMAPMODE (MapMode)", "MapMode"],
|
|
21
|
+
"wmf.enum.poly_fill_mode" => [R::SetPolyFillMode, :mode, [1, 2], "MS-WMF 2.1.1.25 PolyFillMode Enumeration; " \
|
|
22
|
+
"2.3.5.20 META_SETPOLYFILLMODE (PolyFillMode)",
|
|
23
|
+
"PolyFillMode"],
|
|
24
|
+
"wmf.enum.rop2" => [R::SetRop2, :mode, (1..16), "MS-WMF 2.1.1.2 BinaryRasterOperation Enumeration; " \
|
|
25
|
+
"2.3.5.22 META_SETROP2 (DrawMode)", "BinaryRasterOperation"],
|
|
26
|
+
"wmf.enum.stretch_blt_mode" => [R::SetStretchBltMode, :mode, (1..4), "MS-WMF 2.1.1.30 StretchMode Enumeration; " \
|
|
27
|
+
"2.3.5.23 META_SETSTRETCHBLTMODE (StretchMode)",
|
|
28
|
+
"StretchMode"],
|
|
29
|
+
"wmf.enum.ext_flood_fill_mode" => [R::ExtFloodFill, :mode, [0, 1], "MS-WMF 2.1.1.9 FloodFill Enumeration; " \
|
|
30
|
+
"2.3.3.4 META_EXTFLOODFILL (Mode)", "FloodFill"],
|
|
31
|
+
"wmf.enum.brush_style" => [R::CreateBrushIndirect, :brush_style, (0..9), "MS-WMF 2.1.1.4 BrushStyle Enumeration; " \
|
|
32
|
+
"2.3.4.1 META_CREATEBRUSHINDIRECT, " \
|
|
33
|
+
"2.2.2.10 LogBrush Object (BrushStyle)",
|
|
34
|
+
"BrushStyle"]
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
BS_PATTERN = 3
|
|
38
|
+
COLOR_USAGE = [R::DibCreatePatternBrush, R::SetDibToDev, R::StretchDib].freeze
|
|
39
|
+
TEXT_ALIGN_MASK = 0x011F
|
|
40
|
+
HORIZONTAL_ALIGN = [0x0, 0x2, 0x6].freeze # TA_LEFT, TA_RIGHT, TA_CENTER (bit 0x4 alone is no value)
|
|
41
|
+
VERTICAL_ALIGN = [0x0, 0x8, 0x18].freeze # TA_TOP, TA_BOTTOM, TA_BASELINE (bit 0x10 alone is no value)
|
|
42
|
+
LAYOUT_MASK = 0x0009 # LAYOUT_RTL | LAYOUT_BITMAPORIENTATIONPRESERVED; LAYOUT_LTR is 0
|
|
43
|
+
PEN_CAPS = [0x0000, 0x0100, 0x0200].freeze
|
|
44
|
+
PEN_JOINS = [0x0000, 0x1000, 0x2000].freeze
|
|
45
|
+
|
|
46
|
+
module_function
|
|
47
|
+
|
|
48
|
+
def enum(context, klass, field, domain, name)
|
|
49
|
+
each_of(context, klass) do |record|
|
|
50
|
+
value = record.public_send(field)
|
|
51
|
+
next if domain.include?(value)
|
|
52
|
+
|
|
53
|
+
"#{klass.name.split('::').last} #{field} is #{value.inspect}, not a #{name} value"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def color_usage(context)
|
|
58
|
+
each_of(context, *COLOR_USAGE) do |record|
|
|
59
|
+
value = record.color_usage
|
|
60
|
+
# META_DIBCREATEPATTERNBRUSH: with Style BS_PATTERN, DIB_RGB_COLORS is used regardless of this field.
|
|
61
|
+
next if record.is_a?(R::DibCreatePatternBrush) && record.style == BS_PATTERN
|
|
62
|
+
next if [0, 1, 2].include?(value)
|
|
63
|
+
|
|
64
|
+
"color usage is #{value.inspect}, MUST be DIB_RGB_COLORS, DIB_PAL_COLORS or DIB_PAL_INDICES"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def hatch_style(context)
|
|
69
|
+
each_of(context, R::CreateBrushIndirect) do |record|
|
|
70
|
+
next unless record.brush_style == 2 && !(0..5).cover?(record.brush_hatch)
|
|
71
|
+
|
|
72
|
+
"BS_HATCHED brush has HatchStyle #{record.brush_hatch.inspect}, MUST be 0..5"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def pen_style(context)
|
|
77
|
+
each_of(context, R::CreatePenIndirect) do |record|
|
|
78
|
+
style = record.pen_style.to_i
|
|
79
|
+
next if (style & 0xF) <= 8 && PEN_CAPS.include?(style & 0x0F00) && PEN_JOINS.include?(style & 0xF000)
|
|
80
|
+
|
|
81
|
+
format("pen style 0x%04X is not a PenStyle style, end-cap and join combination", style)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def text_align(context)
|
|
86
|
+
each_of(context, R::SetTextAlign) do |record|
|
|
87
|
+
value = record.alignment.to_i
|
|
88
|
+
if value.anybits?(~TEXT_ALIGN_MASK)
|
|
89
|
+
format("text alignment 0x%04X sets bits outside the TextAlignmentMode flags", value)
|
|
90
|
+
elsif !HORIZONTAL_ALIGN.include?(value & 0x6)
|
|
91
|
+
"text alignment 0x#{hex(value, 4)} has horizontal bits 0x#{(value & 0x6).to_s(16).upcase}, " \
|
|
92
|
+
"MUST be TA_LEFT, TA_RIGHT or TA_CENTER"
|
|
93
|
+
elsif !VERTICAL_ALIGN.include?(value & 0x18)
|
|
94
|
+
"text alignment 0x#{hex(value, 4)} has vertical bits 0x#{hex(value & 0x18, 2)}, " \
|
|
95
|
+
"MUST be TA_TOP, TA_BOTTOM or TA_BASELINE"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def hex(value, width) = value.to_s(16).upcase.rjust(width, "0")
|
|
101
|
+
|
|
102
|
+
def layout(context)
|
|
103
|
+
each_of(context, R::SetLayout) do |record|
|
|
104
|
+
next if record.layout.to_i.nobits?(~LAYOUT_MASK)
|
|
105
|
+
|
|
106
|
+
format("layout 0x%04X sets bits outside the Layout flags", record.layout.to_i)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def point_count(context)
|
|
111
|
+
each_of(context, R::Polygon, R::Polyline) do |record|
|
|
112
|
+
count = record.points.size
|
|
113
|
+
if count > 0x7FFF
|
|
114
|
+
"#{count} points do not fit the signed 16-bit NumberOfPoints"
|
|
115
|
+
elsif record.is_a?(R::Polygon) && count == 1
|
|
116
|
+
"polygon has #{count} point(s), MUST have at least 2"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# NumberOfPoints 0 is the one count below 2 the parser keeps (Wmf::Recovery); it draws nothing.
|
|
122
|
+
def polygon_empty(context)
|
|
123
|
+
each_of(context, R::Polygon) { |record| "polygon has no points, MUST have at least 2" if record.points.empty? }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def polypolygon_counts(context)
|
|
127
|
+
each_of(context, R::PolyPolygon) do |record|
|
|
128
|
+
counts = record.points_per_polygon
|
|
129
|
+
if counts.size > 0xFFFF
|
|
130
|
+
"#{counts.size} polygons do not fit the unsigned 16-bit NumberOfPolygons"
|
|
131
|
+
elsif counts.sum != record.points.size
|
|
132
|
+
"PolyPolygon counts add up to #{counts.sum} points but the record holds #{record.points.size}"
|
|
133
|
+
elsif counts.any? { |count| count > 0xFFFF }
|
|
134
|
+
"a PolyPolygon polygon count exceeds the unsigned 16-bit range"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def point_range(context)
|
|
140
|
+
each_of(context, R::Polygon, R::Polyline, R::PolyPolygon) do |record|
|
|
141
|
+
bad = record.points.find { |point| !(-0x8000..0x7FFF).cover?(point.x) || !(-0x8000..0x7FFF).cover?(point.y) }
|
|
142
|
+
"point (#{bad.x}, #{bad.y}) is outside the signed 16-bit coordinate range" if bad
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def each_of(context, *classes)
|
|
147
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
148
|
+
next unless classes.any? { |klass| record.is_a?(klass) }
|
|
149
|
+
|
|
150
|
+
message = yield record
|
|
151
|
+
next unless message
|
|
152
|
+
|
|
153
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
registry = Registry.loading
|
|
159
|
+
WmfRecordRules::ENUMS.each do |id, (klass, field, domain, citation, name)|
|
|
160
|
+
registry.register(id: id, formats: WmfRecordRules::WMF, citation: citation, kind: :enum_domain,
|
|
161
|
+
tolerant_reason: WmfRecordRules::IGNORED) do |context|
|
|
162
|
+
WmfRecordRules.enum(context, klass, field, domain, name)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
[
|
|
166
|
+
["wmf.enum.color_usage", :color_usage,
|
|
167
|
+
"MS-WMF 2.1.1.6 ColorUsage Enumeration; " \
|
|
168
|
+
"2.3.4.8 META_DIBCREATEPATTERNBRUSH, 2.3.1.4 META_SETDIBTODEV, 2.3.1.6 META_STRETCHDIB (ColorUsage)",
|
|
169
|
+
:enum_domain, WmfRecordRules::IGNORED],
|
|
170
|
+
["wmf.enum.hatch_style", :hatch_style, "MS-WMF 2.1.1.12 HatchStyle Enumeration; " \
|
|
171
|
+
"2.2.2.10 LogBrush Object (BrushHatch), 2.3.4.1 META_CREATEBRUSHINDIRECT", :enum_domain, WmfRecordRules::IGNORED],
|
|
172
|
+
["wmf.enum.pen_style", :pen_style, "MS-WMF 2.1.1.23 PenStyle Enumeration; " \
|
|
173
|
+
"2.2.1.4 Pen Object (PenStyle), 2.3.4.5 META_CREATEPENINDIRECT", :enum_domain, WmfRecordRules::IGNORED],
|
|
174
|
+
["wmf.flags.text_align", :text_align, "MS-WMF 2.1.2.3 TextAlignmentMode Flags, 2.1.2.4 VerticalTextAlignmentMode Flags; " \
|
|
175
|
+
"2.3.5.24 META_SETTEXTALIGN (TextAlignmentMode)", :flag_mask, WmfRecordRules::IGNORED],
|
|
176
|
+
["wmf.flags.layout", :layout, "MS-WMF 2.1.1.13 Layout Enumeration; " \
|
|
177
|
+
"2.3.5.16 META_SETLAYOUT (Layout)", :flag_mask, WmfRecordRules::IGNORED],
|
|
178
|
+
["wmf.array.point_count", :point_count, "MS-WMF 2.3.3.14 META_POLYLINE, 2.3.3.15 META_POLYGON (NumberOfPoints)", :point_count, nil],
|
|
179
|
+
["wmf.array.polypolygon_counts", :polypolygon_counts, "MS-WMF 2.2.2.17 PolyPolygon Object (NumberOfPolygons, aPointsPerPolygon); " \
|
|
180
|
+
"2.3.3.16 META_POLYPOLYGON",
|
|
181
|
+
:array_counts, nil],
|
|
182
|
+
["wmf.array.polygon_empty", :polygon_empty, "MS-WMF 2.3.3.15 META_POLYGON (NumberOfPoints)", :point_count,
|
|
183
|
+
"PowerPoint writes a polygon with no points (Apache POI empty-polygon-close.wmf); readers draw nothing and import the file"],
|
|
184
|
+
["wmf.fixed.point_range", :point_range, "MS-WMF 2.2.2.16 PointS Object", :coordinate_range, nil]
|
|
185
|
+
].each do |id, method, citation, kind, reason|
|
|
186
|
+
registry.register(id: id, formats: WmfRecordRules::WMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
187
|
+
WmfRecordRules.public_send(method, context)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WMF rejected-input diagnostics and declared-size rules.
|
|
4
|
+
#
|
|
5
|
+
# Rejections: a WMF the parser refuses never becomes a Metafile, so the lens context is :unparseable and holds the
|
|
6
|
+
# exception. Its neutral Emf::ParseEvidence (check, offset, declared, actual) names the reason; each reason with a
|
|
7
|
+
# `wmf.reject.rNNNN` row becomes one finding citing that row. Rejection is a no-model outcome, so strict and
|
|
8
|
+
# tolerant both report an error (no tolerant_reason), like emf.parse.unparseable. Reasons that cannot surface
|
|
9
|
+
# are left to emf.parse.unparseable: 101 and 401 need a 200 MiB or million-record input, and 301-303 are shadowed by
|
|
10
|
+
# the format detector, which turns such inputs into :unrecognised_signature before any WMF check runs. 305 (header
|
|
11
|
+
# Size) and 1801 (MaxRecord) are not rejections: the parser recovers, keeps the model and records the drift in
|
|
12
|
+
# Metafile#errors, so the declared-size rules below (wmf.header.size_matches_records, wmf.header.max_record) report
|
|
13
|
+
# it, as a warning in the tolerant profile. The same holds for 203 (reported by wmf.placeable.checksum), 406, 701
|
|
14
|
+
# (a font short of its face name) and 801 (an object record with surplus bytes), reported by wmf.record.declared_size,
|
|
15
|
+
# 1003 (wmf.text.negative_length), and 1702 and the clip/paint/invert part of 1704 (the wmf.object.*_reference rules).
|
|
16
|
+
# 701, 801 and 1704 stay rejections for the shapes that cannot be recovered.
|
|
17
|
+
#
|
|
18
|
+
# Declared size: on an accepted model each record carries the rdSize read from the source file
|
|
19
|
+
# (Record#declared_size_words, nil for records built in memory). The rules compare it with the layout and with the
|
|
20
|
+
# header's MaxRecord and Size.
|
|
21
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
22
|
+
|
|
23
|
+
module Emf
|
|
24
|
+
module Conformance
|
|
25
|
+
module WmfRejectionRules
|
|
26
|
+
WMF = %i[wmf unparseable].freeze
|
|
27
|
+
SHADOWED = [101, 401, 301, 302, 303].freeze
|
|
28
|
+
FUNCTION_OFFSET = 4
|
|
29
|
+
FIRST_RECORD_REASON = 402
|
|
30
|
+
|
|
31
|
+
# evidence check => reason code, for every reason that can produce a named finding
|
|
32
|
+
CHECKS = Emf::Wmf::Evidence::REASONS.except(*SHADOWED, *Emf::Wmf::Evidence::DRIFT_REASONS,
|
|
33
|
+
*Emf::Wmf::Evidence::RETIRED_REASONS).to_h do |reason, (check, _text)|
|
|
34
|
+
[check, reason]
|
|
35
|
+
end.freeze
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
def id(reason) = format("wmf.reject.r%04d", reason)
|
|
40
|
+
|
|
41
|
+
# The reason code a parse failure names, or nil when the failure is not a rejection with a wmf.reject row.
|
|
42
|
+
def reason_for(failure)
|
|
43
|
+
CHECKS[failure&.evidence&.check]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def findings(context, reason)
|
|
47
|
+
failure = context.parse_failure
|
|
48
|
+
return [] unless reason_for(failure) == reason
|
|
49
|
+
|
|
50
|
+
evidence = failure.evidence
|
|
51
|
+
[{ message: message(reason, evidence), offset: evidence.offset, record_type: function(context.bytes, evidence, reason) }]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def message(reason, evidence)
|
|
55
|
+
text = Emf::Wmf::Evidence::REASONS.fetch(reason).last.sub("%d") { evidence.declared.to_s }
|
|
56
|
+
values = { declared: evidence.declared, actual: evidence.actual }.compact.map { |name, value| "#{name} #{value}" }
|
|
57
|
+
"#{text} at offset #{evidence.offset}#{" (#{values.join(', ')})" unless values.empty?}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Record function word of the failing record, for record-level reasons whose bytes are available.
|
|
61
|
+
def function(bytes, evidence, reason)
|
|
62
|
+
return if reason < FIRST_RECORD_REASON || bytes.nil? || evidence.offset.nil?
|
|
63
|
+
return if evidence.offset + FUNCTION_OFFSET + 2 > bytes.bytesize
|
|
64
|
+
|
|
65
|
+
bytes.unpack1("v", offset: evidence.offset + FUNCTION_OFFSET)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
module WmfDeclaredSizeRules
|
|
70
|
+
WMF = %i[wmf].freeze
|
|
71
|
+
R = Emf::Model::Wmf::Records
|
|
72
|
+
B = Emf::Wmf::Binary
|
|
73
|
+
MIN_WORDS = 3
|
|
74
|
+
HEADER_WORDS = 9
|
|
75
|
+
SIZE_RECOVERY = "the parser recovers by framing the record with its RecordSize: surplus bytes after an EOF or an " \
|
|
76
|
+
"object payload are ignored and a font face name shorter than 32 bytes is zero-padded; the " \
|
|
77
|
+
"departure is recorded in Metafile#errors (seen in LibreOffice test files)"
|
|
78
|
+
HEADER_DRIFT = "the parser recovered: the model is built from the records actually present and the header drift " \
|
|
79
|
+
"is recorded in Metafile#errors (seen in files from Inkscape and ImageMagick)"
|
|
80
|
+
|
|
81
|
+
module_function
|
|
82
|
+
|
|
83
|
+
# [exact?, words] the record layout requires, or nil when the layout leaves the size open.
|
|
84
|
+
def layout(record)
|
|
85
|
+
case record
|
|
86
|
+
when R::Eof then [true, MIN_WORDS]
|
|
87
|
+
when R::CreateFontIndirect, *B::STATE_RECORDS.values then [false, MIN_WORDS + (record.class::PAYLOAD_BYTES / 2)]
|
|
88
|
+
when *B::FIXED_RECORDS.values then [true, MIN_WORDS + record.class::WIRE_FIELDS.length]
|
|
89
|
+
when *B::OBJECT_RECORDS.values then [true, MIN_WORDS + (record.class::PAYLOAD_BYTES / 2)]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def declared_size(context)
|
|
94
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
95
|
+
words = record.declared_size_words
|
|
96
|
+
next unless words
|
|
97
|
+
|
|
98
|
+
problem = size_problem(record, words)
|
|
99
|
+
at(record, index, problem) if problem
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def size_problem(record, words)
|
|
104
|
+
return "RecordSize is #{words} words, a record is at least #{MIN_WORDS} words" if words < MIN_WORDS
|
|
105
|
+
|
|
106
|
+
exact, expected = layout(record)
|
|
107
|
+
return unless expected
|
|
108
|
+
|
|
109
|
+
name = record.class.name.split("::").last
|
|
110
|
+
return "RecordSize is #{words} words, the #{name} layout is #{expected} words" if exact && words != expected
|
|
111
|
+
return unless !exact && words < expected
|
|
112
|
+
|
|
113
|
+
"RecordSize is #{words} words, the #{name} layout needs at least #{expected} words"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def buffer_declared_size(context)
|
|
117
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
118
|
+
words = record.declared_size_words
|
|
119
|
+
encoded = words && buffer_words(record)
|
|
120
|
+
next unless encoded && encoded != words
|
|
121
|
+
|
|
122
|
+
at(record, index, "RecordSize is #{words} words but the #{record.class.name.split('::').last} buffers occupy #{encoded} words")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def buffer_words(record)
|
|
127
|
+
return unless B.prefix_codec(record) || record.is_a?(R::TextOut)
|
|
128
|
+
|
|
129
|
+
Emf::Wmf::Serializer.serialize_record(record).bytesize / 2
|
|
130
|
+
rescue Emf::SerializeError
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def max_record(context)
|
|
135
|
+
sizes = context.records.map(&:declared_size_words)
|
|
136
|
+
return [] if sizes.empty? || sizes.any?(&:nil?)
|
|
137
|
+
|
|
138
|
+
largest = sizes.max
|
|
139
|
+
declared = context.header.max_record_words.to_i
|
|
140
|
+
return [] if declared == largest
|
|
141
|
+
|
|
142
|
+
[{ message: "WMF header MaxRecord is #{declared} words, the largest record is #{largest} words",
|
|
143
|
+
offset: context.header.placeable ? 34 : 12 }]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def size_matches_records(context)
|
|
147
|
+
sizes = context.records.map(&:declared_size_words)
|
|
148
|
+
return [] if sizes.empty? || sizes.any?(&:nil?)
|
|
149
|
+
|
|
150
|
+
total = HEADER_WORDS + sizes.sum
|
|
151
|
+
declared = context.header.size_words.to_i
|
|
152
|
+
return [] if declared == total
|
|
153
|
+
|
|
154
|
+
[{ message: "WMF header Size is #{declared} words, the header and the records add up to #{total} words",
|
|
155
|
+
offset: context.header.placeable ? 28 : 6 }]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def at(record, index, message)
|
|
159
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
registry = Registry.loading
|
|
164
|
+
manifest = Manifest.rows.to_h { |row| [row.id, row.citation] }
|
|
165
|
+
WmfRejectionRules::CHECKS.each do |check, reason|
|
|
166
|
+
id = WmfRejectionRules.id(reason)
|
|
167
|
+
registry.register(id: id, formats: WmfRejectionRules::WMF, citation: manifest.fetch(id), kind: check) do |context|
|
|
168
|
+
WmfRejectionRules.findings(context, reason)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
[
|
|
173
|
+
["wmf.record.declared_size", :declared_size, :declared_size, WmfDeclaredSizeRules::SIZE_RECOVERY],
|
|
174
|
+
["wmf.buffer.declared_size", :buffer_declared_size, :buffer_declared_size],
|
|
175
|
+
["wmf.header.max_record", :max_record, :max_record, WmfDeclaredSizeRules::HEADER_DRIFT],
|
|
176
|
+
["wmf.header.size_matches_records", :size_matches_records, :size_matches_records, WmfDeclaredSizeRules::HEADER_DRIFT]
|
|
177
|
+
].each do |id, method, kind, tolerant_reason|
|
|
178
|
+
registry.register(id: id, formats: WmfDeclaredSizeRules::WMF, citation: manifest.fetch(id), kind: kind,
|
|
179
|
+
tolerant_reason: tolerant_reason) do |context|
|
|
180
|
+
WmfDeclaredSizeRules.public_send(method, context)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Emf
|
|
6
|
+
module Conformance
|
|
7
|
+
# Parses the input through the normal entry point and applies every rule
|
|
8
|
+
# that matches the metafile's format.
|
|
9
|
+
class Runner
|
|
10
|
+
def initialize(profile, registry)
|
|
11
|
+
@profile = profile
|
|
12
|
+
@registry = registry
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(input, now:)
|
|
16
|
+
filename, bytes, metafile = load(input)
|
|
17
|
+
context = build_context(metafile, bytes)
|
|
18
|
+
Report.new(filename: filename, profile: @profile.name, timestamp: now, diagnostics: diagnostics(context))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def load(input)
|
|
24
|
+
case input
|
|
25
|
+
when Model::Metafile then [nil, nil, input]
|
|
26
|
+
when Pathname then [input.to_s, File.binread(input), nil]
|
|
27
|
+
when String then path?(input) ? [input, File.binread(input), nil] : [nil, input.b, nil]
|
|
28
|
+
else raise ArgumentError, "expected a String, Pathname or Emf::Model::Metafile, got #{input.class}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def path?(input)
|
|
33
|
+
input.encoding.ascii_compatible? && !input.include?("\0") && input.bytesize < 4096 && File.file?(input)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_context(metafile, bytes)
|
|
37
|
+
return Context.new(metafile: metafile, bytes: bytes) if metafile
|
|
38
|
+
|
|
39
|
+
Context.new(metafile: Emf.parse(bytes), bytes: bytes)
|
|
40
|
+
rescue Emf::Native::Unavailable
|
|
41
|
+
raise
|
|
42
|
+
rescue Emf::Error => e
|
|
43
|
+
Context.new(bytes: bytes, parse_failure: e)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def diagnostics(context)
|
|
47
|
+
found = @registry.rules_for(context.format).flat_map do |rule|
|
|
48
|
+
rule.findings(context).map { |finding| diagnostic(rule, finding) }
|
|
49
|
+
end
|
|
50
|
+
found.each_with_index.sort_by { |d, index| [d.offset || -1, d.requirement_id, index] }.map(&:first)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def diagnostic(rule, finding)
|
|
54
|
+
Diagnostic.new(requirement_id: rule.id, spec_citation: rule.citation, severity: @profile.severity_for(rule),
|
|
55
|
+
kind: rule.kind, format: rule.formats.first, offset: finding[:offset], record_index: finding[:record_index],
|
|
56
|
+
record_type: finding[:record_type], message: finding.fetch(:message),
|
|
57
|
+
waiver: @profile.waiver_for(rule))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Conformance
|
|
5
|
+
# The TernaryRasterOperation values of MS-WMF 2.1.1.31, keyed by their operation index (bits 16..23).
|
|
6
|
+
# Extracted from the specification's per-operation "Reverse Polish" entries (all 256), normalising the text's
|
|
7
|
+
# formatting slips: DPSOAN is printed with nine digits (000570389) and SPDNOX with seven (0390604).
|
|
8
|
+
module TernaryRasterOperations
|
|
9
|
+
# rubocop:disable-next Metrics/CollectionLiteralLength -- the specification's table; kept in Ruby so the gem ships it
|
|
10
|
+
VALUES = [
|
|
11
|
+
0x0000_0042, 0x0001_0289, 0x0002_0C89, 0x0003_00AA, 0x0004_0C88, 0x0005_00A9, 0x0006_0865, 0x0007_02C5,
|
|
12
|
+
0x0008_0F08, 0x0009_0245, 0x000A_0329, 0x000B_0B2A, 0x000C_0324, 0x000D_0B25, 0x000E_08A5, 0x000F_0001,
|
|
13
|
+
0x0010_0C85, 0x0011_00A6, 0x0012_0868, 0x0013_02C8, 0x0014_0869, 0x0015_02C9, 0x0016_5CCA, 0x0017_1D54,
|
|
14
|
+
0x0018_0D59, 0x0019_1CC8, 0x001A_06C5, 0x001B_0768, 0x001C_06CA, 0x001D_0766, 0x001E_01A5, 0x001F_0385,
|
|
15
|
+
0x0020_0F09, 0x0021_0248, 0x0022_0326, 0x0023_0B24, 0x0024_0D55, 0x0025_1CC5, 0x0026_06C8, 0x0027_1868,
|
|
16
|
+
0x0028_0369, 0x0029_16CA, 0x002A_0CC9, 0x002B_1D58, 0x002C_0784, 0x002D_060A, 0x002E_064A, 0x002F_0E2A,
|
|
17
|
+
0x0030_032A, 0x0031_0B28, 0x0032_0688, 0x0033_0008, 0x0034_06C4, 0x0035_1864, 0x0036_01A8, 0x0037_0388,
|
|
18
|
+
0x0038_078A, 0x0039_0604, 0x003A_0644, 0x003B_0E24, 0x003C_004A, 0x003D_18A4, 0x003E_1B24, 0x003F_00EA,
|
|
19
|
+
0x0040_0F0A, 0x0041_0249, 0x0042_0D5D, 0x0043_1CC4, 0x0044_0328, 0x0045_0B29, 0x0046_06C6, 0x0047_076A,
|
|
20
|
+
0x0048_0368, 0x0049_16C5, 0x004A_0789, 0x004B_0605, 0x004C_0CC8, 0x004D_1954, 0x004E_0645, 0x004F_0E25,
|
|
21
|
+
0x0050_0325, 0x0051_0B26, 0x0052_06C9, 0x0053_0764, 0x0054_08A9, 0x0055_0009, 0x0056_01A9, 0x0057_0389,
|
|
22
|
+
0x0058_0785, 0x0059_0609, 0x005A_0049, 0x005B_18A9, 0x005C_0649, 0x005D_0E29, 0x005E_1B29, 0x005F_00E9,
|
|
23
|
+
0x0060_0365, 0x0061_16C6, 0x0062_0786, 0x0063_0608, 0x0064_0788, 0x0065_0606, 0x0066_0046, 0x0067_18A8,
|
|
24
|
+
0x0068_58A6, 0x0069_0145, 0x006A_01E9, 0x006B_178A, 0x006C_01E8, 0x006D_1785, 0x006E_1E28, 0x006F_0C65,
|
|
25
|
+
0x0070_0CC5, 0x0071_1D5C, 0x0072_0648, 0x0073_0E28, 0x0074_0646, 0x0075_0E26, 0x0076_1B28, 0x0077_00E6,
|
|
26
|
+
0x0078_01E5, 0x0079_1786, 0x007A_1E29, 0x007B_0C68, 0x007C_1E24, 0x007D_0C69, 0x007E_0955, 0x007F_03C9,
|
|
27
|
+
0x0080_03E9, 0x0081_0975, 0x0082_0C49, 0x0083_1E04, 0x0084_0C48, 0x0085_1E05, 0x0086_17A6, 0x0087_01C5,
|
|
28
|
+
0x0088_00C6, 0x0089_1B08, 0x008A_0E06, 0x008B_0666, 0x008C_0E08, 0x008D_0668, 0x008E_1D7C, 0x008F_0CE5,
|
|
29
|
+
0x0090_0C45, 0x0091_1E08, 0x0092_17A9, 0x0093_01C4, 0x0094_17AA, 0x0095_01C9, 0x0096_0169, 0x0097_588A,
|
|
30
|
+
0x0098_1888, 0x0099_0066, 0x009A_0709, 0x009B_07A8, 0x009C_0704, 0x009D_07A6, 0x009E_16E6, 0x009F_0345,
|
|
31
|
+
0x00A0_00C9, 0x00A1_1B05, 0x00A2_0E09, 0x00A3_0669, 0x00A4_1885, 0x00A5_0065, 0x00A6_0706, 0x00A7_07A5,
|
|
32
|
+
0x00A8_03A9, 0x00A9_0189, 0x00AA_0029, 0x00AB_0889, 0x00AC_0744, 0x00AD_06E9, 0x00AE_0B06, 0x00AF_0229,
|
|
33
|
+
0x00B0_0E05, 0x00B1_0665, 0x00B2_1974, 0x00B3_0CE8, 0x00B4_070A, 0x00B5_07A9, 0x00B6_16E9, 0x00B7_0348,
|
|
34
|
+
0x00B8_074A, 0x00B9_06E6, 0x00BA_0B09, 0x00BB_0226, 0x00BC_1CE4, 0x00BD_0D7D, 0x00BE_0269, 0x00BF_08C9,
|
|
35
|
+
0x00C0_00CA, 0x00C1_1B04, 0x00C2_1884, 0x00C3_006A, 0x00C4_0E04, 0x00C5_0664, 0x00C6_0708, 0x00C7_07AA,
|
|
36
|
+
0x00C8_03A8, 0x00C9_0184, 0x00CA_0749, 0x00CB_06E4, 0x00CC_0020, 0x00CD_0888, 0x00CE_0B08, 0x00CF_0224,
|
|
37
|
+
0x00D0_0E0A, 0x00D1_066A, 0x00D2_0705, 0x00D3_07A4, 0x00D4_1D78, 0x00D5_0CE9, 0x00D6_16EA, 0x00D7_0349,
|
|
38
|
+
0x00D8_0745, 0x00D9_06E8, 0x00DA_1CE9, 0x00DB_0D75, 0x00DC_0B04, 0x00DD_0228, 0x00DE_0268, 0x00DF_08C8,
|
|
39
|
+
0x00E0_03A5, 0x00E1_0185, 0x00E2_0746, 0x00E3_06EA, 0x00E4_0748, 0x00E5_06E5, 0x00E6_1CE8, 0x00E7_0D79,
|
|
40
|
+
0x00E8_1D74, 0x00E9_5CE6, 0x00EA_02E9, 0x00EB_0849, 0x00EC_02E8, 0x00ED_0848, 0x00EE_0086, 0x00EF_0A08,
|
|
41
|
+
0x00F0_0021, 0x00F1_0885, 0x00F2_0B05, 0x00F3_022A, 0x00F4_0B0A, 0x00F5_0225, 0x00F6_0265, 0x00F7_08C5,
|
|
42
|
+
0x00F8_02E5, 0x00F9_0845, 0x00FA_0089, 0x00FB_0A09, 0x00FC_008A, 0x00FD_0A0A, 0x00FE_02A9, 0x00FF_0062
|
|
43
|
+
].freeze
|
|
44
|
+
BY_INDEX = VALUES.to_h { |value| [(value >> 16) & 0xFF, value] }.freeze
|
|
45
|
+
|
|
46
|
+
module_function
|
|
47
|
+
|
|
48
|
+
# The specified value for this operation index.
|
|
49
|
+
def expected(value) = BY_INDEX[(value >> 16) & 0xFF]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|