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,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WMF bitmap, text, palette, region, escape and embedded-EMF buffer rules over typed public
|
|
4
|
+
# records (MS-WMF 2.3.1 bitmap, 2.3.3 drawing, 2.3.4 object, 2.3.5 state and 2.3.6 escape
|
|
5
|
+
# records). Only values the model exposes are judged: records the model keeps opaque
|
|
6
|
+
# (UnknownRecord, escape subtypes without a view, undecodable bytes) are preserved and never
|
|
7
|
+
# reported, except a text record the parser kept verbatim because its StringLength is negative, and no rule
|
|
8
|
+
# here needs the declared record size.
|
|
9
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
10
|
+
|
|
11
|
+
module Emf
|
|
12
|
+
module Conformance
|
|
13
|
+
module WmfBufferRules
|
|
14
|
+
WMF = %i[wmf].freeze
|
|
15
|
+
R = Emf::Model::Wmf::Records
|
|
16
|
+
EE = Emf::Wmf::EmbeddedEmf
|
|
17
|
+
UINT16 = 0xFFFF
|
|
18
|
+
INT16 = 0x7FFF
|
|
19
|
+
DIB_RECORDS = [R::DibBitBlt, R::DibStretchBlt, R::SetDibToDev, R::StretchDib].freeze
|
|
20
|
+
BITMAP16_RECORDS = [R::BitBlt, R::StretchBlt].freeze
|
|
21
|
+
PALETTE_RECORDS = [R::CreatePalette, R::SetPalEntries, R::AnimatePalette].freeze
|
|
22
|
+
CLIPPED_OR_OPAQUE = 0x0006
|
|
23
|
+
REGION_HEADER_BYTES = 22
|
|
24
|
+
SEQUENCE_REASONS = %i[sequence_mismatch limit_exceeded remaining_mismatch record_count_mismatch incomplete].freeze
|
|
25
|
+
|
|
26
|
+
PRESERVED = "bytes are preserved verbatim; a consumer skips the unusable part instead of rejecting the record"
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def dib_layout(context)
|
|
31
|
+
each_of(context, *DIB_RECORDS) do |record|
|
|
32
|
+
next if record.bitmap.nil? || record.typed_image
|
|
33
|
+
|
|
34
|
+
"#{name(record)} DeviceIndependentBitmap does not decode: header, color table or pixel extent disagrees " \
|
|
35
|
+
"with the #{record.bitmap.bytesize} bytes carried"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def bitmap16_layout(context)
|
|
40
|
+
each_of(context, *BITMAP16_RECORDS) do |record|
|
|
41
|
+
next if record.bitmap.nil? || record.typed_image
|
|
42
|
+
|
|
43
|
+
"#{name(record)} Bitmap16 does not decode: Width, Height, Planes, BitsPixel or pixel extent disagrees " \
|
|
44
|
+
"with the #{record.bitmap.bytesize} bytes carried"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def text_length(context)
|
|
49
|
+
each_of(context, R::TextOut) do |record|
|
|
50
|
+
count = record.text.bytesize
|
|
51
|
+
if count > INT16
|
|
52
|
+
"#{name(record)} string is #{count} bytes, StringLength is a signed 16-bit count"
|
|
53
|
+
elsif (count + record.padding.bytesize).odd?
|
|
54
|
+
"#{name(record)} string and padding take #{count + record.padding.bytesize} bytes, MUST be word aligned"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# A text record with a negative StringLength cannot be framed as text, so the parser keeps it as an
|
|
60
|
+
# UnknownRecord (Wmf::Recovery); the sign is read from its own bytes.
|
|
61
|
+
def text_negative(context)
|
|
62
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
63
|
+
next unless record.is_a?(R::UnknownRecord) && Emf::Wmf::Recovery.record(record.raw_bytes, record.type_id, nil)&.last == 1003
|
|
64
|
+
|
|
65
|
+
count_at = Emf::Wmf::Recovery::TEXT_COUNTS.fetch(record.type_id & 0xFF).first
|
|
66
|
+
{ message: "#{record.type_id & 0xFF == 0x32 ? 'ExtTextOut' : 'TextOut'} StringLength is " \
|
|
67
|
+
"#{record.raw_bytes.byteslice(count_at, 2).unpack1('s<')}, MUST NOT be negative",
|
|
68
|
+
offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A region whose ObjectCount is two bytes wide (libUEMF) has no readable header under the spec layout, so the
|
|
73
|
+
# parser keeps it verbatim as an UnknownRecord (Wmf::Recovery).
|
|
74
|
+
def region_header(context)
|
|
75
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
76
|
+
next unless record.is_a?(R::UnknownRecord) && record.type_id & 0xFF == Emf::Wmf::Recovery::REGION &&
|
|
77
|
+
Emf::Wmf::Recovery.record(record.raw_bytes, record.type_id, nil)&.last == 1203
|
|
78
|
+
|
|
79
|
+
{ message: "region header does not follow the Region Object layout (ObjectCount is not 4 bytes wide)",
|
|
80
|
+
offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def text_spacing(context)
|
|
85
|
+
each_of(context, R::ExtTextOut) do |record|
|
|
86
|
+
next if record.dx.nil? || record.dx.size == record.text.bytesize
|
|
87
|
+
|
|
88
|
+
"ExtTextOut has #{record.dx.size} Dx entries for a #{record.text.bytesize}-byte string, MUST match StringLength"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def text_rectangle(context)
|
|
93
|
+
each_of(context, R::ExtTextOut) do |record|
|
|
94
|
+
wanted = record.options.to_i.anybits?(CLIPPED_OR_OPAQUE)
|
|
95
|
+
next if wanted == !record.rectangle.nil?
|
|
96
|
+
|
|
97
|
+
if wanted
|
|
98
|
+
"ExtTextOut options select ETO_OPAQUE or ETO_CLIPPED but the Rectangle is absent"
|
|
99
|
+
else
|
|
100
|
+
"ExtTextOut has a Rectangle but its options select neither ETO_OPAQUE nor ETO_CLIPPED"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def palette_count(context)
|
|
106
|
+
each_of(context, *PALETTE_RECORDS, R::ResizePalette) do |record|
|
|
107
|
+
count = record.is_a?(R::ResizePalette) ? record.entry_count : record.entries.size
|
|
108
|
+
next if count.is_a?(Integer) && count.between?(0, UINT16)
|
|
109
|
+
|
|
110
|
+
"#{name(record)} entry count #{count.inspect} does not fit the unsigned 16-bit field"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def palette_entries(context)
|
|
115
|
+
each_of(context, *PALETTE_RECORDS) do |record|
|
|
116
|
+
bad = record.entries.find do |entry|
|
|
117
|
+
[entry.red, entry.green, entry.blue, entry.flags].any? { |value| !value.is_a?(Integer) || !value.between?(0, 0xFF) }
|
|
118
|
+
end
|
|
119
|
+
"#{name(record)} holds a PaletteEntry with a component outside 0..255" if bad
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def palette_start(context)
|
|
124
|
+
each_of(context, R::CreatePalette) do |record|
|
|
125
|
+
format("CreatePalette Start is 0x%04X, MUST be 0x0300", record.start.to_i) unless record.start == 0x0300
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def region_scans(context)
|
|
130
|
+
each_of(context, R::CreateRegion) do |record|
|
|
131
|
+
scans = record.scans
|
|
132
|
+
if scans.size > INT16
|
|
133
|
+
"region has #{scans.size} scans, ScanCount is a signed 16-bit count"
|
|
134
|
+
elsif (scan = scans.find { |item| item.lines.size * 2 > UINT16 })
|
|
135
|
+
"region scan at Top #{scan.top} has #{scan.lines.size * 2} scan points, the count is an unsigned 16-bit field"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def region_size(context)
|
|
141
|
+
each_of(context, R::CreateRegion) do |record|
|
|
142
|
+
next if record.scans.size > INT16 || record.scans.any? { |scan| scan.lines.size * 2 > UINT16 }
|
|
143
|
+
|
|
144
|
+
size = REGION_HEADER_BYTES + record.scans.sum { |scan| 8 + (scan.lines.size * 4) }
|
|
145
|
+
"region needs #{size} bytes, RegionSize is a signed 16-bit field" if size > INT16
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def escape_count(context)
|
|
150
|
+
each_of(context, R::Escape) do |record|
|
|
151
|
+
bytes = record.data.bytesize
|
|
152
|
+
if bytes > UINT16
|
|
153
|
+
"Escape carries #{bytes} data bytes, ByteCount is an unsigned 16-bit field"
|
|
154
|
+
elsif record.padding.nil? == bytes.odd?
|
|
155
|
+
"Escape data of #{bytes} bytes #{bytes.odd? ? 'lacks' : 'has'} a padding byte, the record MUST stay word aligned"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def embedded_segment(context)
|
|
161
|
+
each_of(context, R::Escape) do |record|
|
|
162
|
+
record.embedded_emf_segment
|
|
163
|
+
nil
|
|
164
|
+
rescue EE::Invalid => e
|
|
165
|
+
"embedded EMF escape segment is invalid: #{e.reason}" unless e.reason == :not_embedded_emf
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def embedded_sequence(context)
|
|
170
|
+
embedded_runs(context) do |run|
|
|
171
|
+
"embedded EMF escape sequence is inconsistent: #{run.error}" if SEQUENCE_REASONS.include?(run.error)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def embedded_checksum(context)
|
|
176
|
+
embedded_runs(context) do |run|
|
|
177
|
+
next unless run.stream && !run.stream.checksum_valid
|
|
178
|
+
|
|
179
|
+
"embedded EMF stream does not match the Checksum of its first escape segment"
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Findings for each run of consecutive 0x000F escapes, positioned at its first record.
|
|
184
|
+
def embedded_runs(context)
|
|
185
|
+
EE.sequences(context.records).filter_map do |run|
|
|
186
|
+
message = yield run
|
|
187
|
+
next unless message
|
|
188
|
+
|
|
189
|
+
record = context.records[run.first_record]
|
|
190
|
+
{ message: message, offset: record.offset, record_index: run.first_record + 1, record_type: record.type_id.to_i }
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def name(record) = record.class.name.split("::").last
|
|
195
|
+
|
|
196
|
+
def each_of(context, *classes)
|
|
197
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
198
|
+
next unless classes.any? { |klass| record.is_a?(klass) }
|
|
199
|
+
|
|
200
|
+
message = yield record
|
|
201
|
+
next unless message
|
|
202
|
+
|
|
203
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# [id, method, citation, kind, tolerant_reason]
|
|
209
|
+
[
|
|
210
|
+
["wmf.bitmap.dib_layout", :dib_layout,
|
|
211
|
+
"MS-WMF 2.2.2.9 DeviceIndependentBitmap Object; 2.3.1.2.1 META_DIBBITBLT, 2.3.1.3.1 " \
|
|
212
|
+
"META_DIBSTRETCHBLT (Target), 2.3.1.4 META_SETDIBTODEV, 2.3.1.6 META_STRETCHDIB (DIB)",
|
|
213
|
+
:buffer_layout, WmfBufferRules::PRESERVED],
|
|
214
|
+
["wmf.bitmap.bitmap16_layout", :bitmap16_layout, "MS-WMF 2.2.2.1 Bitmap16 Object; 2.3.1.1.1 META_BITBLT, 2.3.1.5.1 META_STRETCHBLT " \
|
|
215
|
+
"(Target)",
|
|
216
|
+
:buffer_layout, WmfBufferRules::PRESERVED],
|
|
217
|
+
["wmf.text.length", :text_length, "MS-WMF 2.3.3.20 META_TEXTOUT, 2.3.3.5 META_EXTTEXTOUT (StringLength, String)",
|
|
218
|
+
:string_length, nil],
|
|
219
|
+
["wmf.text.negative_length", :text_negative,
|
|
220
|
+
"MS-WMF 2.3.3.20 META_TEXTOUT, 2.3.3.5 META_EXTTEXTOUT (StringLength, String)", :string_length,
|
|
221
|
+
"the parser keeps the record verbatim as an opaque record; readers (LibreOffice) import the file"],
|
|
222
|
+
["wmf.region.header", :region_header, "MS-WMF 2.2.1.5 Region Object (ObjectCount, RegionSize); 2.3.4.6 META_CREATEREGION",
|
|
223
|
+
:buffer_layout,
|
|
224
|
+
"libUEMF, the library Inkscape writes WMF with, declares ObjectCount as 2 bytes; the parser keeps the record " \
|
|
225
|
+
"verbatim as an opaque region and readers (LibreOffice) import the file"],
|
|
226
|
+
["wmf.text.spacing_count", :text_spacing, "MS-WMF 2.3.3.5 META_EXTTEXTOUT (Dx)", :array_counts, nil],
|
|
227
|
+
["wmf.text.rectangle_options", :text_rectangle, "MS-WMF 2.3.3.5 META_EXTTEXTOUT (fwOpts, Rectangle)", :field_dependency,
|
|
228
|
+
"readers ignore a Rectangle the options do not select"],
|
|
229
|
+
["wmf.palette.entry_count", :palette_count,
|
|
230
|
+
"MS-WMF 2.3.4.3 META_CREATEPALETTE, 2.3.5.19 META_SETPALENTRIES, 2.3.5.1 META_ANIMATEPALETTE, " \
|
|
231
|
+
"2.3.5.9 META_RESIZEPALETTE (NumberOfEntries); 2.2.1.3 Palette Object (NumberOfEntries)",
|
|
232
|
+
:array_counts, nil],
|
|
233
|
+
["wmf.palette.entry_fields", :palette_entries,
|
|
234
|
+
"MS-WMF 2.2.2.13 PaletteEntry Object; 2.2.1.3 Palette Object (aPaletteEntries); " \
|
|
235
|
+
"2.3.4.3 META_CREATEPALETTE, 2.3.5.19 META_SETPALENTRIES, 2.3.5.1 META_ANIMATEPALETTE",
|
|
236
|
+
:coordinate_range, nil],
|
|
237
|
+
["wmf.palette.start", :palette_start, "MS-WMF 2.2.1.3 Palette Object (Start, MUST be 0x0300 for META_CREATEPALETTE); 2.3.4.3 " \
|
|
238
|
+
"META_CREATEPALETTE", :fixed_value,
|
|
239
|
+
"readers ignore the Start of a created palette"],
|
|
240
|
+
["wmf.region.scan_counts", :region_scans, "MS-WMF 2.2.1.5 Region Object (ScanCount, aScans), 2.2.2.21 Scan Object; 2.3.4.6 " \
|
|
241
|
+
"META_CREATEREGION", :array_counts, nil],
|
|
242
|
+
["wmf.region.size", :region_size, "MS-WMF 2.2.1.5 Region Object (RegionSize); 2.3.4.6 META_CREATEREGION", :buffer_layout, nil],
|
|
243
|
+
["wmf.escape.byte_count", :escape_count, "MS-WMF 2.3.6.1 META_ESCAPE (ByteCount, EscapeData)", :byte_count, nil],
|
|
244
|
+
["wmf.escape.embedded_emf.segment", :embedded_segment, "MS-WMF 2.3.6.25 META_ESCAPE_ENHANCED_METAFILE", :buffer_layout,
|
|
245
|
+
"producers write other data under Escape 0x000F (Micrografx Flowcharter); readers skip an embedded stream they " \
|
|
246
|
+
"cannot use and play the WMF records"],
|
|
247
|
+
["wmf.escape.embedded_emf.sequence", :embedded_sequence,
|
|
248
|
+
"MS-WMF 2.3.6.25 META_ESCAPE_ENHANCED_METAFILE (CommentRecordCount, CurrentRecordSize, " \
|
|
249
|
+
"RemainingBytes, EnhancedMetafileDataSize)",
|
|
250
|
+
:array_counts, nil],
|
|
251
|
+
["wmf.escape.embedded_emf.checksum", :embedded_checksum, "MS-WMF 2.3.6.25 META_ESCAPE_ENHANCED_METAFILE (Checksum)", :checksum,
|
|
252
|
+
"readers do not validate the embedded stream Checksum"]
|
|
253
|
+
].each do |id, method, citation, kind, reason|
|
|
254
|
+
Registry.loading.register(id: id, formats: WmfBufferRules::WMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
255
|
+
WmfBufferRules.public_send(method, context)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WMF header, placeable header and record-framing rules over the accepted public
|
|
4
|
+
# model (MS-WMF 2.2.2 headers, 2.3.2 control records). They read only
|
|
5
|
+
# Metafile#header, Metafile#records and the input length; declared record sizes
|
|
6
|
+
# are not on the public WMF model, so size rules stay blocked (see manifest).
|
|
7
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
8
|
+
|
|
9
|
+
module Emf
|
|
10
|
+
module Conformance
|
|
11
|
+
module WmfFramingRules
|
|
12
|
+
WMF = %i[wmf].freeze
|
|
13
|
+
PLACEABLE_KEY = 0x9AC6CDD7
|
|
14
|
+
HEADER_WORDS = 9
|
|
15
|
+
PLACEABLE_BYTES = 22
|
|
16
|
+
EOF_FUNCTION = 0
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def header_type(context)
|
|
21
|
+
type = context.header.type
|
|
22
|
+
return [] if [1, 2].include?(type)
|
|
23
|
+
|
|
24
|
+
[{ message: "WMF header Type is #{type.inspect}, MUST be 1 (memory) or 2 (disk)", offset: header_offset(context) }]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def header_version(context)
|
|
28
|
+
version = context.header.version
|
|
29
|
+
return [] if [0x0100, 0x0300].include?(version)
|
|
30
|
+
|
|
31
|
+
[{ message: format("WMF header Version is 0x%04X, MUST be 0x0100 or 0x0300", version.to_i), offset: header_offset(context) + 4 }]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def header_size(context)
|
|
35
|
+
words = context.header.size_words.to_i
|
|
36
|
+
found = []
|
|
37
|
+
found << "WMF header Size is #{words} words, less than the #{HEADER_WORDS}-word header" if words < HEADER_WORDS
|
|
38
|
+
if context.byte_length && (words * 2) + header_offset(context) > context.byte_length
|
|
39
|
+
found << "WMF header Size #{words} words runs past the #{context.byte_length}-byte input"
|
|
40
|
+
end
|
|
41
|
+
found.map { |message| { message: message, offset: header_offset(context) + 6 } }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def header_members(context)
|
|
45
|
+
members = context.header.number_of_members.to_i
|
|
46
|
+
return [] if members.zero?
|
|
47
|
+
|
|
48
|
+
[{ message: "WMF header NumberOfMembers is #{members}, SHOULD be 0", offset: header_offset(context) + 16 }]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# HWmf MUST be 0 only for a metafile on disk (Type 2); an in-memory metafile (Type 1) carries its handle.
|
|
52
|
+
def placeable_handle(context)
|
|
53
|
+
placeable = context.header.placeable
|
|
54
|
+
return [] unless placeable && context.header.type.to_i == 2 && !placeable.handle.to_i.zero?
|
|
55
|
+
|
|
56
|
+
[{ message: "WMF placeable HWmf is #{placeable.handle} on a disk metafile (Type 2), MUST be 0", offset: 4 }]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def placeable_checksum(context)
|
|
60
|
+
placeable = context.header.placeable
|
|
61
|
+
return [] unless placeable
|
|
62
|
+
|
|
63
|
+
expected = checksum(placeable)
|
|
64
|
+
return [] if placeable.checksum == expected
|
|
65
|
+
|
|
66
|
+
[{ message: format("WMF placeable Checksum is 0x%<actual>04X, the XOR of the preceding ten words is 0x%<expected>04X",
|
|
67
|
+
actual: placeable.checksum.to_i, expected: expected), offset: 20 }]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def eof_missing(context)
|
|
71
|
+
last = context.records.last
|
|
72
|
+
return [] if last && last.type_id.to_i == EOF_FUNCTION
|
|
73
|
+
|
|
74
|
+
[{ message: "WMF metafile does not end with a META_EOF record", offset: last&.offset, record_index: context.records.size,
|
|
75
|
+
record_type: last&.type_id }]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def eof_not_last(context)
|
|
79
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
80
|
+
next unless record.type_id.to_i == EOF_FUNCTION && index != context.records.size - 1
|
|
81
|
+
|
|
82
|
+
at(record, index, "META_EOF is followed by #{context.records.size - 1 - index} more record(s)")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def unknown_function(context)
|
|
87
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
88
|
+
next unless record.is_a?(Emf::Model::Wmf::Records::UnknownRecord)
|
|
89
|
+
# A specified text function kept verbatim for its negative StringLength is reported by wmf.text.negative_length.
|
|
90
|
+
next if Emf::Wmf::Binary::TYPED_FUNCTIONS.include?(record.type_id.to_i & 0xFF)
|
|
91
|
+
|
|
92
|
+
at(record, index, format("record function 0x%04X is not a specified MS-WMF record type", record.type_id.to_i))
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def trailing_bytes(context)
|
|
97
|
+
extra = context.metafile.trailing.to_s.bytesize
|
|
98
|
+
return [] if extra.zero?
|
|
99
|
+
|
|
100
|
+
[{ message: "#{extra} bytes follow the first META_EOF record", offset: context.byte_length && (context.byte_length - extra),
|
|
101
|
+
record_index: context.records.size }]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def header_offset(context) = context.header.placeable ? PLACEABLE_BYTES : 0
|
|
105
|
+
|
|
106
|
+
def checksum(placeable)
|
|
107
|
+
words = [PLACEABLE_KEY & 0xFFFF, PLACEABLE_KEY >> 16, placeable.handle, placeable.left, placeable.top, placeable.right,
|
|
108
|
+
placeable.bottom, placeable.inch, 0, 0]
|
|
109
|
+
words.map { |word| word.to_i & 0xFFFF }.reduce(0, :^)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def at(record, index, message)
|
|
113
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
registry = Registry.loading
|
|
118
|
+
[
|
|
119
|
+
["wmf.header.type", :header_type, "MS-WMF 2.3.2.2 META_HEADER (Type); 2.1.1.18 MetafileType Enumeration", :header_type, nil],
|
|
120
|
+
["wmf.header.version", :header_version, "MS-WMF 2.3.2.2 META_HEADER (Version); " \
|
|
121
|
+
"2.1.1.19 MetafileVersion Enumeration", :header_version, nil],
|
|
122
|
+
["wmf.header.size", :header_size, "MS-WMF 2.3.2.2 META_HEADER (SizeLow, SizeHigh)", :header_size,
|
|
123
|
+
"the parser reads the records actually present and records the Size drift in Metafile#errors " \
|
|
124
|
+
"(seen in files from Inkscape)"],
|
|
125
|
+
["wmf.header.number_of_members", :header_members, "MS-WMF 2.3.2.2 META_HEADER (NumberOfMembers)", :header_members,
|
|
126
|
+
"NumberOfMembers is reserved and no reader consults it", :warning],
|
|
127
|
+
["wmf.placeable.handle", :placeable_handle, "MS-WMF 2.3.2.3 META_PLACEABLE (HWmf)", :placeable_handle,
|
|
128
|
+
"readers ignore the handle of an in-file placeable header"],
|
|
129
|
+
["wmf.placeable.checksum", :placeable_checksum, "MS-WMF 2.3.2.3 META_PLACEABLE (Checksum)", :placeable_checksum,
|
|
130
|
+
"readers ignore the placeable checksum; the parser keeps the declared value and records the mismatch in " \
|
|
131
|
+
"Metafile#errors (seen in LibreOffice test files)"],
|
|
132
|
+
["wmf.eof.missing", :eof_missing, "MS-WMF 2.3.2.1 META_EOF", :eof_missing, nil],
|
|
133
|
+
["wmf.eof.not_last", :eof_not_last, "MS-WMF 2.3.2.1 META_EOF", :eof_not_last, nil],
|
|
134
|
+
["wmf.record.unknown_function", :unknown_function, "MS-WMF 2.1.1.1 RecordType Enumeration; " \
|
|
135
|
+
"2.3 WMF Records (unknown record types are ignored)", :unknown_function,
|
|
136
|
+
"unspecified functions are skipped by size, so the stream stays readable"],
|
|
137
|
+
["wmf.eof.trailing_bytes", :trailing_bytes, "MS-WMF 2.3.2.2 META_HEADER (SizeLow, SizeHigh)", :trailing_bytes,
|
|
138
|
+
"bytes after the declared Size are ignored by readers"]
|
|
139
|
+
].each do |id, method, citation, kind, reason, severity = :error|
|
|
140
|
+
registry.register(id: id, formats: WmfFramingRules::WMF, citation: citation, kind: kind, severity: severity,
|
|
141
|
+
tolerant_reason: reason) do |context|
|
|
142
|
+
WmfFramingRules.public_send(method, context)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WMF rules for the fixed-layout records whose only stated constraints are their exact layout and a
|
|
4
|
+
# reserved or enumerated field (MS-WMF 2.3.3 drawing, 2.3.5 state records). The parser keeps any words
|
|
5
|
+
# after the last field in `suffix`, so a record can pass decoding with a RecordSize larger than its
|
|
6
|
+
# layout. Records whose size is exact at decoding time (the rectangle, point and offset records of
|
|
7
|
+
# Binary::FIXED_RECORDS) are checked by wmf.record.declared_size instead.
|
|
8
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
9
|
+
|
|
10
|
+
module Emf
|
|
11
|
+
module Conformance
|
|
12
|
+
module WmfLayoutRules
|
|
13
|
+
WMF = %i[wmf].freeze
|
|
14
|
+
R = Emf::Model::Wmf::Records
|
|
15
|
+
KEPT = "the surplus words stay in the record's suffix, so nothing is dropped"
|
|
16
|
+
COLORREF_RESERVED_SHIFT = 24
|
|
17
|
+
COLORREF_CITATION = "MS-WMF 2.2.2.8 ColorRef Object (Reserved); 2.3.3.7 META_FLOODFILL, 2.3.3.19 META_SETPIXEL, " \
|
|
18
|
+
"2.3.5.14 META_SETBKCOLOR, 2.3.5.26 META_SETTEXTCOLOR"
|
|
19
|
+
|
|
20
|
+
# record class => [parameter bytes the layout defines, section]. Each section lists the record's fields; none lists more.
|
|
21
|
+
LAYOUTS = {
|
|
22
|
+
R::FloodFill => [8, "2.3.3.7 META_FLOODFILL"], R::SetPixel => [8, "2.3.3.19 META_SETPIXEL"],
|
|
23
|
+
R::PatBlt => [12, "2.3.3.12 META_PATBLT"], R::ExcludeClipRect => [8, "2.3.5.2 META_EXCLUDECLIPRECT"],
|
|
24
|
+
R::IntersectClipRect => [8, "2.3.5.3 META_INTERSECTCLIPRECT"], R::OffsetClipRgn => [4, "2.3.5.5 META_OFFSETCLIPRGN"],
|
|
25
|
+
R::RealizePalette => [0, "2.3.5.8 META_REALIZEPALETTE"], R::SetBkColor => [4, "2.3.5.14 META_SETBKCOLOR"],
|
|
26
|
+
R::SetMapperFlags => [4, "2.3.5.18 META_SETMAPPERFLAGS"], R::SetRelAbs => [0, "2.3.5.21 META_SETRELABS"],
|
|
27
|
+
R::SetTextCharExtra => [2, "2.3.5.25 META_SETTEXTCHAREXTRA"], R::SetTextColor => [4, "2.3.5.26 META_SETTEXTCOLOR"],
|
|
28
|
+
R::SetTextJustification => [4, "2.3.5.27 META_SETTEXTJUSTIFICATION"]
|
|
29
|
+
}.freeze
|
|
30
|
+
COLOR_FIELDS = { R::SetBkColor => :color_ref, R::SetTextColor => :color_ref, R::FloodFill => :color, R::SetPixel => :color }.freeze
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
def layout_size(context)
|
|
35
|
+
each_of(context, *LAYOUTS.keys) do |record|
|
|
36
|
+
surplus = record.suffix.to_s.bytesize
|
|
37
|
+
next if surplus.zero?
|
|
38
|
+
|
|
39
|
+
payload, section = LAYOUTS.fetch(record.class)
|
|
40
|
+
words = 3 + ((payload + surplus) / 2)
|
|
41
|
+
note = payload.zero? ? ", the record has no parameters" : ""
|
|
42
|
+
"#{section.split.last} RecordSize is #{words} words, the layout is #{3 + (payload / 2)} words " \
|
|
43
|
+
"(#{surplus} surplus bytes#{note})"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def raster_operation(context)
|
|
48
|
+
each_of(context, R::PatBlt) do |record|
|
|
49
|
+
value = record.raster_operation.to_i
|
|
50
|
+
if value >= (1 << COLORREF_RESERVED_SHIFT)
|
|
51
|
+
next format("PatBlt RasterOperation 0x%<value>08X has bits set above the 24-bit ternary code, " \
|
|
52
|
+
"so it is no TernaryRasterOperation value", value: value)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
expected = TernaryRasterOperations.expected(value)
|
|
56
|
+
next if expected.nil? || expected == value
|
|
57
|
+
|
|
58
|
+
format("PatBlt RasterOperation 0x%<value>08X is not a TernaryRasterOperation value; " \
|
|
59
|
+
"operation 0x%<index>02X is 0x%<expected>08X", value: value, index: (value >> 16) & 0xFF, expected: expected)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
PALETTE_RESERVED = [0x01, 0x02].freeze
|
|
64
|
+
|
|
65
|
+
def colorref_reserved(context)
|
|
66
|
+
colorref_findings(context) { |reserved| !PALETTE_RESERVED.include?(reserved) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def colorref_palette(context)
|
|
70
|
+
colorref_findings(context) { |reserved| PALETTE_RESERVED.include?(reserved) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def colorref_findings(context)
|
|
74
|
+
each_of(context, *COLOR_FIELDS.keys) do |record|
|
|
75
|
+
value = record.public_send(COLOR_FIELDS.fetch(record.class)).to_i
|
|
76
|
+
reserved = value >> COLORREF_RESERVED_SHIFT
|
|
77
|
+
next if reserved.zero? || !yield(reserved)
|
|
78
|
+
|
|
79
|
+
format("%<name>s ColorRef 0x%<value>08X has Reserved byte 0x%<reserved>02X, MUST be 0x00",
|
|
80
|
+
name: record.class.name.split("::").last, value: value, reserved: reserved)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def each_of(context, *classes)
|
|
85
|
+
context.records.each_with_index.filter_map do |record, index|
|
|
86
|
+
next unless classes.any? { |klass| record.instance_of?(klass) }
|
|
87
|
+
|
|
88
|
+
message = yield record
|
|
89
|
+
next unless message
|
|
90
|
+
|
|
91
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
[
|
|
97
|
+
["wmf.record.layout_size", :layout_size, :record_layout, WmfLayoutRules::KEPT,
|
|
98
|
+
"MS-WMF 2.3.3.7, 2.3.3.12, 2.3.3.19, 2.3.5.2, 2.3.5.3, 2.3.5.5, 2.3.5.8, 2.3.5.14, 2.3.5.18, 2.3.5.21, 2.3.5.25, " \
|
|
99
|
+
"2.3.5.26, 2.3.5.27 (RecordSize; the record lists no field after its last parameter)"],
|
|
100
|
+
["wmf.fixed.raster_operation", :raster_operation, :flag_mask, nil,
|
|
101
|
+
"MS-WMF 2.3.3.12 META_PATBLT (RasterOperation); 2.1.1.31 TernaryRasterOperation Enumeration"],
|
|
102
|
+
["wmf.fixed.colorref_reserved", :colorref_reserved, :reserved_zero, nil, WmfLayoutRules::COLORREF_CITATION],
|
|
103
|
+
["wmf.fixed.colorref_palette", :colorref_palette, :reserved_zero,
|
|
104
|
+
"GDI writes PALETTEINDEX and PALETTERGB colors with 0x01 or 0x02 in the Reserved byte, and readers resolve them",
|
|
105
|
+
WmfLayoutRules::COLORREF_CITATION]
|
|
106
|
+
].each do |id, method, kind, reason, citation|
|
|
107
|
+
Registry.loading.register(id: id, formats: WmfLayoutRules::WMF, citation: citation, kind: kind, tolerant_reason: reason) do |context|
|
|
108
|
+
WmfLayoutRules.public_send(method, context)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|