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,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Emr
|
|
5
|
+
# The lazy source of a fixed-layout or point-array record that is only its
|
|
6
|
+
# bytes: a WireAdapter over it holds the frozen input and the record's
|
|
7
|
+
# span, and makes no BinData wire until `#wire` is asked for. `#values`
|
|
8
|
+
# reads the bytes directly. Materialising reads the span with the same
|
|
9
|
+
# FastFill the eager parse uses, so the wire is the one a parse makes.
|
|
10
|
+
class FilledRecord
|
|
11
|
+
EMPTY_SUFFIX = "".b.freeze
|
|
12
|
+
private_constant :EMPTY_SUFFIX
|
|
13
|
+
|
|
14
|
+
# The source for the record at `offset` of the frozen binary `data`,
|
|
15
|
+
# or nil when it needs the eager parse: no fixed-layout filler, a span
|
|
16
|
+
# shorter than the layout, a point count past the span, or a header
|
|
17
|
+
# disagreeing with the span. The caller has checked the span against
|
|
18
|
+
# `data`.
|
|
19
|
+
def self.for(type_id, offset, length, data)
|
|
20
|
+
filler = Binary::FastFill.for(type_id) || Binary::FastFill.for_points(type_id)
|
|
21
|
+
return unless filler && length >= filler.size && filler.fits?(data, offset, length)
|
|
22
|
+
|
|
23
|
+
new(filler, data, offset, length, type_id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(filler, data, offset, length, type_id)
|
|
27
|
+
@filler = filler
|
|
28
|
+
@data = data
|
|
29
|
+
@offset = offset
|
|
30
|
+
@length = length
|
|
31
|
+
@type_id = type_id
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
attr_reader :length
|
|
35
|
+
|
|
36
|
+
# The Ruby parser has always answered a BinData Uint32le here (the wire's
|
|
37
|
+
# i_type), so a byte-backed record keeps that type.
|
|
38
|
+
def type_id
|
|
39
|
+
BinData::Uint32le.new(@type_id)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_binary_s
|
|
43
|
+
@data.byteslice(@offset, @length)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def append_to(out)
|
|
47
|
+
out << to_binary_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def wire_class
|
|
51
|
+
@filler.wire_class
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# [frozen binary data, record offset, untouched]: the bytes the wire is
|
|
55
|
+
# filled from, and no wire exists yet.
|
|
56
|
+
def value_source
|
|
57
|
+
[@data, @offset, true]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def materialize
|
|
61
|
+
if (wire = @filler.exact_wire(@data, @offset, @length))
|
|
62
|
+
@suffix = EMPTY_SUFFIX
|
|
63
|
+
return wire
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
wire, @suffix = @filler.call_at(@data, @offset, @length)
|
|
67
|
+
raise FormatError, "record disagrees with its bytes" unless wire
|
|
68
|
+
|
|
69
|
+
wire
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def suffix(_wire)
|
|
73
|
+
@suffix
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/emf/emr/parser.rb
CHANGED
|
@@ -7,38 +7,71 @@ module Emf
|
|
|
7
7
|
module Parser
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
|
-
def call(bytes)
|
|
11
|
-
|
|
10
|
+
def call(bytes, deferred_admission: nil)
|
|
11
|
+
ResourceLimits.check!(:input_bytes, MAX_INPUT_BYTES, bytes.bytesize)
|
|
12
12
|
raise FormatError, "not an EMF file (signature mismatch)" unless Emf::Detector.call(bytes) == :emf
|
|
13
13
|
|
|
14
14
|
io = StringIO.new(bytes, "rb")
|
|
15
15
|
header_wire = read_header(io)
|
|
16
16
|
header = Model::Emr::Header.from_wire(header_wire)
|
|
17
|
+
data = bytes.b.freeze
|
|
18
|
+
pos = io.pos
|
|
17
19
|
|
|
18
20
|
records = []
|
|
19
21
|
errors = []
|
|
22
|
+
diagnostics = []
|
|
20
23
|
emf_plus_bytes = +""
|
|
21
|
-
trailing =
|
|
24
|
+
trailing = "".b
|
|
25
|
+
|
|
26
|
+
size = data.bytesize
|
|
27
|
+
while pos < size
|
|
28
|
+
offset = pos
|
|
29
|
+
if (source = filled_record_at(data, offset, size))
|
|
30
|
+
records << Model::Emr::Records::WireAdapter.lazy(source: source, offset: offset)
|
|
31
|
+
pos = offset + source.length
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
deferred_admission = admit_deferred(data, offset, deferred_admission)
|
|
36
|
+
|
|
37
|
+
wire, suffix = read_record_at(data, offset, errors, diagnostics: diagnostics, record_index: records.length + 1)
|
|
38
|
+
if wire.nil?
|
|
39
|
+
trailing << bytes.byteslice(offset..)
|
|
40
|
+
break
|
|
41
|
+
end
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
offset = io.pos
|
|
25
|
-
wire = read_record(io, offset, errors)
|
|
26
|
-
break if wire.nil?
|
|
43
|
+
pos = offset + data.unpack1("V", offset: offset + 4)
|
|
27
44
|
|
|
28
|
-
records << Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset)
|
|
29
|
-
emf_plus_bytes <<
|
|
45
|
+
records << Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset, suffix: suffix)
|
|
46
|
+
emf_plus_bytes << comment_emf_plus(wire)
|
|
30
47
|
|
|
31
48
|
if wire.is_a?(Emf::Emr::Binary::Records::Eof)
|
|
32
|
-
trailing <<
|
|
49
|
+
trailing << data.byteslice(pos..) if pos < data.bytesize
|
|
33
50
|
break
|
|
34
51
|
end
|
|
35
52
|
end
|
|
36
53
|
|
|
54
|
+
metafile(header, records, errors, emf_plus_bytes, trailing, diagnostics)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# PolyText can charge the same bytes repeatedly before rejecting
|
|
58
|
+
# overlapping descriptors. It has no FilledRecord fast path.
|
|
59
|
+
def admit_deferred(data, offset, deferred)
|
|
60
|
+
return unless deferred
|
|
61
|
+
|
|
62
|
+
type = data.unpack1("V", offset: offset)
|
|
63
|
+
return deferred unless [96, 97].include?(type)
|
|
64
|
+
|
|
65
|
+
ResourceLimits.preflight(data)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def metafile(header, records, errors, emf_plus_bytes, trailing, diagnostics = [])
|
|
37
69
|
Model::Metafile.new(
|
|
38
70
|
format: emf_plus_bytes.empty? ? :emf : :emf_plus,
|
|
39
71
|
header: header,
|
|
40
72
|
records: records,
|
|
41
73
|
errors: errors,
|
|
74
|
+
diagnostics: diagnostics,
|
|
42
75
|
emf_plus: emf_plus_bytes.empty? ? nil : emf_plus_bytes,
|
|
43
76
|
trailing: trailing
|
|
44
77
|
)
|
|
@@ -60,6 +93,8 @@ module Emf
|
|
|
60
93
|
end
|
|
61
94
|
|
|
62
95
|
Emf::Emr::Binary::Header.read(full)
|
|
96
|
+
rescue IOError
|
|
97
|
+
raise FormatError, "EMF header nSize #{n_size} cannot hold its fields"
|
|
63
98
|
end
|
|
64
99
|
|
|
65
100
|
# Per-type byte-size validation for records with variable-length arrays.
|
|
@@ -67,6 +102,9 @@ module Emf
|
|
|
67
102
|
# corrupt and we should preserve them as Raw rather than letting bindata
|
|
68
103
|
# allocate a giant array. The element size is bytes per array entry.
|
|
69
104
|
VARIABLE_ARRAY_RECORDS = {
|
|
105
|
+
Binary::TypeCodes::EXTCREATEPEN => { count_offset: 48, element_size: 4 },
|
|
106
|
+
Binary::TypeCodes::CREATEPALETTE => { count_offset: 14, count_width: 2, element_size: 4 },
|
|
107
|
+
Binary::TypeCodes::SETPALETTEENTRIES => { count_offset: 16, element_size: 4 },
|
|
70
108
|
Binary::TypeCodes::POLYGON => { count_offset: 24, element_size: 8 },
|
|
71
109
|
Binary::TypeCodes::POLYLINE => { count_offset: 24, element_size: 8 },
|
|
72
110
|
Binary::TypeCodes::POLYBEZIER => { count_offset: 24, element_size: 8 },
|
|
@@ -93,14 +131,39 @@ module Emf
|
|
|
93
131
|
# Bounds the whole-file parse: a 100 MB EMF is unusual; refuse bigger.
|
|
94
132
|
MAX_INPUT_BYTES = 200 * 1024 * 1024
|
|
95
133
|
|
|
96
|
-
|
|
134
|
+
REGION_DATA_OFFSETS = { 71 => 32, 72 => 40, 73 => 28, 74 => 28, 75 => 16 }.freeze
|
|
135
|
+
|
|
136
|
+
# The checks read the record in place: `base` is its offset in `data`
|
|
137
|
+
# (0 for a record already sliced), `n_size` its length, so no slice or
|
|
138
|
+
# string is made per checked record.
|
|
139
|
+
def variable_array_sane?(type_id, n_size, record_bytes)
|
|
140
|
+
sane_at?(type_id, n_size, record_bytes, 0)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def sane_at?(type_id, n_size, data, base)
|
|
144
|
+
region_data_sane?(type_id, n_size, data, base) &&
|
|
145
|
+
sized_arrays_sane?(type_id, n_size, data, base) &&
|
|
146
|
+
ext_text_out_sane?(type_id, n_size, data, base)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def region_data_sane?(type_id, n_size, data, base)
|
|
150
|
+
data_offset = REGION_DATA_OFFSETS[type_id]
|
|
151
|
+
return true unless data_offset
|
|
152
|
+
|
|
153
|
+
length_offset = type_id == 75 ? 8 : 24
|
|
154
|
+
return false if n_size < length_offset + 4
|
|
155
|
+
|
|
156
|
+
data.unpack1(UINT32, offset: base + length_offset) <= n_size - data_offset
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def sized_arrays_sane?(type_id, n_size, data, base)
|
|
97
160
|
spec = VARIABLE_ARRAY_RECORDS[type_id]
|
|
98
161
|
return true unless spec
|
|
99
162
|
|
|
100
|
-
|
|
101
|
-
return true if
|
|
163
|
+
count_width = spec.fetch(:count_width, 4)
|
|
164
|
+
return true if n_size < spec[:count_offset] + count_width
|
|
102
165
|
|
|
103
|
-
count =
|
|
166
|
+
count = data.unpack1(count_width == 2 ? UINT16 : UINT32, offset: base + spec[:count_offset])
|
|
104
167
|
return false if count > MAX_SANE_ARRAY_LENGTH
|
|
105
168
|
|
|
106
169
|
# For single-array records, element_size is the per-entry size.
|
|
@@ -108,77 +171,203 @@ module Emf
|
|
|
108
171
|
# are always uint32 (4 bytes); element_size applies only to the
|
|
109
172
|
# second array (aptl/apts).
|
|
110
173
|
first_array_entry_size = spec[:count2_offset] ? 4 : spec[:element_size]
|
|
111
|
-
bytes_needed = spec[:count_offset] +
|
|
174
|
+
bytes_needed = spec[:count_offset] + count_width + (count * first_array_entry_size)
|
|
112
175
|
return false if bytes_needed > n_size
|
|
113
176
|
|
|
114
177
|
# Multi-array records also need to validate the second count.
|
|
115
178
|
return true unless spec[:count2_offset]
|
|
116
179
|
|
|
117
|
-
count2 =
|
|
180
|
+
count2 = data.unpack1(UINT32, offset: base + spec[:count2_offset])
|
|
118
181
|
return false if count2 > MAX_SANE_ARRAY_LENGTH
|
|
119
182
|
|
|
120
183
|
bytes_needed + (count2 * spec[:element_size]) <= n_size
|
|
121
184
|
end
|
|
122
185
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
186
|
+
UINT32 = "V"
|
|
187
|
+
UINT16 = "v"
|
|
188
|
+
ETO_NO_RECT = 0x100
|
|
189
|
+
ETO_PDY = 0x2000
|
|
190
|
+
EXT_TEXT_OUT_CHAR_WIDTH = {
|
|
191
|
+
Binary::TypeCodes::EXTTEXTOUTA => 1,
|
|
192
|
+
Binary::TypeCodes::EXTTEXTOUTW => 2
|
|
193
|
+
}.freeze
|
|
194
|
+
|
|
195
|
+
# Types whose declared counts or spans are checked against nSize.
|
|
196
|
+
CHECKED_TYPES = [REGION_DATA_OFFSETS, VARIABLE_ARRAY_RECORDS, EXT_TEXT_OUT_CHAR_WIDTH]
|
|
197
|
+
.flat_map(&:keys).to_h { |type_id| [type_id, true] }.freeze
|
|
198
|
+
|
|
199
|
+
# Mirrors the native decoder: the string and Dx spans implied by nChars
|
|
200
|
+
# must lie inside the record, after the fixed fields, and not overlap.
|
|
201
|
+
def ext_text_out_sane?(type_id, n_size, data, base)
|
|
202
|
+
width = EXT_TEXT_OUT_CHAR_WIDTH[type_id]
|
|
203
|
+
return true unless width
|
|
204
|
+
|
|
205
|
+
spans, fixed = ext_text_out_spans(n_size, data, base, width)
|
|
206
|
+
return false unless spans
|
|
207
|
+
|
|
208
|
+
spans.all? { |off, len, align| span_inside?(off, len, align, fixed, n_size) } &&
|
|
209
|
+
(spans.size < 2 || !spans_overlap?(*spans))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def ext_text_out_spans(n_size, data, base, width)
|
|
213
|
+
return if n_size < 60
|
|
214
|
+
|
|
215
|
+
count = data.unpack1(UINT32, offset: base + 44)
|
|
216
|
+
off_string = data.unpack1(UINT32, offset: base + 48)
|
|
217
|
+
options = data.unpack1(UINT32, offset: base + 52)
|
|
218
|
+
dx_at = options.anybits?(ETO_NO_RECT) ? 56 : 72
|
|
219
|
+
return if n_size < dx_at + 4
|
|
220
|
+
|
|
221
|
+
off_dx = data.unpack1(UINT32, offset: base + dx_at)
|
|
222
|
+
dx_entry = options.anybits?(ETO_PDY) ? 8 : 4
|
|
223
|
+
string = [off_string, count * width, width] unless off_string.zero? && count.zero?
|
|
224
|
+
dx = [off_dx, count * dx_entry, 4] unless off_dx.zero?
|
|
225
|
+
[[string, dx].compact, dx_at + 4]
|
|
128
226
|
end
|
|
129
227
|
|
|
228
|
+
def span_inside?(off, len, align, fixed, n_size)
|
|
229
|
+
(off % align).zero? && off >= fixed && off + len <= n_size
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def spans_overlap?(first, second)
|
|
233
|
+
first[0] < second[0] + second[1] && second[0] < first[0] + first[1]
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def short_header?(available, offset, errors)
|
|
237
|
+
return false if available >= 8
|
|
238
|
+
|
|
239
|
+
errors << ParseError.new(offset: offset, record_code: nil,
|
|
240
|
+
message: "truncated record header at offset #{offset} (#{available} bytes)")
|
|
241
|
+
true
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Reads the record at `offset` of the stream and advances past it.
|
|
130
245
|
def read_record(io, offset, errors)
|
|
131
|
-
|
|
132
|
-
|
|
246
|
+
data = io.string.b
|
|
247
|
+
result = read_record_at(data, offset, errors)
|
|
248
|
+
io.seek(offset + data.unpack1("V", offset: offset + 4)) if result
|
|
249
|
+
result
|
|
250
|
+
end
|
|
133
251
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
252
|
+
# The byte-backed source of the fixed-layout or point-array record at
|
|
253
|
+
# `offset` of the frozen binary `data`, or nil when the record needs
|
|
254
|
+
# `read_record_at` (any other type, a short or truncated span, a count
|
|
255
|
+
# that overruns its record, or an error to report). The wire is made
|
|
256
|
+
# only when asked for, from the same filler `read_typed` uses.
|
|
257
|
+
def filled_record_at(data, offset, size)
|
|
258
|
+
return if size - offset < 8
|
|
140
259
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
260
|
+
type_id = data.unpack1(UINT32, offset: offset)
|
|
261
|
+
n_size = data.unpack1(UINT32, offset: offset + 4)
|
|
262
|
+
return if n_size < 8 || size - offset < n_size
|
|
263
|
+
|
|
264
|
+
source = Emr::FilledRecord.for(type_id, offset, n_size, data)
|
|
265
|
+
return unless source
|
|
266
|
+
return source unless CHECKED_TYPES.key?(type_id)
|
|
267
|
+
|
|
268
|
+
source if sane_at?(type_id, n_size, data, offset)
|
|
269
|
+
end
|
|
147
270
|
|
|
148
|
-
|
|
271
|
+
# The record starting at `offset` of the binary `data` as [wire, suffix],
|
|
272
|
+
# or nil when none can be read there (the caller keeps the rest as
|
|
273
|
+
# trailing bytes).
|
|
274
|
+
def read_record_at(data, offset, errors, diagnostics: nil, record_index: nil)
|
|
275
|
+
header = record_header_at(data, offset, errors, diagnostics, record_index)
|
|
276
|
+
return unless header
|
|
277
|
+
|
|
278
|
+
type_id, n_size = header
|
|
279
|
+
record_bytes = data.byteslice(offset, n_size) if CHECKED_TYPES.key?(type_id)
|
|
280
|
+
unless record_bytes.nil? || variable_array_sane?(type_id, n_size, record_bytes)
|
|
149
281
|
errors << ParseError.new(
|
|
150
282
|
offset: offset, record_code: type_id,
|
|
151
283
|
message: "variable-array record at offset #{offset} declares " \
|
|
152
284
|
"more entries than nSize allows; preserving as Raw"
|
|
153
285
|
)
|
|
154
|
-
|
|
286
|
+
diagnostic(diagnostics, record_index, type_id, offset, :preserved_raw)
|
|
287
|
+
return [build_raw(record_bytes), "".b]
|
|
155
288
|
end
|
|
156
289
|
|
|
157
|
-
|
|
158
|
-
|
|
290
|
+
read_typed(type_id, n_size, data, offset)
|
|
291
|
+
rescue ResourceLimitError
|
|
292
|
+
raise
|
|
159
293
|
rescue StandardError => e
|
|
294
|
+
diagnostic(diagnostics, record_index, type_id, offset, :preserved_raw)
|
|
160
295
|
errors << ParseError.new(offset: offset, record_code: type_id,
|
|
161
296
|
message: "#{e.class}: #{e.message}")
|
|
162
|
-
build_raw(
|
|
297
|
+
[build_raw(record_bytes || data.byteslice(offset, n_size)), "".b]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def record_header_at(data, offset, errors, diagnostics, record_index)
|
|
301
|
+
available = data.bytesize - offset
|
|
302
|
+
return nil if available.zero?
|
|
303
|
+
|
|
304
|
+
if short_header?(available, offset, errors)
|
|
305
|
+
diagnostic(diagnostics, record_index, data.unpack1("V", offset: offset) || 0, offset, :skipped)
|
|
306
|
+
return nil
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
type_id = data.unpack1("V", offset: offset)
|
|
310
|
+
n_size = data.unpack1("V", offset: offset + 4)
|
|
311
|
+
if n_size < 8
|
|
312
|
+
diagnostic(diagnostics, record_index, type_id, offset, :skipped)
|
|
313
|
+
errors << ParseError.new(offset: offset, record_code: type_id,
|
|
314
|
+
message: "record nSize #{n_size} < 8 bytes (offset #{offset})")
|
|
315
|
+
return nil
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
if available < n_size
|
|
319
|
+
diagnostic(diagnostics, record_index, type_id, offset, :skipped)
|
|
320
|
+
errors << ParseError.new(offset: offset, record_code: type_id,
|
|
321
|
+
message: "truncated record at offset #{offset} (claimed #{n_size}, got #{available - 8})")
|
|
322
|
+
return nil
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
[type_id, n_size]
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def diagnostic(diagnostics, record_index, type_id, offset, disposition)
|
|
329
|
+
return unless diagnostics
|
|
330
|
+
|
|
331
|
+
diagnostics << Model::Diagnostic.new(record_index: record_index, record_code: type_id,
|
|
332
|
+
byte_offset: offset, disposition: disposition)
|
|
163
333
|
end
|
|
164
334
|
|
|
165
|
-
def
|
|
166
|
-
Emf::Emr::Binary::
|
|
335
|
+
def read_typed(type_id, n_size, data, offset)
|
|
336
|
+
filler = Emf::Emr::Binary::FastFill.for(type_id) || Emf::Emr::Binary::FastFill.for_points(type_id) ||
|
|
337
|
+
Emf::Emr::Binary::FastFill.for_text(type_id)
|
|
338
|
+
if filler && n_size >= filler.size
|
|
339
|
+
filled = filler.call_at(data, offset, n_size)
|
|
340
|
+
return filled if filled
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
record_bytes = data.byteslice(offset, n_size)
|
|
344
|
+
wire = Emf::Emr::Binary::Records.lookup(type_id).read(record_bytes)
|
|
345
|
+
[wire, Model::Emr::Records::WireAdapter.suffix_of(record_bytes, wire)]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Public API since before the offset-aware checks; kept for callers.
|
|
349
|
+
def read_uint32(bytes, offset)
|
|
350
|
+
bytes.unpack1(UINT32, offset: offset)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def build_raw(record_bytes)
|
|
354
|
+
Emf::Emr::Binary::Records::Raw.read(record_bytes)
|
|
167
355
|
end
|
|
168
356
|
|
|
169
357
|
EMF_PLUS_IDENTIFIER = 0x2B464D45 # "EMF+" little-endian
|
|
170
358
|
|
|
171
|
-
def
|
|
172
|
-
|
|
359
|
+
def comment_emf_plus(wire)
|
|
360
|
+
wire.is_a?(Emf::Emr::Binary::Records::Comment) ? extract_emf_plus(wire) : ""
|
|
361
|
+
end
|
|
173
362
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return +"" unless
|
|
363
|
+
def extract_emf_plus(comment_wire)
|
|
364
|
+
data = comment_wire.data.to_s.b
|
|
365
|
+
# cbData can claim more bytes than the record holds; read only what is there.
|
|
366
|
+
return +"" unless comment_wire.cb_data&.>=(4) && data.bytesize >= 4
|
|
367
|
+
return +"" unless data.unpack1("V") == EMF_PLUS_IDENTIFIER
|
|
179
368
|
|
|
180
369
|
# Payload after the 4-byte identifier is the EMF+ record(s).
|
|
181
|
-
|
|
370
|
+
data.byteslice(4..)
|
|
182
371
|
end
|
|
183
372
|
end
|
|
184
373
|
end
|
data/lib/emf/emr/serializer.rb
CHANGED
|
@@ -6,15 +6,14 @@ module Emf
|
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
8
|
def call(metafile)
|
|
9
|
-
bytes =
|
|
9
|
+
bytes = String.new(encoding: Encoding::BINARY)
|
|
10
10
|
bytes << metafile.header.to_wire.to_binary_s
|
|
11
11
|
metafile.records.each do |record|
|
|
12
|
-
|
|
13
|
-
when Model::Emr::Records::WireAdapter
|
|
14
|
-
bytes << record.wire.to_binary_s
|
|
15
|
-
else
|
|
12
|
+
unless record.is_a?(Model::Emr::Records::WireAdapter)
|
|
16
13
|
raise SerializeError, "cannot serialize #{record.class} (TODO 10 lands domain to_wire)"
|
|
17
14
|
end
|
|
15
|
+
|
|
16
|
+
record.append_to(bytes)
|
|
18
17
|
end
|
|
19
18
|
bytes << metafile.trailing
|
|
20
19
|
bytes
|
data/lib/emf/emr.rb
CHANGED
data/lib/emf/error.rb
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Emf
|
|
4
|
-
class Error < StandardError
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
# Backend-neutral ParseEvidence for a failed header or framing check, or nil when none is available.
|
|
6
|
+
attr_reader :evidence
|
|
7
|
+
|
|
8
|
+
def with_evidence(evidence)
|
|
9
|
+
@evidence ||= evidence
|
|
10
|
+
self
|
|
11
|
+
end
|
|
12
|
+
end
|
|
5
13
|
|
|
6
14
|
class FormatError < Error; end
|
|
7
15
|
|
|
@@ -15,5 +23,20 @@ module Emf
|
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
|
|
26
|
+
class ResourceLimitError < Error
|
|
27
|
+
attr_reader :resource, :limit, :actual
|
|
28
|
+
|
|
29
|
+
def initialize(resource:, limit:, actual:)
|
|
30
|
+
@resource = resource
|
|
31
|
+
@limit = limit
|
|
32
|
+
@actual = actual
|
|
33
|
+
super("#{resource} limit exceeded (#{actual} > #{limit})")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
18
37
|
class SerializeError < Error; end
|
|
38
|
+
|
|
39
|
+
module EmfPlus
|
|
40
|
+
class StaleStreamError < Error; end
|
|
41
|
+
end
|
|
19
42
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Model
|
|
5
|
+
class Diagnostic
|
|
6
|
+
TYPE_NAMES = Emf::Emr::Binary::TypeCodes.constants.to_h do |name|
|
|
7
|
+
[Emf::Emr::Binary::TypeCodes.const_get(name), "EMR_#{name}".freeze]
|
|
8
|
+
end.freeze
|
|
9
|
+
|
|
10
|
+
attr_reader :record_index, :record_code, :record_type, :byte_offset, :disposition
|
|
11
|
+
|
|
12
|
+
def initialize(record_index:, record_code:, byte_offset:, disposition:)
|
|
13
|
+
raise ArgumentError, "invalid diagnostic disposition" unless %i[skipped preserved_raw].include?(disposition)
|
|
14
|
+
unless [record_index, record_code, byte_offset].all? { |value| value.is_a?(Integer) && value >= 0 }
|
|
15
|
+
raise ArgumentError, "diagnostic positions and code must be nonnegative integers"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@record_index = record_index
|
|
19
|
+
@record_code = record_code
|
|
20
|
+
@record_type = TYPE_NAMES.fetch(record_code) { format("UNKNOWN(0x%08X)", record_code).freeze }
|
|
21
|
+
@byte_offset = byte_offset
|
|
22
|
+
@disposition = disposition
|
|
23
|
+
freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{ record_index: record_index, record_code: record_code, record_type: record_type,
|
|
28
|
+
byte_offset: byte_offset, disposition: disposition }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ==(other)
|
|
32
|
+
other.is_a?(self.class) && to_h == other.to_h
|
|
33
|
+
end
|
|
34
|
+
alias eql? ==
|
|
35
|
+
|
|
36
|
+
def hash
|
|
37
|
+
to_h.hash
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/emf/model/emr/header.rb
CHANGED
|
@@ -37,7 +37,31 @@ module Emf
|
|
|
37
37
|
:n_handles, :s_reserved, :n_description, :off_description,
|
|
38
38
|
:n_pal_entries, :device_pixels, :device_mm, :cb_pixel_format,
|
|
39
39
|
:off_pixel_format, :b_open_gl, :szl_micrometers, :n_size,
|
|
40
|
-
:trailing
|
|
40
|
+
:trailing
|
|
41
|
+
|
|
42
|
+
# The description as UTF-8 (UTF-16LE nDescription chars at offDescription,
|
|
43
|
+
# trailing NULs dropped), or nil when absent or outside the header record.
|
|
44
|
+
# An explicit `description:` given at construction wins.
|
|
45
|
+
def description
|
|
46
|
+
return @description if @description
|
|
47
|
+
|
|
48
|
+
bytes = description_bytes
|
|
49
|
+
return unless bytes
|
|
50
|
+
|
|
51
|
+
bytes.dup.force_encoding(Encoding::UTF_16LE).encode(Encoding::UTF_8, invalid: :replace, undef: :replace).sub(/\0+\z/, "")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Raw description bytes (2 * nDescription at offDescription from record
|
|
55
|
+
# start), or nil when nDescription is 0 or the range leaves the record.
|
|
56
|
+
def description_bytes
|
|
57
|
+
slice_of_record(off_description, n_description * 2)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Raw PIXELFORMATDESCRIPTOR bytes (cbPixelFormat at offPixelFormat), or
|
|
61
|
+
# nil when cbPixelFormat is 0/absent or the range leaves the record.
|
|
62
|
+
def pixel_format_bytes
|
|
63
|
+
slice_of_record(off_pixel_format, cb_pixel_format)
|
|
64
|
+
end
|
|
41
65
|
|
|
42
66
|
def self.from_wire(wire)
|
|
43
67
|
new(
|
|
@@ -63,6 +87,12 @@ module Emf
|
|
|
63
87
|
)
|
|
64
88
|
end
|
|
65
89
|
|
|
90
|
+
# The header record bytes as #to_wire writes them now (equal to the source bytes until an edit).
|
|
91
|
+
# Not cached: the fields and `trailing` stay mutable, and the buffers must follow them.
|
|
92
|
+
def record_bytes
|
|
93
|
+
to_wire.to_binary_s
|
|
94
|
+
end
|
|
95
|
+
|
|
66
96
|
def to_wire
|
|
67
97
|
Emf::Emr::Binary::Header.new(
|
|
68
98
|
i_type: 1,
|
|
@@ -87,6 +117,15 @@ module Emf
|
|
|
87
117
|
trailing: trailing
|
|
88
118
|
)
|
|
89
119
|
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def slice_of_record(offset, length)
|
|
124
|
+
return unless offset&.positive? && length&.positive?
|
|
125
|
+
|
|
126
|
+
bytes = record_bytes
|
|
127
|
+
bytes.byteslice(offset, length) if offset + length <= bytes.bytesize
|
|
128
|
+
end
|
|
90
129
|
end
|
|
91
130
|
end
|
|
92
131
|
end
|