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,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Drawing geometry rules: point arrays, PolyDraw, GradientFill and region data
|
|
4
|
+
# (MS-EMF 2.3.5, 2.2.7, 2.2.8, 2.2.24-2.2.26, 2.1.15, 2.1.26).
|
|
5
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
6
|
+
|
|
7
|
+
module Emf
|
|
8
|
+
module Conformance
|
|
9
|
+
module GeometryRules
|
|
10
|
+
EMF = %i[emf emf_plus].freeze
|
|
11
|
+
# type => [count field, bytes per point]
|
|
12
|
+
POINT_ARRAYS = { 2 => [:cptl, 8], 3 => [:cptl, 8], 4 => [:cptl, 8], 5 => [:cptl, 8], 6 => [:cptl, 8],
|
|
13
|
+
85 => [:cpts, 4], 86 => [:cpts, 4], 87 => [:cpts, 4], 88 => [:cpts, 4], 89 => [:cpts, 4] }.freeze
|
|
14
|
+
POLY_POLY = { 7 => [:c_ptl_total, 8], 8 => [:c_ptl_total, 8], 90 => [:c_pts_total, 4], 91 => [:c_pts_total, 4] }.freeze
|
|
15
|
+
POLY_DRAW = { 56 => [:cptl, 8], 92 => [:cpts, 4] }.freeze
|
|
16
|
+
POLYLINES = [7, 90].freeze
|
|
17
|
+
BEZIER = 4
|
|
18
|
+
POINT_TYPES = [0x02, 0x03, 0x04, 0x05, 0x06].freeze # LINETO, BEZIERTO and MOVETO, optionally with CLOSEFIGURE (not on MOVETO)
|
|
19
|
+
GRADIENT_FIXED = 36
|
|
20
|
+
GRADIENT_MODES = [0, 1, 2].freeze
|
|
21
|
+
GRADIENT_TRIANGLE = 2
|
|
22
|
+
REGION_RECORDS = { 71 => 32, 72 => 40, 73 => 28, 74 => 28, 75 => 16 }.freeze # fixed part before RgnData
|
|
23
|
+
BEZIER_COUNT = { 0 => "a multiple of 3", 1 => "one more than a multiple of 3" }.freeze
|
|
24
|
+
COUNT_WINS = "readers take Count points and ignore the rest of the record"
|
|
25
|
+
EXTRA_POINTS = "the spec says extra points MUST be ignored, so a reader still draws the curve or polygon"
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
def field(record, name)
|
|
30
|
+
wire = record.wire
|
|
31
|
+
wire.respond_to?(name) ? wire.public_send(name).to_i : nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def at(record, index, message)
|
|
35
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def each_of(context, types)
|
|
39
|
+
context.records.each_with_index do |record, index|
|
|
40
|
+
type = context.record_type(record)
|
|
41
|
+
yield record, index, type if types.include?(type)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def padded(bytes) = (bytes + 3) & ~3
|
|
46
|
+
|
|
47
|
+
def size_finding(context, record, index, type, expected, what)
|
|
48
|
+
size = context.record_size(record)
|
|
49
|
+
return if size == expected
|
|
50
|
+
|
|
51
|
+
at(record, index, "#{RecordTypes.name(type)} #{what}, so Size MUST be #{expected}, not #{size}")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def point_array_size(context)
|
|
55
|
+
found = []
|
|
56
|
+
each_of(context, POINT_ARRAYS.keys) do |record, index, type|
|
|
57
|
+
name, width = POINT_ARRAYS.fetch(type)
|
|
58
|
+
count = field(record, name)
|
|
59
|
+
next if count.nil?
|
|
60
|
+
|
|
61
|
+
found << size_finding(context, record, index, type, 28 + padded(count * width), "holds #{count} points")
|
|
62
|
+
end
|
|
63
|
+
found.compact
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def bezier_count(context)
|
|
67
|
+
found = []
|
|
68
|
+
each_of(context, [2, 85, 5, 88]) do |record, index, type|
|
|
69
|
+
count = field(record, POINT_ARRAYS.fetch(type).first)
|
|
70
|
+
next if count.nil?
|
|
71
|
+
|
|
72
|
+
expected = [2, 85].include?(type) ? 1 : 0
|
|
73
|
+
next if (count % 3) == expected
|
|
74
|
+
|
|
75
|
+
found << at(record, index, "#{RecordTypes.name(type)} Count #{count} MUST be #{BEZIER_COUNT.fetch(expected)}")
|
|
76
|
+
end
|
|
77
|
+
found
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def poly_poly_totals(record, type)
|
|
81
|
+
total_name = POLY_POLY.fetch(type).first
|
|
82
|
+
counts = record.wire.a_poly_counts.to_a.map(&:to_i)
|
|
83
|
+
[counts, field(record, total_name)]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def poly_poly_size(context)
|
|
87
|
+
found = []
|
|
88
|
+
each_of(context, POLY_POLY.keys) do |record, index, type|
|
|
89
|
+
polys = field(record, :n_polys)
|
|
90
|
+
total = field(record, POLY_POLY.fetch(type).first)
|
|
91
|
+
next if polys.nil? || total.nil?
|
|
92
|
+
|
|
93
|
+
found << size_finding(context, record, index, type, 32 + (4 * polys) + padded(total * POLY_POLY.fetch(type).last),
|
|
94
|
+
"holds #{polys} polygons and #{total} points")
|
|
95
|
+
end
|
|
96
|
+
found.compact
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def poly_poly_counts(context)
|
|
100
|
+
found = []
|
|
101
|
+
each_of(context, POLY_POLY.keys) do |record, index, type|
|
|
102
|
+
next unless record.wire.respond_to?(:a_poly_counts)
|
|
103
|
+
|
|
104
|
+
counts, total = poly_poly_totals(record, type)
|
|
105
|
+
sum = counts.sum
|
|
106
|
+
found << at(record, index, "#{RecordTypes.name(type)} per-figure counts sum to #{sum} but Count says #{total}") if sum != total
|
|
107
|
+
short = counts.count { |c| c < 2 }
|
|
108
|
+
if POLYLINES.include?(type) && short.positive?
|
|
109
|
+
found << at(record, index,
|
|
110
|
+
"#{RecordTypes.name(type)} has #{short} polyline(s) with fewer than 2 points, each count MUST be >= 2")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
found
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def poly_draw_size(context)
|
|
117
|
+
found = []
|
|
118
|
+
each_of(context, POLY_DRAW.keys) do |record, index, type|
|
|
119
|
+
name, width = POLY_DRAW.fetch(type)
|
|
120
|
+
count = field(record, name)
|
|
121
|
+
next if count.nil?
|
|
122
|
+
|
|
123
|
+
found << size_finding(context, record, index, type, 28 + padded((count * width) + count), "holds #{count} points and types")
|
|
124
|
+
end
|
|
125
|
+
found.compact
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def bezier_runs(types)
|
|
129
|
+
types.chunk_while { |a, b| (a & ~1) == BEZIER && (b & ~1) == BEZIER }
|
|
130
|
+
.select { |run| (run.first & ~1) == BEZIER }.map(&:size)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def poly_draw_types(context)
|
|
134
|
+
found = []
|
|
135
|
+
each_of(context, POLY_DRAW.keys) do |record, index, type|
|
|
136
|
+
next unless record.wire.respond_to?(:ab_types)
|
|
137
|
+
|
|
138
|
+
types = record.wire.ab_types.to_a.map(&:to_i)
|
|
139
|
+
bad = types.reject { |t| POINT_TYPES.include?(t) }.uniq
|
|
140
|
+
unless bad.empty?
|
|
141
|
+
found << at(record, index, "#{RecordTypes.name(type)} point types #{bad.map do |t|
|
|
142
|
+
format('0x%02X', t)
|
|
143
|
+
end.join(', ')} are not in the Point enumeration")
|
|
144
|
+
end
|
|
145
|
+
odd = bezier_runs(types - bad).reject { |run| (run % 3).zero? }
|
|
146
|
+
unless odd.empty?
|
|
147
|
+
found << at(record, index,
|
|
148
|
+
"#{RecordTypes.name(type)} has PT_BEZIERTO runs of #{odd.join(', ')} points, they MUST come in sets of three")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
found
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# GradientFill: header, nVer TriVertex (16 bytes), then nTri index objects (12 bytes each,
|
|
155
|
+
# a rectangle's 8 bytes plus its mandatory 4 bytes of padding).
|
|
156
|
+
def gradient_fields(record)
|
|
157
|
+
%i[n_ver n_tri ul_mode].map { |name| field(record, name) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def gradient_mode(context)
|
|
161
|
+
found = []
|
|
162
|
+
each_of(context, [118]) do |record, index, _type|
|
|
163
|
+
mode = field(record, :ul_mode)
|
|
164
|
+
next if mode.nil? || GRADIENT_MODES.include?(mode)
|
|
165
|
+
|
|
166
|
+
found << at(record, index, "GRADIENTFILL ulMode #{mode} is not GRADIENT_FILL_RECT_H, RECT_V or TRIANGLE")
|
|
167
|
+
end
|
|
168
|
+
found
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def gradient_size(context)
|
|
172
|
+
found = []
|
|
173
|
+
each_of(context, [118]) do |record, index, type|
|
|
174
|
+
n_ver, n_tri, = gradient_fields(record)
|
|
175
|
+
next if n_ver.nil?
|
|
176
|
+
|
|
177
|
+
found << size_finding(context, record, index, type, GRADIENT_FIXED + (16 * n_ver) + (12 * n_tri),
|
|
178
|
+
"holds #{n_ver} vertexes and #{n_tri} index objects")
|
|
179
|
+
end
|
|
180
|
+
found.compact
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def gradient_index(context)
|
|
184
|
+
found = []
|
|
185
|
+
each_of(context, [118]) do |record, index, _type|
|
|
186
|
+
n_ver, n_tri, mode = gradient_fields(record)
|
|
187
|
+
next if n_ver.nil?
|
|
188
|
+
|
|
189
|
+
bad = out_of_range_indexes(record.wire.body.to_s.b, n_ver, n_tri, mode)
|
|
190
|
+
found << at(record, index, "GRADIENTFILL vertex index #{bad.first} is not below nVer (#{n_ver})") if bad.any?
|
|
191
|
+
end
|
|
192
|
+
found
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def out_of_range_indexes(body, n_ver, n_tri, mode)
|
|
196
|
+
per = mode == GRADIENT_TRIANGLE ? 3 : 2
|
|
197
|
+
start = 16 * n_ver
|
|
198
|
+
available = [(body.bytesize - start) / 12, n_tri].min
|
|
199
|
+
return [] if available <= 0
|
|
200
|
+
|
|
201
|
+
(0...available).flat_map { |i| body.byteslice(start + (12 * i), 4 * per).unpack("V#{per}") }.reject { |v| v < n_ver }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def region_data(record)
|
|
205
|
+
wire = record.wire
|
|
206
|
+
return unless wire.respond_to?(:rgn_data) && field(record, :cb_rgn_data).to_i.positive?
|
|
207
|
+
|
|
208
|
+
wire.rgn_data
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def region_header(context)
|
|
212
|
+
found = []
|
|
213
|
+
each_of(context, REGION_RECORDS.keys) do |record, index, type|
|
|
214
|
+
data = region_data(record)
|
|
215
|
+
next unless data
|
|
216
|
+
|
|
217
|
+
unless data.header_size.to_i == 32
|
|
218
|
+
found << at(record, index,
|
|
219
|
+
"#{RecordTypes.name(type)} RegionDataHeader Size is #{data.header_size.to_i}, MUST be 32")
|
|
220
|
+
end
|
|
221
|
+
unless data.region_type.to_i == 1
|
|
222
|
+
found << at(record, index,
|
|
223
|
+
"#{RecordTypes.name(type)} RegionDataHeader Type is #{data.region_type.to_i}, MUST be 1")
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
found
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def region_rects(context)
|
|
230
|
+
found = []
|
|
231
|
+
each_of(context, REGION_RECORDS.keys) do |record, index, type|
|
|
232
|
+
data = region_data(record)
|
|
233
|
+
next unless data
|
|
234
|
+
|
|
235
|
+
count = data.count_rects.to_i
|
|
236
|
+
size = data.rgn_size.to_i
|
|
237
|
+
cb = field(record, :cb_rgn_data)
|
|
238
|
+
if size != count * 16
|
|
239
|
+
found << at(record, index,
|
|
240
|
+
"#{RecordTypes.name(type)} CountRects #{count} needs #{count * 16} bytes but RgnSize is #{size}")
|
|
241
|
+
end
|
|
242
|
+
if cb != 32 + size
|
|
243
|
+
found << at(record, index,
|
|
244
|
+
"#{RecordTypes.name(type)} RgnDataSize #{cb} is not the 32-byte header plus RgnSize #{size}")
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
found
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def region_size(context)
|
|
251
|
+
found = []
|
|
252
|
+
each_of(context, REGION_RECORDS.keys) do |record, index, type|
|
|
253
|
+
cb = field(record, :cb_rgn_data)
|
|
254
|
+
next if cb.nil?
|
|
255
|
+
|
|
256
|
+
found << size_finding(context, record, index, type, REGION_RECORDS.fetch(type) + cb, "carries #{cb} bytes of region data")
|
|
257
|
+
end
|
|
258
|
+
found.compact
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
registry = Registry.loading
|
|
263
|
+
{
|
|
264
|
+
"emf.geometry.point_array_size" => [:point_array_size, "MS-EMF 2.3.5", :point_array_size, GeometryRules::COUNT_WINS],
|
|
265
|
+
"emf.geometry.bezier_count" => [:bezier_count, "MS-EMF 2.3.5.16", :bezier_count, GeometryRules::EXTRA_POINTS],
|
|
266
|
+
"emf.geometry.poly_poly_size" => [:poly_poly_size, "MS-EMF 2.3.5.28", :poly_poly_size, nil],
|
|
267
|
+
"emf.geometry.poly_poly_counts" => [:poly_poly_counts, "MS-EMF 2.3.5.28", :poly_poly_counts, GeometryRules::EXTRA_POINTS],
|
|
268
|
+
"emf.geometry.poly_draw_size" => [:poly_draw_size, "MS-EMF 2.3.5.20", :poly_draw_size, nil],
|
|
269
|
+
"emf.geometry.poly_draw_types" => [:poly_draw_types, "MS-EMF 2.1.26", :poly_draw_types, nil],
|
|
270
|
+
"emf.geometry.gradient_mode" => [:gradient_mode, "MS-EMF 2.1.15", :gradient_mode, nil],
|
|
271
|
+
"emf.geometry.gradient_size" => [:gradient_size, "MS-EMF 2.3.5.12", :gradient_size, nil],
|
|
272
|
+
"emf.geometry.gradient_index" => [:gradient_index, "MS-EMF 2.2.7", :gradient_index, nil],
|
|
273
|
+
"emf.region.header" => [:region_header, "MS-EMF 2.2.25", :region_header, nil],
|
|
274
|
+
"emf.region.rects" => [:region_rects, "MS-EMF 2.2.25", :region_rects, nil],
|
|
275
|
+
"emf.region.size" => [:region_size, "MS-EMF 2.3.5", :region_size, nil]
|
|
276
|
+
}.each do |id, (method, citation, kind, reason)|
|
|
277
|
+
registry.register(id: id, formats: GeometryRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
278
|
+
GeometryRules.public_send(method, context)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# EMR_HEADER / Header object rules (MS-EMF 2.2.9, 2.3.4.2).
|
|
4
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
5
|
+
|
|
6
|
+
module Emf
|
|
7
|
+
module Conformance
|
|
8
|
+
module HeaderRules
|
|
9
|
+
EMF = %i[emf emf_plus].freeze
|
|
10
|
+
SIGNATURE = 0x464D4520
|
|
11
|
+
FIXED_HEADER_SIZE = 88 # EmfMetafileHeader fixed part (2.3.4.2)
|
|
12
|
+
CREATE_OBJECT_TYPES = [38, 39, 49, 82, 93, 94, 95, 99, 122].freeze
|
|
13
|
+
DRIFT = "the numeric header fields are advisory for renderers; every corpus file with a drifted value still renders"
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def finding(message) = { message: message, offset: 0, record_index: 0, record_type: RecordTypes::HEADER }
|
|
18
|
+
|
|
19
|
+
def signature(context)
|
|
20
|
+
sig = context.header.signature.to_i
|
|
21
|
+
return [] if sig == SIGNATURE
|
|
22
|
+
|
|
23
|
+
[finding(format("RecordSignature is 0x%08X, MUST be ENHMETA_SIGNATURE 0x464D4520", sig))]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def size_bounds(context)
|
|
27
|
+
size = context.header.n_size.to_i
|
|
28
|
+
problems = []
|
|
29
|
+
problems << "header Size #{size} is below the #{FIXED_HEADER_SIZE}-byte fixed part" if size < FIXED_HEADER_SIZE
|
|
30
|
+
problems << "header Size #{size} is not a multiple of 4" unless (size % 4).zero?
|
|
31
|
+
problems.map { |message| finding(message) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def bytes_mismatch(context)
|
|
35
|
+
actual = context.byte_length
|
|
36
|
+
declared = context.header.n_bytes.to_i
|
|
37
|
+
return [] if actual.nil? || declared == actual
|
|
38
|
+
|
|
39
|
+
[finding("header Bytes is #{declared} but the metafile is #{actual} bytes")]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def records_mismatch(context)
|
|
43
|
+
declared = context.header.n_records.to_i
|
|
44
|
+
return [] if declared == context.record_count
|
|
45
|
+
|
|
46
|
+
[finding("header Records is #{declared} but the metafile holds #{context.record_count} records")]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def handle_count(context)
|
|
50
|
+
handles = context.header.n_handles.to_i
|
|
51
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
52
|
+
type = context.record_type(record)
|
|
53
|
+
next unless CREATE_OBJECT_TYPES.include?(type)
|
|
54
|
+
|
|
55
|
+
handle = record.to_binary_s.unpack1("V", offset: 8)
|
|
56
|
+
next if handle.nil? || handle < handles
|
|
57
|
+
|
|
58
|
+
{ message: "object handle index #{handle} created at record #{index + 1} is not below header Handles (#{handles})",
|
|
59
|
+
offset: record.offset, record_index: index + 1, record_type: type }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def description_bounds(context)
|
|
64
|
+
header = context.header
|
|
65
|
+
chars = header.n_description.to_i
|
|
66
|
+
return [] if chars.zero? && header.off_description.to_i.zero?
|
|
67
|
+
|
|
68
|
+
offset = header.off_description.to_i
|
|
69
|
+
problems = []
|
|
70
|
+
problems << "description offset #{offset} is inside the #{FIXED_HEADER_SIZE}-byte fixed part" if offset < FIXED_HEADER_SIZE
|
|
71
|
+
if offset + (2 * chars) > header.n_size.to_i
|
|
72
|
+
problems << "description (#{chars} UTF-16 chars at offset #{offset}) ends past header Size #{header.n_size}"
|
|
73
|
+
end
|
|
74
|
+
problems.map { |message| finding(message) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# HeaderExtension1 (and with it the pixel format fields) exists only when
|
|
78
|
+
# the variable-length data starts at byte 100 or later (MS-EMF 2.3.4.2,
|
|
79
|
+
# HeaderSize rule); a description at byte 88 means the original 88-byte header.
|
|
80
|
+
def extension1?(header)
|
|
81
|
+
return header.n_size.to_i >= 100 if header.n_description.to_i.zero?
|
|
82
|
+
|
|
83
|
+
header.off_description.to_i >= 100
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def pixel_format_bounds(context)
|
|
87
|
+
header = context.header
|
|
88
|
+
return [] unless extension1?(header)
|
|
89
|
+
|
|
90
|
+
size = header.cb_pixel_format.to_i
|
|
91
|
+
offset = header.off_pixel_format.to_i
|
|
92
|
+
return [] if size.zero? && offset.zero?
|
|
93
|
+
return [] if offset >= FIXED_HEADER_SIZE && offset + size <= header.n_size.to_i
|
|
94
|
+
|
|
95
|
+
[finding("pixel format (#{size} bytes at offset #{offset}) lies outside the header record (Size #{header.n_size})")]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
registry = Registry.loading
|
|
100
|
+
{
|
|
101
|
+
"emf.header.signature" => [:signature, "MS-EMF 2.2.9", :signature, nil],
|
|
102
|
+
"emf.header.size_bounds" => [:size_bounds, "MS-EMF 2.3.4.2", :header_size, nil],
|
|
103
|
+
"emf.header.bytes" => [:bytes_mismatch, "MS-EMF 2.2.9", :bytes_mismatch, HeaderRules::DRIFT],
|
|
104
|
+
"emf.header.records" => [:records_mismatch, "MS-EMF 2.2.9", :records_mismatch, HeaderRules::DRIFT],
|
|
105
|
+
"emf.header.handles" => [:handle_count, "MS-EMF 2.2.9", :handle_count, HeaderRules::DRIFT],
|
|
106
|
+
"emf.header.description_bounds" => [:description_bounds, "MS-EMF 2.2.9", :description_bounds, HeaderRules::DRIFT],
|
|
107
|
+
"emf.header.pixel_format_bounds" => [:pixel_format_bounds, "MS-EMF 2.2.10", :pixel_format_bounds, HeaderRules::DRIFT]
|
|
108
|
+
}.each do |id, (method, citation, kind, reason)|
|
|
109
|
+
registry.register(id: id, formats: HeaderRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
110
|
+
HeaderRules.public_send(method, context)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Object creation and manipulation rules (MS-EMF 2.3.7, 2.3.8, 2.1.31, 2.2.17-2.2.20).
|
|
4
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
5
|
+
|
|
6
|
+
module Emf
|
|
7
|
+
module Conformance
|
|
8
|
+
module ObjectRules
|
|
9
|
+
EMF = %i[emf emf_plus].freeze
|
|
10
|
+
DEFAULT_PALETTE = 0x8000000F
|
|
11
|
+
STOCK = ((0x80000000..0x80000008).to_a + (0x8000000A..0x80000013).to_a).freeze # MS-EMF 2.1.31
|
|
12
|
+
# Record types that put a new handle in the object table, and the wire field that holds it.
|
|
13
|
+
CREATE = { 38 => :ih_pen, 39 => :ih_brush, 49 => :ih_pal, 82 => :ih_object, 93 => :ih_brush,
|
|
14
|
+
94 => :ih_brush, 95 => :ih_pen, 99 => :ih_cs, 122 => :ih_cs }.freeze
|
|
15
|
+
# Records that name an existing handle: type => wire field.
|
|
16
|
+
SELECT = { 37 => :ih_object, 48 => :ih_pal, 100 => :ih_cs }.freeze
|
|
17
|
+
DELETE = { 40 => :ih_object, 101 => :ih_cs }.freeze
|
|
18
|
+
USE = { 50 => :ih_pal, 51 => :ih_pal }.freeze
|
|
19
|
+
REFERENCES = SELECT.merge(DELETE).merge(USE).freeze
|
|
20
|
+
FIXED_SIZES = { 38 => 28, 39 => 24, 48 => 12, 100 => 12, 111 => 24 }.freeze
|
|
21
|
+
PS_GEOMETRIC = 0x10000
|
|
22
|
+
PS_NULL = 5
|
|
23
|
+
PS_ALTERNATE = 8
|
|
24
|
+
BS_SOLID = 0
|
|
25
|
+
BS_NULL = 1
|
|
26
|
+
BS_HATCHED = 2
|
|
27
|
+
MAX_PALETTE = 0x400
|
|
28
|
+
MIN_FONT_RECORD = 12 + 320 # fixed part plus a LogFontPanose (2.3.7.8)
|
|
29
|
+
COSMETIC_WIDTH = "GDI writes PS_SOLID pens wider than 1 and renders them at that width; 165 corpus files do this"
|
|
30
|
+
PEN_BRUSH = "the same section's brush table lists BS_PATTERN and BS_DIBPATTERN as supported for the pen; GDI writes " \
|
|
31
|
+
"DIB-pattern geometric pens (5 corpus files)"
|
|
32
|
+
FONT_SHORT = "the same section says a LogFont MAY be present instead of a LogFontExDv, so a shorter elw is not uniformly invalid"
|
|
33
|
+
HANDLE_RANGE = "vectory's svg2emf, ps2emf and eps2emf output declares Handles=1 while selecting object indices 1 and 2; " \
|
|
34
|
+
"renderers resolve handles from the live table, not the header count, and display the file"
|
|
35
|
+
UNKNOWN_HANDLE = "readers skip a SELECTOBJECT or DELETEOBJECT whose handle is not in the table and keep drawing"
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
# nil when the record has no such field, or its body is too short to decode it.
|
|
40
|
+
def field(record, name)
|
|
41
|
+
wire = record.wire
|
|
42
|
+
wire.respond_to?(name) ? wire.public_send(name).to_i : nil
|
|
43
|
+
rescue ArgumentError
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def at(record, index, message)
|
|
48
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def each_of(context, types)
|
|
52
|
+
context.records.each_with_index do |record, index|
|
|
53
|
+
type = context.record_type(record)
|
|
54
|
+
yield record, index, type if types.include?(type)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def hex(value) = format("0x%08X", value)
|
|
59
|
+
|
|
60
|
+
def fixed_size(context)
|
|
61
|
+
found = []
|
|
62
|
+
each_of(context, FIXED_SIZES.keys) do |record, index, type|
|
|
63
|
+
size = context.record_size(record)
|
|
64
|
+
next if size == FIXED_SIZES.fetch(type)
|
|
65
|
+
|
|
66
|
+
found << at(record, index, "#{RecordTypes.name(type)} Size is #{size}, MUST be #{FIXED_SIZES.fetch(type)}")
|
|
67
|
+
end
|
|
68
|
+
found
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# PenStyle (2.1.25): line style 0-8, cap 0/0x100/0x200, join 0/0x1000/0x2000, type 0/PS_GEOMETRIC.
|
|
72
|
+
def font_size(context)
|
|
73
|
+
found = []
|
|
74
|
+
each_of(context, [82]) do |record, index, _type|
|
|
75
|
+
size = context.record_size(record)
|
|
76
|
+
next if size >= MIN_FONT_RECORD
|
|
77
|
+
|
|
78
|
+
found << at(record, index, "EXTCREATEFONTINDIRECTW Size #{size} leaves #{size - 12} bytes for elw, " \
|
|
79
|
+
"less than a LogFontPanose (#{MIN_FONT_RECORD - 12})")
|
|
80
|
+
end
|
|
81
|
+
found
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def pen_style_problem(style)
|
|
85
|
+
return "line style #{style & 0xFF} is outside PS_SOLID..PS_ALTERNATE" if (style & 0xFF) > PS_ALTERNATE
|
|
86
|
+
return "end cap #{hex(style & 0xF00)} is not round, square or flat" unless [0, 0x100, 0x200].include?(style & 0xF00)
|
|
87
|
+
return "line join #{hex(style & 0xF000)} is not round, bevel or miter" unless [0, 0x1000, 0x2000].include?(style & 0xF000)
|
|
88
|
+
return "pen type #{hex(style & 0xF0000)} is not PS_COSMETIC or PS_GEOMETRIC" unless [0, PS_GEOMETRIC].include?(style & 0xF0000)
|
|
89
|
+
return "bits #{hex(style & 0xFFF00000)} are outside the PenStyle enumeration" unless style.nobits?(0xFFF00000)
|
|
90
|
+
|
|
91
|
+
"PS_ALTERNATE is applicable only to PS_COSMETIC pens" if (style & 0xFF) == PS_ALTERNATE && style.anybits?(PS_GEOMETRIC)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def pen_style(context)
|
|
95
|
+
found = []
|
|
96
|
+
each_of(context, [38, 95]) do |record, index, type|
|
|
97
|
+
style = field(record, :pen_style)
|
|
98
|
+
problem = style && pen_style_problem(style)
|
|
99
|
+
found << at(record, index, "#{RecordTypes.name(type)} PenStyle #{hex(style)}: #{problem}") if problem
|
|
100
|
+
end
|
|
101
|
+
found
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def pen_width(record, type)
|
|
105
|
+
return record.wire.width.x.to_i if type == 38 && record.wire.respond_to?(:width)
|
|
106
|
+
|
|
107
|
+
field(record, :width)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def cosmetic_width(context)
|
|
111
|
+
found = []
|
|
112
|
+
each_of(context, [38, 95]) do |record, index, type|
|
|
113
|
+
style = field(record, :pen_style)
|
|
114
|
+
next if style.nil? || style.anybits?(PS_GEOMETRIC)
|
|
115
|
+
|
|
116
|
+
width = pen_width(record, type)
|
|
117
|
+
next if width.nil? || width == 1
|
|
118
|
+
|
|
119
|
+
found << at(record, index, "#{RecordTypes.name(type)} is a PS_COSMETIC pen with width #{width}, MUST be 1")
|
|
120
|
+
end
|
|
121
|
+
found
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def brush_style(context)
|
|
125
|
+
found = []
|
|
126
|
+
each_of(context, [39]) do |record, index, _type|
|
|
127
|
+
style = field(record, :brush_style)
|
|
128
|
+
next if style.nil? || [BS_SOLID, BS_NULL, BS_HATCHED].include?(style)
|
|
129
|
+
|
|
130
|
+
found << at(record, index, "CREATEBRUSHINDIRECT BrushStyle #{style} MUST be BS_SOLID, BS_HATCHED or BS_NULL")
|
|
131
|
+
end
|
|
132
|
+
found
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def extpen_brush_problem(pen_style, brush, hatch)
|
|
136
|
+
if pen_style.anybits?(PS_GEOMETRIC)
|
|
137
|
+
"a PS_GEOMETRIC pen MUST use BS_SOLID or BS_HATCHED" unless [BS_SOLID, BS_HATCHED].include?(brush) ||
|
|
138
|
+
(brush == BS_NULL && (pen_style & 0xFF) == PS_NULL)
|
|
139
|
+
elsif brush == BS_HATCHED && ![0x8, 0xA].include?(hatch)
|
|
140
|
+
"a cosmetic BS_HATCHED pen MUST use HS_SOLIDTEXTCLR or HS_SOLIDBKCLR, not #{hatch}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def extpen_brush(context)
|
|
145
|
+
found = []
|
|
146
|
+
each_of(context, [95]) do |record, index, _type|
|
|
147
|
+
style = field(record, :pen_style)
|
|
148
|
+
brush = field(record, :brush_style)
|
|
149
|
+
next if style.nil? || brush.nil?
|
|
150
|
+
|
|
151
|
+
problem = extpen_brush_problem(style, brush, field(record, :brush_hatch))
|
|
152
|
+
found << at(record, index, "EXTCREATEPEN brush style #{brush}: #{problem}") if problem
|
|
153
|
+
end
|
|
154
|
+
found
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def palette_version(context)
|
|
158
|
+
found = []
|
|
159
|
+
each_of(context, [49]) do |record, index, _type|
|
|
160
|
+
version = field(record, :version)
|
|
161
|
+
next if version.nil? || version == 0x300
|
|
162
|
+
|
|
163
|
+
found << at(record, index, "CREATEPALETTE LogPalette Version is #{format('0x%04X', version)}, MUST be 0x0300")
|
|
164
|
+
end
|
|
165
|
+
found
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def palette_entries(context)
|
|
169
|
+
found = []
|
|
170
|
+
each_of(context, [49, 51]) do |record, index, type|
|
|
171
|
+
count = field(record, :entry_count)
|
|
172
|
+
next if count.nil?
|
|
173
|
+
next if count.positive? && (type == 49 || count <= MAX_PALETTE)
|
|
174
|
+
|
|
175
|
+
found << at(record, index,
|
|
176
|
+
"#{RecordTypes.name(type)} NumberOfEntries is #{count}, MUST be #{type == 49 ? '> 0' : "> 0 and <= #{MAX_PALETTE}"}")
|
|
177
|
+
end
|
|
178
|
+
found
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Palette records whose entry array is sized by a count: the record Size must match the layout.
|
|
182
|
+
PALETTE_FIXED = { 49 => 16, 50 => 20 }.freeze
|
|
183
|
+
|
|
184
|
+
def palette_size(context)
|
|
185
|
+
found = []
|
|
186
|
+
each_of(context, PALETTE_FIXED.keys) do |record, index, type|
|
|
187
|
+
count = field(record, :entry_count)
|
|
188
|
+
next if count.nil?
|
|
189
|
+
|
|
190
|
+
expected = PALETTE_FIXED.fetch(type) + (4 * count)
|
|
191
|
+
size = context.record_size(record)
|
|
192
|
+
next if size == expected
|
|
193
|
+
|
|
194
|
+
found << at(record, index, "#{RecordTypes.name(type)} holds #{count} palette entries, so Size MUST be #{expected}, not #{size}")
|
|
195
|
+
end
|
|
196
|
+
found
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def index_zero(context)
|
|
200
|
+
found = []
|
|
201
|
+
each_of(context, [37, 40, 48]) do |record, index, type|
|
|
202
|
+
next unless field(record, REFERENCES.fetch(type))&.zero?
|
|
203
|
+
|
|
204
|
+
found << at(record, index, "#{RecordTypes.name(type)} object index 0 is reserved for the metafile itself, MUST NOT be used")
|
|
205
|
+
end
|
|
206
|
+
found
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def delete_stock(context)
|
|
210
|
+
found = []
|
|
211
|
+
each_of(context, [40]) do |record, index, _type|
|
|
212
|
+
handle = field(record, :ih_object)
|
|
213
|
+
next unless STOCK.include?(handle)
|
|
214
|
+
|
|
215
|
+
found << at(record, index, "DELETEOBJECT names the stock object #{hex(handle)}, which cannot be deleted")
|
|
216
|
+
end
|
|
217
|
+
found
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# A stock index is a valid operand of SELECTOBJECT; SELECTPALETTE takes only DEFAULT_PALETTE.
|
|
221
|
+
def stock_ok?(type, handle)
|
|
222
|
+
return handle == DEFAULT_PALETTE if type == 48
|
|
223
|
+
|
|
224
|
+
type == 37 && STOCK.include?(handle)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def handle_range(context)
|
|
228
|
+
limit = context.header.n_handles.to_i
|
|
229
|
+
found = []
|
|
230
|
+
each_of(context, REFERENCES.keys) do |record, index, type|
|
|
231
|
+
handle = field(record, REFERENCES.fetch(type))
|
|
232
|
+
next if handle.nil? || handle.zero? || handle < limit || stock_ok?(type, handle)
|
|
233
|
+
next if type == 40 && STOCK.include?(handle) # emf.object.delete_stock reports it
|
|
234
|
+
|
|
235
|
+
found << at(record, index,
|
|
236
|
+
"#{RecordTypes.name(type)} names object index #{hex(handle)}, " \
|
|
237
|
+
"not below header Handles (#{limit}) and not a stock object")
|
|
238
|
+
end
|
|
239
|
+
found
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Walks the stream in order, keeping the live handle table.
|
|
243
|
+
def lifecycle(context)
|
|
244
|
+
live = {}
|
|
245
|
+
found = []
|
|
246
|
+
context.records.each_with_index do |record, index|
|
|
247
|
+
type = context.record_type(record)
|
|
248
|
+
name = CREATE[type] || REFERENCES[type]
|
|
249
|
+
handle = name && field(record, name)
|
|
250
|
+
next if handle.nil?
|
|
251
|
+
|
|
252
|
+
if CREATE.key?(type)
|
|
253
|
+
live[handle] = index + 1
|
|
254
|
+
else
|
|
255
|
+
problem = missing_handle(live, type, handle)
|
|
256
|
+
found << at(record, index, "#{RecordTypes.name(type)} #{problem}") if problem
|
|
257
|
+
live.delete(handle) if DELETE.key?(type)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
found
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def missing_handle(live, _type, handle)
|
|
264
|
+
return if handle.zero? || handle >= 0x80000000 || live.key?(handle) # zero and stock are other rules' business
|
|
265
|
+
|
|
266
|
+
"uses object index #{handle}, which the stream never created or already deleted"
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
registry = Registry.loading
|
|
271
|
+
{
|
|
272
|
+
"emf.object.fixed_size" => [:fixed_size, "MS-EMF 2.3.7", :object_fixed_size, nil],
|
|
273
|
+
"emf.object.font_size" => [:font_size, "MS-EMF 2.3.7.8", :font_size, ObjectRules::FONT_SHORT],
|
|
274
|
+
"emf.object.pen_style" => [:pen_style, "MS-EMF 2.1.25", :pen_style, nil],
|
|
275
|
+
"emf.object.pen_cosmetic_width" => [:cosmetic_width, "MS-EMF 2.2.19", :pen_cosmetic_width, ObjectRules::COSMETIC_WIDTH],
|
|
276
|
+
"emf.object.brush_style" => [:brush_style, "MS-EMF 2.3.7.1", :brush_style, nil],
|
|
277
|
+
"emf.object.extpen_brush" => [:extpen_brush, "MS-EMF 2.2.20", :extpen_brush, ObjectRules::PEN_BRUSH],
|
|
278
|
+
"emf.object.palette_version" => [:palette_version, "MS-EMF 2.3.7.6", :palette_version, nil],
|
|
279
|
+
"emf.object.palette_entries" => [:palette_entries, "MS-EMF 2.3.8.4", :palette_entries, nil],
|
|
280
|
+
"emf.object.palette_size" => [:palette_size, "MS-EMF 2.3.7.6", :palette_size, nil],
|
|
281
|
+
"emf.object.index_zero" => [:index_zero, "MS-EMF 2.3.8.5", :index_zero, nil],
|
|
282
|
+
"emf.object.delete_stock" => [:delete_stock, "MS-EMF 2.3.8.3", :delete_stock, nil],
|
|
283
|
+
"emf.object.handle_range" => [:handle_range, "MS-EMF 2.3.8", :handle_range, ObjectRules::HANDLE_RANGE],
|
|
284
|
+
"emf.object.lifecycle" => [:lifecycle, "MS-EMF 2.3.8", :object_lifecycle, ObjectRules::UNKNOWN_HANDLE]
|
|
285
|
+
}.each do |id, (method, citation, kind, reason)|
|
|
286
|
+
registry.register(id: id, formats: ObjectRules::EMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
287
|
+
ObjectRules.public_send(method, context)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|