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,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# State record rules (MS-EMF 2.3.11): fixed sizes, enumeration domains, nonzero
|
|
4
|
+
# divisors, RESTOREDC sign, and the length fields of the profile and UFI records.
|
|
5
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
6
|
+
|
|
7
|
+
module Emf
|
|
8
|
+
module Conformance
|
|
9
|
+
module StateRules
|
|
10
|
+
EMF = %i[emf emf_plus].freeze
|
|
11
|
+
|
|
12
|
+
# type => Size the record section states ("This value is 0x...").
|
|
13
|
+
STATED_SIZE = { 16 => 12, 23 => 32, 34 => 12, 57 => 12, 98 => 12, 115 => 12 }.freeze
|
|
14
|
+
# type => Size implied by a record that has fixed fields only (no Size value is stated).
|
|
15
|
+
LAYOUT_SIZE = { 9 => 16, 10 => 16, 11 => 16, 12 => 16, 13 => 16, 17 => 12, 18 => 12, 19 => 12, 20 => 12, 21 => 12,
|
|
16
|
+
22 => 12, 24 => 12, 25 => 12, 27 => 16, 31 => 24, 32 => 24, 33 => 8, 52 => 8, 58 => 12, 104 => 48,
|
|
17
|
+
109 => 16, 120 => 16 }.freeze
|
|
18
|
+
# Types whose Size BELOW the layout is the error emf.opaque.fixed_size (any record, decoded or Raw); this table
|
|
19
|
+
# then only speaks of a Size above it, as a warning.
|
|
20
|
+
SHORT_BY_OPAQUE = [104, 109].freeze
|
|
21
|
+
SECTION = { 9 => "2.3.11.30", 10 => "2.3.11.31", 11 => "2.3.11.28", 12 => "2.3.11.29", 13 => "2.3.11.12",
|
|
22
|
+
16 => "2.3.11.20", 17 => "2.3.11.19", 18 => "2.3.11.11", 19 => "2.3.11.22", 20 => "2.3.11.23",
|
|
23
|
+
21 => "2.3.11.24", 22 => "2.3.11.25", 23 => "2.3.11.13", 24 => "2.3.11.26", 25 => "2.3.11.10",
|
|
24
|
+
27 => "2.3.11.4", 31 => "2.3.11.7", 32 => "2.3.11.8", 33 => "2.3.11", 34 => "2.3.11.6",
|
|
25
|
+
52 => "2.3.11", 57 => "2.3.11.9", 58 => "2.3.11.21", 98 => "2.3.11.14", 104 => "2.3.11.5",
|
|
26
|
+
109 => "2.3.11.2", 115 => "2.3.11.17", 120 => "2.3.11.27" }.freeze
|
|
27
|
+
|
|
28
|
+
TEXT_ALIGN_MASK = 0x11F # TA_UPDATECP, horizontal 0x6, vertical 0x18, TA_RTLREADING
|
|
29
|
+
HORIZONTAL_ALIGN = [0, 2, 6].freeze # TA_LEFT, TA_RIGHT, TA_CENTER
|
|
30
|
+
VERTICAL_ALIGN = [0, 8, 24].freeze # TA_TOP, TA_BOTTOM, TA_BASELINE
|
|
31
|
+
LAYOUT_BITS = 0x9 # LAYOUT_RTL, LAYOUT_BITMAPORIENTATIONPRESERVED (LAYOUT_LTR is 0)
|
|
32
|
+
|
|
33
|
+
TEXT_ALIGN_SWEEP = "spec/fixtures/emf test-039 and test-040 write every alignment value 0x0..0x16, including the " \
|
|
34
|
+
"undefined 0x4, 0x10 and combinations of them; each component is still read from its own bits"
|
|
35
|
+
|
|
36
|
+
# rule id => [type, wire field, valid?, enumeration label, record section, severity, tolerant reason]
|
|
37
|
+
ENUMS = {
|
|
38
|
+
"emf.state.map_mode" => [17, :map_mode, (1..8).method(:cover?), "MapMode (MM_TEXT..MM_ANISOTROPIC, 1..8)", "2.3.11.19", :error],
|
|
39
|
+
"emf.state.bk_mode" => [18, :bk_mode, (1..2).method(:cover?), "BackgroundMode (TRANSPARENT 1, OPAQUE 2)", "2.3.11.11", :error],
|
|
40
|
+
"emf.state.poly_fill_mode" => [19, :poly_fill_mode, (1..2).method(:cover?), "PolygonFillMode (ALTERNATE 1, WINDING 2)",
|
|
41
|
+
"2.3.11.22", :error],
|
|
42
|
+
"emf.state.rop2" => [20, :rop2, (1..16).method(:cover?), "Binary Raster Op (R2_BLACK..R2_WHITE, 1..16)", "2.3.11.23", :error],
|
|
43
|
+
"emf.state.stretch_mode" => [21, :stretch_mode, (1..4).method(:cover?), "StretchMode (1..4); the record says MAY", "2.3.11.24",
|
|
44
|
+
:warning],
|
|
45
|
+
"emf.state.arc_direction" => [57, :arc_direction, (1..2).method(:cover?), "ArcDirection (AD_COUNTERCLOCKWISE 1, AD_CLOCKWISE 2)",
|
|
46
|
+
"2.3.11.9", :error],
|
|
47
|
+
"emf.state.icm_mode" => [98, :mode, (1..4).method(:cover?), "ICMMode (ICM_OFF..ICM_DONE_OUTSIDEDC, 1..4)", "2.3.11.14", :error],
|
|
48
|
+
"emf.state.layout_mode" => [115, :mode, lambda { |v|
|
|
49
|
+
v.nobits?(~LAYOUT_BITS)
|
|
50
|
+
}, "LayoutMode flags (0x0, 0x1, 0x8)", "2.3.11.17", :error],
|
|
51
|
+
"emf.state.mapper_flags" => [16, :flags, [0, 1].method(:include?), "font mapper Flags (0 or 1)", "2.3.11.20", :warning],
|
|
52
|
+
"emf.state.text_align" => [22, :text_align_mode, lambda { |v|
|
|
53
|
+
v.nobits?(~TEXT_ALIGN_MASK) && HORIZONTAL_ALIGN.include?(v & 0x6) && VERTICAL_ALIGN.include?(v & 0x18)
|
|
54
|
+
}, "TextAlignmentMode flags (one horizontal and one vertical value, optional TA_UPDATECP and TA_RTLREADING)", "2.3.11.25", :error,
|
|
55
|
+
TEXT_ALIGN_SWEEP]
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
# rule id => [type, [wire fields], record section]
|
|
59
|
+
NONZERO = {
|
|
60
|
+
"emf.state.scale_viewport_ext" => [31, %i[x_num x_denom y_num y_denom], "2.3.11.7"],
|
|
61
|
+
"emf.state.scale_window_ext" => [32, %i[x_num x_denom y_num y_denom], "2.3.11.8"]
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
# type => [name field/prefix bytes, record section] for the cbName + cbData profile payload.
|
|
65
|
+
PROFILE = { 112 => [12, "2.3.11.15"], 113 => [12, "2.3.11.16"], 121 => [16, "2.3.11.1"] }.freeze
|
|
66
|
+
|
|
67
|
+
module_function
|
|
68
|
+
|
|
69
|
+
def each_wire(context, types)
|
|
70
|
+
context.records.each_with_index do |record, index|
|
|
71
|
+
next unless types.include?(context.record_type(record))
|
|
72
|
+
|
|
73
|
+
wire = record.wire
|
|
74
|
+
yield(record, index, wire) if wire.is_a?(Emf::Emr::Binary::Record)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def at(record, index, message)
|
|
79
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def size_findings(context, table, label)
|
|
83
|
+
found = []
|
|
84
|
+
each_wire(context, table.keys) do |record, index, _wire|
|
|
85
|
+
type = context.record_type(record)
|
|
86
|
+
size = context.record_size(record)
|
|
87
|
+
next if size == table.fetch(type)
|
|
88
|
+
next if size < table.fetch(type) && table.equal?(LAYOUT_SIZE) && SHORT_BY_OPAQUE.include?(type)
|
|
89
|
+
|
|
90
|
+
found << at(record, index, "#{RecordTypes.name(type)} (#{SECTION.fetch(type)}) Size is #{size}, #{label} #{table.fetch(type)}")
|
|
91
|
+
end
|
|
92
|
+
found
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def fixed_size(context) = size_findings(context, STATED_SIZE, "the record section states")
|
|
96
|
+
|
|
97
|
+
def layout_size(context) = size_findings(context, LAYOUT_SIZE, "its fixed fields occupy")
|
|
98
|
+
|
|
99
|
+
def enum_check(context, id)
|
|
100
|
+
type, field, valid, label, section, = ENUMS.fetch(id)
|
|
101
|
+
found = []
|
|
102
|
+
each_wire(context, [type]) do |record, index, wire|
|
|
103
|
+
value = wire.public_send(field).to_i
|
|
104
|
+
next if valid.call(value)
|
|
105
|
+
|
|
106
|
+
found << at(record, index, "#{RecordTypes.name(type)} (#{section}) #{field} 0x#{value.to_s(16)} is not a valid #{label}")
|
|
107
|
+
end
|
|
108
|
+
found
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def nonzero(context, id)
|
|
112
|
+
type, fields, section = NONZERO.fetch(id)
|
|
113
|
+
found = []
|
|
114
|
+
each_wire(context, [type]) do |record, index, wire|
|
|
115
|
+
zero = fields.select { |field| wire.public_send(field).to_i.zero? }
|
|
116
|
+
next if zero.empty?
|
|
117
|
+
|
|
118
|
+
found << at(record, index, "#{RecordTypes.name(type)} (#{section}) #{zero.join(', ')} MUST NOT be zero")
|
|
119
|
+
end
|
|
120
|
+
found
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def restoredc_negative(context)
|
|
124
|
+
found = []
|
|
125
|
+
each_wire(context, [34]) do |record, index, wire|
|
|
126
|
+
saved = wire.saved_dc.to_i
|
|
127
|
+
found << at(record, index, "EMR_RESTOREDC SavedDC is #{saved}, MUST be negative") unless saved.negative?
|
|
128
|
+
end
|
|
129
|
+
found
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def color_adjustment(context)
|
|
133
|
+
found = []
|
|
134
|
+
each_wire(context, [23]) do |record, index, wire|
|
|
135
|
+
# A body under 24 bytes is a Size under 32: emf.state.fixed_size already reports that.
|
|
136
|
+
next if wire.body.to_s.bytesize < 24
|
|
137
|
+
|
|
138
|
+
size = wire.adjustment_size
|
|
139
|
+
found << at(record, index, "ColorAdjustment Size is #{size}, MUST be 0x0018") unless size == 24
|
|
140
|
+
index_value = wire.illuminant_index
|
|
141
|
+
if index_value > 8
|
|
142
|
+
found << at(record, index,
|
|
143
|
+
"ColorAdjustment IlluminantIndex #{index_value} is not in the Illuminant enumeration (0..8)")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
found
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def pixel_format_version(context)
|
|
150
|
+
found = []
|
|
151
|
+
each_wire(context, [104]) do |record, index, wire|
|
|
152
|
+
next if wire.body.to_s.bytesize < 40
|
|
153
|
+
|
|
154
|
+
version = wire.pfd_version
|
|
155
|
+
found << at(record, index, "PixelFormatDescriptor nVersion is #{version}, MUST be 1") unless version == 1
|
|
156
|
+
end
|
|
157
|
+
found
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def color_match_target(context)
|
|
161
|
+
found = []
|
|
162
|
+
each_wire(context, [121]) do |record, index, wire|
|
|
163
|
+
next if wire.body.to_s.bytesize < 16
|
|
164
|
+
|
|
165
|
+
unless (1..3).cover?(wire.dw_action)
|
|
166
|
+
found << at(record, index,
|
|
167
|
+
"EMR_COLORMATCHTOTARGETW dwAction #{wire.dw_action} is not in the ColorSpace enumeration (1..3)")
|
|
168
|
+
end
|
|
169
|
+
unless (0..1).cover?(wire.dw_flags)
|
|
170
|
+
found << at(record, index,
|
|
171
|
+
"EMR_COLORMATCHTOTARGETW dwFlags #{wire.dw_flags} is not in the ColorMatchToTarget enumeration (0..1)")
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
found
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def profile_bounds(context)
|
|
178
|
+
found = []
|
|
179
|
+
each_wire(context, PROFILE.keys) do |record, index, wire|
|
|
180
|
+
prefix, section = PROFILE.fetch(context.record_type(record))
|
|
181
|
+
body = wire.body.to_s.bytesize
|
|
182
|
+
name = RecordTypes.name(context.record_type(record))
|
|
183
|
+
if body < prefix
|
|
184
|
+
next found << at(record, index,
|
|
185
|
+
"#{name} (#{section}) holds #{body} bytes after Size, fewer than its #{prefix}-byte fixed part")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
need = prefix + wire.cb_name + wire.cb_data
|
|
189
|
+
next unless need > body
|
|
190
|
+
|
|
191
|
+
found << at(record, index,
|
|
192
|
+
"#{name} (#{section}) cbName #{wire.cb_name} + cbData #{wire.cb_data} need #{need} bytes after Size, " \
|
|
193
|
+
"the record holds #{body}")
|
|
194
|
+
end
|
|
195
|
+
found
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def linked_ufis_bounds(context)
|
|
199
|
+
found = []
|
|
200
|
+
each_wire(context, [119]) do |record, index, wire|
|
|
201
|
+
body = wire.body.to_s.bytesize
|
|
202
|
+
next found << at(record, index, "EMR_SETLINKEDUFIS holds #{body} bytes after Size, fewer than uNumLinkedUFI") if body < 4
|
|
203
|
+
|
|
204
|
+
count = wire.u_num_linked_ufi
|
|
205
|
+
need = 12 + (count * 8) # uNumLinkedUFI + ufis + 8 reserved bytes
|
|
206
|
+
if need > body
|
|
207
|
+
found << at(record, index,
|
|
208
|
+
"EMR_SETLINKEDUFIS (2.3.11.18) uNumLinkedUFI #{count} needs #{need} bytes after Size, the record holds #{body}")
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
found
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
registry = Registry.loading
|
|
216
|
+
state = StateRules::EMF
|
|
217
|
+
|
|
218
|
+
registry.register(id: "emf.state.fixed_size", formats: state, citation: "MS-EMF 2.3.11", kind: :fixed_size, severity: :error) do |c|
|
|
219
|
+
StateRules.fixed_size(c)
|
|
220
|
+
end
|
|
221
|
+
registry.register(id: "emf.state.layout_size", formats: state, citation: "MS-EMF 2.3.11", kind: :layout_size, severity: :warning) do |c|
|
|
222
|
+
StateRules.layout_size(c)
|
|
223
|
+
end
|
|
224
|
+
StateRules::ENUMS.each do |id, (_type, _field, _valid, _label, section, severity, reason)|
|
|
225
|
+
registry.register(id: id, formats: state, citation: "MS-EMF #{section}", kind: id.split(".").last.to_sym, severity: severity,
|
|
226
|
+
tolerant_reason: reason) do |c|
|
|
227
|
+
StateRules.enum_check(c, id)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
StateRules::NONZERO.each do |id, (_type, _fields, section)|
|
|
231
|
+
registry.register(id: id, formats: state, citation: "MS-EMF #{section}", kind: :nonzero_divisor) { |c| StateRules.nonzero(c, id) }
|
|
232
|
+
end
|
|
233
|
+
{
|
|
234
|
+
"emf.state.restoredc_negative" => [:restoredc_negative, "MS-EMF 2.3.11.6", :saved_dc],
|
|
235
|
+
"emf.state.color_adjustment" => [:color_adjustment, "MS-EMF 2.2.2", :color_adjustment],
|
|
236
|
+
"emf.state.pixel_format_version" => [:pixel_format_version, "MS-EMF 2.2.22", :pixel_format_version],
|
|
237
|
+
"emf.state.color_match_target" => [:color_match_target, "MS-EMF 2.3.11.1", :color_match_target],
|
|
238
|
+
"emf.state.profile_bounds" => [:profile_bounds, "MS-EMF 2.3.11.15", :profile_bounds],
|
|
239
|
+
"emf.state.linked_ufis_bounds" => [:linked_ufis_bounds, "MS-EMF 2.3.11.18", :linked_ufis_bounds]
|
|
240
|
+
}.each do |id, (method, citation, kind)|
|
|
241
|
+
registry.register(id: id, formats: state, citation: citation, kind: kind) { |c| StateRules.public_send(method, c) }
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Text output rules (MS-EMF 2.2.5 EmrText, 2.3.5.7/.8 EXTTEXTOUT, 2.3.5.32/.33 POLYTEXTOUT,
|
|
4
|
+
# 2.3.5.37 SMALLTEXTOUT). Offsets, counts and option bits are read from the record's own
|
|
5
|
+
# bytes; the characters themselves are never decoded.
|
|
6
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
7
|
+
|
|
8
|
+
module Emf
|
|
9
|
+
module Conformance
|
|
10
|
+
module TextRules
|
|
11
|
+
EMF = %i[emf emf_plus].freeze
|
|
12
|
+
ETO_PDY = 0x2000
|
|
13
|
+
ETO_GLYPH_INDEX = 0x10
|
|
14
|
+
ETO_NO_RECT = 0x100
|
|
15
|
+
ETO_SMALL_CHARS = 0x200
|
|
16
|
+
# ExtTextOutOptions (MS-EMF 2.1.11).
|
|
17
|
+
DEFINED_OPTIONS = [0x2, 0x4, 0x10, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x10000].sum.freeze
|
|
18
|
+
|
|
19
|
+
# type => [name, wide characters?]
|
|
20
|
+
EXT = { 83 => ["EMR_EXTTEXTOUTA", false], 84 => ["EMR_EXTTEXTOUTW", true] }.freeze
|
|
21
|
+
POLY = { 96 => ["EMR_POLYTEXTOUTA", false], 97 => ["EMR_POLYTEXTOUTW", true] }.freeze
|
|
22
|
+
SMALL = 108
|
|
23
|
+
EXT_TEXT_AT = 36
|
|
24
|
+
POLY_TABLE_AT = 40
|
|
25
|
+
UNREADABLE = "the record Size still delimits the stream, so a string or Dx array that cannot be located is skipped by readers"
|
|
26
|
+
|
|
27
|
+
# id => [citation, kind, severity, tolerant_reason]
|
|
28
|
+
RULES = {
|
|
29
|
+
"emf.exttextout.string_bounds" => ["MS-EMF 2.2.5", :string_bounds, :error, UNREADABLE],
|
|
30
|
+
"emf.exttextout.string_offset_alignment" => ["MS-EMF 2.2.5", :string_alignment, :error, nil],
|
|
31
|
+
"emf.exttextout.dx_bounds" => ["MS-EMF 2.2.5", :dx_bounds, :error, UNREADABLE],
|
|
32
|
+
"emf.exttextout.dx_offset_alignment" => ["MS-EMF 2.2.5", :dx_alignment, :error, nil],
|
|
33
|
+
"emf.exttextout.options_defined" => ["MS-EMF 2.1.11", :exttextout_options, :warning, nil],
|
|
34
|
+
"emf.exttextout.string_encoding" => ["MS-EMF 2.2.5", :string_encoding, :error,
|
|
35
|
+
"readers decode a damaged UTF-16LE string character by character and still draw the rest"],
|
|
36
|
+
"emf.smalltextout.text_string" => ["MS-EMF 2.3.5.37", :small_text, :error, UNREADABLE],
|
|
37
|
+
"emf.smalltextout.options_defined" => ["MS-EMF 2.1.11", :smalltextout_options, :warning, nil],
|
|
38
|
+
"emf.polytextout.strings" => ["MS-EMF 2.3.5.32", :poly_strings, :error, UNREADABLE],
|
|
39
|
+
"emf.polytextout.dx" => ["MS-EMF 2.3.5.32", :poly_dx, :error, UNREADABLE],
|
|
40
|
+
"emf.polytextout.options_defined" => ["MS-EMF 2.1.11", :polytextout_options, :warning, nil],
|
|
41
|
+
"emf.opaque.text_arrays" => ["MS-EMF 2.3.5.32", :poly_table, :error, UNREADABLE]
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
# Findings of one metafile, grouped by rule id; computed once and shared by all rules.
|
|
45
|
+
CACHE = ObjectSpace::WeakMap.new
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
def findings(context, id)
|
|
50
|
+
# The WeakMap holds its values weakly: keep a strong reference until the lookup is done.
|
|
51
|
+
grouped = CACHE[context] || (CACHE[context] = analyze(context))
|
|
52
|
+
grouped.fetch(id, [])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def analyze(context)
|
|
56
|
+
out = Hash.new { |hash, id| hash[id] = [] }
|
|
57
|
+
context.records.each_with_index do |record, index|
|
|
58
|
+
type = context.record_type(record)
|
|
59
|
+
next unless EXT.key?(type) || POLY.key?(type) || type == SMALL
|
|
60
|
+
|
|
61
|
+
Scan.new(record, index, context.record_size(record), out).run(type)
|
|
62
|
+
end
|
|
63
|
+
out
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Option, string and Dx checks shared by the EXTTEXTOUT and POLYTEXTOUT descriptors; mixed into Scan.
|
|
67
|
+
module BufferChecks
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def check_options(id, label, options)
|
|
71
|
+
undefined = options & ~DEFINED_OPTIONS
|
|
72
|
+
return if undefined.zero?
|
|
73
|
+
|
|
74
|
+
add(id,
|
|
75
|
+
format("%<label>s Options 0x%<options>X sets bits outside ExtTextOutOptions (0x%<bits>X)",
|
|
76
|
+
label: label, options: options, bits: undefined))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def check_string(label, entry, wide, fixed_end, bounds_id, alignment_id)
|
|
80
|
+
width = wide ? 2 : 1
|
|
81
|
+
length = entry[:chars] * width
|
|
82
|
+
return if entry[:chars].zero?
|
|
83
|
+
|
|
84
|
+
if entry[:off_string] < fixed_end || entry[:off_string] + length > @size
|
|
85
|
+
add(bounds_id,
|
|
86
|
+
"#{label} string of #{entry[:chars]} chars at #{entry[:off_string]} is outside the record body (#{fixed_end}..#{@size})")
|
|
87
|
+
elsif (entry[:off_string] % width).positive?
|
|
88
|
+
add(alignment_id, "#{label} offString #{entry[:off_string]} is not #{width * 8}-bit aligned")
|
|
89
|
+
elsif wide && entry[:options].nobits?(ETO_GLYPH_INDEX) # glyph indexes are arbitrary 16-bit codes, not UTF-16
|
|
90
|
+
check_encoding(label, entry, length)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# UTF16-LE: an unpaired surrogate is not a character (MS-EMF 2.2.5, OutputString).
|
|
95
|
+
def check_encoding(label, entry, length)
|
|
96
|
+
text = @bytes.byteslice(entry[:off_string], length).force_encoding(Encoding::UTF_16LE)
|
|
97
|
+
return if text.valid_encoding?
|
|
98
|
+
|
|
99
|
+
add("emf.exttextout.string_encoding",
|
|
100
|
+
"#{label} string at offset #{entry[:off_string]} is not valid UTF-16LE (unpaired surrogate)")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def check_dx(label, entry, fixed_end, bounds_id, alignment_id)
|
|
104
|
+
return if entry[:chars].zero? || entry[:off_dx].zero?
|
|
105
|
+
|
|
106
|
+
length = entry[:chars] * (entry[:options].anybits?(ETO_PDY) ? 2 : 1) * 4
|
|
107
|
+
if entry[:off_dx] < fixed_end || entry[:off_dx] + length > @size
|
|
108
|
+
add(bounds_id,
|
|
109
|
+
"#{label} Dx array of #{length / 4} values at #{entry[:off_dx]} is outside the record body (#{fixed_end}..#{@size})")
|
|
110
|
+
elsif (entry[:off_dx] % 4).positive?
|
|
111
|
+
add(alignment_id, "#{label} offDx #{entry[:off_dx]} is not 32-bit aligned")
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# One record's checks; every method adds to `out[rule id]`.
|
|
117
|
+
class Scan
|
|
118
|
+
include BufferChecks
|
|
119
|
+
|
|
120
|
+
def initialize(record, index, size, out)
|
|
121
|
+
@record = record
|
|
122
|
+
@index = index
|
|
123
|
+
@out = out
|
|
124
|
+
@bytes = record.to_binary_s
|
|
125
|
+
@size = [size.to_i, @bytes.bytesize].min
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def run(type)
|
|
129
|
+
if EXT.key?(type)
|
|
130
|
+
ext_text(*EXT.fetch(type))
|
|
131
|
+
elsif POLY.key?(type)
|
|
132
|
+
poly_text(*POLY.fetch(type))
|
|
133
|
+
else
|
|
134
|
+
small_text
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
def add(id, message)
|
|
141
|
+
@out[id] << { message: message, offset: @record.offset, record_index: @index + 1, record_type: @record.type_id.to_i }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def u32(pos) = @bytes.unpack1("V", offset: pos)
|
|
145
|
+
|
|
146
|
+
# EmrText fixed part: Reference, Chars, offString, Options, [Rectangle], offDx.
|
|
147
|
+
# offDx is nil when the record ends before it; callers check `length` first.
|
|
148
|
+
def text_entry(start)
|
|
149
|
+
options = u32(start + 16)
|
|
150
|
+
length = options.anybits?(ETO_NO_RECT) ? 24 : 40
|
|
151
|
+
off_dx = u32(start + length - 4) if start + length <= @bytes.bytesize
|
|
152
|
+
{ chars: u32(start + 8), off_string: u32(start + 12), options: options, off_dx: off_dx, length: length }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def ext_text(name, wide)
|
|
156
|
+
return unless @size >= EXT_TEXT_AT + 24
|
|
157
|
+
|
|
158
|
+
entry = text_entry(EXT_TEXT_AT)
|
|
159
|
+
if @size < EXT_TEXT_AT + entry[:length]
|
|
160
|
+
return add("emf.exttextout.string_bounds",
|
|
161
|
+
"#{name} is shorter than its #{EXT_TEXT_AT + entry[:length]}-byte EmrText fixed part")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
check_options("emf.exttextout.options_defined", name, entry[:options])
|
|
165
|
+
check_string(name, entry, wide, EXT_TEXT_AT + entry[:length], "emf.exttextout.string_bounds",
|
|
166
|
+
"emf.exttextout.string_offset_alignment")
|
|
167
|
+
check_dx(name, entry, EXT_TEXT_AT + entry[:length], "emf.exttextout.dx_bounds", "emf.exttextout.dx_offset_alignment")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def poly_text(name, wide)
|
|
171
|
+
entries = poly_table(name) or return
|
|
172
|
+
|
|
173
|
+
entries.each_with_index do |entry, i|
|
|
174
|
+
label = "#{name} string #{i}"
|
|
175
|
+
check_options("emf.polytextout.options_defined", label, entry[:options])
|
|
176
|
+
check_string(label, entry, wide, @table_end, "emf.polytextout.strings", "emf.polytextout.strings")
|
|
177
|
+
check_dx(label, entry, @table_end, "emf.polytextout.dx", "emf.polytextout.dx")
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# cStrings descriptors, each 24 bytes (ETO_NO_RECT) or 40, must fit in the record; nil when they do not.
|
|
182
|
+
def poly_table(name)
|
|
183
|
+
count = u32(POLY_TABLE_AT - 4) if @size >= POLY_TABLE_AT
|
|
184
|
+
unless count
|
|
185
|
+
add("emf.opaque.text_arrays", "#{name} Size #{@size} is smaller than its 40-byte fixed part")
|
|
186
|
+
return nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
@table_end = POLY_TABLE_AT
|
|
190
|
+
(0...count).map do |i|
|
|
191
|
+
length = @table_end + 24 <= @size && (u32(@table_end + 16).anybits?(ETO_NO_RECT) ? 24 : 40)
|
|
192
|
+
if !length || @table_end + length > @size
|
|
193
|
+
add("emf.opaque.text_arrays", "#{name} cStrings #{count} descriptors run past the record at descriptor #{i}")
|
|
194
|
+
return nil
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
text_entry(@table_end).tap { @table_end += length }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def small_text
|
|
202
|
+
return unless @size >= 36
|
|
203
|
+
|
|
204
|
+
options = u32(20)
|
|
205
|
+
check_options("emf.smalltextout.options_defined", "EMR_SMALLTEXTOUT", options)
|
|
206
|
+
start = options.anybits?(ETO_NO_RECT) ? 36 : 52
|
|
207
|
+
length = u32(16) * (options.anybits?(ETO_SMALL_CHARS) ? 1 : 2)
|
|
208
|
+
return unless start + length > @size
|
|
209
|
+
|
|
210
|
+
add("emf.smalltextout.text_string",
|
|
211
|
+
"EMR_SMALLTEXTOUT TextString of #{length} bytes at offset #{start} runs past the #{@size}-byte record")
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
TextRules::RULES.each do |id, (citation, kind, severity, reason)|
|
|
217
|
+
Registry.loading.register(id: id, formats: TextRules::EMF, citation: citation, kind: kind, severity: severity,
|
|
218
|
+
tolerant_reason: reason) { |context| TextRules.findings(context, id) }
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Transform record rules (MS-EMF 2.3.12, 2.2.28): fixed sizes, the
|
|
4
|
+
# ModifyWorldTransformMode enumeration and the XForm matrix values.
|
|
5
|
+
return if Emf::Conformance::Registry.loaded_elsewhere?(__FILE__)
|
|
6
|
+
|
|
7
|
+
module Emf
|
|
8
|
+
module Conformance
|
|
9
|
+
module TransformRules
|
|
10
|
+
EMF = %i[emf emf_plus].freeze
|
|
11
|
+
SET_WORLD = 35
|
|
12
|
+
MODIFY_WORLD = 36
|
|
13
|
+
STATED_SIZE = { SET_WORLD => 32, MODIFY_WORLD => 36 }.freeze
|
|
14
|
+
MATRIX_FIELDS = %i[m11 m12 m21 m22 dx dy].freeze
|
|
15
|
+
MWT_IDENTITY = 1
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def each_wire(context)
|
|
20
|
+
context.records.each_with_index do |record, index|
|
|
21
|
+
next unless STATED_SIZE.key?(context.record_type(record))
|
|
22
|
+
|
|
23
|
+
wire = record.wire
|
|
24
|
+
yield(record, index, wire) if wire.is_a?(Emf::Emr::Binary::Record)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def at(record, index, message)
|
|
29
|
+
{ message: message, offset: record.offset, record_index: index + 1, record_type: record.type_id.to_i }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fixed_size(context)
|
|
33
|
+
found = []
|
|
34
|
+
each_wire(context) do |record, index, _wire|
|
|
35
|
+
type = context.record_type(record)
|
|
36
|
+
size = context.record_size(record)
|
|
37
|
+
next if size == STATED_SIZE.fetch(type)
|
|
38
|
+
|
|
39
|
+
found << at(record, index, "#{RecordTypes.name(type)} Size is #{size}, the record section states #{STATED_SIZE.fetch(type)}")
|
|
40
|
+
end
|
|
41
|
+
found
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def modify_mode(context)
|
|
45
|
+
found = []
|
|
46
|
+
each_wire(context) do |record, index, wire|
|
|
47
|
+
next unless context.record_type(record) == MODIFY_WORLD
|
|
48
|
+
|
|
49
|
+
mode = wire.modify_mode.to_i
|
|
50
|
+
next if (1..4).cover?(mode)
|
|
51
|
+
|
|
52
|
+
found << at(record, index,
|
|
53
|
+
"EMR_MODIFYWORLDTRANSFORM ModifyWorldTransformMode #{mode} is not in the enumeration (MWT_IDENTITY..MWT_SET, 1..4)")
|
|
54
|
+
end
|
|
55
|
+
found
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# MWT_IDENTITY ignores the transform data, so its matrix is not inspected.
|
|
59
|
+
def finite_matrix(context)
|
|
60
|
+
found = []
|
|
61
|
+
each_wire(context) do |record, index, wire|
|
|
62
|
+
next if context.record_type(record) == MODIFY_WORLD && wire.modify_mode.to_i == MWT_IDENTITY
|
|
63
|
+
|
|
64
|
+
bad = MATRIX_FIELDS.reject { |field| wire.xform.public_send(field).to_f.finite? }
|
|
65
|
+
next if bad.empty?
|
|
66
|
+
|
|
67
|
+
found << at(record, index, "#{RecordTypes.name(context.record_type(record))} XForm #{bad.join(', ')} is not a finite number")
|
|
68
|
+
end
|
|
69
|
+
found
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
registry = Registry.loading
|
|
74
|
+
transform = TransformRules::EMF
|
|
75
|
+
registry.register(id: "emf.transform.fixed_size", formats: transform, citation: "MS-EMF 2.3.12", kind: :fixed_size) do |c|
|
|
76
|
+
TransformRules.fixed_size(c)
|
|
77
|
+
end
|
|
78
|
+
registry.register(id: "emf.transform.modify_mode", formats: transform, citation: "MS-EMF 2.3.12.1", kind: :modify_mode) do |c|
|
|
79
|
+
TransformRules.modify_mode(c)
|
|
80
|
+
end
|
|
81
|
+
# The XForm section defines six FLOAT values and states no finiteness requirement.
|
|
82
|
+
registry.register(id: "emf.transform.finite_matrix", formats: transform, citation: "MS-EMF 2.2.28", kind: :finite_matrix,
|
|
83
|
+
severity: :warning) { |c| TransformRules.finite_matrix(c) }
|
|
84
|
+
end
|
|
85
|
+
end
|