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,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
4
|
+
|
|
5
|
+
require_relative "emf_plus_common"
|
|
6
|
+
|
|
7
|
+
# Per-field enumeration and range rules for the decoded EMF+ graphics objects
|
|
8
|
+
# (MS-EMFPLUS 2.2.1). CHECKS is a table of object type => check lambda; each
|
|
9
|
+
# lambda gets the decoded object and returns the problems it finds. Only a
|
|
10
|
+
# clause that says a value "MUST" be, or "is defined in", an enumeration is an
|
|
11
|
+
# error here; the bounds of the payload are emfplus.object.payload's.
|
|
12
|
+
module Emf
|
|
13
|
+
module Conformance
|
|
14
|
+
module EmfPlusFields
|
|
15
|
+
Objects = Emf::EmfPlus::Objects
|
|
16
|
+
Support = EmfPlusSupport
|
|
17
|
+
|
|
18
|
+
# Enumerations, MS-EMFPLUS 2.1.1. LineCapType lists AnchorMask (0xF0) among its values, and a real producer writes it
|
|
19
|
+
# as an EndCap (LibreOffice emfio TestEmfPlusDrawLineWithCaps).
|
|
20
|
+
BRUSH_TYPE = (0..4)
|
|
21
|
+
HATCH_STYLE = (0..0x34)
|
|
22
|
+
WRAP_MODE = (0..4)
|
|
23
|
+
UNIT_TYPE = (0..6)
|
|
24
|
+
LINE_CAP = [0, 1, 2, 3, 0x10, 0x11, 0x12, 0x13, 0x14, 0xF0, 0xFF].freeze
|
|
25
|
+
LINE_JOIN = (0..3)
|
|
26
|
+
LINE_STYLE = (0..5)
|
|
27
|
+
DASHED_LINE_CAP = [0, 2, 3].freeze # DashedLineCapType has no value 1
|
|
28
|
+
PEN_ALIGNMENT = (0..4)
|
|
29
|
+
CUSTOM_LINE_CAP_TYPE = (0..1)
|
|
30
|
+
# Flags, MS-EMFPLUS 2.1.2: every defined bit.
|
|
31
|
+
BRUSH_DATA_FLAGS = 0x1DF # BrushData 0x01..0x10, 0x40, 0x80, 0x100 (0x20 is undefined)
|
|
32
|
+
PEN_DATA_FLAGS = 0x1FFF
|
|
33
|
+
CUSTOM_LINE_CAP_DATA_FLAGS = 0x3
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
def hex(value) = format("0x%X", value)
|
|
38
|
+
|
|
39
|
+
def in_domain(label, value, domain, name)
|
|
40
|
+
return if value.nil?
|
|
41
|
+
|
|
42
|
+
ok = domain.is_a?(Array) ? domain.include?(value) : domain.cover?(value)
|
|
43
|
+
return if ok
|
|
44
|
+
|
|
45
|
+
shown = domain.is_a?(Array) ? domain.map { |v| hex(v) }.join(", ") : "#{hex(domain.first)}..#{hex(domain.last)}"
|
|
46
|
+
"#{label} #{value} is not a #{name} value (#{shown})"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def flags_within(label, value, mask, name)
|
|
50
|
+
return if value.nil? || value.nobits?(~mask)
|
|
51
|
+
|
|
52
|
+
"#{label} #{hex(value)} sets bits outside the defined #{name} flags (#{hex(mask)})"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def zero_point(label, point)
|
|
56
|
+
return if point.nil? || (point.x.to_f.zero? && point.y.to_f.zero?)
|
|
57
|
+
|
|
58
|
+
"#{label} is {#{point.x}, #{point.y}}, MUST be {0.0, 0.0}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# --- Brush, Pen, CustomLineCap ------------------------------------------
|
|
62
|
+
|
|
63
|
+
def brush(object)
|
|
64
|
+
type = object.brush_type
|
|
65
|
+
problems = [in_domain("EmfPlusBrush Type", type, BRUSH_TYPE, "BrushType")]
|
|
66
|
+
data = object.data
|
|
67
|
+
case type
|
|
68
|
+
when Objects::Brush::HATCH
|
|
69
|
+
problems << in_domain("EmfPlusHatchBrushData HatchStyle", data.hatch_style, HATCH_STYLE, "HatchStyle")
|
|
70
|
+
when Objects::Brush::TEXTURE, Objects::Brush::PATH_GRADIENT, Objects::Brush::LINEAR_GRADIENT
|
|
71
|
+
problems << flags_within("BrushDataFlags", data.brush_data_flags, BRUSH_DATA_FLAGS, "BrushData")
|
|
72
|
+
problems << in_domain("WrapMode", data.wrap_mode, WRAP_MODE, "WrapMode")
|
|
73
|
+
end
|
|
74
|
+
problems
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def pen(object)
|
|
78
|
+
flags = object.pen_data_flags
|
|
79
|
+
[("EmfPlusPen Type is #{object.pen_type}, MUST be 0" unless object.pen_type.zero?),
|
|
80
|
+
flags_within("PenDataFlags", flags, PEN_DATA_FLAGS, "PenData"),
|
|
81
|
+
in_domain("PenUnit", object.pen_unit, UNIT_TYPE, "UnitType"),
|
|
82
|
+
in_domain("StartCap", object.start_cap, LINE_CAP, "LineCapType"),
|
|
83
|
+
in_domain("EndCap", object.end_cap, LINE_CAP, "LineCapType"),
|
|
84
|
+
in_domain("PenAlignment", object.pen_alignment, PEN_ALIGNMENT, "PenAlignment"),
|
|
85
|
+
*[object.custom_start_cap, object.custom_end_cap].compact.flat_map { |cap| custom_line_cap(cap) }]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Join, LineStyle and DashedLineCapType: corrupt-by-design LibreOffice fuzz regressions write garbage here and
|
|
89
|
+
# are expected to stay readable, so this group alone is waived in tolerant (see PEN_LINE_WAIVER).
|
|
90
|
+
def pen_line(object)
|
|
91
|
+
[in_domain("Join", object.join, LINE_JOIN, "LineJoinType"),
|
|
92
|
+
in_domain("LineStyle", object.line_style, LINE_STYLE, "LineStyle"),
|
|
93
|
+
in_domain("DashedLineCapType", object.dashed_line_cap, DASHED_LINE_CAP, "DashedLineCapType")]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def custom_line_cap(object)
|
|
97
|
+
data = object.data
|
|
98
|
+
problems = [in_domain("EmfPlusCustomLineCap Type", object.cap_type, CUSTOM_LINE_CAP_TYPE, "CustomLineCapDataType")]
|
|
99
|
+
case object.cap_type
|
|
100
|
+
when Objects::CustomLineCap::DEFAULT
|
|
101
|
+
problems.concat(custom_line_cap_default(data))
|
|
102
|
+
when Objects::CustomLineCap::ADJUSTABLE_ARROW
|
|
103
|
+
problems.concat(custom_line_cap_arrow(data))
|
|
104
|
+
end
|
|
105
|
+
problems
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def custom_line_cap_default(data)
|
|
109
|
+
[flags_within("CustomLineCapDataFlags", data.custom_line_cap_data_flags, CUSTOM_LINE_CAP_DATA_FLAGS, "CustomLineCapData"),
|
|
110
|
+
in_domain("BaseCap", data.base_cap, LINE_CAP, "LineCapType"),
|
|
111
|
+
in_domain("StrokeStartCap", data.stroke_start_cap, LINE_CAP, "LineCapType"),
|
|
112
|
+
in_domain("StrokeEndCap", data.stroke_end_cap, LINE_CAP, "LineCapType"),
|
|
113
|
+
in_domain("StrokeJoin", data.stroke_join, LINE_JOIN, "LineJoinType"),
|
|
114
|
+
zero_point("FillHotSpot", data.fill_hot_spot), zero_point("StrokeHotSpot", data.stroke_hot_spot)]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def custom_line_cap_arrow(data)
|
|
118
|
+
[in_domain("LineStartCap", data.line_start_cap, LINE_CAP, "LineCapType"),
|
|
119
|
+
in_domain("LineEndCap", data.line_end_cap, LINE_CAP, "LineCapType"),
|
|
120
|
+
in_domain("LineJoin", data.line_join, LINE_JOIN, "LineJoinType"),
|
|
121
|
+
zero_point("FillHotSpot", data.fill_hot_spot), zero_point("LineHotSpot", data.line_hot_spot)]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Object type => check. Each group below owns its own entries.
|
|
125
|
+
CHECKS = {
|
|
126
|
+
# Brush, Pen, CustomLineCap
|
|
127
|
+
Objects::BRUSH => method(:brush),
|
|
128
|
+
Objects::PEN => method(:pen),
|
|
129
|
+
Objects::CUSTOM_LINE_CAP => method(:custom_line_cap),
|
|
130
|
+
# Path, Region, Font, StringFormat, ImageAttributes
|
|
131
|
+
**[Objects::PATH, Objects::REGION, Objects::FONT, Objects::STRING_FORMAT, Objects::IMAGE_ATTRIBUTES]
|
|
132
|
+
.to_h { |type| [type, ->(object) { EmfPlusFieldsB.findings_for(object) }] }
|
|
133
|
+
}.freeze
|
|
134
|
+
|
|
135
|
+
# Same shape as CHECKS, for the fields a real corpus file violates.
|
|
136
|
+
WAIVED_CHECKS = {
|
|
137
|
+
# Brush, Pen, CustomLineCap
|
|
138
|
+
Objects::PEN => method(:pen_line)
|
|
139
|
+
}.freeze
|
|
140
|
+
|
|
141
|
+
PEN_LINE_WAIVER = "fuzz-regression files LibreOffice ships as readable inputs write out-of-domain pen Join / " \
|
|
142
|
+
"DashedLineCapType values (vcl graphicfilter emf_fail_crash-2, emf_pass_crash-1, emf_fail_hang-1, " \
|
|
143
|
+
"emf_fail_hang-2); importers ignore them and keep drawing"
|
|
144
|
+
|
|
145
|
+
def object_fields(stream, located, table = CHECKS)
|
|
146
|
+
logical = EmfPlusPayloads.completed_object(stream, located)
|
|
147
|
+
check = logical && table[logical.object_type]
|
|
148
|
+
return unless check
|
|
149
|
+
|
|
150
|
+
label = "EmfPlusObject (object type #{logical.object_type}, ID #{logical.emf_object_id})"
|
|
151
|
+
check.call(logical.body).compact.map { |problem| "#{label} #{problem}" }
|
|
152
|
+
rescue Emf::ParseError
|
|
153
|
+
nil # reported by emfplus.object.payload
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
Registry.loading.register(id: "emfplus.object.fields", formats: [:emf_plus], citation: "MS-EMFPLUS 2.2.1",
|
|
158
|
+
kind: :object_fields, tolerant_reason: nil) do |context|
|
|
159
|
+
EmfPlusPayloads.per_object(context) { |stream, located| EmfPlusFields.object_fields(stream, located) }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
Registry.loading.register(id: "emfplus.object.pen_line_fields", formats: [:emf_plus], citation: "MS-EMFPLUS 2.2.2.34",
|
|
163
|
+
kind: :object_pen_line, tolerant_reason: EmfPlusFields::PEN_LINE_WAIVER) do |context|
|
|
164
|
+
EmfPlusPayloads.per_object(context) do |stream, located|
|
|
165
|
+
EmfPlusFields.object_fields(stream, located, EmfPlusFields::WAIVED_CHECKS)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
4
|
+
|
|
5
|
+
# Field-domain checks of the Path, Region, Font, StringFormat and ImageAttributes graphics objects
|
|
6
|
+
# (MS-EMFPLUS 2.2.1.3, 2.2.1.5, 2.2.1.6, 2.2.1.8, 2.2.1.9). No rule is registered here: the shared
|
|
7
|
+
# "emfplus.object.fields" rule (emf_plus_fields.rb) calls findings_for(object) for each completed object.
|
|
8
|
+
# Only MUST-level domains are reported. SHOULD-level text (HotkeyPrefix/StringFormat flag descriptions,
|
|
9
|
+
# "Windows never uses UnitTypeDisplay" in Font SizeUnit) is not an error and is not reported.
|
|
10
|
+
module Emf
|
|
11
|
+
module Conformance
|
|
12
|
+
module EmfPlusFieldsB
|
|
13
|
+
Objects = Emf::EmfPlus::Objects
|
|
14
|
+
|
|
15
|
+
UNIT_TYPE = (0..6) # 2.1.1.32; 2.2.1.3 SizeUnit "is in the UnitType enumeration"
|
|
16
|
+
FONT_STYLE_MASK = 0xF # 2.1.2.4: Bold, Italic, Underline, Strikeout
|
|
17
|
+
PATH_POINT_TYPES = [0x0, 0x1, 0x3].freeze # 2.1.1.22: Start, Line, Bezier
|
|
18
|
+
PATH_POINT_FLAGS_MASK = 0x0B # 2.1.2.6 (high nibble): DashMode 0x1, PathMarker 0x2, CloseSubpath 0x8
|
|
19
|
+
WRAP_MODE = (0..4) # 2.1.1.33
|
|
20
|
+
OBJECT_CLAMP = (0..1) # 2.2.1.5 "MUST be one of": RectClamp, BitmapClamp
|
|
21
|
+
STRING_ALIGNMENT = (0..2) # 2.1.1.28
|
|
22
|
+
DIGIT_SUBSTITUTION = (0..3) # 2.1.1.29
|
|
23
|
+
HOTKEY_PREFIX = (0..2) # 2.1.1.14
|
|
24
|
+
STRING_TRIMMING = (0..5) # 2.1.1.30
|
|
25
|
+
# 2.1.2.8: DirectionRightToLeft, DirectionVertical, NoFitBlackBox, DisplayFormatControl, NoFontFallback,
|
|
26
|
+
# MeasureTrailingSpaces, NoWrap, LineLimit, NoClip, BypassGDI.
|
|
27
|
+
STRING_FORMAT_FLAGS_MASK = 0x1 | 0x2 | 0x4 | 0x20 | 0x400 | 0x800 | 0x1000 | 0x2000 | 0x4000 | 0x80000000
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# `object` is a completed logical object (object_type, body) or an already decoded Objects::Typed.
|
|
32
|
+
def findings_for(object)
|
|
33
|
+
typed = object.is_a?(Objects::Typed) ? object : object.body
|
|
34
|
+
case typed
|
|
35
|
+
when Objects::Path then path(typed)
|
|
36
|
+
when Objects::Region then region(typed)
|
|
37
|
+
when Objects::Font then font(typed)
|
|
38
|
+
when Objects::StringFormat then string_format(typed)
|
|
39
|
+
when Objects::ImageAttributes then image_attributes(typed)
|
|
40
|
+
else []
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def domain(label, value, allowed)
|
|
45
|
+
return [] if allowed.cover?(value)
|
|
46
|
+
|
|
47
|
+
["#{label} #{value} is outside #{allowed.first}..#{allowed.last}"]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def path(path)
|
|
51
|
+
types = path.point_types.flat_map { |entry| entry.respond_to?(:point_type) ? [entry.point_type] : [entry] }
|
|
52
|
+
types.flat_map { |type| path_point_type(type) }.uniq.map { |message| "EmfPlusPath #{message}" }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def path_point_type(type)
|
|
56
|
+
messages = []
|
|
57
|
+
messages << "PathPointType #{type.type} is not Start (0), Line (1) or Bezier (3)" unless PATH_POINT_TYPES.include?(type.type)
|
|
58
|
+
unless type.flags.nobits?(~PATH_POINT_FLAGS_MASK)
|
|
59
|
+
messages << "PathPointType flags 0x#{format('%X', type.flags)} hold bits other than DashMode, PathMarker and CloseSubpath"
|
|
60
|
+
end
|
|
61
|
+
messages
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# 2.2.1.8: RegionNode is an array of RegionNodeCount + 1 nodes; the tree read from the data must hold exactly that many.
|
|
65
|
+
def region(region)
|
|
66
|
+
expected = region.node_count + 1
|
|
67
|
+
actual = region.root.node_count
|
|
68
|
+
return [] if actual == expected
|
|
69
|
+
|
|
70
|
+
["EmfPlusRegion RegionNodeCount #{region.node_count} declares #{expected} nodes but the node tree holds #{actual}"]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def font(font)
|
|
74
|
+
messages = domain("EmfPlusFont SizeUnit", font.size_unit, UNIT_TYPE)
|
|
75
|
+
unless font.style_flags.nobits?(~FONT_STYLE_MASK)
|
|
76
|
+
messages << "EmfPlusFont FontStyleFlags 0x#{format('%X',
|
|
77
|
+
font.style_flags & 0xFFFFFFFF)} sets undefined bits"
|
|
78
|
+
end
|
|
79
|
+
messages
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def string_format(object)
|
|
83
|
+
body = object.body
|
|
84
|
+
messages = []
|
|
85
|
+
messages.concat(domain("EmfPlusStringFormat StringAlignment", body.string_alignment, STRING_ALIGNMENT))
|
|
86
|
+
messages.concat(domain("EmfPlusStringFormat LineAlign", body.line_align, STRING_ALIGNMENT))
|
|
87
|
+
messages.concat(domain("EmfPlusStringFormat DigitSubstitution", body.digit_substitution, DIGIT_SUBSTITUTION))
|
|
88
|
+
messages.concat(domain("EmfPlusStringFormat HotkeyPrefix", body.hotkey_prefix, HOTKEY_PREFIX))
|
|
89
|
+
messages.concat(domain("EmfPlusStringFormat Trimming", body.trimming, STRING_TRIMMING))
|
|
90
|
+
unless object.flags.nobits?(~STRING_FORMAT_FLAGS_MASK)
|
|
91
|
+
messages << "EmfPlusStringFormat StringFormatFlags 0x#{format('%X', object.flags)} sets bits that are not StringFormat flags"
|
|
92
|
+
end
|
|
93
|
+
messages
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def image_attributes(attributes)
|
|
97
|
+
domain("EmfPlusImageAttributes WrapMode", attributes.wrap_mode, WRAP_MODE) +
|
|
98
|
+
domain("EmfPlusImageAttributes ObjectClamp", attributes.object_clamp, OBJECT_CLAMP)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
4
|
+
|
|
5
|
+
require_relative "emf_plus_common"
|
|
6
|
+
|
|
7
|
+
# EMF+ framing and control rules beyond emf_plus.rb: fixed Size/DataSize values,
|
|
8
|
+
# DataSize consistency, undecodable bodies, the Header signature and
|
|
9
|
+
# EndOfFile placement.
|
|
10
|
+
module Emf
|
|
11
|
+
module Conformance
|
|
12
|
+
module EmfPlusFraming
|
|
13
|
+
Records = Emf::EmfPlus::Records
|
|
14
|
+
Support = EmfPlusSupport
|
|
15
|
+
|
|
16
|
+
# DataSize each record type MUST have (Size is then 12 + DataSize), as stated in MS-EMFPLUS 2.3.x.
|
|
17
|
+
FIXED_DATA_SIZE = {
|
|
18
|
+
Records::OffsetClip => 8, Records::ResetClip => 0, Records::SetClipPath => 0, Records::SetClipRect => 16,
|
|
19
|
+
Records::SetClipRegion => 0, Records::EndOfFile => 0, Records::GetDC => 0, Records::Header => 16,
|
|
20
|
+
Records::Clear => 4, Records::DrawPath => 4, Records::FillPath => 4, Records::FillRegion => 4,
|
|
21
|
+
Records::SetAntiAliasMode => 0, Records::SetCompositingMode => 0, Records::SetCompositingQuality => 0,
|
|
22
|
+
Records::SetInterpolationMode => 0, Records::SetPixelOffsetMode => 0, Records::SetRenderingOrigin => 8,
|
|
23
|
+
Records::SetTextContrast => 0, Records::SetTextRenderingHint => 0, Records::BeginContainer => 36,
|
|
24
|
+
Records::BeginContainerNoParams => 4, Records::EndContainer => 4, Records::Restore => 4, Records::Save => 4,
|
|
25
|
+
Records::MultiplyWorldTransform => 24, Records::ResetWorldTransform => 0, Records::RotateWorldTransform => 4,
|
|
26
|
+
Records::ScaleWorldTransform => 8, Records::SetPageTransform => 4, Records::SetWorldTransform => 24,
|
|
27
|
+
Records::TranslateWorldTransform => 8
|
|
28
|
+
}.transform_keys { |klass| klass::TYPE }.freeze
|
|
29
|
+
# The spec gives SetAntiAliasMode a DataSize only, not a Size.
|
|
30
|
+
SIZE_UNSPECIFIED = [Records::SetAntiAliasMode::TYPE].freeze
|
|
31
|
+
|
|
32
|
+
# Records whose Size and DataSize a formula rule states exactly: type => [rule module, check method]. Where that
|
|
33
|
+
# check names a DataSize problem, the generic emfplus.record.data_size and emfplus.record.body_bounds stay silent.
|
|
34
|
+
FORMULA_OWNERS = {
|
|
35
|
+
Records::DrawEllipse::TYPE => %i[EmfPlusDrawing ellipse_size], Records::FillEllipse::TYPE => %i[EmfPlusDrawing ellipse_size],
|
|
36
|
+
Records::DrawDriverString::TYPE => %i[EmfPlusPayloads driver_string_size],
|
|
37
|
+
Records::DrawString::TYPE => %i[EmfPlusPayloads draw_string_size],
|
|
38
|
+
Records::DrawImage::TYPE => %i[EmfPlusPayloads draw_image_size],
|
|
39
|
+
Records::DrawImagePoints::TYPE => %i[EmfPlusPayloads draw_image_size],
|
|
40
|
+
Records::SerializableObject::TYPE => %i[EmfPlusPayloads serializable_size],
|
|
41
|
+
Records::SetTSClip::TYPE => %i[EmfPlusPayloads ts_clip_size], Records::SetTSGraphics::TYPE => %i[EmfPlusPayloads ts_graphics_size]
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
module_function
|
|
45
|
+
|
|
46
|
+
def formula_owned?(located)
|
|
47
|
+
owner, check = FORMULA_OWNERS[located.record.type]
|
|
48
|
+
owner && Conformance.const_get(owner).public_send(check, located).to_s.include?("DataSize")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def fixed_size(located)
|
|
52
|
+
record = located.record
|
|
53
|
+
expected = FIXED_DATA_SIZE[record.type]
|
|
54
|
+
return unless expected
|
|
55
|
+
|
|
56
|
+
return if page_transform_bodyless?(record)
|
|
57
|
+
|
|
58
|
+
size = Support.size_of(record)
|
|
59
|
+
data_size = Support.data_size_of(record)
|
|
60
|
+
problems = []
|
|
61
|
+
problems << "DataSize is #{data_size}, MUST be #{expected}" unless data_size == expected
|
|
62
|
+
problems << "Size is #{size}, MUST be #{expected + 12}" unless size == expected + 12 || SIZE_UNSPECIFIED.include?(record.type)
|
|
63
|
+
"#{Support.type_name(record)} #{problems.join(' and ')}" unless problems.empty?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The exact body-less SetPageTransform: Size 12, DataSize 0 (the spec says Size 16, DataSize 4).
|
|
67
|
+
def page_transform_bodyless?(record)
|
|
68
|
+
record.type == Records::SetPageTransform::TYPE && Support.data_size_of(record).zero? && Support.size_of(record) == 12
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def page_transform_bodyless(located)
|
|
72
|
+
record = located.record
|
|
73
|
+
return unless page_transform_bodyless?(record)
|
|
74
|
+
|
|
75
|
+
"#{Support.type_name(record)} has Size 12 and DataSize 0, MUST be Size 16 and DataSize 4 (its PageScale is missing)"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# A defined record whose fields (padded to 4) decode to fewer bytes than DataSize leaves bytes unaccounted for.
|
|
79
|
+
# DataSize is 32-bit aligned (MS-EMFPLUS 2.3.3), so padding after an odd-length field is not a surplus.
|
|
80
|
+
def data_size_mismatch(located)
|
|
81
|
+
record = located.record
|
|
82
|
+
return unless Support.typed?(record) && !FIXED_DATA_SIZE.key?(record.type)
|
|
83
|
+
|
|
84
|
+
used = record.body.num_bytes
|
|
85
|
+
# An unaligned DataSize is emfplus.record.data_size_alignment's; compare what it rounds up to.
|
|
86
|
+
return if ((record.data_size + 3) & ~3) == ((used + 3) & ~3) || formula_owned?(located)
|
|
87
|
+
|
|
88
|
+
"#{Support.type_name(record)} DataSize is #{record.data_size} but its fields occupy #{used} bytes"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def data_size_alignment(located)
|
|
92
|
+
data_size = Support.data_size_of(located.record)
|
|
93
|
+
return if (data_size % 4).zero?
|
|
94
|
+
|
|
95
|
+
"#{Support.type_name(located.record)} DataSize #{data_size} is not a multiple of 4"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def undecodable(located)
|
|
99
|
+
record = located.record
|
|
100
|
+
return if Support.typed?(record) || !Records.registry.key?(record.type) || formula_owned?(located)
|
|
101
|
+
|
|
102
|
+
"#{Support.type_name(record)} body does not fit in its DataSize #{record.frame.data_size} " \
|
|
103
|
+
"(a count or length field runs past the record)"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def header_signature(located)
|
|
107
|
+
record = located.record
|
|
108
|
+
return unless record.is_a?(Records::Header) && !record.valid_signature?
|
|
109
|
+
|
|
110
|
+
format("EmfPlusHeader MetafileSignature is 0x%05X, MUST be 0xDBC01", record.graphics_signature)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def eof_findings(context)
|
|
114
|
+
located = Support.records(context)
|
|
115
|
+
return [] if located.empty?
|
|
116
|
+
|
|
117
|
+
eof_at = located.rindex { |item| item.record.is_a?(Records::EndOfFile) }
|
|
118
|
+
return [missing_eof(located.last)] unless eof_at
|
|
119
|
+
|
|
120
|
+
eof = located.fetch(eof_at)
|
|
121
|
+
problems = []
|
|
122
|
+
problems << "records follow it in the stream" if eof_at < located.size - 1
|
|
123
|
+
problems << "its EMR_COMMENT is not the record immediately before EMR_EOF" unless last_comment?(context, eof)
|
|
124
|
+
if problems.empty?
|
|
125
|
+
[]
|
|
126
|
+
else
|
|
127
|
+
[Support.finding(eof,
|
|
128
|
+
"EmfPlusEndOfFile MUST be the last EMF+ record, in the EMF record before EMR_EOF; #{problems.join(' and ')}")]
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def missing_eof(last)
|
|
133
|
+
Support.finding(last, "no EmfPlusEndOfFile: the last EMF+ record MUST be EmfPlusEndOfFile")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def last_comment?(context, eof)
|
|
137
|
+
context.records.size >= 2 && eof.comment_index == context.records.size - 2
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
registry = Registry.loading
|
|
142
|
+
EmfPlusSupport.per_record(id: "emfplus.record.fixed_size", citation: "MS-EMFPLUS 2.3", kind: :record_fixed_size) do |located|
|
|
143
|
+
EmfPlusFraming.fixed_size(located)
|
|
144
|
+
end
|
|
145
|
+
EmfPlusSupport.per_record(id: "emfplus.record.page_transform_bodyless", citation: "MS-EMFPLUS 2.3",
|
|
146
|
+
kind: :record_page_transform_bodyless,
|
|
147
|
+
tolerant_reason: "LibreOffice's TestAlignRtlReading and tdf93750 carry a body-less SetPageTransform and " \
|
|
148
|
+
"import; the record is kept with its bytes intact") do |located|
|
|
149
|
+
EmfPlusFraming.page_transform_bodyless(located)
|
|
150
|
+
end
|
|
151
|
+
EmfPlusSupport.per_record(id: "emfplus.record.data_size", citation: "MS-EMFPLUS 2.3", kind: :record_data_size,
|
|
152
|
+
tolerant_reason: "the surplus bytes stay in the record's trailing data, so nothing is dropped") do |located|
|
|
153
|
+
EmfPlusFraming.data_size_mismatch(located)
|
|
154
|
+
end
|
|
155
|
+
EmfPlusSupport.per_record(id: "emfplus.record.data_size_alignment", citation: "MS-EMFPLUS 2.3.3",
|
|
156
|
+
kind: :record_data_size_alignment,
|
|
157
|
+
tolerant_reason: "DataSize is informational once Size is aligned; the record frames and decodes") do |located|
|
|
158
|
+
EmfPlusFraming.data_size_alignment(located)
|
|
159
|
+
end
|
|
160
|
+
EmfPlusSupport.per_record(id: "emfplus.record.body_bounds", citation: "MS-EMFPLUS 2.3", kind: :record_body_bounds,
|
|
161
|
+
tolerant_reason: "the record is kept as an unknown record with its bytes intact") do |located|
|
|
162
|
+
EmfPlusFraming.undecodable(located)
|
|
163
|
+
end
|
|
164
|
+
EmfPlusSupport.per_record(id: "emfplus.header.signature", citation: "MS-EMFPLUS 2.2.2.19", kind: :header_signature) do |located|
|
|
165
|
+
EmfPlusFraming.header_signature(located)
|
|
166
|
+
end
|
|
167
|
+
registry.register(id: "emfplus.eof.placement", formats: [:emf_plus], citation: "MS-EMFPLUS 1.3", kind: :eof_placement,
|
|
168
|
+
tolerant_reason: "records after the EMF+ end of file stay readable and are kept") do |context|
|
|
169
|
+
EmfPlusFraming.eof_findings(context)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
4
|
+
|
|
5
|
+
require_relative "emf_plus_common"
|
|
6
|
+
|
|
7
|
+
# Turns the public object-resolution results (Stream#resolve -> ObjectTable::Resolution)
|
|
8
|
+
# into diagnostics at the referring record. The resolver itself is not copied.
|
|
9
|
+
# Object-definition problems (incomplete chains, definition IDs out of range) are
|
|
10
|
+
# ObjectTable#errors, which emf_plus.rb already reports as emfplus.stream.framing.
|
|
11
|
+
module Emf
|
|
12
|
+
module Conformance
|
|
13
|
+
module EmfPlusObjects
|
|
14
|
+
Support = EmfPlusSupport
|
|
15
|
+
|
|
16
|
+
OPTIONAL_REFERENCE = "the referenced object (StringFormatId, ImageAttributesID, a clip Region) is optional or absent and " \
|
|
17
|
+
"producers write 0xFFFFFFFF, 0 or a never-defined slot for none; importers skip it and keep drawing " \
|
|
18
|
+
"(corpus: LibreOffice emfio TestDrawStringTransparent, TestDrawImagePointsTypeBitmap, " \
|
|
19
|
+
"TestDrawStringWithBrush, cppcanvas fdo77229, graphicfilter pass_fdo38580-3)"
|
|
20
|
+
|
|
21
|
+
KINDS = {
|
|
22
|
+
empty_slot: ["emfplus.object.empty_slot", "refers to an object slot with no object defined before this record"],
|
|
23
|
+
wrong_type: ["emfplus.object.wrong_type", "refers to a slot holding an object of a different type"],
|
|
24
|
+
object_id_out_of_range: ["emfplus.object.id_range", "has an object ID outside 0..63"]
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# Optional references (MS-EMFPLUS: StringFormatId, ImageAttributesID; the clip Region of SetClipRegion as
|
|
28
|
+
# written by the corpus producers). Every other role (pen, brush, path, font, image, fill region) is mandatory.
|
|
29
|
+
OPTIONAL_ROLES = %i[string_format image_attributes].freeze
|
|
30
|
+
|
|
31
|
+
OPTIONAL_KINDS = {
|
|
32
|
+
empty_slot: ["emfplus.object.optional_empty_slot",
|
|
33
|
+
"refers to an optional object slot with no object defined before this record"],
|
|
34
|
+
object_id_out_of_range: ["emfplus.object.optional_id_range",
|
|
35
|
+
"has an optional object reference with an ID outside 0..63"]
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# A SetClipRegion naming a never-defined slot is a measured producer pattern (LibreOffice fdo38580-3); an
|
|
41
|
+
# out-of-range id is not, and MS-EMFPLUS 2.3.1.5 limits the Region ObjectID to 0..63, so it stays mandatory.
|
|
42
|
+
def optional?(located, resolution)
|
|
43
|
+
return true if OPTIONAL_ROLES.include?(resolution.role)
|
|
44
|
+
|
|
45
|
+
located.record.is_a?(Emf::EmfPlus::Records::SetClipRegion) && resolution.diagnostic&.kind == :empty_slot
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def findings(context, kind, optional: false)
|
|
49
|
+
stream = context.metafile.emf_plus_stream
|
|
50
|
+
return [] unless stream
|
|
51
|
+
|
|
52
|
+
Support.records(context).flat_map do |located|
|
|
53
|
+
stream.resolve(located.handle).filter_map do |resolution|
|
|
54
|
+
next unless resolution.diagnostic&.kind == kind
|
|
55
|
+
next unless kind == :wrong_type || optional?(located, resolution) == optional
|
|
56
|
+
|
|
57
|
+
Support.finding(located, "#{Support.type_name(located.record)} #{resolution.role} reference " \
|
|
58
|
+
"#{resolution.raw_id} #{(optional ? OPTIONAL_KINDS : KINDS).fetch(kind).last}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# MS-EMFPLUS 2.3.4.9: an E-flag DrawImagePoints needs an Effect specified in an earlier
|
|
65
|
+
# SerializableObject. Stream#effect_for is the public link (the immediately preceding record).
|
|
66
|
+
Registry.loading.register(id: "emfplus.object.effect_missing", formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.4.9",
|
|
67
|
+
kind: :effect_link, tolerant_reason: nil) do |context|
|
|
68
|
+
stream = context.metafile.emf_plus_stream
|
|
69
|
+
next [] unless stream
|
|
70
|
+
|
|
71
|
+
located_records = EmfPlusSupport.records(context)
|
|
72
|
+
located_records.each_with_index.filter_map do |located, index|
|
|
73
|
+
# Type and flags come from the record frame, so an E-flag draw whose body does not decode is still checked.
|
|
74
|
+
record = located.record
|
|
75
|
+
draw = Emf::EmfPlus::Records::DrawImagePoints
|
|
76
|
+
next unless record.type == draw::TYPE && record.flags.anybits?(draw::EFFECT_FLAG)
|
|
77
|
+
next if stream.effect_for(located.handle)
|
|
78
|
+
# The clause says "an earlier" SerializableObject, not the immediately preceding one.
|
|
79
|
+
next if located_records.first(index).any? { |earlier| earlier.record.type == Emf::EmfPlus::Records::SerializableObject::TYPE }
|
|
80
|
+
|
|
81
|
+
EmfPlusSupport.finding(located, "DrawImagePoints has the effect flag but no SerializableObject record precedes it")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
EmfPlusObjects::KINDS.each do |kind, (id, _)|
|
|
86
|
+
Registry.loading.register(id: id, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.4", kind: kind,
|
|
87
|
+
tolerant_reason: nil) { |context| EmfPlusObjects.findings(context, kind) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
EmfPlusObjects::OPTIONAL_KINDS.each do |kind, (id, _)|
|
|
91
|
+
Registry.loading.register(id: id, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.4", kind: :"optional_#{kind}",
|
|
92
|
+
tolerant_reason: EmfPlusObjects::OPTIONAL_REFERENCE) do |context|
|
|
93
|
+
EmfPlusObjects.findings(context, kind, optional: true)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|