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,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# COMMENT dispatch, Escape, OpenGL and the opaque record types' outer size and count
|
|
4
|
+
# bounds (MS-EMF 2.3.3, 2.3.6, 2.3.9 and the record sections cited per rule). Payload
|
|
5
|
+
# semantics stay out: only offsets, sizes and counts are read from the record bytes.
|
|
6
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
7
|
+
|
|
8
|
+
module Emf
|
|
9
|
+
module Conformance
|
|
10
|
+
module CommentEscapeRules
|
|
11
|
+
EMF = %i[emf emf_plus].freeze
|
|
12
|
+
COMMENT = 70
|
|
13
|
+
PUBLIC = 0x43494447
|
|
14
|
+
SPOOL = 0
|
|
15
|
+
FORMAT_SIGNATURES = [0x464D4520, 0x46535045].freeze
|
|
16
|
+
EPS_SIGNATURE = 0x46535045
|
|
17
|
+
COLOR_SPACE_TYPES = [0, 0x73524742, 0x57696E20].freeze
|
|
18
|
+
GAMUT_INTENTS = [1, 2, 4, 8].freeze
|
|
19
|
+
PUBLIC_IDS = { 2 => :begin_group, 3 => :end_group, 0x40000004 => :multiformats, 0x80000001 => :windows_metafile }.freeze
|
|
20
|
+
|
|
21
|
+
# type => [name, minimum size, exact size or nil, [[count field offsets...], per-count bytes (0 = bytes), variable start]]
|
|
22
|
+
# One diagnostic per defect (W37): types whose size or counts another rule states more precisely are not listed here.
|
|
23
|
+
# EMR_SETCOLORADJUSTMENT emf.state.fixed_size (decoded), emf.record.fixed_size_raw (Raw)
|
|
24
|
+
# EMR_SETCOLORSPACE, _COLORCORRECTPALETTE emf.object.fixed_size
|
|
25
|
+
# EMR_POLYTEXTOUTA / W emf.opaque.text_arrays (fixed part and descriptor bounds)
|
|
26
|
+
OPAQUE = {
|
|
27
|
+
99 => ["EMR_CREATECOLORSPACE", 340, false, nil],
|
|
28
|
+
101 => ["EMR_DELETECOLORSPACE", 12, false, nil],
|
|
29
|
+
104 => ["EMR_PIXELFORMAT", 48, false, nil],
|
|
30
|
+
109 => ["EMR_FORCEUFIMAPPING", 16, false, nil],
|
|
31
|
+
112 => ["EMR_SETICMPROFILEA", 20, false, [[12, 16], 1]],
|
|
32
|
+
113 => ["EMR_SETICMPROFILEW", 20, false, [[12, 16], 1]],
|
|
33
|
+
119 => ["EMR_SETLINKEDUFIS", 20, false, [[8], 8]],
|
|
34
|
+
121 => ["EMR_COLORMATCHTOTARGETW", 24, false, [[16, 20], 1]],
|
|
35
|
+
122 => ["EMR_CREATECOLORSPACEW", 608, false, [[604], 1]]
|
|
36
|
+
}.freeze
|
|
37
|
+
# Types whose minimum size and counts a decoded record gets from the more specific emf.state.profile_bounds and
|
|
38
|
+
# emf.state.linked_ufis_bounds (the same constraint, same threshold). A Raw record is only seen here.
|
|
39
|
+
STATE_BOUNDS = [112, 113, 119, 121].freeze
|
|
40
|
+
ESCAPES = { 105 => ["EMR_DRAWESCAPE", 16], 106 => ["EMR_EXTESCAPE", 16], 110 => ["EMR_NAMEDESCAPE", 20] }.freeze
|
|
41
|
+
OPENGL = { 102 => ["EMR_GLSRECORD", 12], 103 => ["EMR_GLSBOUNDEDRECORD", 28] }.freeze
|
|
42
|
+
|
|
43
|
+
PARSER_RECOVERS = "the parser keeps a malformed record's bytes and skips it by Size, so the stream stays readable"
|
|
44
|
+
# id => [citation, kind, severity, tolerant_reason]
|
|
45
|
+
RULES = {
|
|
46
|
+
"emf.comment.padding" => ["MS-EMF 2.3.3", :comment_padding, :error, nil],
|
|
47
|
+
"emf.comment.header_size" => ["MS-EMF 2.3.3.4", :comment_header_size, :error, nil],
|
|
48
|
+
"emf.comment.public_payload" => ["MS-EMF 2.3.3.4", :comment_public_payload, :error, nil],
|
|
49
|
+
"emf.comment.public_identifier" => ["MS-EMF 2.3.3.4", :comment_public_identifier, :warning, nil],
|
|
50
|
+
"emf.comment.windows_metafile_reserved" => ["MS-EMF 2.3.3.4.4", :comment_wmf_reserved, :warning, nil],
|
|
51
|
+
"emf.comment.group_unclosed" => ["MS-EMF 2.3.3.4.1", :comment_group_unclosed, :error,
|
|
52
|
+
"producers leave groups open at the end of the file and readers only skip the group markers"],
|
|
53
|
+
"emf.comment.group_end" => ["MS-EMF 2.3.3.4.2", :comment_group_end, :error, nil],
|
|
54
|
+
"emf.comment.group_description" => ["MS-EMF 2.3.3.4.1", :comment_group_description, :warning, nil],
|
|
55
|
+
"emf.comment.multiformats_signature" => ["MS-EMF 2.2.4", :comment_multiformats_signature, :error,
|
|
56
|
+
"Quartz-style producers embed PDF data (signature \"PDF \", test-186) in MULTIFORMATS"],
|
|
57
|
+
"emf.comment.multiformats_eps_version" => ["MS-EMF 2.2.4", :comment_multiformats_eps_version, :error, nil],
|
|
58
|
+
"emf.escape.record_size" => ["MS-EMF 2.3.6", :escape_size, :error, PARSER_RECOVERS],
|
|
59
|
+
"emf.escape.payload_bounds" => ["MS-EMF 2.3.6", :escape_payload, :error, nil],
|
|
60
|
+
"emf.escape.driver_name" => ["MS-EMF 2.3.6.3", :escape_driver_name, :error, nil],
|
|
61
|
+
"emf.opengl.record_size" => ["MS-EMF 2.3.9", :opengl_size, :error, PARSER_RECOVERS],
|
|
62
|
+
"emf.opengl.payload_bounds" => ["MS-EMF 2.3.9", :opengl_payload, :error, nil],
|
|
63
|
+
"emf.opaque.fixed_size" => ["MS-EMF 2.3", :opaque_size, :error, PARSER_RECOVERS],
|
|
64
|
+
"emf.opaque.payload_bounds" => ["MS-EMF 2.3", :opaque_payload, :error, nil],
|
|
65
|
+
"emf.opaque.colorspace_fields" => ["MS-WMF 2.2.2.11", :colorspace_fields, :error, nil]
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
CACHE = ObjectSpace::WeakMap.new
|
|
69
|
+
|
|
70
|
+
module_function
|
|
71
|
+
|
|
72
|
+
def findings(context, id)
|
|
73
|
+
# The WeakMap holds its values weakly: keep a strong reference until the lookup is done.
|
|
74
|
+
grouped = CACHE[context] || (CACHE[context] = analyze(context))
|
|
75
|
+
grouped.fetch(id, [])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def analyze(context)
|
|
79
|
+
out = Hash.new { |hash, id| hash[id] = [] }
|
|
80
|
+
open_groups = []
|
|
81
|
+
context.records.each_with_index do |record, index|
|
|
82
|
+
type = context.record_type(record)
|
|
83
|
+
scan = Scan.new(record, index, context.record_size(record), out)
|
|
84
|
+
if type == COMMENT then scan.comment(open_groups)
|
|
85
|
+
elsif ESCAPES.key?(type) then scan.escape(*ESCAPES.fetch(type), type)
|
|
86
|
+
elsif OPENGL.key?(type) then scan.opengl(*OPENGL.fetch(type))
|
|
87
|
+
elsif OPAQUE.key?(type) && !state_bounded?(record, type) then scan.opaque(*OPAQUE.fetch(type))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
open_groups.each { |group| group.add("emf.comment.group_unclosed", "EMR_COMMENT_BEGINGROUP has no matching ENDGROUP") }
|
|
91
|
+
out
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def state_bounded?(record, type)
|
|
95
|
+
STATE_BOUNDS.include?(type) && record.wire.is_a?(Emf::Emr::Binary::Record)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Checks of the EMR_COMMENT_PUBLIC sub-records (MS-EMF 2.3.3.4.x); mixed into Scan.
|
|
99
|
+
module PublicChecks
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def public_comment(data_size, open_groups)
|
|
103
|
+
return add("emf.comment.header_size", "public comment DataSize #{data_size} cannot hold PublicCommentIdentifier") if data_size < 8
|
|
104
|
+
|
|
105
|
+
kind = PUBLIC_IDS[u32(16)]
|
|
106
|
+
unless kind
|
|
107
|
+
return add("emf.comment.public_identifier",
|
|
108
|
+
format("public comment identifier 0x%08X is not in the EmrComment enumeration", u32(16)))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
send(:"public_#{kind}", data_size, open_groups)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def public_begin_group(data_size, open_groups)
|
|
115
|
+
return add("emf.comment.header_size", "BEGINGROUP DataSize #{data_size} is smaller than its 28-byte fixed part") if data_size < 28
|
|
116
|
+
|
|
117
|
+
open_groups << self
|
|
118
|
+
unless (u32(36) * 2) <= data_size - 28
|
|
119
|
+
return add("emf.comment.public_payload",
|
|
120
|
+
"BEGINGROUP description of #{u32(36)} characters runs past DataSize")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
group_description(data_size)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The description is a null-terminated string; nDescription may or may not count the terminator.
|
|
127
|
+
def group_description(data_size)
|
|
128
|
+
chars = u32(36)
|
|
129
|
+
return if chars.zero?
|
|
130
|
+
|
|
131
|
+
text = @bytes.byteslice(40, [(chars + 1) * 2, data_size - 28].min)
|
|
132
|
+
return if text.unpack("v*").first(chars + 1).include?(0)
|
|
133
|
+
|
|
134
|
+
add("emf.comment.group_description", "BEGINGROUP description of #{chars} characters is not null-terminated")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def public_end_group(_data_size, open_groups)
|
|
138
|
+
return add("emf.comment.group_end", "EMR_COMMENT_ENDGROUP has no matching BEGINGROUP") if open_groups.empty?
|
|
139
|
+
|
|
140
|
+
open_groups.pop
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def public_multiformats(data_size, _open_groups)
|
|
144
|
+
if data_size < 28
|
|
145
|
+
return add("emf.comment.header_size",
|
|
146
|
+
"MULTIFORMATS DataSize #{data_size} is smaller than its 28-byte fixed part")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
count = u32(36)
|
|
150
|
+
if count * 16 > data_size - 28
|
|
151
|
+
return add("emf.comment.public_payload",
|
|
152
|
+
"MULTIFORMATS CountFormats #{count} needs #{count * 16} bytes, #{data_size - 28} remain")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
count.times do |i|
|
|
156
|
+
format_identity(i)
|
|
157
|
+
size = u32(48 + (i * 16))
|
|
158
|
+
offset = u32(52 + (i * 16))
|
|
159
|
+
if offset % 4 != 0 || offset + size > data_size
|
|
160
|
+
add("emf.comment.public_payload",
|
|
161
|
+
"MULTIFORMATS format #{i} data #{offset}+#{size} is misaligned or past DataSize")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def format_identity(index)
|
|
167
|
+
signature = u32(40 + (index * 16))
|
|
168
|
+
unless FORMAT_SIGNATURES.include?(signature)
|
|
169
|
+
return add("emf.comment.multiformats_signature",
|
|
170
|
+
"MULTIFORMATS format #{index} Signature #{hex(signature)} is not in the FormatSignature enumeration")
|
|
171
|
+
end
|
|
172
|
+
return unless signature == EPS_SIGNATURE && u32(44 + (index * 16)) != 1
|
|
173
|
+
|
|
174
|
+
add("emf.comment.multiformats_eps_version",
|
|
175
|
+
"MULTIFORMATS format #{index} is EPS but its Version is #{u32(44 + (index * 16))}, MUST be 1")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def public_windows_metafile(data_size, _open_groups)
|
|
179
|
+
if data_size < 24
|
|
180
|
+
return add("emf.comment.header_size",
|
|
181
|
+
"WINDOWS_METAFILE DataSize #{data_size} is smaller than its 24-byte fixed part")
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
reserved = @bytes.unpack1("v", offset: 22)
|
|
185
|
+
flags = u32(28)
|
|
186
|
+
unless reserved.zero? && flags.zero?
|
|
187
|
+
add("emf.comment.windows_metafile_reserved",
|
|
188
|
+
"Reserved #{reserved} and Flags #{flags} MUST be 0")
|
|
189
|
+
end
|
|
190
|
+
return unless u32(32) > data_size - 24
|
|
191
|
+
|
|
192
|
+
add("emf.comment.public_payload",
|
|
193
|
+
"WinMetafileSize #{u32(32)} exceeds the #{data_size - 24} bytes DataSize leaves")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# One record's checks; every method adds to `out[rule id]`.
|
|
198
|
+
class Scan
|
|
199
|
+
include PublicChecks
|
|
200
|
+
|
|
201
|
+
def initialize(record, index, size, out)
|
|
202
|
+
@record = record
|
|
203
|
+
@index = index
|
|
204
|
+
@size = size
|
|
205
|
+
@out = out
|
|
206
|
+
@bytes = record.to_binary_s
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def add(id, message)
|
|
210
|
+
@out[id] << { message: message, offset: @record.offset, record_index: @index + 1, record_type: @record.type_id.to_i }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def comment(open_groups)
|
|
214
|
+
return if @size < 12 || @bytes.bytesize < 12
|
|
215
|
+
|
|
216
|
+
data_size = u32(8)
|
|
217
|
+
return if data_size > @size - 12
|
|
218
|
+
|
|
219
|
+
pad = @size - 12 - data_size
|
|
220
|
+
if pad > 3
|
|
221
|
+
add("emf.comment.padding",
|
|
222
|
+
"comment has #{pad} bytes after DataSize bytes of data, at most 3 padding bytes are allowed")
|
|
223
|
+
end
|
|
224
|
+
return if data_size < 4
|
|
225
|
+
|
|
226
|
+
case u32(12)
|
|
227
|
+
when PUBLIC then public_comment(data_size, open_groups)
|
|
228
|
+
when SPOOL then if data_size < 8
|
|
229
|
+
add("emf.comment.header_size",
|
|
230
|
+
"EMFSPOOL comment DataSize #{data_size} cannot hold its record identifier")
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def escape(name, fixed, type)
|
|
236
|
+
return add("emf.escape.record_size", "#{name} Size #{@size} is smaller than its #{fixed}-byte fixed part") if @size < fixed
|
|
237
|
+
|
|
238
|
+
if type == 110
|
|
239
|
+
named_escape(name)
|
|
240
|
+
elsif u32(12) > @size - fixed
|
|
241
|
+
add("emf.escape.payload_bounds", "#{name} cjIn #{u32(12)} exceeds the #{@size - fixed} bytes the record holds")
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def opengl(name, fixed)
|
|
246
|
+
return add("emf.opengl.record_size", "#{name} Size #{@size} is smaller than its #{fixed}-byte fixed part") if @size < fixed
|
|
247
|
+
|
|
248
|
+
count = u32(fixed - 4)
|
|
249
|
+
return unless count > @size - fixed
|
|
250
|
+
|
|
251
|
+
add("emf.opengl.payload_bounds",
|
|
252
|
+
"#{name} cbData #{count} exceeds the #{@size - fixed} bytes the record holds")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def opaque(name, minimum, exact, counted)
|
|
256
|
+
if @size < minimum || (exact && @size != minimum)
|
|
257
|
+
return add("emf.opaque.fixed_size", "#{name} Size #{@size} is not #{'at least ' unless exact}#{minimum}")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
colorspace_fields(name) if [99, 122].include?(@record.type_id.to_i)
|
|
261
|
+
return unless counted
|
|
262
|
+
|
|
263
|
+
fields, unit = counted
|
|
264
|
+
total = fields.sum { |pos| u32(pos) } * unit
|
|
265
|
+
return unless total > @size - minimum
|
|
266
|
+
|
|
267
|
+
add("emf.opaque.payload_bounds",
|
|
268
|
+
"#{name} counts need #{total} bytes, the record holds #{@size - minimum}")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
private
|
|
272
|
+
|
|
273
|
+
def u32(pos) = @bytes.unpack1("V", offset: pos)
|
|
274
|
+
|
|
275
|
+
def hex(value) = format("0x%<value>08X", value: value)
|
|
276
|
+
|
|
277
|
+
# The embedded LogColorSpace / LogColorSpaceW starts after ihCS (MS-WMF 2.2.2.11, 2.2.2.12).
|
|
278
|
+
def colorspace_fields(name)
|
|
279
|
+
return if @bytes.bytesize < 32
|
|
280
|
+
|
|
281
|
+
{
|
|
282
|
+
"Signature" => [u32(12) == 0x50534F43, hex(u32(12)), "0x50534F43"],
|
|
283
|
+
"Version" => [u32(16) == 0x400, hex(u32(16)), "0x00000400"],
|
|
284
|
+
"ColorSpaceType" => [COLOR_SPACE_TYPES.include?(u32(24)), hex(u32(24)), "a LogicalColorSpace value"],
|
|
285
|
+
"Intent" => [GAMUT_INTENTS.include?(u32(28)), u32(28).to_s, "a GamutMappingIntent value"]
|
|
286
|
+
}.each do |field, (ok, got, want)|
|
|
287
|
+
add("emf.opaque.colorspace_fields", "#{name} LogColorSpace #{field} #{got} MUST be #{want}") unless ok
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def named_escape(name)
|
|
292
|
+
driver = u32(12)
|
|
293
|
+
data = u32(16)
|
|
294
|
+
add("emf.escape.driver_name", "#{name} cjDriver #{driver} MUST be an even number") if driver.odd?
|
|
295
|
+
if driver + data > @size - 20
|
|
296
|
+
add("emf.escape.payload_bounds", "#{name} cjDriver #{driver} + cjIn #{data} exceed the #{@size - 20} bytes the record holds")
|
|
297
|
+
elsif driver < 2 || @bytes.byteslice(20 + driver - 2, 2) != "\0\0".b
|
|
298
|
+
add("emf.escape.driver_name", "#{name} DriverName is not null-terminated within cjDriver #{driver}")
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
CommentEscapeRules::RULES.each do |id, (citation, kind, severity, reason)|
|
|
305
|
+
Registry.loading.register(id: id, formats: CommentEscapeRules::EMF, citation: citation, kind: kind, severity: severity,
|
|
306
|
+
tolerant_reason: reason) { |context| CommentEscapeRules.findings(context, id) }
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# EMR_HEADER description / pixel-format buffers and the EMR_EOF palette
|
|
4
|
+
# (MS-EMF 2.2.9, 2.2.10, 2.2.22, 2.3.4.1, 2.3.4.2). Reads the buffers through
|
|
5
|
+
# the public accessors: header.description_bytes / pixel_format_bytes and the
|
|
6
|
+
# EOF wire's palette_bytes.
|
|
7
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
8
|
+
|
|
9
|
+
module Emf
|
|
10
|
+
module Conformance
|
|
11
|
+
module ControlBufferRules
|
|
12
|
+
EMF = %i[emf emf_plus].freeze
|
|
13
|
+
EOF_FIXED = 16 # Type, Size, nPalEntries, offPalEntries; the palette starts right after (GDI writes offset 16)
|
|
14
|
+
PFD_SIZE = 40
|
|
15
|
+
PFD_DOUBLEBUFFER = 0x1
|
|
16
|
+
PFD_SUPPORT_GDI = 0x10
|
|
17
|
+
DRIFT = "the descriptive header buffers are advisory; renderers do not read them"
|
|
18
|
+
PALETTE_DRIFT = "the palette is read from the offset EOF declares, so a mismatched count does not stop rendering"
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def header_finding(message) = { message: message, offset: 0, record_index: 0, record_type: RecordTypes::HEADER }
|
|
23
|
+
|
|
24
|
+
# EmfDescription is a null-terminated UTF-16LE string of nDescription characters.
|
|
25
|
+
def description_terminator(context)
|
|
26
|
+
bytes = context.header.description_bytes
|
|
27
|
+
return [] if bytes.nil? || bytes.byteslice(-2, 2) == "\0\0".b
|
|
28
|
+
|
|
29
|
+
[header_finding("description (#{context.header.n_description} UTF-16 chars) is not null-terminated")]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Each buffer must lie at or after the fixed part and the two must not overlap.
|
|
33
|
+
def buffers_overlap(context)
|
|
34
|
+
header = context.header
|
|
35
|
+
return [] unless pixel_format_fields?(header)
|
|
36
|
+
|
|
37
|
+
ranges = { "description" => range(header.off_description, header.n_description.to_i * 2),
|
|
38
|
+
"pixel format" => range(header.off_pixel_format, header.cb_pixel_format) }.compact
|
|
39
|
+
return [] unless ranges.size == 2
|
|
40
|
+
|
|
41
|
+
(first, a), (second, b) = ranges.to_a
|
|
42
|
+
return [] unless a.begin < b.end && b.begin < a.end
|
|
43
|
+
|
|
44
|
+
[header_finding("#{first} (#{a}) overlaps #{second} (#{b})")]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pixel_format_size(context)
|
|
48
|
+
header = context.header
|
|
49
|
+
return [] unless pixel_format_fields?(header)
|
|
50
|
+
|
|
51
|
+
size = header.cb_pixel_format.to_i
|
|
52
|
+
return [] if size.zero? || header.off_pixel_format.to_i.zero? || size == PFD_SIZE
|
|
53
|
+
|
|
54
|
+
[header_finding("cbPixelFormat is #{size}, a PixelFormatDescriptor is #{PFD_SIZE} bytes")]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def pixel_format_content(context)
|
|
58
|
+
return [] unless pixel_format_fields?(context.header)
|
|
59
|
+
|
|
60
|
+
bytes = context.header.pixel_format_bytes
|
|
61
|
+
return [] if bytes.nil? || bytes.bytesize != PFD_SIZE
|
|
62
|
+
|
|
63
|
+
size, version, flags, type = bytes.unpack("vvVC")
|
|
64
|
+
problems = []
|
|
65
|
+
problems << "PixelFormatDescriptor nSize is #{size}, MUST describe its #{PFD_SIZE} bytes" unless size == PFD_SIZE
|
|
66
|
+
problems << "PixelFormatDescriptor nVersion is #{version}, MUST be 1" unless version == 1
|
|
67
|
+
problems << format("PixelFormatDescriptor iPixelType is %d, MUST be 0 (RGBA) or 1 (color index)", type) if type > 1
|
|
68
|
+
both = PFD_DOUBLEBUFFER | PFD_SUPPORT_GDI
|
|
69
|
+
problems << "PixelFormatDescriptor sets both PFD_DOUBLEBUFFER and PFD_SUPPORT_GDI" if flags & both == both
|
|
70
|
+
problems.map { |message| header_finding(message) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# nPalEntries in the header counts the palette that the EOF record carries.
|
|
74
|
+
def palette_count(context)
|
|
75
|
+
eof = eof_record(context)
|
|
76
|
+
return [] unless eof
|
|
77
|
+
|
|
78
|
+
declared = context.header.n_pal_entries.to_i
|
|
79
|
+
actual = eof.wire.n_pal_entries.to_i
|
|
80
|
+
return [] if declared == actual
|
|
81
|
+
|
|
82
|
+
[at(eof, context, "header nPalEntries is #{declared} but EMR_EOF carries #{actual} palette entries")]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The nPalEntries LogPaletteEntry objects (4 bytes each) must lie inside the
|
|
86
|
+
# record, after the fixed part and before SizeLast.
|
|
87
|
+
def palette_bounds(context)
|
|
88
|
+
eof = eof_record(context)
|
|
89
|
+
return [] unless eof
|
|
90
|
+
|
|
91
|
+
count = eof.wire.n_pal_entries.to_i
|
|
92
|
+
return [] if count.zero?
|
|
93
|
+
|
|
94
|
+
offset = eof.wire.off_pal_entries.to_i
|
|
95
|
+
size = context.record_size(eof)
|
|
96
|
+
return [at(eof, context, "palette offset #{offset} is inside the #{EOF_FIXED}-byte fixed part of EMR_EOF")] if offset < EOF_FIXED
|
|
97
|
+
|
|
98
|
+
return [] if offset + (4 * count) <= size - 4 && eof.wire.palette_bytes
|
|
99
|
+
|
|
100
|
+
[at(eof, context, "palette (#{count} entries at offset #{offset}) does not fit before SizeLast in a #{size}-byte EMR_EOF")]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# cbPixelFormat/offPixelFormat exist only in a HeaderExtension1 or later header.
|
|
104
|
+
def pixel_format_fields?(header) = HeaderRules.extension1?(header)
|
|
105
|
+
|
|
106
|
+
def range(offset, length)
|
|
107
|
+
offset = offset.to_i
|
|
108
|
+
length = length.to_i
|
|
109
|
+
offset...(offset + length) if offset.positive? && length.positive?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def eof_record(context)
|
|
113
|
+
last = context.records.last
|
|
114
|
+
last if last && context.record_type(last) == RecordTypes::EOF_RECORD
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def at(record, context, message)
|
|
118
|
+
{ message: message, offset: record.offset, record_index: context.records.size, record_type: RecordTypes::EOF_RECORD }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
registry = Registry.loading
|
|
123
|
+
{
|
|
124
|
+
"emf.header.description_terminator" => [:description_terminator, "MS-EMF 2.3.4.2", :description_terminator,
|
|
125
|
+
ControlBufferRules::DRIFT],
|
|
126
|
+
"emf.header.buffers_overlap" => [:buffers_overlap, "MS-EMF 2.3.4.2", :buffers_overlap, ControlBufferRules::DRIFT],
|
|
127
|
+
"emf.header.pixel_format_size" => [:pixel_format_size, "MS-EMF 2.2.10", :pixel_format_size, ControlBufferRules::DRIFT],
|
|
128
|
+
"emf.header.pixel_format_content" => [:pixel_format_content, "MS-EMF 2.2.22", :pixel_format_content, ControlBufferRules::DRIFT],
|
|
129
|
+
"emf.eof.palette_entries" => [:palette_count, "MS-EMF 2.2.9", :palette_count, ControlBufferRules::PALETTE_DRIFT],
|
|
130
|
+
"emf.eof.palette_bounds" => [:palette_bounds, "MS-EMF 2.3.4.1", :palette_bounds, ControlBufferRules::PALETTE_DRIFT]
|
|
131
|
+
}.each do |id, (method, citation, kind, reason)|
|
|
132
|
+
registry.register(id: id, formats: ControlBufferRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
133
|
+
ControlBufferRules.public_send(method, context)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Rules that name two corruptions in spec/fixtures/emf-corrupted whose records the parser keeps as
|
|
4
|
+
# Raw (a Size below the record's fixed layout), which the decoded-wire rules of the state and text
|
|
5
|
+
# families skip:
|
|
6
|
+
# bad_corrupted_2014-12-13-132605.emf EMR_SETICMMODE with Size 8 (stated 12)
|
|
7
|
+
# bad_corrupted_2017-05-12-074000.emf EMR_EXTTEXTOUTW with Size 12 (EmrText needs 24 bytes)
|
|
8
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
9
|
+
|
|
10
|
+
module Emf
|
|
11
|
+
module Conformance
|
|
12
|
+
module CorpusNamedRules
|
|
13
|
+
EMF = %i[emf emf_plus].freeze
|
|
14
|
+
RECOVERED = "the parser recovered and kept the bytes as a raw record, and readers advance by the declared Size"
|
|
15
|
+
# Type and Size (8) + Bounds (16) + iGraphicsMode, exScale, eyScale (12).
|
|
16
|
+
EXT_TEXT_AT = 36
|
|
17
|
+
# EmrText without the optional Rectangle: Reference, Chars, offString, Options, offDx.
|
|
18
|
+
EMR_TEXT_MIN = 24
|
|
19
|
+
EXT_TEXT_TYPES = { 83 => "EMR_EXTTEXTOUTA", 84 => "EMR_EXTTEXTOUTW" }.freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def raw?(record) = !record.wire.is_a?(Emf::Emr::Binary::Record)
|
|
24
|
+
|
|
25
|
+
def at(record, index, message)
|
|
26
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# type => [required Size, exact?]. A decodable state record is checked by emf.state.fixed_size and
|
|
30
|
+
# emf.state.layout_size; only a Raw one escapes them.
|
|
31
|
+
def size_table
|
|
32
|
+
table = StateRules::STATED_SIZE.transform_values { |size| [size, true] }
|
|
33
|
+
StateRules::LAYOUT_SIZE.each { |type, size| table[type] ||= [size, false] }
|
|
34
|
+
table
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def raw_fixed_size(context)
|
|
38
|
+
table = size_table
|
|
39
|
+
found = []
|
|
40
|
+
context.records.each_with_index do |record, index|
|
|
41
|
+
type = context.record_type(record)
|
|
42
|
+
required, exact = table[type]
|
|
43
|
+
next unless required && raw?(record)
|
|
44
|
+
|
|
45
|
+
size = context.record_size(record)
|
|
46
|
+
next if exact ? size == required : size >= required
|
|
47
|
+
next if StateRules::SHORT_BY_OPAQUE.include?(type) # a short one is emf.opaque.fixed_size, any record
|
|
48
|
+
|
|
49
|
+
found << at(record, index,
|
|
50
|
+
"#{RecordTypes.name(type)} (#{StateRules::SECTION.fetch(type)}) Size is #{size}, " \
|
|
51
|
+
"#{exact ? 'the record section states' : 'its fixed fields occupy'} #{required}; kept as a raw record")
|
|
52
|
+
end
|
|
53
|
+
found
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def exttextout_fixed_part(context)
|
|
57
|
+
minimum = EXT_TEXT_AT + EMR_TEXT_MIN
|
|
58
|
+
found = []
|
|
59
|
+
context.records.each_with_index do |record, index|
|
|
60
|
+
name = EXT_TEXT_TYPES[context.record_type(record)]
|
|
61
|
+
next unless name && raw?(record)
|
|
62
|
+
|
|
63
|
+
size = context.record_size(record)
|
|
64
|
+
next if size >= minimum
|
|
65
|
+
|
|
66
|
+
found << at(record, index, "#{name} Size #{size} is smaller than its #{minimum}-byte fixed part " \
|
|
67
|
+
"(header, Bounds, iGraphicsMode, scales and an EmrText without Rectangle)")
|
|
68
|
+
end
|
|
69
|
+
found
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
registry = Registry.loading
|
|
74
|
+
registry.register(id: "emf.record.fixed_size_raw", formats: CorpusNamedRules::EMF, citation: "MS-EMF 2.3.11",
|
|
75
|
+
kind: :fixed_size_raw, tolerant_reason: CorpusNamedRules::RECOVERED) do |context|
|
|
76
|
+
CorpusNamedRules.raw_fixed_size(context)
|
|
77
|
+
end
|
|
78
|
+
registry.register(id: "emf.record.exttextout_fixed_part", formats: CorpusNamedRules::EMF, citation: "MS-EMF 2.2.5",
|
|
79
|
+
kind: :exttextout_fixed_part, tolerant_reason: CorpusNamedRules::RECOVERED) do |context|
|
|
80
|
+
CorpusNamedRules.exttextout_fixed_part(context)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Record framing, EOF and comment rules (MS-EMF 2.1.1, 2.3, 2.3.3, 2.3.4.1).
|
|
4
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
5
|
+
|
|
6
|
+
module Emf
|
|
7
|
+
module Conformance
|
|
8
|
+
module FramingRules
|
|
9
|
+
EMF = %i[emf emf_plus].freeze
|
|
10
|
+
TOLERATED = "records of unassigned type are skipped by size, so the stream stays readable"
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def alignment(context)
|
|
15
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
16
|
+
size = context.record_size(record)
|
|
17
|
+
next if (size % 4).zero?
|
|
18
|
+
|
|
19
|
+
at(record, index, "record Size #{size} is not a multiple of 4")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def undefined_type(context)
|
|
24
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
25
|
+
type = context.record_type(record)
|
|
26
|
+
next if RecordTypes.defined?(type)
|
|
27
|
+
|
|
28
|
+
at(record, index, format("record type 0x%04X is not in the RecordType enumeration", type))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def eof_missing(context)
|
|
33
|
+
last = context.records.last
|
|
34
|
+
return [] if last && context.record_type(last) == RecordTypes::EOF_RECORD
|
|
35
|
+
|
|
36
|
+
[{ message: "metafile does not end with an EMR_EOF record", offset: last&.offset || context.header.n_size.to_i,
|
|
37
|
+
record_index: context.records.size, record_type: last && context.record_type(last) }]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def eof_size_last(context)
|
|
41
|
+
last = context.records.last
|
|
42
|
+
return [] unless last && context.record_type(last) == RecordTypes::EOF_RECORD
|
|
43
|
+
|
|
44
|
+
bytes = last.to_binary_s
|
|
45
|
+
size = context.record_size(last)
|
|
46
|
+
size_last = bytes.byteslice(-4, 4)&.unpack1("V")
|
|
47
|
+
return [] if size_last == size
|
|
48
|
+
|
|
49
|
+
at(last, context.records.size - 1, "EMR_EOF SizeLast is #{size_last.inspect}, MUST equal Size (#{size})")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def trailing_bytes(context)
|
|
53
|
+
extra = context.metafile.trailing.bytesize
|
|
54
|
+
return [] if extra.zero?
|
|
55
|
+
|
|
56
|
+
[{ message: "#{extra} bytes follow the last record", offset: context.byte_length && (context.byte_length - extra),
|
|
57
|
+
record_index: context.records.size }]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def comment_data_size(context)
|
|
61
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
62
|
+
next unless context.record_type(record) == RecordTypes::COMMENT
|
|
63
|
+
|
|
64
|
+
size = context.record_size(record)
|
|
65
|
+
data_size = record.to_binary_s.unpack1("V", offset: 8)
|
|
66
|
+
next if data_size.nil? || data_size <= size - 12
|
|
67
|
+
|
|
68
|
+
at(record, index, "comment DataSize #{data_size} exceeds the #{[size - 12, 0].max} bytes the record holds after DataSize")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def at(record, index, message)
|
|
73
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
registry = Registry.loading
|
|
78
|
+
{
|
|
79
|
+
"emf.record.size_alignment" => [:alignment, "MS-EMF 2.3", :size_alignment,
|
|
80
|
+
"readers advance by the declared Size, so a misaligned record still delimits the stream"],
|
|
81
|
+
"emf.record.undefined_type" => [:undefined_type, "MS-EMF 2.1.1", :undefined_type, FramingRules::TOLERATED],
|
|
82
|
+
"emf.eof.missing" => [:eof_missing, "MS-EMF 2.3.4.1", :eof_missing, "truncated files still render the records that survive"],
|
|
83
|
+
"emf.eof.size_last" => [:eof_size_last, "MS-EMF 2.3.4.1", :eof_size_last,
|
|
84
|
+
"no reader uses SizeLast; some producers write the metafile length there"],
|
|
85
|
+
"emf.eof.trailing_bytes" => [:trailing_bytes, "MS-EMF 2.3.4.1", :trailing_bytes, "bytes after EMR_EOF are ignored by readers"],
|
|
86
|
+
"emf.comment.data_size" => [:comment_data_size, "MS-EMF 2.3.3", :comment_data_size,
|
|
87
|
+
"readers clamp DataSize to the record, so the oversized value is never followed"]
|
|
88
|
+
}.each do |id, (method, citation, kind, reason)|
|
|
89
|
+
registry.register(id: id, formats: FramingRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
90
|
+
FramingRules.public_send(method, context)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|