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
data/lib/emf/wmf/binary.rb
CHANGED
|
@@ -1,9 +1,158 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "emf/wmf/text_codec"
|
|
4
|
+
require "emf/wmf/palette_codec"
|
|
5
|
+
require "emf/wmf/region_codec"
|
|
6
|
+
require "emf/wmf/brush_codec"
|
|
7
|
+
require "emf/wmf/bitmap_codec"
|
|
8
|
+
require "emf/wmf/geometry_codec"
|
|
9
|
+
require "emf/wmf/dc_escape_codec"
|
|
10
|
+
|
|
4
11
|
module Emf
|
|
5
12
|
module Wmf
|
|
6
13
|
module Binary
|
|
14
|
+
PLACEABLE_KEY = 0x9AC6CDD7
|
|
15
|
+
HEADER_BYTES = 18
|
|
16
|
+
PLACEABLE_BYTES = 22
|
|
17
|
+
MAX_INPUT_BYTES = 200 * 1024 * 1024
|
|
18
|
+
MAX_RECORDS = 1_000_000
|
|
19
|
+
|
|
20
|
+
FIXED_RECORDS = {
|
|
21
|
+
0x14 => Model::Wmf::Records::MoveTo,
|
|
22
|
+
0x13 => Model::Wmf::Records::LineTo,
|
|
23
|
+
0x0B => Model::Wmf::Records::SetWindowOrg,
|
|
24
|
+
0x0C => Model::Wmf::Records::SetWindowExt,
|
|
25
|
+
0x0D => Model::Wmf::Records::SetViewportOrg,
|
|
26
|
+
0x0E => Model::Wmf::Records::SetViewportExt,
|
|
27
|
+
0x0F => Model::Wmf::Records::OffsetWindowOrg,
|
|
28
|
+
0x11 => Model::Wmf::Records::OffsetViewportOrg,
|
|
29
|
+
0x17 => Model::Wmf::Records::Arc,
|
|
30
|
+
0x18 => Model::Wmf::Records::Ellipse,
|
|
31
|
+
0x1A => Model::Wmf::Records::Pie,
|
|
32
|
+
0x1B => Model::Wmf::Records::Rectangle,
|
|
33
|
+
0x1C => Model::Wmf::Records::RoundRect,
|
|
34
|
+
0x30 => Model::Wmf::Records::Chord
|
|
35
|
+
}.freeze
|
|
36
|
+
FIXED_FUNCTIONS = FIXED_RECORDS.invert.freeze
|
|
37
|
+
|
|
38
|
+
POINT_ARRAY_RECORDS = {
|
|
39
|
+
0x24 => Model::Wmf::Records::Polygon,
|
|
40
|
+
0x25 => Model::Wmf::Records::Polyline,
|
|
41
|
+
0x38 => Model::Wmf::Records::PolyPolygon
|
|
42
|
+
}.freeze
|
|
43
|
+
POINT_ARRAY_FUNCTIONS = POINT_ARRAY_RECORDS.invert.freeze
|
|
44
|
+
|
|
45
|
+
STATE_RECORDS = {
|
|
46
|
+
0x01 => Model::Wmf::Records::SetBkColor,
|
|
47
|
+
0x02 => Model::Wmf::Records::SetBkMode,
|
|
48
|
+
0x03 => Model::Wmf::Records::SetMapMode,
|
|
49
|
+
0x31 => Model::Wmf::Records::SetMapperFlags,
|
|
50
|
+
0x06 => Model::Wmf::Records::SetPolyFillMode,
|
|
51
|
+
0x04 => Model::Wmf::Records::SetRop2,
|
|
52
|
+
0x07 => Model::Wmf::Records::SetStretchBltMode,
|
|
53
|
+
0x2E => Model::Wmf::Records::SetTextAlign,
|
|
54
|
+
0x08 => Model::Wmf::Records::SetTextCharExtra,
|
|
55
|
+
0x09 => Model::Wmf::Records::SetTextColor,
|
|
56
|
+
0x0A => Model::Wmf::Records::SetTextJustification,
|
|
57
|
+
0x49 => Model::Wmf::Records::SetLayout
|
|
58
|
+
}.freeze
|
|
59
|
+
STATE_FUNCTIONS = STATE_RECORDS.invert.freeze
|
|
60
|
+
PREFIX_FUNCTIONS = ([0xFB] + STATE_RECORDS.keys + PaletteCodec::RECORDS.keys + RegionCodec::RECORDS.keys +
|
|
61
|
+
BrushCodec::RECORDS.keys + BitmapCodec::RECORDS.keys + GeometryCodec::RECORDS.keys +
|
|
62
|
+
DcEscapeCodec::RECORDS.keys).freeze
|
|
63
|
+
|
|
64
|
+
OBJECT_RECORDS = {
|
|
65
|
+
0xFA => Model::Wmf::Records::CreatePenIndirect,
|
|
66
|
+
0xFC => Model::Wmf::Records::CreateBrushIndirect,
|
|
67
|
+
0x2D => Model::Wmf::Records::SelectObject,
|
|
68
|
+
0xF0 => Model::Wmf::Records::DeleteObject
|
|
69
|
+
}.freeze
|
|
70
|
+
OBJECT_FUNCTIONS = OBJECT_RECORDS.invert.freeze
|
|
71
|
+
# Opaque creators still consume the lowest available object-table slot.
|
|
72
|
+
OBJECT_CREATORS = [0xFA, 0xFC, 0xFB, 0xF7, 0xF9, 0xFF, 0x42].freeze
|
|
73
|
+
TEXT_FUNCTIONS = [0x21, 0x32].freeze
|
|
74
|
+
TYPED_FUNCTIONS = (FIXED_RECORDS.keys + POINT_ARRAY_RECORDS.keys + OBJECT_RECORDS.keys + STATE_RECORDS.keys +
|
|
75
|
+
TEXT_FUNCTIONS + PaletteCodec::RECORDS.keys + RegionCodec::RECORDS.keys + BrushCodec::RECORDS.keys +
|
|
76
|
+
BitmapCodec::RECORDS.keys + GeometryCodec::RECORDS.keys + DcEscapeCodec::RECORDS.keys + [0xFB]).freeze
|
|
77
|
+
|
|
78
|
+
module_function
|
|
79
|
+
|
|
80
|
+
def prefix_codec(record)
|
|
81
|
+
[PaletteCodec, RegionCodec, BrushCodec, BitmapCodec, GeometryCodec, DcEscapeCodec]
|
|
82
|
+
.find { |codec| codec::FUNCTIONS.key?(record.class) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def checksum(bytes)
|
|
86
|
+
bytes.unpack("v10").reduce(0, :^)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def integer(value, range, name)
|
|
90
|
+
return value if value.is_a?(Integer) && range.cover?(value)
|
|
91
|
+
|
|
92
|
+
raise SerializeError, "WMF #{name} out of range: #{value.inspect}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def uint8(value, name)
|
|
96
|
+
integer(value, 0..0xFF, name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def uint16(value, name)
|
|
100
|
+
integer(value, 0..0xFFFF, name)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def uint32(value, name)
|
|
104
|
+
integer(value, 0..0xFFFFFFFF, name)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Yields (record, message) for a table violation the parser cannot recover from. A reference to no live object
|
|
108
|
+
# (1702) or, for a clip, paint or invert record, to a non-region (1704) is recovered: the reference is ignored,
|
|
109
|
+
# the record kept, and `recovered` (if given) is called with (record, reason, index).
|
|
110
|
+
def validate_objects(records, capacity, recovered: nil)
|
|
111
|
+
slots = Array.new(capacity, false)
|
|
112
|
+
records.each do |record|
|
|
113
|
+
function = record.type_id & 0xFF
|
|
114
|
+
if OBJECT_CREATORS.include?(function)
|
|
115
|
+
index = slots.index(false)
|
|
116
|
+
yield record, "object table capacity exceeded" unless index
|
|
117
|
+
slots[index] = function
|
|
118
|
+
elsif record.is_a?(Model::Wmf::Records::FillRegion)
|
|
119
|
+
validate_region_references(record, slots) { |message| yield record, message }
|
|
120
|
+
elsif record.is_a?(Model::Wmf::Records::ObjectReference)
|
|
121
|
+
validate_object_reference(record, capacity, slots, recovered && lambda { |reason, index|
|
|
122
|
+
recovered.call(record, reason, index)
|
|
123
|
+
}) do |message|
|
|
124
|
+
yield record, message
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def validate_object_reference(record, capacity, slots, recovered)
|
|
131
|
+
index = record.object_index
|
|
132
|
+
# Preserve high-bit stock selectors without interpreting their numeric ID.
|
|
133
|
+
return if record.is_a?(Model::Wmf::Records::SelectObject) && index.anybits?(0x8000)
|
|
134
|
+
return recovered&.call(1702, index) unless index < capacity && slots[index]
|
|
135
|
+
|
|
136
|
+
yield "object reference #{index} is not a palette" if record.is_a?(Model::Wmf::Records::SelectPalette) && slots[index] != 0xF7
|
|
137
|
+
return recovered&.call(1704, index) if not_region?(record, slots[index])
|
|
138
|
+
|
|
139
|
+
slots[index] = false if record.is_a?(Model::Wmf::Records::DeleteObject)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def not_region?(record, slot) = record.is_a?(Model::Wmf::Records::RegionReference) && slot != 0xFF
|
|
143
|
+
|
|
144
|
+
def validate_region_references(record, slots)
|
|
145
|
+
region = record.region_index
|
|
146
|
+
brush = record.brush_index
|
|
147
|
+
yield "invalid region reference #{region}" unless region < slots.length && slots[region]
|
|
148
|
+
yield "object reference #{region} is not a region" unless slots[region] == 0xFF
|
|
149
|
+
yield "invalid brush reference #{brush}" unless brush < slots.length && slots[brush]
|
|
150
|
+
yield "object reference #{brush} is not a brush" unless [0xFC, 0xF9, 0x42].include?(slots[brush])
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def int16(value, name)
|
|
154
|
+
integer(value, -0x8000..0x7FFF, name)
|
|
155
|
+
end
|
|
7
156
|
end
|
|
8
157
|
end
|
|
9
158
|
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Wmf
|
|
5
|
+
module BitmapCodec
|
|
6
|
+
RECORDS = { 0x22 => Model::Wmf::Records::BitBlt, 0x40 => Model::Wmf::Records::DibBitBlt,
|
|
7
|
+
0x23 => Model::Wmf::Records::StretchBlt, 0x41 => Model::Wmf::Records::DibStretchBlt,
|
|
8
|
+
0x33 => Model::Wmf::Records::SetDibToDev, 0x43 => Model::Wmf::Records::StretchDib }.freeze
|
|
9
|
+
FUNCTIONS = RECORDS.invert.freeze
|
|
10
|
+
BLT_FIELDS = %i[raster_operation source_y source_x height width destination_y destination_x reserved].freeze
|
|
11
|
+
STRETCH_FIELDS = %i[raster_operation source_height source_width source_y source_x destination_height destination_width
|
|
12
|
+
destination_y destination_x reserved].freeze
|
|
13
|
+
SET_FIELDS = %i[color_usage scan_count start_scan source_y source_x height width destination_y destination_x].freeze
|
|
14
|
+
DIB_FIELDS = (STRETCH_FIELDS[0, 1] + [:color_usage] + STRETCH_FIELDS[1, 8]).freeze
|
|
15
|
+
FIELDS = { 0x22 => BLT_FIELDS, 0x40 => BLT_FIELDS, 0x23 => STRETCH_FIELDS, 0x41 => STRETCH_FIELDS,
|
|
16
|
+
0x33 => SET_FIELDS, 0x43 => DIB_FIELDS }.freeze
|
|
17
|
+
|
|
18
|
+
FORMATS = { uint32: "V", uint16: "v", int16: "s<" }.freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def optional?(kind)
|
|
23
|
+
[0x22, 0x40, 0x23, 0x41].include?(kind)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def field_type(kind, field)
|
|
27
|
+
return :uint32 if field == :raster_operation
|
|
28
|
+
return :uint16 if kind == 0x33 || %i[color_usage reserved].include?(field)
|
|
29
|
+
|
|
30
|
+
:int16
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def wire_fields(kind, absent)
|
|
34
|
+
fields = FIELDS.fetch(kind).reject { |field| field == :reserved }
|
|
35
|
+
fields.insert(fields.index([0x22, 0x40].include?(kind) ? :height : :destination_height), :reserved) if absent
|
|
36
|
+
fields
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def format_for(kind, fields)
|
|
40
|
+
fields.map { |field| FORMATS.fetch(field_type(kind, field)) }.join
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def decode(raw, function, offset)
|
|
44
|
+
kind = function & 0xFF
|
|
45
|
+
absent = optional?(kind) && (raw.bytesize - 6) / 2 == function >> 8
|
|
46
|
+
names = wire_fields(kind, absent)
|
|
47
|
+
format = format_for(kind, names)
|
|
48
|
+
length = names.sum { |field| field == :raster_operation ? 4 : 2 }
|
|
49
|
+
yield "truncated bitmap transfer prefix" if raw.bytesize < 6 + length
|
|
50
|
+
fields = names.zip(raw.byteslice(6, length).unpack(format)).to_h
|
|
51
|
+
fields[:reserved] ||= 0 if optional?(kind)
|
|
52
|
+
if absent
|
|
53
|
+
fields[:bitmap] = nil
|
|
54
|
+
fields[:suffix] = raw.byteslice((6 + length)..)
|
|
55
|
+
else
|
|
56
|
+
yield "empty bitmap transfer source" unless raw.bytesize > 6 + length
|
|
57
|
+
fields[:bitmap] = raw.byteslice((6 + length)..)
|
|
58
|
+
fields[:suffix] = "".b
|
|
59
|
+
end
|
|
60
|
+
RECORDS.fetch(kind).new(**fields, type_id: function, offset: offset)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def byte_size(record)
|
|
64
|
+
kind = FUNCTIONS.fetch(record.class)
|
|
65
|
+
prefix = wire_fields(kind, optional?(kind) && record.bitmap.nil?).sum { |field| field == :raster_operation ? 4 : 2 }
|
|
66
|
+
6 + prefix + (record.bitmap&.bytesize || 0) + record.suffix.bytesize
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def encode(record)
|
|
70
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
71
|
+
kind = FUNCTIONS.fetch(record.class)
|
|
72
|
+
raise SerializeError, "WMF bitmap RecordFunction mismatch" unless function & 0xFF == kind
|
|
73
|
+
|
|
74
|
+
fields = FIELDS.fetch(kind).to_h { |field| [field, record.public_send(field)] }
|
|
75
|
+
fields[:bitmap] = record.bitmap
|
|
76
|
+
fields[:suffix] = record.suffix
|
|
77
|
+
body = encode_fields(fields, function)
|
|
78
|
+
[3 + (body.bytesize / 2), function].pack("Vv") + body
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def encode_fields(fields, function)
|
|
82
|
+
kind = function & 0xFF
|
|
83
|
+
FIELDS.fetch(kind).each { |field| Binary.public_send(field_type(kind, field), fields.fetch(field), field) }
|
|
84
|
+
absent = optional?(kind) && fields[:bitmap].nil?
|
|
85
|
+
image, suffix = source_bytes(fields, kind, absent)
|
|
86
|
+
names = wire_fields(kind, absent)
|
|
87
|
+
size = names.sum { |field| field == :raster_operation ? 4 : 2 } + image.bytesize + suffix.bytesize
|
|
88
|
+
validate_layout(kind, absent, size, function)
|
|
89
|
+
|
|
90
|
+
names.map { |field| fields.fetch(field) }.pack(format_for(kind, names)) + image.b + suffix.b
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def source_bytes(fields, kind, absent)
|
|
94
|
+
suffix = fields.fetch(:suffix, "".b)
|
|
95
|
+
validate_bytes(suffix)
|
|
96
|
+
raise SerializeError, "WMF bitmap suffix requires absent source" unless absent || suffix.empty?
|
|
97
|
+
raise SerializeError, "WMF present bitmap reserved word must be zero" if optional?(kind) && !absent && fields[:reserved] != 0
|
|
98
|
+
|
|
99
|
+
image = absent ? "".b : fields[:bitmap]
|
|
100
|
+
validate_bytes(image)
|
|
101
|
+
raise SerializeError, "empty WMF bitmap source" if !absent && image.empty?
|
|
102
|
+
|
|
103
|
+
[image, suffix]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validate_layout(kind, absent, size, function)
|
|
107
|
+
raise SerializeError, "WMF bitmap too large" if size > Binary::MAX_INPUT_BYTES - 6
|
|
108
|
+
raise SerializeError, "ambiguous WMF bitmap source layout" if optional?(kind) && absent != (size / 2 == function >> 8)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def validate_bytes(value)
|
|
112
|
+
return if value.is_a?(String) && value.bytesize.even? && value.bytesize <= Binary::MAX_INPUT_BYTES - 6
|
|
113
|
+
|
|
114
|
+
raise SerializeError, "invalid WMF bitmap bytes"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Wmf
|
|
5
|
+
module BrushCodec
|
|
6
|
+
RECORDS = { 0xF9 => Model::Wmf::Records::CreatePatternBrush, 0x42 => Model::Wmf::Records::DibCreatePatternBrush }.freeze
|
|
7
|
+
FUNCTIONS = RECORDS.invert.freeze
|
|
8
|
+
SIGNED_FIELDS = %i[bitmap_type width height width_bytes].freeze
|
|
9
|
+
BYTE_FIELDS = %i[planes bits_pixel].freeze
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def decode(raw, function, offset)
|
|
14
|
+
fields = { type_id: function, offset: offset }
|
|
15
|
+
if function & 0xFF == 0xF9
|
|
16
|
+
yield "truncated pattern brush prefix" if raw.bytesize < 38
|
|
17
|
+
values = raw.byteslice(6, 10).unpack("s<4C2")
|
|
18
|
+
fields.merge!((SIGNED_FIELDS + BYTE_FIELDS).zip(values).to_h)
|
|
19
|
+
length = pattern_length(fields[:width], fields[:height], fields[:planes], fields[:bits_pixel])
|
|
20
|
+
yield "invalid pattern brush extent" unless length && length <= raw.bytesize - 38
|
|
21
|
+
fields.merge!(bits: raw.byteslice(16, 4), reserved: raw.byteslice(20, 18),
|
|
22
|
+
pattern: raw.byteslice(38, length), suffix: raw.byteslice((38 + length)..))
|
|
23
|
+
else
|
|
24
|
+
yield "truncated DIB brush prefix" if raw.bytesize < 12
|
|
25
|
+
style, usage = raw.byteslice(6, 4).unpack("v2")
|
|
26
|
+
yield "invalid DIB brush color usage" unless style == 3 || usage <= 2
|
|
27
|
+
fields.merge!(style: style, color_usage: usage, target: raw.byteslice(10..))
|
|
28
|
+
end
|
|
29
|
+
RECORDS.fetch(function & 0xFF).new(**fields)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def pattern_length(width, height, planes, bits_pixel)
|
|
33
|
+
return unless width.positive? && height.positive? && planes == 1 && bits_pixel.positive?
|
|
34
|
+
|
|
35
|
+
(((width * bits_pixel) + 15) / 16) * 2 * height
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def byte_size(record)
|
|
39
|
+
if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
|
|
40
|
+
38 + record.pattern.bytesize + record.suffix.bytesize
|
|
41
|
+
else
|
|
42
|
+
10 + record.target.bytesize
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def encode(record)
|
|
47
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
48
|
+
raise SerializeError, "WMF brush RecordFunction mismatch" unless function & 0xFF == FUNCTIONS[record.class]
|
|
49
|
+
|
|
50
|
+
payload = encode_payload(record)
|
|
51
|
+
if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
|
|
52
|
+
validate_string(record.suffix, "suffix", even: true)
|
|
53
|
+
validate_size(payload.bytesize + record.suffix.bytesize)
|
|
54
|
+
payload += record.suffix.b
|
|
55
|
+
end
|
|
56
|
+
validate_size(payload.bytesize)
|
|
57
|
+
|
|
58
|
+
[3 + (payload.bytesize / 2), function].pack("Vv") + payload
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def encode_payload(record)
|
|
62
|
+
if record.is_a?(Model::Wmf::Records::CreatePatternBrush)
|
|
63
|
+
pattern_payload(record)
|
|
64
|
+
else
|
|
65
|
+
style = Binary.uint16(record.style, "style")
|
|
66
|
+
usage = Binary.uint16(record.color_usage, "color_usage")
|
|
67
|
+
raise SerializeError, "invalid WMF DIB brush color usage" unless style == 3 || usage <= 2
|
|
68
|
+
|
|
69
|
+
validate_string(record.target, "target", even: true)
|
|
70
|
+
raise SerializeError, "empty WMF DIB brush target" if record.target.empty?
|
|
71
|
+
|
|
72
|
+
validate_size(4 + record.target.bytesize)
|
|
73
|
+
[style, usage].pack("v2") + record.target.b
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def pattern_payload(record)
|
|
78
|
+
signed = SIGNED_FIELDS.map { |field| Binary.int16(record.public_send(field), field) }
|
|
79
|
+
bytes = BYTE_FIELDS.map { |field| Binary.uint8(record.public_send(field), field) }
|
|
80
|
+
length = pattern_length(record.width, record.height, record.planes, record.bits_pixel)
|
|
81
|
+
validate_string(record.bits, "bits", length: 4)
|
|
82
|
+
validate_string(record.reserved, "reserved", length: 18)
|
|
83
|
+
validate_string(record.pattern, "pattern", even: true)
|
|
84
|
+
raise SerializeError, "invalid WMF pattern brush extent" unless length && length == record.pattern.bytesize
|
|
85
|
+
|
|
86
|
+
validate_size(32 + record.pattern.bytesize)
|
|
87
|
+
[*signed, *bytes].pack("s<4C2") + record.bits.b + record.reserved.b + record.pattern.b
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def validate_size(size)
|
|
91
|
+
raise SerializeError, "WMF brush too large" if size > Binary::MAX_INPUT_BYTES - 6
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def validate_string(value, name, length: nil, even: false)
|
|
95
|
+
return if value.is_a?(String) && (!length || value.bytesize == length) && (!even || value.bytesize.even?) &&
|
|
96
|
+
value.bytesize <= Binary::MAX_INPUT_BYTES - 6
|
|
97
|
+
|
|
98
|
+
raise SerializeError, "invalid WMF brush #{name} bytes"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Wmf
|
|
5
|
+
module DcEscapeCodec
|
|
6
|
+
RECORDS = {
|
|
7
|
+
0x1E => Model::Wmf::Records::SaveDc,
|
|
8
|
+
0x27 => Model::Wmf::Records::RestoreDc,
|
|
9
|
+
0x05 => Model::Wmf::Records::SetRelAbs,
|
|
10
|
+
0x26 => Model::Wmf::Records::Escape
|
|
11
|
+
}.freeze
|
|
12
|
+
FUNCTIONS = RECORDS.invert.freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def decode(raw, function, offset)
|
|
17
|
+
klass = RECORDS.fetch(function & 0xFF)
|
|
18
|
+
fields = { type_id: function, offset: offset }
|
|
19
|
+
prefix = 6
|
|
20
|
+
if klass == Model::Wmf::Records::RestoreDc
|
|
21
|
+
prefix = 8
|
|
22
|
+
yield "truncated RestoreDc prefix" if raw.bytesize < prefix
|
|
23
|
+
fields[:saved_dc] = raw.byteslice(6, 2).unpack1("s<")
|
|
24
|
+
elsif klass == Model::Wmf::Records::Escape
|
|
25
|
+
yield "truncated Escape prefix" if raw.bytesize < 10
|
|
26
|
+
fields[:escape_function], count = raw.byteslice(6, 4).unpack("v2")
|
|
27
|
+
prefix = 10 + count + (count % 2)
|
|
28
|
+
yield "truncated Escape data or padding" if raw.bytesize < prefix
|
|
29
|
+
fields[:data] = raw.byteslice(10, count)
|
|
30
|
+
fields[:padding] = raw.getbyte(10 + count) if count.odd?
|
|
31
|
+
end
|
|
32
|
+
klass.new(**fields, suffix: raw.byteslice(prefix..))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def byte_size(record)
|
|
36
|
+
prefix = case record
|
|
37
|
+
when Model::Wmf::Records::RestoreDc then 8
|
|
38
|
+
when Model::Wmf::Records::Escape then 10 + record.data.bytesize + (record.data.bytesize % 2)
|
|
39
|
+
else 6
|
|
40
|
+
end
|
|
41
|
+
prefix + record.suffix.bytesize
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def encode(record)
|
|
45
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
46
|
+
raise SerializeError, "WMF DC/Escape RecordFunction mismatch" unless function & 0xFF == FUNCTIONS[record.class]
|
|
47
|
+
unless record.suffix.is_a?(String) && record.suffix.bytesize.even?
|
|
48
|
+
raise SerializeError, "WMF DC/Escape suffix must be a String with even length"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
payload = case record
|
|
52
|
+
when Model::Wmf::Records::RestoreDc then [Binary.int16(record.saved_dc, "saved_dc")].pack("s<")
|
|
53
|
+
when Model::Wmf::Records::Escape then encode_escape(record)
|
|
54
|
+
else "".b
|
|
55
|
+
end
|
|
56
|
+
payload += record.suffix.b
|
|
57
|
+
[3 + (payload.bytesize / 2), function].pack("Vv") + payload
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def encode_escape(record)
|
|
61
|
+
raise SerializeError, "WMF Escape data must be a byte string" unless record.data.is_a?(String)
|
|
62
|
+
|
|
63
|
+
count = Binary.uint16(record.data.bytesize, "Escape ByteCount")
|
|
64
|
+
function = Binary.uint16(record.escape_function, "EscapeFunction")
|
|
65
|
+
padding = "".b
|
|
66
|
+
if count.odd?
|
|
67
|
+
padding = [Binary.uint8(record.padding, "Escape padding")].pack("C")
|
|
68
|
+
elsif !record.padding.nil?
|
|
69
|
+
raise SerializeError, "WMF even Escape data must not have padding"
|
|
70
|
+
end
|
|
71
|
+
[function, count].pack("v2") + record.data.b + padding
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/emf/wmf/dib.rb
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Wmf
|
|
5
|
+
# Read-only views of the packed bitmap interiors retained by WMF records.
|
|
6
|
+
# The raw bytes on the record stay authoritative; compressed pixels, header
|
|
7
|
+
# extensions and padding are kept verbatim and nothing is decoded or converted.
|
|
8
|
+
# Malformed or unsupported layouts return nil, as in the Rust views.
|
|
9
|
+
module Dib
|
|
10
|
+
CoreHeader = Struct.new(:width, :height, :planes, :bit_count, keyword_init: true)
|
|
11
|
+
InfoHeader = Struct.new(:width, :height, :planes, :bit_count, :compression, :image_size, :x_pels_per_meter,
|
|
12
|
+
:y_pels_per_meter, :colors_used, :colors_important, :extension, keyword_init: true)
|
|
13
|
+
# kind is :none, :rgb_triples, :rgb_quads or :palette_indices.
|
|
14
|
+
ColorTable = Struct.new(:kind, :rows, keyword_init: true)
|
|
15
|
+
Image = Struct.new(:header, :bitfield_masks, :colors, :pixels, :trailing, keyword_init: true)
|
|
16
|
+
Layout = Struct.new(:header, :masks, :color_count, :entry_size, :pixel_start, :pixel_size, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
COMPRESSIONS = [0, 1, 2, 3, 4, 5, 11, 12, 13].freeze
|
|
19
|
+
BIT_COUNTS = [0, 1, 4, 8, 16, 24, 32].freeze
|
|
20
|
+
RLE_BITS = { 1 => 8, 12 => 8, 2 => 4, 13 => 4 }.freeze
|
|
21
|
+
PROFILE_SIGNATURES = [0x4C494E4B, 0x4D424544].freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def parse(bytes, color_usage = 0)
|
|
26
|
+
bytes = bytes.b
|
|
27
|
+
layout = layout(bytes, color_usage) or return
|
|
28
|
+
header = layout.header
|
|
29
|
+
Image.new(header: header_view(bytes, header),
|
|
30
|
+
bitfield_masks: layout.masks.positive? ? bytes.byteslice(header, 12).unpack("V3") : nil,
|
|
31
|
+
colors: color_table(bytes, layout, color_usage),
|
|
32
|
+
pixels: bytes.byteslice(layout.pixel_start, layout.pixel_size),
|
|
33
|
+
trailing: bytes.byteslice((layout.pixel_start + layout.pixel_size)..))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def header_view(bytes, size)
|
|
37
|
+
if size == 12
|
|
38
|
+
width, height, planes, bits = bytes.unpack("@4v4")
|
|
39
|
+
return CoreHeader.new(width: width, height: height, planes: planes, bit_count: bits)
|
|
40
|
+
end
|
|
41
|
+
width, height, planes, bits, compression, image_size, x_pels, y_pels, used, important = bytes.unpack("@4l<2v2V2l<2V2")
|
|
42
|
+
InfoHeader.new(width: width, height: height, planes: planes, bit_count: bits, compression: compression,
|
|
43
|
+
image_size: image_size, x_pels_per_meter: x_pels, y_pels_per_meter: y_pels, colors_used: used,
|
|
44
|
+
colors_important: important, extension: bytes.byteslice(40, size - 40))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def color_table(bytes, layout, color_usage)
|
|
48
|
+
return ColorTable.new(kind: :none, rows: []) if color_usage == 2
|
|
49
|
+
|
|
50
|
+
raw = bytes.byteslice(layout.header + layout.masks, layout.pixel_start - layout.header - layout.masks)
|
|
51
|
+
case layout.entry_size
|
|
52
|
+
when 2 then ColorTable.new(kind: :palette_indices, rows: raw.unpack("v*"))
|
|
53
|
+
when 3 then ColorTable.new(kind: :rgb_triples, rows: raw.bytes.each_slice(3).to_a)
|
|
54
|
+
else ColorTable.new(kind: :rgb_quads, rows: raw.bytes.each_slice(4).to_a)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def layout(bytes, usage)
|
|
59
|
+
header = header_size(bytes, usage) or return
|
|
60
|
+
|
|
61
|
+
width, height, planes, bits, compression, image_size, used = header_fields(bytes, header)
|
|
62
|
+
return unless valid_shape?(width, height, planes, bits) && valid_compression?(compression, bits, height)
|
|
63
|
+
|
|
64
|
+
masks = mask_bytes(compression, header)
|
|
65
|
+
colors = color_count(bits, used, usage)
|
|
66
|
+
size = pixel_size(compression, width, bits, height, image_size)
|
|
67
|
+
return unless masks && colors && size
|
|
68
|
+
|
|
69
|
+
build_layout(bytes, header, masks, colors, usage, size)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def header_size(bytes, usage)
|
|
73
|
+
return unless bytes.bytesize >= 4 && usage.between?(0, 2)
|
|
74
|
+
|
|
75
|
+
size = bytes.unpack1("V")
|
|
76
|
+
size unless (size != 12 && size < 40) || size > bytes.bytesize
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_layout(bytes, header, masks, colors, usage, size)
|
|
80
|
+
entry_size = color_entry_size(header, usage)
|
|
81
|
+
start = header + masks + (colors * entry_size)
|
|
82
|
+
return unless start + size <= bytes.bytesize && valid_profile?(bytes, header, start + size)
|
|
83
|
+
|
|
84
|
+
Layout.new(header: header, masks: masks, color_count: colors, entry_size: entry_size, pixel_start: start, pixel_size: size)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def color_entry_size(header, usage)
|
|
88
|
+
return 2 if usage == 1
|
|
89
|
+
|
|
90
|
+
header == 12 ? 3 : 4
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def header_fields(bytes, header)
|
|
94
|
+
return bytes.unpack("@4v4") + [0, 0, 0] if header == 12
|
|
95
|
+
|
|
96
|
+
width, height, planes, bits, compression, image_size = bytes.unpack("@4l<2v2V2")
|
|
97
|
+
[width, height, planes, bits, compression, image_size, bytes.unpack1("@32V")]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def valid_shape?(width, height, planes, bits)
|
|
101
|
+
width.positive? && !height.zero? && planes == 1 && BIT_COUNTS.include?(bits)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def valid_compression?(compression, bits, height)
|
|
105
|
+
return false unless COMPRESSIONS.include?(compression)
|
|
106
|
+
return false if height.negative? && ![0, 3].include?(compression)
|
|
107
|
+
return false if bits.zero? && ![4, 5].include?(compression)
|
|
108
|
+
|
|
109
|
+
RLE_BITS.fetch(compression, bits) == bits && (compression != 3 || [16, 32].include?(bits))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Embedded masks occupy the extension in V4/V5 and later headers.
|
|
113
|
+
def mask_bytes(compression, header)
|
|
114
|
+
return 0 unless compression == 3
|
|
115
|
+
return 12 if header == 40
|
|
116
|
+
|
|
117
|
+
0 if header >= 52
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def color_count(bits, used, usage)
|
|
121
|
+
max_colors = bits.between?(1, 8) ? 1 << bits : 0
|
|
122
|
+
return 0 if usage == 2
|
|
123
|
+
return max_colors if used.zero?
|
|
124
|
+
return if max_colors.positive? && used > max_colors
|
|
125
|
+
|
|
126
|
+
used
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def pixel_size(compression, width, bits, height, image_size)
|
|
130
|
+
return (((width * bits) + 31) / 32 * 4) * height.abs if [0, 3, 11].include?(compression)
|
|
131
|
+
|
|
132
|
+
image_size unless image_size.zero?
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# A packed V5 profile may follow pixels, but may not overlap header/table/pixels.
|
|
136
|
+
def valid_profile?(bytes, header, pixel_end)
|
|
137
|
+
return true unless header >= 124 && PROFILE_SIGNATURES.include?(bytes.unpack1("@56V"))
|
|
138
|
+
|
|
139
|
+
profile, profile_size = bytes.unpack("@112V2")
|
|
140
|
+
profile_size.zero? || (profile >= pixel_end && profile + profile_size <= bytes.bytesize)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
module Bitmap16
|
|
145
|
+
Image = Struct.new(:bitmap_type, :width, :height, :width_bytes, :planes, :bits_pixel, :pixels, :trailing,
|
|
146
|
+
keyword_init: true)
|
|
147
|
+
|
|
148
|
+
module_function
|
|
149
|
+
|
|
150
|
+
# The extent derives from Width and BitsPixel, independent of WidthBytes, which is
|
|
151
|
+
# preserved verbatim even when inconsistent with that stride.
|
|
152
|
+
def parse(bytes)
|
|
153
|
+
bytes = bytes.b
|
|
154
|
+
return unless bytes.bytesize >= 10
|
|
155
|
+
|
|
156
|
+
type, width, height, width_bytes, planes, bits = bytes.unpack("s<4C2")
|
|
157
|
+
return unless width.positive? && height.positive? && planes == 1 && bits.positive?
|
|
158
|
+
|
|
159
|
+
size = ((((width * bits) + 15) / 16) * 2) * height
|
|
160
|
+
return unless 10 + size <= bytes.bytesize
|
|
161
|
+
|
|
162
|
+
Image.new(bitmap_type: type, width: width, height: height, width_bytes: width_bytes, planes: planes,
|
|
163
|
+
bits_pixel: bits, pixels: bytes.byteslice(10, size), trailing: bytes.byteslice((10 + size)..))
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Typed interior of a bitmap transfer record: a Bitmap16::Image, a Dib::Image,
|
|
168
|
+
# ABSENT for an optional source that is not present, or nil when malformed.
|
|
169
|
+
module BitmapImage
|
|
170
|
+
Absent = Class.new { def inspect = "#<Emf::Wmf::BitmapImage::ABSENT>" }
|
|
171
|
+
ABSENT = Absent.new.freeze
|
|
172
|
+
|
|
173
|
+
module_function
|
|
174
|
+
|
|
175
|
+
def of(record)
|
|
176
|
+
bitmap = record.bitmap
|
|
177
|
+
return ABSENT if bitmap.nil?
|
|
178
|
+
|
|
179
|
+
case record
|
|
180
|
+
when Model::Wmf::Records::BitBlt, Model::Wmf::Records::StretchBlt then Bitmap16.parse(bitmap)
|
|
181
|
+
when Model::Wmf::Records::DibBitBlt, Model::Wmf::Records::DibStretchBlt then Dib.parse(bitmap, 0)
|
|
182
|
+
else Dib.parse(bitmap, record.color_usage)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|