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,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class WmfRegionFields
|
|
6
|
+
def initialize(record, raw)
|
|
7
|
+
@record = record
|
|
8
|
+
@raw = raw
|
|
9
|
+
@type = record.fetch(:type)
|
|
10
|
+
@offset = record.fetch(:offset)
|
|
11
|
+
@payload = record.fetch(:payload)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
validate(@record.fetch(:family) == 9 && @raw.bytesize >= 6 && @raw.bytesize.even?)
|
|
16
|
+
klass = Wmf::RegionCodec::RECORDS.fetch(@type & 0xFF)
|
|
17
|
+
fields = klass == Model::Wmf::Records::CreateRegion ? region : scalar(klass)
|
|
18
|
+
record = klass.new(**fields, type_id: @type, offset: @offset)
|
|
19
|
+
prefix = Wmf::RegionCodec.encode_payload(record)
|
|
20
|
+
validate(@raw.bytesize >= prefix.bytesize + 6 && @raw.byteslice(6, prefix.bytesize) == prefix)
|
|
21
|
+
record.suffix = @raw.byteslice((6 + prefix.bytesize)..)
|
|
22
|
+
record
|
|
23
|
+
rescue SerializeError
|
|
24
|
+
validate(false)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def scalar(klass)
|
|
30
|
+
validate(@payload.bytesize == klass::WIRE_FIELDS.length * 4)
|
|
31
|
+
values = @payload.unpack("V*").zip(klass::WIRE_TYPES).map do |value, type|
|
|
32
|
+
if type == :int16
|
|
33
|
+
value -= 0x100000000 if value >= 0x80000000
|
|
34
|
+
validate(value.between?(-0x8000, 0x7FFF))
|
|
35
|
+
else
|
|
36
|
+
validate(value <= 0xFFFF)
|
|
37
|
+
end
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
klass::WIRE_FIELDS.zip(values).to_h
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def region
|
|
44
|
+
validate(@payload.bytesize >= 28)
|
|
45
|
+
next_in_chain, object_count = @payload.unpack("V2")
|
|
46
|
+
bounds = @payload.byteslice(8, 16).unpack("l<4")
|
|
47
|
+
count = @payload.byteslice(24, 4).unpack1("V")
|
|
48
|
+
validate(next_in_chain <= 0xFFFF && bounds.all? { |value| value.between?(-0x8000, 0x7FFF) })
|
|
49
|
+
validate(count <= 0x7FFF && count <= (@payload.bytesize - 28) / 12)
|
|
50
|
+
preflight_scans(count)
|
|
51
|
+
{ next_in_chain: next_in_chain, object_count: object_count,
|
|
52
|
+
bounds: Model::Wmf::Records::RegionBounds.new(**Wmf::RegionCodec::BOUNDS_FIELDS.zip(bounds).to_h),
|
|
53
|
+
scans: scans(count) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def preflight_scans(count)
|
|
57
|
+
cursor = 28
|
|
58
|
+
source_size = 22
|
|
59
|
+
count.times do
|
|
60
|
+
validate(@payload.bytesize - cursor >= 12)
|
|
61
|
+
top, bottom, lines = @payload.byteslice(cursor, 12).unpack("V3")
|
|
62
|
+
validate(top <= 0xFFFF && bottom <= 0xFFFF && lines <= 0x3FFF && lines <= (@payload.bytesize - cursor - 12) / 8)
|
|
63
|
+
source_size += 8 + (lines * 4)
|
|
64
|
+
validate(source_size <= 0x7FFF)
|
|
65
|
+
(lines * 2).times do |index|
|
|
66
|
+
validate(@payload.byteslice(cursor + 12 + (index * 4), 4).unpack1("V") <= 0xFFFF)
|
|
67
|
+
end
|
|
68
|
+
cursor += 12 + (lines * 8)
|
|
69
|
+
end
|
|
70
|
+
validate(cursor == @payload.bytesize && @raw.bytesize >= source_size + 6)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def scans(count)
|
|
74
|
+
cursor = 28
|
|
75
|
+
Array.new(count) do
|
|
76
|
+
top, bottom, count_lines = @payload.byteslice(cursor, 12).unpack("V3")
|
|
77
|
+
lines = @payload.byteslice(cursor + 12, count_lines * 8).unpack("V*").each_slice(2).map do |left, right|
|
|
78
|
+
Model::Wmf::Records::RegionScanLine.new(left: left, right: right)
|
|
79
|
+
end
|
|
80
|
+
cursor += 12 + (count_lines * 8)
|
|
81
|
+
Model::Wmf::Records::RegionScan.new(top: top, bottom: bottom, lines: lines)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate(condition)
|
|
86
|
+
return if condition
|
|
87
|
+
|
|
88
|
+
raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF region record at offset #{@offset}")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
# Maps the native WMFR answer (codec, reason, offset, function, detail) to the exact error the Ruby contract
|
|
6
|
+
# raises: its class, message, offset and record code. Rust names the first failing phase; this table owns
|
|
7
|
+
# only the wording. Reason codes are codec * 100 + n and are never renumbered.
|
|
8
|
+
module WmfRejection
|
|
9
|
+
Found = Struct.new(:codec, :reason, :offset, :function, :detail)
|
|
10
|
+
|
|
11
|
+
# Whole-input failures: FormatError with the message as is. 203 (placeable checksum), 305 (Size), 406 (EOF
|
|
12
|
+
# length), 1003 (negative text length), 1702 (no live object) and 1801 (MaxRecord) are recovered, not
|
|
13
|
+
# rejected, and 404 is retired with them. 701, 801 and 1704 are rejected only for the shapes Wmf::Recovery
|
|
14
|
+
# cannot keep.
|
|
15
|
+
INPUT = {
|
|
16
|
+
201 => "truncated WMF placeable header",
|
|
17
|
+
202 => "WMF placeable reserved field must be zero",
|
|
18
|
+
301 => "truncated WMF standard header",
|
|
19
|
+
302 => "invalid WMF Type",
|
|
20
|
+
303 => "invalid WMF HeaderSize",
|
|
21
|
+
304 => "invalid WMF Version",
|
|
22
|
+
306 => "WMF on-disk placeable handle must be zero"
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# Record failures: ParseError "WMF <text> at offset <offset>". `%d` takes the numeric detail.
|
|
26
|
+
RECORD = {
|
|
27
|
+
402 => "truncated record header", 403 => "invalid record length %d",
|
|
28
|
+
405 => "missing EOF record",
|
|
29
|
+
501 => "invalid fixed record length", 601 => "invalid state record length", 701 => "invalid font record length",
|
|
30
|
+
801 => "invalid object record length",
|
|
31
|
+
901 => "missing point array count", 902 => "truncated polygon counts", 903 => "invalid point count",
|
|
32
|
+
904 => "point array length mismatch",
|
|
33
|
+
1001 => "truncated text record", 1002 => "truncated text rectangle",
|
|
34
|
+
1004 => "truncated text string", 1005 => "truncated text spacing array",
|
|
35
|
+
1101 => "truncated palette prefix", 1102 => "truncated palette entries",
|
|
36
|
+
1201 => "truncated region/clipping prefix", 1202 => "truncated region header", 1203 => "invalid region header",
|
|
37
|
+
1204 => "truncated region scan", 1205 => "invalid region scan", 1206 => "invalid region scan size or maximum",
|
|
38
|
+
1301 => "truncated pattern brush prefix", 1302 => "invalid pattern brush extent",
|
|
39
|
+
1303 => "truncated DIB brush prefix", 1304 => "invalid DIB brush color usage",
|
|
40
|
+
1401 => "truncated bitmap transfer prefix", 1402 => "empty bitmap transfer source",
|
|
41
|
+
1501 => "truncated geometry prefix", 1502 => "invalid extended flood fill mode",
|
|
42
|
+
1601 => "truncated RestoreDc prefix", 1602 => "truncated Escape prefix", 1603 => "truncated Escape data or padding",
|
|
43
|
+
1701 => "object table capacity exceeded",
|
|
44
|
+
1703 => "object reference %d is not a palette", 1704 => "object reference %d is not a region",
|
|
45
|
+
1705 => "invalid region reference %d", 1706 => "invalid brush reference %d",
|
|
46
|
+
1707 => "object reference %d is not a brush"
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
LAYOUT = "a4V7Q<2"
|
|
50
|
+
LENGTH = 48
|
|
51
|
+
|
|
52
|
+
module_function
|
|
53
|
+
|
|
54
|
+
# Returns nil when the contract accepts the input.
|
|
55
|
+
def read(bytes)
|
|
56
|
+
magic, version, present, codec, reason, has_offset, has_function, function, offset, detail =
|
|
57
|
+
bytes.unpack(LAYOUT)
|
|
58
|
+
raise FormatError, "invalid native WMF rejection" unless bytes.bytesize == LENGTH && magic == "WMFR" && version == 1
|
|
59
|
+
|
|
60
|
+
return if present.zero?
|
|
61
|
+
|
|
62
|
+
Found.new(codec, reason, has_offset == 1 ? offset : nil, has_function == 1 ? function : nil, detail)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def error(found, input)
|
|
66
|
+
evidence = Wmf::Evidence.build(found.reason, input, found.offset, found.detail)
|
|
67
|
+
if found.reason == 101
|
|
68
|
+
return ResourceLimitError.new(resource: :input_bytes, limit: Wmf::Binary::MAX_INPUT_BYTES,
|
|
69
|
+
actual: input.bytesize).with_evidence(evidence)
|
|
70
|
+
end
|
|
71
|
+
if found.reason == 401
|
|
72
|
+
return ResourceLimitError.new(resource: :record_count, limit: Wmf::Binary::MAX_RECORDS,
|
|
73
|
+
actual: Wmf::Binary::MAX_RECORDS + 1).with_evidence(evidence)
|
|
74
|
+
end
|
|
75
|
+
return FormatError.new(INPUT.fetch(found.reason)).with_evidence(evidence) if INPUT.key?(found.reason)
|
|
76
|
+
|
|
77
|
+
template = RECORD.fetch(found.reason) { raise FormatError, "unknown native WMF rejection #{found.reason}" }
|
|
78
|
+
text = template.sub("%d", found.detail.to_s)
|
|
79
|
+
ParseError.new(offset: found.offset, record_code: found.function, message: "WMF #{text} at offset #{found.offset}")
|
|
80
|
+
.with_evidence(evidence)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class WmfRemainingFields
|
|
6
|
+
def initialize(record, raw)
|
|
7
|
+
@record = record
|
|
8
|
+
@raw = raw
|
|
9
|
+
@type = record.fetch(:type)
|
|
10
|
+
@offset = record.fetch(:offset)
|
|
11
|
+
@payload = record.fetch(:payload)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
validate(@raw.bytesize >= 6 && @raw.bytesize.even? && @payload.bytesize <= Wmf::Binary::MAX_INPUT_BYTES)
|
|
16
|
+
validate(@raw.unpack("Vv") == [@raw.bytesize / 2, @type])
|
|
17
|
+
geometry = Wmf::GeometryCodec::RECORDS.key?(@type & 0xFF)
|
|
18
|
+
codec = geometry ? Wmf::GeometryCodec : Wmf::DcEscapeCodec
|
|
19
|
+
validate(@record.fetch(:family) == (geometry ? 12 : 13))
|
|
20
|
+
klass = codec::RECORDS.fetch(@type & 0xFF)
|
|
21
|
+
fields = if geometry
|
|
22
|
+
scalar_fields(klass)
|
|
23
|
+
elsif @type & 0xFF == 0x26
|
|
24
|
+
escape_fields
|
|
25
|
+
else
|
|
26
|
+
dc_fields
|
|
27
|
+
end
|
|
28
|
+
model = klass.new(**fields, type_id: @type, offset: @offset)
|
|
29
|
+
validate(codec.encode(model) == @raw)
|
|
30
|
+
model
|
|
31
|
+
rescue SerializeError
|
|
32
|
+
validate(false)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def scalar_fields(klass)
|
|
38
|
+
validate(@payload.bytesize == klass::WIRE_FIELDS.length * 4 && @raw.bytesize >= 6 + klass::PAYLOAD_BYTES)
|
|
39
|
+
values = klass::WIRE_TYPES.each_with_index.map do |type, index|
|
|
40
|
+
@payload.byteslice(index * 4, 4).unpack1(type == :int16 ? "l<" : "V")
|
|
41
|
+
end
|
|
42
|
+
klass::WIRE_FIELDS.zip(values).to_h.merge(suffix: @raw.byteslice((6 + klass::PAYLOAD_BYTES)..))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def dc_fields
|
|
46
|
+
restore = @type & 0xFF == 0x27
|
|
47
|
+
validate(@payload.bytesize == (restore ? 4 : 0) && @raw.bytesize >= (restore ? 8 : 6))
|
|
48
|
+
fields = { suffix: @raw.byteslice((restore ? 8 : 6)..) }
|
|
49
|
+
fields[:saved_dc] = @payload.unpack1("l<") if restore
|
|
50
|
+
fields
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def escape_fields
|
|
54
|
+
validate(@payload.bytesize >= 12)
|
|
55
|
+
function, length = @payload.unpack("VQ<")
|
|
56
|
+
validate(length <= 0xFFFF && @payload.bytesize == 12 + length)
|
|
57
|
+
prefix = 10 + length + (length % 2)
|
|
58
|
+
validate(@raw.bytesize >= prefix)
|
|
59
|
+
{ escape_function: function, data: @payload.byteslice(12, length),
|
|
60
|
+
padding: length.odd? ? @raw.getbyte(10 + length) : nil, suffix: @raw.byteslice(prefix..) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate(condition)
|
|
64
|
+
return if condition
|
|
65
|
+
|
|
66
|
+
raise ParseError.new(offset: @offset, record_code: @type, message: "invalid native WMF typed record at offset #{@offset}")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class WmfSnapshot
|
|
6
|
+
attr_reader :header, :placeable, :records, :diagnostics, :trailing, :source_length
|
|
7
|
+
|
|
8
|
+
def initialize(bytes)
|
|
9
|
+
@bytes = bytes
|
|
10
|
+
@position = 0
|
|
11
|
+
raise FormatError, "invalid native WMF snapshot schema" unless take(4) == "WMFS" && u32 == 1
|
|
12
|
+
raise FormatError, "invalid native WMF snapshot length" unless u64 == bytes.bytesize
|
|
13
|
+
|
|
14
|
+
record_count, diagnostic_count, trailing_length, @source_length = Array.new(4) { u64 }
|
|
15
|
+
if record_count > bytes.bytesize / 40 || diagnostic_count > bytes.bytesize / 40
|
|
16
|
+
raise FormatError, "invalid native WMF snapshot counts"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@header = Array.new(10) { u32 }
|
|
20
|
+
@placeable = take(32).unpack("Vl<4V3")
|
|
21
|
+
@records = Array.new(record_count) { read_record }
|
|
22
|
+
@diagnostics = Array.new(diagnostic_count) { take(40).unpack("V4Q<3") }
|
|
23
|
+
@trailing = take(trailing_length)
|
|
24
|
+
raise FormatError, "unconsumed native WMF snapshot bytes" unless @position == bytes.bytesize
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def take(length)
|
|
28
|
+
raise FormatError, "truncated native WMF snapshot" if length.negative? || length > @bytes.bytesize - @position
|
|
29
|
+
|
|
30
|
+
result = @bytes.byteslice(@position, length)
|
|
31
|
+
@position += length
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def u32
|
|
36
|
+
take(4).unpack1("V")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def u64
|
|
40
|
+
take(8).unpack1("Q<")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def read_record
|
|
46
|
+
index, offset, length, type, family, payload_length = take(40).unpack("Q<3V2Q<")
|
|
47
|
+
{ index: index, offset: offset, length: length, type: type, family: family, payload: take(payload_length) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class WmfTextFields
|
|
6
|
+
def initialize(record, raw)
|
|
7
|
+
@record = record
|
|
8
|
+
@raw = raw
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
payload = @record.fetch(:payload)
|
|
13
|
+
validate(@record.fetch(:family) == 5 && payload.bytesize >= 72)
|
|
14
|
+
x, y, options, has_rect, count, padding_length, dx_length, suffix_length, has_dx, reserved = payload.unpack("l<2V2Q<4V2")
|
|
15
|
+
rectangle = payload.byteslice(56, 16).unpack("l<4")
|
|
16
|
+
extended = @record.fetch(:type) & 0xFF == 0x32
|
|
17
|
+
validate_lengths(payload, count, padding_length, dx_length, suffix_length, has_rect, has_dx, reserved)
|
|
18
|
+
fields = {
|
|
19
|
+
x: x, y: y, text: payload.byteslice(72, count), padding: payload.byteslice(72 + count, padding_length),
|
|
20
|
+
type_id: @record.fetch(:type), offset: @record.fetch(:offset)
|
|
21
|
+
}
|
|
22
|
+
return text_out(fields, options, has_rect, has_dx, suffix_length) unless extended
|
|
23
|
+
|
|
24
|
+
validate(has_rect == (options.anybits?(6) ? 1 : 0) && padding_length <= 1)
|
|
25
|
+
expected = 14 + (has_rect * 8) + count + padding_length + (dx_length * 2) + suffix_length
|
|
26
|
+
validate(@raw.bytesize == expected)
|
|
27
|
+
fields[:rectangle] = Model::Wmf::Records::TextRectangle.new(**%i[left top right bottom].zip(rectangle).to_h) if has_rect == 1
|
|
28
|
+
fields[:options] = options
|
|
29
|
+
fields[:dx] = payload.byteslice(72 + count + padding_length, dx_length * 4).unpack("l<*") if has_dx == 1
|
|
30
|
+
fields[:suffix] = payload.byteslice(72 + count + padding_length + (dx_length * 4), suffix_length)
|
|
31
|
+
Model::Wmf::Records::ExtTextOut.new(**fields)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def validate_lengths(payload, count, padding_length, dx_length, suffix_length, rectangle, spacing, reserved)
|
|
37
|
+
validate(reserved.zero? && count <= 0x7FFF && [0, 1].include?(rectangle) && [0, 1].include?(spacing))
|
|
38
|
+
validate(payload.bytesize == 72 + count + padding_length + (dx_length * 4) + suffix_length)
|
|
39
|
+
validate(dx_length == (spacing == 1 ? count : 0))
|
|
40
|
+
validate(padding_length % 2 == count % 2)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def text_out(fields, options, rectangle, spacing, suffix_length)
|
|
44
|
+
validate(options.zero? && rectangle.zero? && spacing.zero? && suffix_length.zero?)
|
|
45
|
+
validate(@raw.bytesize >= 12 && @raw.bytesize == 12 + fields.fetch(:text).bytesize + fields.fetch(:padding).bytesize)
|
|
46
|
+
Model::Wmf::Records::TextOut.new(**fields)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate(condition)
|
|
50
|
+
return if condition
|
|
51
|
+
|
|
52
|
+
raise ParseError.new(offset: @record.fetch(:offset), record_code: @record.fetch(:type), message: "invalid native WMF text record")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/emf/native.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
class Unavailable < Error; end
|
|
6
|
+
|
|
7
|
+
def self.backend
|
|
8
|
+
require_relative "native/library_path"
|
|
9
|
+
path = LibraryPath.new.call
|
|
10
|
+
raise Unavailable, "native backend is not installed (set EMF_NATIVE_LIBRARY)" if path.nil? || path.empty?
|
|
11
|
+
|
|
12
|
+
require_relative "native/loader"
|
|
13
|
+
require_relative "native/snapshot"
|
|
14
|
+
require_relative "native/header"
|
|
15
|
+
require_relative "native/hydrator"
|
|
16
|
+
require_relative "native/header_errors"
|
|
17
|
+
require_relative "native/diagnostic_contract"
|
|
18
|
+
Adapter.new(Loader.new(path))
|
|
19
|
+
rescue LoadError => e
|
|
20
|
+
raise Unavailable, "native backend dependencies could not load: #{e.message}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Adapter
|
|
24
|
+
def initialize(loader)
|
|
25
|
+
@loader = loader
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse(bytes)
|
|
29
|
+
input = bytes.b
|
|
30
|
+
ResourceLimits.preflight(input)
|
|
31
|
+
return parse_wmf(input) if %i[wmf_apm wmf_standard].include?(Detector.call(input))
|
|
32
|
+
|
|
33
|
+
parse_emf(input)
|
|
34
|
+
rescue FormatError, ParseError => e
|
|
35
|
+
raise ParseEvidenceBuilder.attach(e, input)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_emf(input)
|
|
39
|
+
@loader.document(input) do |document|
|
|
40
|
+
snapshot = Snapshot.new(@loader.snapshot(document))
|
|
41
|
+
DiagnosticContract.new(snapshot, input).call(Hydrator.new(snapshot, input).call)
|
|
42
|
+
end
|
|
43
|
+
rescue FormatError => e
|
|
44
|
+
raise HeaderErrors.translate(e, input)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse_wmf(input)
|
|
48
|
+
require_relative "native/wmf_hydrator"
|
|
49
|
+
WmfHydrator.parse(@loader, input)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def serialize(metafile)
|
|
53
|
+
case metafile.format
|
|
54
|
+
when :emf, :emf_plus then Emr::Serializer.call(metafile)
|
|
55
|
+
when :wmf then Wmf::Serializer.call(metafile)
|
|
56
|
+
else raise SerializeError, "native backend cannot serialize #{metafile.format.inspect}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
# Backend-neutral account of the check that rejected an input before a metafile model existed.
|
|
5
|
+
#
|
|
6
|
+
# check - Symbol naming the failed check (stable vocabulary; see Detector, Emr and Wmf::Evidence)
|
|
7
|
+
# offset - byte offset in the input where the failing field or record starts
|
|
8
|
+
# declared - what the input states (a size, count, checksum, type), nil when it states nothing comparable
|
|
9
|
+
# actual - what was found or required instead (bytes available, computed checksum, minimum size), nil when
|
|
10
|
+
# the check is a domain test with no single comparable value
|
|
11
|
+
#
|
|
12
|
+
# Units are bytes unless the check name says words. Reachable as `error.evidence` on Emf::FormatError and
|
|
13
|
+
# Emf::ParseError raised by either backend; nil when a rejection has no neutral description.
|
|
14
|
+
ParseEvidence = Struct.new(:check, :offset, :declared, :actual, keyword_init: true)
|
|
15
|
+
|
|
16
|
+
module ParseEvidenceBuilder
|
|
17
|
+
EMF_MAGIC_OFFSET = Detector::EMF_MAGIC_OFFSET
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# Returns `error` with evidence attached when it can be derived from its message and the input.
|
|
22
|
+
def attach(error, bytes)
|
|
23
|
+
return error if error.evidence
|
|
24
|
+
|
|
25
|
+
error.with_evidence(derive(error, bytes))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def derive(error, bytes)
|
|
29
|
+
from_detector(error.message, bytes) || from_emf_header(error.message, bytes) || Wmf::Evidence.from_error(error, bytes)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def from_detector(message, bytes)
|
|
33
|
+
case message
|
|
34
|
+
when /\Ainput too short/
|
|
35
|
+
ParseEvidence.new(check: :input_too_short, offset: 0, declared: 4, actual: bytes.bytesize)
|
|
36
|
+
when /\Aunrecognised format \(signature=1/
|
|
37
|
+
ParseEvidence.new(check: :emf_magic_missing, offset: EMF_MAGIC_OFFSET, declared: Detector::EMF_MAGIC,
|
|
38
|
+
actual: bytes.bytesize >= EMF_MAGIC_OFFSET + 4 ? bytes.unpack1("V", offset: EMF_MAGIC_OFFSET) : nil)
|
|
39
|
+
when /\Aunrecognised format/
|
|
40
|
+
ParseEvidence.new(check: :unrecognised_signature, offset: 0, declared: bytes.unpack1("V"), actual: nil)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def from_emf_header(message, bytes)
|
|
45
|
+
case message
|
|
46
|
+
when /\Ainput too large/
|
|
47
|
+
ParseEvidence.new(check: :emf_input_too_large, offset: 0, declared: Emr::Parser::MAX_INPUT_BYTES, actual: bytes.bytesize)
|
|
48
|
+
when /\AEMF header nSize too small/
|
|
49
|
+
ParseEvidence.new(check: :emf_header_size_too_small, offset: 4, declared: bytes.unpack1("V", offset: 4), actual: 8)
|
|
50
|
+
when /\Atruncated EMF header \(claimed/
|
|
51
|
+
ParseEvidence.new(check: :emf_header_truncated, offset: 0, declared: bytes.unpack1("V", offset: 4), actual: bytes.bytesize)
|
|
52
|
+
when /\AEMF header nSize \d+ cannot hold its fields/
|
|
53
|
+
ParseEvidence.new(check: :emf_header_cannot_hold_fields, offset: 4, declared: bytes.unpack1("V", offset: 4), actual: nil)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
class ResourceBudget
|
|
5
|
+
# Mirror bitmap.rs range validation before charging each copied buffer.
|
|
6
|
+
module Bitmap
|
|
7
|
+
SHAPES = {
|
|
8
|
+
76 => [100, 84, nil], 77 => [108, 84, nil],
|
|
9
|
+
78 => [128, 84, 112], 79 => [140, 96, 124],
|
|
10
|
+
80 => [76, 48, nil], 81 => [80, 48, nil],
|
|
11
|
+
93 => [32, 16, nil], 94 => [32, 16, nil],
|
|
12
|
+
114 => [108, 84, nil], 116 => [108, 84, nil]
|
|
13
|
+
}.freeze
|
|
14
|
+
KINDS = SHAPES.keys.freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def fixed_payload_len(kind)
|
|
19
|
+
SHAPES.fetch(kind).first - 8
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def decode(kind, payload, budget) # rubocop:disable Naming/PredicateMethod
|
|
23
|
+
fixed, source, mask = SHAPES.fetch(kind)
|
|
24
|
+
fixed -= 8
|
|
25
|
+
return false if payload.length < fixed
|
|
26
|
+
|
|
27
|
+
spans = buffer_spans(payload, fixed, source, mask)
|
|
28
|
+
return false unless spans
|
|
29
|
+
|
|
30
|
+
cursor = fixed
|
|
31
|
+
spans.each do |start, stop|
|
|
32
|
+
return false if start < cursor
|
|
33
|
+
|
|
34
|
+
cursor = stop
|
|
35
|
+
end
|
|
36
|
+
cursor = fixed
|
|
37
|
+
spans.each do |start, stop|
|
|
38
|
+
[start - cursor, stop - start].each do |length|
|
|
39
|
+
budget.charge_alloc(length)
|
|
40
|
+
budget.charge_work((length + 15) / 16)
|
|
41
|
+
end
|
|
42
|
+
cursor = stop
|
|
43
|
+
end
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def buffer_spans(payload, fixed, source, mask)
|
|
48
|
+
mask_bits = mask + 8 if mask
|
|
49
|
+
spans = [source, source + 8, mask, mask_bits].filter_map do |at|
|
|
50
|
+
next unless at
|
|
51
|
+
|
|
52
|
+
offset = payload.u32(at - 8)
|
|
53
|
+
length = payload.u32(at - 4)
|
|
54
|
+
next if offset.zero? && length.zero?
|
|
55
|
+
|
|
56
|
+
start = offset - 8
|
|
57
|
+
stop = start + length
|
|
58
|
+
return nil if start < fixed || stop > payload.length
|
|
59
|
+
|
|
60
|
+
[start, stop]
|
|
61
|
+
end
|
|
62
|
+
spans.sort_by! { |start, stop| [start, stop - start] }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
class ResourceBudget
|
|
5
|
+
module Gradient
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def decode(_kind, payload, budget) # rubocop:disable Naming/PredicateMethod
|
|
9
|
+
return false if payload.length < 28
|
|
10
|
+
|
|
11
|
+
vertices = payload.u32(16)
|
|
12
|
+
meshes = payload.u32(20)
|
|
13
|
+
mode = payload.u32(24)
|
|
14
|
+
element = mode == 2 ? 12 : 8
|
|
15
|
+
indexed = 28 + (vertices * 16) + (meshes * element)
|
|
16
|
+
return false if mode > 2 || indexed > payload.length
|
|
17
|
+
|
|
18
|
+
# Native reserves work even when an invalid index later recovers as Raw.
|
|
19
|
+
budget.charge_work(vertices + meshes)
|
|
20
|
+
index_start = 28 + (vertices * 16)
|
|
21
|
+
(meshes * element / 4).times do |index|
|
|
22
|
+
return false if payload.u32(index_start + (index * 4)) >= vertices
|
|
23
|
+
end
|
|
24
|
+
# Native charges twelve bytes per rectangle even when padding is absent.
|
|
25
|
+
budget.charge_alloc((vertices * budget.layout(:tri_vertex)) + (meshes * 12))
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
class ResourceBudget
|
|
5
|
+
module PolyText
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def decode(kind, payload, budget) # rubocop:disable Naming/PredicateMethod
|
|
9
|
+
return false if payload.length < 32
|
|
10
|
+
|
|
11
|
+
count = payload.u32(28)
|
|
12
|
+
return false if count > (payload.length - 32) / 24
|
|
13
|
+
|
|
14
|
+
budget.charge_work(count)
|
|
15
|
+
fixed = table_end(payload, count)
|
|
16
|
+
return false unless fixed
|
|
17
|
+
|
|
18
|
+
width = kind == 96 ? 1 : 2
|
|
19
|
+
budget.vector(count, budget.layout(width == 1 ? :poly_text_entry_a : :poly_text_entry_w))
|
|
20
|
+
budget.vector(count * 2, budget.layout(:poly_text_span))
|
|
21
|
+
spans = []
|
|
22
|
+
cursor = 32
|
|
23
|
+
count.times do |index|
|
|
24
|
+
entry = entry_spans(payload, budget, cursor, fixed, width, index)
|
|
25
|
+
return false unless entry
|
|
26
|
+
|
|
27
|
+
cursor, ranges = entry
|
|
28
|
+
spans.concat(ranges)
|
|
29
|
+
end
|
|
30
|
+
budget.charge_work(spans.length * spans.length.bit_length)
|
|
31
|
+
cursor = fixed
|
|
32
|
+
spans.sort!.each do |start, stop, _index, _is_dx|
|
|
33
|
+
return false if start < cursor
|
|
34
|
+
|
|
35
|
+
unused = start - cursor
|
|
36
|
+
budget.charge_work((unused + 15) / 16)
|
|
37
|
+
budget.vector(unused, 1)
|
|
38
|
+
cursor = stop
|
|
39
|
+
end
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def entry_spans(payload, budget, cursor, fixed, width, index)
|
|
44
|
+
chars = payload.u32(cursor + 8)
|
|
45
|
+
string = payload.u32(cursor + 12)
|
|
46
|
+
options = payload.u32(cursor + 16)
|
|
47
|
+
descriptor_size = options.anybits?(0x100) ? 24 : 40
|
|
48
|
+
dx = payload.u32(cursor + descriptor_size - 4)
|
|
49
|
+
dx_count = chars * (options.anybits?(0x2000) ? 2 : 1)
|
|
50
|
+
spans = []
|
|
51
|
+
unless string.zero? && chars.zero?
|
|
52
|
+
range = budget.span(payload.length, string, chars * width, width, fixed)
|
|
53
|
+
return unless range
|
|
54
|
+
|
|
55
|
+
spans << [*range, index, 0]
|
|
56
|
+
end
|
|
57
|
+
unless dx.zero?
|
|
58
|
+
range = budget.span(payload.length, dx, dx_count * 4, 4, fixed)
|
|
59
|
+
return unless range
|
|
60
|
+
|
|
61
|
+
spans << [*range, index, 1]
|
|
62
|
+
end
|
|
63
|
+
budget.charge_work(chars)
|
|
64
|
+
budget.vector(chars, width)
|
|
65
|
+
unless dx.zero?
|
|
66
|
+
budget.charge_work(dx_count)
|
|
67
|
+
budget.vector(dx_count, 4)
|
|
68
|
+
end
|
|
69
|
+
[cursor + descriptor_size, spans]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def table_end(payload, count)
|
|
73
|
+
cursor = 32
|
|
74
|
+
count.times do
|
|
75
|
+
return if payload.length - cursor < 24
|
|
76
|
+
|
|
77
|
+
options = payload.u32(cursor + 16)
|
|
78
|
+
cursor += options.anybits?(0x100) ? 24 : 40
|
|
79
|
+
return if cursor > payload.length
|
|
80
|
+
end
|
|
81
|
+
cursor
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|