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,320 @@
|
|
|
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+ payload rules over the public decoded records and objects: the Size and
|
|
8
|
+
# DataSize formulas of the text, image, serializable-object and terminal-server
|
|
9
|
+
# records, the fields those records fix, and the bounds of the object payloads
|
|
10
|
+
# the public model decodes (EmfPlusImage, EmfPlusFont, EmfPlusStringFormat, ...).
|
|
11
|
+
# Findings land on the record that carries the defect; undecodable records are
|
|
12
|
+
# read from their raw payload, so a count that runs past the record still gets a
|
|
13
|
+
# precise message next to emfplus.record.body_bounds.
|
|
14
|
+
module Emf
|
|
15
|
+
module Conformance
|
|
16
|
+
module EmfPlusPayloads
|
|
17
|
+
Records = Emf::EmfPlus::Records
|
|
18
|
+
Objects = Emf::EmfPlus::Objects
|
|
19
|
+
Support = EmfPlusSupport
|
|
20
|
+
|
|
21
|
+
HEADER = 12
|
|
22
|
+
ALIGN = 3 # a record may pad its data to 4 bytes (MS-EMFPLUS 2.3.3)
|
|
23
|
+
DRIVER_STRING_OPTIONS = 0x0000000F
|
|
24
|
+
OBJECT_TYPES = (1..9)
|
|
25
|
+
IMAGE_DATA_TYPES = (0..2)
|
|
26
|
+
BITMAP_DATA_TYPES = (0..1)
|
|
27
|
+
METAFILE_DATA_TYPES = (1..5)
|
|
28
|
+
TS_ENUMS = {
|
|
29
|
+
"AntiAliasMode (SmoothingMode)" => [:anti_alias_mode, (0..5)],
|
|
30
|
+
"TextRenderHint (TextRenderingHint)" => [:text_render_hint, (0..5)],
|
|
31
|
+
"CompositingMode" => [:compositing_mode, (0..1)],
|
|
32
|
+
"CompositingQuality" => [:compositing_quality, (1..5)],
|
|
33
|
+
"TextContrast" => [:text_contrast, (0..12)],
|
|
34
|
+
"FilterType" => [:filter_type, [0, 1, 2, 3, 4, 6, 7]],
|
|
35
|
+
"PixelOffset (PixelOffsetMode)" => [:pixel_offset, (0..4)]
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# The record-specific bytes after the 12-byte header, for typed and unknown records alike.
|
|
41
|
+
def payload(record) = record.to_binary.byteslice(HEADER..).to_s
|
|
42
|
+
|
|
43
|
+
def u32(payload, offset) = payload.byteslice(offset, 4)&.unpack1("V")
|
|
44
|
+
|
|
45
|
+
def name(record) = Support.type_name(record)
|
|
46
|
+
|
|
47
|
+
# Size and DataSize each in `minimum..minimum + 3` (the data may carry up to 3 padding bytes), or nil.
|
|
48
|
+
def padded_sizes(record, data_minimum)
|
|
49
|
+
size = Support.size_of(record)
|
|
50
|
+
data_size = Support.data_size_of(record)
|
|
51
|
+
problems = []
|
|
52
|
+
problems << "DataSize is #{data_size}, MUST be #{data_minimum} plus up to 3 padding bytes" unless
|
|
53
|
+
(data_minimum..(data_minimum + ALIGN)).cover?(data_size)
|
|
54
|
+
problems << "Size is #{size}, MUST be #{data_minimum + HEADER} plus up to 3 padding bytes" unless
|
|
55
|
+
((data_minimum + HEADER)..(data_minimum + HEADER + ALIGN)).cover?(size)
|
|
56
|
+
problems
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def exact_sizes(record, data_expected)
|
|
60
|
+
size = Support.size_of(record)
|
|
61
|
+
data_size = Support.data_size_of(record)
|
|
62
|
+
problems = []
|
|
63
|
+
problems << "DataSize is #{data_size}, MUST be #{data_expected}" unless data_size == data_expected
|
|
64
|
+
problems << "Size is #{size}, MUST be #{data_expected + HEADER}" unless size == data_expected + HEADER
|
|
65
|
+
problems
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def sized(located, problems, formula)
|
|
69
|
+
return if problems.empty?
|
|
70
|
+
|
|
71
|
+
"#{name(located.record)} #{problems.join(' and ')} (#{formula})"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# --- text -----------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def driver_string_size(located)
|
|
77
|
+
record = located.record
|
|
78
|
+
return unless record.type == Records::DrawDriverString::TYPE
|
|
79
|
+
|
|
80
|
+
data = payload(record)
|
|
81
|
+
count = u32(data, 12)
|
|
82
|
+
return unless count
|
|
83
|
+
|
|
84
|
+
matrix = u32(data, 8) == 1
|
|
85
|
+
minimum = (count * 10) + 16 + (matrix ? 24 : 0)
|
|
86
|
+
sized(located, padded_sizes(record, minimum),
|
|
87
|
+
"GlyphCount #{count}: DataSize = GlyphCount * 10 + #{matrix ? 40 : 16}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def driver_string_matrix(located)
|
|
91
|
+
return unless located.record.type == Records::DrawDriverString::TYPE
|
|
92
|
+
|
|
93
|
+
value = u32(payload(located.record), 8)
|
|
94
|
+
return if value.nil? || [0, 1].include?(value)
|
|
95
|
+
|
|
96
|
+
"DrawDriverString MatrixPresent is #{value}, MUST be 0 or 1"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def driver_string_options(located)
|
|
100
|
+
return unless located.record.type == Records::DrawDriverString::TYPE
|
|
101
|
+
|
|
102
|
+
value = u32(payload(located.record), 4)
|
|
103
|
+
return if value.nil? || value.nobits?(~DRIVER_STRING_OPTIONS)
|
|
104
|
+
|
|
105
|
+
format("DrawDriverString DriverStringOptionsFlags 0x%<value>08X sets bits outside the four DriverStringOptions flags",
|
|
106
|
+
value: value)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def draw_string_size(located)
|
|
110
|
+
record = located.record
|
|
111
|
+
return unless record.type == Records::DrawString::TYPE
|
|
112
|
+
|
|
113
|
+
length = u32(payload(record), 8)
|
|
114
|
+
return unless length
|
|
115
|
+
|
|
116
|
+
sized(located, padded_sizes(record, (length * 2) + 28), "Length #{length}: DataSize = Length * 2 + 28")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# --- images ---------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
def draw_image_size(located)
|
|
122
|
+
record = located.record
|
|
123
|
+
compressed = record.flags.anybits?(0x4000)
|
|
124
|
+
case record.type
|
|
125
|
+
when Records::DrawImage::TYPE
|
|
126
|
+
sized(located, exact_sizes(record, compressed ? 0x20 : 0x28), "the C flag selects EmfPlusRect or EmfPlusRectF")
|
|
127
|
+
when Records::DrawImagePoints::TYPE
|
|
128
|
+
sized(located, image_points_sizes(record, compressed), "the P and C flags select the point format")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# EmfPlusPointR elements are 1 or 2 bytes per coordinate, so for P the text's 0x24 is the writer's figure;
|
|
133
|
+
# a DataSize that matches the decoded points (padded or not) is accepted too.
|
|
134
|
+
def image_points_sizes(record, compressed)
|
|
135
|
+
return exact_sizes(record, compressed ? 0x28 : 0x34) unless record.flags.anybits?(0x0800)
|
|
136
|
+
return exact_sizes(record, 0x24) if Support.data_size_of(record) == 0x24 || !Support.typed?(record)
|
|
137
|
+
|
|
138
|
+
padded_sizes(record, record.body.num_bytes)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# --- serializable objects -------------------------------------------
|
|
142
|
+
|
|
143
|
+
def serializable_size(located)
|
|
144
|
+
record = located.record
|
|
145
|
+
return unless record.is_a?(Records::SerializableObject)
|
|
146
|
+
|
|
147
|
+
problems = exact_sizes(record, record.buffer_size + 0x14)
|
|
148
|
+
problems << "BufferSize #{record.buffer_size} is not a multiple of 4" unless (record.buffer_size % 4).zero?
|
|
149
|
+
sized(located, problems, "DataSize = BufferSize + 0x14, Size = BufferSize + 0x20")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def serializable_guid(located)
|
|
153
|
+
record = located.record
|
|
154
|
+
return unless record.is_a?(Records::SerializableObject)
|
|
155
|
+
return if Emf::EmfPlus::Effects.lookup(record.object_guid)
|
|
156
|
+
|
|
157
|
+
"SerializableObject ObjectGUID #{record.object_guid} is not an image effect identifier"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def serializable_buffer(located)
|
|
161
|
+
record = located.record
|
|
162
|
+
return unless record.is_a?(Records::SerializableObject) && Emf::EmfPlus::Effects.lookup(record.object_guid)
|
|
163
|
+
|
|
164
|
+
effect = record.effect
|
|
165
|
+
return unless effect.respond_to?(:trailing) && !effect.trailing.empty?
|
|
166
|
+
|
|
167
|
+
"SerializableObject Buffer holds #{effect.trailing.bytesize} bytes beyond its #{record.object_guid} effect parameter block"
|
|
168
|
+
rescue Emf::ParseError => e
|
|
169
|
+
"SerializableObject Buffer does not hold its effect parameter block: #{e.message}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# --- terminal server ------------------------------------------------
|
|
173
|
+
|
|
174
|
+
def ts_clip_size(located)
|
|
175
|
+
record = located.record
|
|
176
|
+
return unless record.is_a?(Records::SetTSClip)
|
|
177
|
+
|
|
178
|
+
count = record.rect_count
|
|
179
|
+
if record.compressed?
|
|
180
|
+
# The text fixes 4 bytes per rectangle and also describes 1-2 bytes per coordinate; only the shared lower bound holds.
|
|
181
|
+
return if Support.data_size_of(record) >= count * 4 && Support.size_of(record) >= (count * 4) + HEADER
|
|
182
|
+
|
|
183
|
+
"SetTSClip DataSize #{Support.data_size_of(record)} is below #{count * 4} (NumRects #{count} at 4 bytes each, C set)"
|
|
184
|
+
else
|
|
185
|
+
sized(located, exact_sizes(record, count * 8), "NumRects #{count}, 8 bytes per rectangle (C clear)")
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def ts_graphics_size(located)
|
|
190
|
+
record = located.record
|
|
191
|
+
return unless record.is_a?(Records::SetTSGraphics)
|
|
192
|
+
|
|
193
|
+
data_size = Support.data_size_of(record)
|
|
194
|
+
problems = exact_sizes(record, record.palette? ? data_size : 36)
|
|
195
|
+
problems.reject! { |problem| problem.start_with?("DataSize") } if record.palette?
|
|
196
|
+
sized(located, problems, record.palette? ? "T set: Size = 0x30 + the Palette size" : "T clear: DataSize 0x24, Size 0x30")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def ts_graphics_palette(located)
|
|
200
|
+
record = located.record
|
|
201
|
+
return unless record.is_a?(Records::SetTSGraphics) && record.palette?
|
|
202
|
+
return if exact_palette?(record.palette) && record.decoded_palette
|
|
203
|
+
|
|
204
|
+
"SetTSGraphics Palette of #{record.palette.bytesize} bytes is not exactly one EmfPlusPalette (8 + PaletteCount * 4 bytes)"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# PaletteCount sized the whole palette; check it before decoding so a huge count never reaches the array reader.
|
|
208
|
+
def exact_palette?(bytes) = bytes.bytesize >= 8 && bytes.bytesize == 8 + (bytes.unpack1("@4V") * 4)
|
|
209
|
+
|
|
210
|
+
def ts_graphics_enums(located)
|
|
211
|
+
record = located.record
|
|
212
|
+
return unless record.is_a?(Records::SetTSGraphics)
|
|
213
|
+
|
|
214
|
+
TS_ENUMS.filter_map do |label, (reader, domain)|
|
|
215
|
+
value = record.public_send(reader)
|
|
216
|
+
next if domain.include?(value)
|
|
217
|
+
|
|
218
|
+
"SetTSGraphics #{label} value #{value} is outside #{domain_text(domain)}"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def domain_text(domain) = domain.is_a?(Range) ? "#{domain.first}..#{domain.last}" : domain.join(", ")
|
|
223
|
+
|
|
224
|
+
# --- object payloads --------------------------------------------------
|
|
225
|
+
|
|
226
|
+
def object_type(located)
|
|
227
|
+
record = located.record
|
|
228
|
+
return unless record.is_a?(Records::Object) && !OBJECT_TYPES.cover?(record.object_type)
|
|
229
|
+
|
|
230
|
+
"EmfPlusObject ObjectType is #{record.object_type}, MUST be 1..9 (Brush..CustomLineCap)"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# The object this record completes (a chain's last chunk, or a single record), or nil.
|
|
234
|
+
def completed_object(stream, located)
|
|
235
|
+
record = located.record
|
|
236
|
+
return unless record.is_a?(Records::Object) && record.emf_object_id < Emf::EmfPlus::ObjectTable::SLOTS
|
|
237
|
+
|
|
238
|
+
index = located.handle.stream_index
|
|
239
|
+
object = stream.objects.lookup(record.emf_object_id, at: index + 1)
|
|
240
|
+
object unless object.nil? || object.equal?(stream.objects.lookup(record.emf_object_id, at: index))
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def object_payload(stream, located)
|
|
244
|
+
logical = completed_object(stream, located)
|
|
245
|
+
return unless logical
|
|
246
|
+
|
|
247
|
+
logical.body
|
|
248
|
+
nil
|
|
249
|
+
rescue Emf::ParseError => e
|
|
250
|
+
"EmfPlusObject (object type #{logical.object_type}, ID #{logical.emf_object_id}) payload does not fit: #{e.message}"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def image_fields(stream, located)
|
|
254
|
+
logical = completed_object(stream, located)
|
|
255
|
+
return unless logical&.object_type == Objects::IMAGE
|
|
256
|
+
|
|
257
|
+
body = logical.body
|
|
258
|
+
problems = image_problems(body)
|
|
259
|
+
problems.map { |problem| "EmfPlusImage (object ID #{logical.emf_object_id}) #{problem}" }
|
|
260
|
+
rescue Emf::ParseError
|
|
261
|
+
nil # reported by emfplus.object.payload
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def image_problems(image)
|
|
265
|
+
problems = []
|
|
266
|
+
problems << "Type #{image.image_type} is not an ImageDataType (0..2)" unless IMAGE_DATA_TYPES.cover?(image.image_type)
|
|
267
|
+
bitmap = image.bitmap
|
|
268
|
+
problems.concat(bitmap_problems(bitmap)) if bitmap
|
|
269
|
+
metafile = image.metafile
|
|
270
|
+
if metafile && !METAFILE_DATA_TYPES.cover?(metafile.metafile_type)
|
|
271
|
+
problems << "metafile Type #{metafile.metafile_type} is not a MetafileDataType (1..5)"
|
|
272
|
+
end
|
|
273
|
+
problems
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def bitmap_problems(bitmap)
|
|
277
|
+
problems = []
|
|
278
|
+
problems << "bitmap Type #{bitmap.bitmap_type} is not a BitmapDataType (0..1)" unless BITMAP_DATA_TYPES.cover?(bitmap.bitmap_type)
|
|
279
|
+
problems << "bitmap Stride #{bitmap.stride} is not a multiple of 4" if bitmap.bitmap_type.zero? && !(bitmap.stride % 4).zero?
|
|
280
|
+
problems
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def per_object(context, &)
|
|
284
|
+
stream = context.metafile.emf_plus_stream
|
|
285
|
+
return [] unless stream
|
|
286
|
+
|
|
287
|
+
Support.records(context).flat_map do |located|
|
|
288
|
+
Array(yield(stream, located)).map { |message| Support.finding(located, message) }
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
{
|
|
294
|
+
"emfplus.draw.driver_string" => ["MS-EMFPLUS 2.3.4.6", :driver_string_size, :driver_string_size],
|
|
295
|
+
"emfplus.draw.driver_string_matrix" => ["MS-EMFPLUS 2.3.4.6", :driver_string_matrix_present, :driver_string_matrix],
|
|
296
|
+
"emfplus.draw.driver_string_options" => ["MS-EMFPLUS 2.3.4.6", :driver_string_options, :driver_string_options],
|
|
297
|
+
"emfplus.draw.string" => ["MS-EMFPLUS 2.3.4.14", :draw_string_size, :draw_string_size],
|
|
298
|
+
"emfplus.draw.image" => ["MS-EMFPLUS 2.3.4", :draw_image_size, :draw_image_size],
|
|
299
|
+
"emfplus.object.serializable" => ["MS-EMFPLUS 2.3.5.2", :serializable_size, :serializable_size],
|
|
300
|
+
"emfplus.object.serializable_guid" => ["MS-EMFPLUS 2.3.5.2", :serializable_guid, :serializable_guid],
|
|
301
|
+
"emfplus.object.serializable_buffer" => ["MS-EMFPLUS 2.3.5.2", :serializable_buffer, :serializable_buffer],
|
|
302
|
+
"emfplus.terminal.clip" => ["MS-EMFPLUS 2.3.8.1", :ts_clip_size, :ts_clip_size],
|
|
303
|
+
"emfplus.terminal.graphics" => ["MS-EMFPLUS 2.3.8.2", :ts_graphics_size, :ts_graphics_size],
|
|
304
|
+
"emfplus.terminal.graphics_palette" => ["MS-EMFPLUS 2.3.8.2", :ts_graphics_palette, :ts_graphics_palette],
|
|
305
|
+
"emfplus.terminal.graphics_enums" => ["MS-EMFPLUS 2.3.8.2", :ts_graphics_enums, :ts_graphics_enums],
|
|
306
|
+
"emfplus.object.type" => ["MS-EMFPLUS 2.3.5.1", :object_type, :object_type]
|
|
307
|
+
}.each do |id, (citation, kind, check)|
|
|
308
|
+
EmfPlusSupport.per_record(id: id, citation: citation, kind: kind) { |located| EmfPlusPayloads.public_send(check, located) }
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
{
|
|
312
|
+
"emfplus.object.payload" => ["MS-EMFPLUS 2.3.5.1", :object_payload, :object_payload],
|
|
313
|
+
"emfplus.image.fields" => ["MS-EMFPLUS 2.2.1.4", :image_fields, :image_fields]
|
|
314
|
+
}.each do |id, (citation, kind, check)|
|
|
315
|
+
Registry.loading.register(id: id, formats: [:emf_plus], citation: citation, kind: kind, tolerant_reason: nil) do |context|
|
|
316
|
+
EmfPlusPayloads.per_object(context) { |stream, located| EmfPlusPayloads.public_send(check, stream, located) }
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
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+ property, clip combine-mode and page-unit rules: enum domains and ranges
|
|
8
|
+
# of values the public records decode from Flags.
|
|
9
|
+
module Emf
|
|
10
|
+
module Conformance
|
|
11
|
+
module EmfPlusProperties
|
|
12
|
+
Records = Emf::EmfPlus::Records
|
|
13
|
+
Support = EmfPlusSupport
|
|
14
|
+
|
|
15
|
+
# record class => [decoded reader, allowed values, enumeration name] (MS-EMFPLUS 2.1.1 enumerations).
|
|
16
|
+
ENUMS = {
|
|
17
|
+
Records::SetAntiAliasMode => [:smoothing_mode, (0..5), "SmoothingMode"],
|
|
18
|
+
Records::SetTextRenderingHint => [:text_rendering_hint, (0..5), "TextRenderingHint"],
|
|
19
|
+
Records::SetInterpolationMode => [:interpolation_mode, (0..7), "InterpolationMode"],
|
|
20
|
+
Records::SetPixelOffsetMode => [:pixel_offset_mode, (0..4), "PixelOffsetMode"],
|
|
21
|
+
Records::SetCompositingMode => [:compositing_mode, (0..1), "CompositingMode"],
|
|
22
|
+
Records::SetClipRect => [:combine_mode, (0..5), "CombineMode"],
|
|
23
|
+
Records::SetClipPath => [:combine_mode, (0..5), "CombineMode"],
|
|
24
|
+
Records::SetClipRegion => [:combine_mode, (0..5), "CombineMode"]
|
|
25
|
+
}.freeze
|
|
26
|
+
QUALITY = [:compositing_quality, (1..5), "CompositingQuality"].freeze
|
|
27
|
+
UNIT_TYPE = (0..6)
|
|
28
|
+
UNIT_WORLD_OR_DISPLAY = (0..1)
|
|
29
|
+
CONTRAST = (1000..2200)
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
def enum_domain(located, table = ENUMS)
|
|
34
|
+
record = located.record
|
|
35
|
+
reader, domain, name = table.find { |klass, _| record.is_a?(klass) }&.last
|
|
36
|
+
return unless reader
|
|
37
|
+
|
|
38
|
+
value = record.public_send(reader)
|
|
39
|
+
return if domain.cover?(value)
|
|
40
|
+
|
|
41
|
+
"#{Support.type_name(record)} #{name} value #{value} is outside #{domain.first}..#{domain.last}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def compositing_quality(located) = enum_domain(located, { Records::SetCompositingQuality => QUALITY })
|
|
45
|
+
|
|
46
|
+
def text_contrast(located)
|
|
47
|
+
record = located.record
|
|
48
|
+
return unless record.is_a?(Records::SetTextContrast) && !CONTRAST.cover?(record.text_contrast)
|
|
49
|
+
|
|
50
|
+
"EmfPlusSetTextContrast TextContrast #{record.text_contrast} is outside 1000..2200"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def page_unit_value(record)
|
|
54
|
+
case record
|
|
55
|
+
when Records::SetPageTransform, Records::BeginContainer then record.page_unit
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def page_unit_domain(located)
|
|
60
|
+
unit = page_unit_value(located.record)
|
|
61
|
+
return if unit.nil? || UNIT_TYPE.cover?(unit)
|
|
62
|
+
|
|
63
|
+
"#{Support.type_name(located.record)} PageUnit #{unit} is not a UnitType value (0..6)"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def page_unit_discouraged(located)
|
|
67
|
+
unit = page_unit_value(located.record)
|
|
68
|
+
return unless unit && UNIT_WORLD_OR_DISPLAY.cover?(unit)
|
|
69
|
+
|
|
70
|
+
"#{Support.type_name(located.record)} PageUnit #{unit} (UnitType#{unit.zero? ? 'World' : 'Display'}) SHOULD NOT be used"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
EmfPlusSupport.per_record(id: "emfplus.property.enum", citation: "MS-EMFPLUS 2.3.6", kind: :property_enum) do |located|
|
|
75
|
+
EmfPlusProperties.enum_domain(located)
|
|
76
|
+
end
|
|
77
|
+
# Only CompositingQuality is waived: real producers write 0 (spec/fixtures/emf/test-000.emf) and Windows treats any
|
|
78
|
+
# invalid value as CompositingQualityDefault (MS-EMFPLUS product behavior note 3).
|
|
79
|
+
EmfPlusSupport.per_record(id: "emfplus.property.compositing_quality", citation: "MS-EMFPLUS 2.3.6.4",
|
|
80
|
+
kind: :property_enum,
|
|
81
|
+
tolerant_reason: "a real producer writes CompositingQuality 0 (spec/fixtures/emf/test-000.emf) " \
|
|
82
|
+
"and Windows treats an invalid value as CompositingQualityDefault") do |located|
|
|
83
|
+
EmfPlusProperties.compositing_quality(located)
|
|
84
|
+
end
|
|
85
|
+
EmfPlusSupport.per_record(id: "emfplus.property.text_contrast", citation: "MS-EMFPLUS 2.3.6.7", kind: :text_contrast) do |located|
|
|
86
|
+
EmfPlusProperties.text_contrast(located)
|
|
87
|
+
end
|
|
88
|
+
EmfPlusSupport.per_record(id: "emfplus.transform.page_unit", citation: "MS-EMFPLUS 2.3.9", kind: :page_unit) do |located|
|
|
89
|
+
EmfPlusProperties.page_unit_domain(located)
|
|
90
|
+
end
|
|
91
|
+
EmfPlusSupport.per_record(id: "emfplus.transform.page_unit_discouraged", citation: "MS-EMFPLUS 2.3.9",
|
|
92
|
+
kind: :page_unit_discouraged, severity: :warning) do |located|
|
|
93
|
+
EmfPlusProperties.page_unit_discouraged(located)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
4
|
+
|
|
5
|
+
require_relative "emf_plus_common"
|
|
6
|
+
|
|
7
|
+
# Save/Restore and BeginContainer/EndContainer matching over the record sequence.
|
|
8
|
+
# Restore and EndContainer name a StackIndex a previous Save or Begin record
|
|
9
|
+
# assigned (MS-EMFPLUS 2.3.7); the two are separate stacks in the spec's wording.
|
|
10
|
+
module Emf
|
|
11
|
+
module Conformance
|
|
12
|
+
module EmfPlusState
|
|
13
|
+
Records = Emf::EmfPlus::Records
|
|
14
|
+
Support = EmfPlusSupport
|
|
15
|
+
|
|
16
|
+
# id => [opener classes, closer class, closer name, opener name, which defects the rule reports].
|
|
17
|
+
# :unknown is a StackIndex no earlier opener ever assigned; :stale is one that was assigned and is already
|
|
18
|
+
# popped (or skipped over). Only the Restore rule splits them; EndContainer reports both.
|
|
19
|
+
PAIRS = {
|
|
20
|
+
"emfplus.state.restore_unmatched" =>
|
|
21
|
+
[[Records::Save], Records::Restore, "EmfPlusRestore", "EmfPlusSave", :stale],
|
|
22
|
+
"emfplus.state.end_container_unmatched" =>
|
|
23
|
+
[[Records::BeginContainer, Records::BeginContainerNoParams], Records::EndContainer, "EmfPlusEndContainer",
|
|
24
|
+
"a begin-container record", :any]
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# LibreOffice's TestEmfPlusSave.emf restores StackIndex 0 before any EmfPlusSave. GDI+ fails such a restore and
|
|
28
|
+
# draws on, so the tolerant profile warns; a restore of a state that was saved and already popped stays an error.
|
|
29
|
+
UNKNOWN_RESTORE = "emfplus.state.restore_unknown_state"
|
|
30
|
+
UNKNOWN_RESTORE_REASON = "a Restore naming a StackIndex no earlier EmfPlusSave assigned fails in GDI+ and is " \
|
|
31
|
+
"ignored; the rest of the stream still renders"
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# Restoring (or ending) index N pops every level pushed after N's push, like GDI+.
|
|
36
|
+
def unmatched(context, opens, closer, closer_name, opener_name, report = :any)
|
|
37
|
+
stack = []
|
|
38
|
+
seen = {}
|
|
39
|
+
open_count = Hash.new(0)
|
|
40
|
+
Support.records(context).filter_map do |located|
|
|
41
|
+
record = located.record
|
|
42
|
+
if opens.any? { |klass| record.is_a?(klass) }
|
|
43
|
+
stack.push(record.stack_index)
|
|
44
|
+
open_count[record.stack_index] += 1
|
|
45
|
+
seen[record.stack_index] = true
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
next unless record.is_a?(closer)
|
|
49
|
+
|
|
50
|
+
# open_count keeps an unmatched index O(1) instead of a full-stack scan.
|
|
51
|
+
if open_count[record.stack_index].positive?
|
|
52
|
+
pop_to(stack, open_count, record.stack_index)
|
|
53
|
+
next
|
|
54
|
+
end
|
|
55
|
+
unknown = !seen.key?(record.stack_index)
|
|
56
|
+
next if (report == :unknown && !unknown) || (report == :stale && unknown)
|
|
57
|
+
|
|
58
|
+
Support.finding(located, "#{closer_name} StackIndex #{record.stack_index} matches no open #{opener_name}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def pop_to(stack, open_count, index)
|
|
63
|
+
stack.slice!(stack.rindex(index)..).each { |popped| open_count[popped] -= 1 }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
EmfPlusState::PAIRS.each do |id, args|
|
|
68
|
+
Registry.loading.register(id: id, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.7", kind: :state_matching,
|
|
69
|
+
tolerant_reason: nil) { |context| EmfPlusState.unmatched(context, *args) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
Registry.loading.register(id: EmfPlusState::UNKNOWN_RESTORE, formats: [:emf_plus], citation: "MS-EMFPLUS 2.3.7",
|
|
73
|
+
kind: :state_matching, tolerant_reason: EmfPlusState::UNKNOWN_RESTORE_REASON) do |context|
|
|
74
|
+
EmfPlusState.unmatched(context, *EmfPlusState::PAIRS.fetch("emfplus.state.restore_unmatched")[0..3], :unknown)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|