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/parser.rb
CHANGED
|
@@ -5,8 +5,153 @@ module Emf
|
|
|
5
5
|
module Parser
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
-
def call(
|
|
9
|
-
|
|
8
|
+
def call(bytes)
|
|
9
|
+
ResourceLimits.check!(:input_bytes, Binary::MAX_INPUT_BYTES, bytes.bytesize)
|
|
10
|
+
|
|
11
|
+
errors = []
|
|
12
|
+
placeable, start = read_placeable(bytes, errors)
|
|
13
|
+
header = read_header(bytes, start, placeable)
|
|
14
|
+
records = read_records(bytes, start + Binary::HEADER_BYTES, bytes.bytesize, errors)
|
|
15
|
+
Binary.validate_objects(records, header.number_of_objects,
|
|
16
|
+
recovered: Evidence.reference_recovery(bytes, errors)) do |record, message|
|
|
17
|
+
parse_error(record.offset, record.type_id, message)
|
|
18
|
+
end
|
|
19
|
+
# The walk is bounded by the real input and ends at the first EOF record, so a header Size or MaxRecord that
|
|
20
|
+
# disagrees with the records is recovered, like the shapes of Recovery and the object references that
|
|
21
|
+
# name nothing usable: the model is kept and each departure is recorded in errors.
|
|
22
|
+
stop = records.last.offset + (records.last.declared_size_words * 2)
|
|
23
|
+
Model::Metafile.new(format: :wmf, header: header, records: records,
|
|
24
|
+
errors: Evidence.ordered(errors + Evidence.drift_errors(bytes)),
|
|
25
|
+
trailing: bytes.byteslice(stop..).to_s)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def read_placeable(bytes, errors)
|
|
29
|
+
return [nil, 0] unless bytes.bytesize >= 4 && bytes.unpack1("V") == Binary::PLACEABLE_KEY
|
|
30
|
+
raise FormatError, "truncated WMF placeable header" if bytes.bytesize < Binary::PLACEABLE_BYTES
|
|
31
|
+
|
|
32
|
+
_key, handle, left, top, right, bottom, inch, reserved, checksum = bytes.unpack("Vvs<4vVv")
|
|
33
|
+
raise FormatError, "WMF placeable reserved field must be zero" unless reserved.zero?
|
|
34
|
+
|
|
35
|
+
errors << Evidence.recovered_error(203, bytes, nil) unless checksum == Binary.checksum(bytes.byteslice(0, 20))
|
|
36
|
+
|
|
37
|
+
header = Model::Wmf::PlaceableHeader.new(handle: handle, left: left, top: top, right: right, bottom: bottom,
|
|
38
|
+
inch: inch, checksum: checksum)
|
|
39
|
+
[header, Binary::PLACEABLE_BYTES]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def read_header(bytes, start, placeable)
|
|
43
|
+
raise FormatError, "truncated WMF standard header" if bytes.bytesize - start < Binary::HEADER_BYTES
|
|
44
|
+
|
|
45
|
+
type, header_size, version, size, objects, max_record, members = bytes.byteslice(start, Binary::HEADER_BYTES).unpack("v3VvVv")
|
|
46
|
+
raise FormatError, "invalid WMF Type" unless [1, 2].include?(type)
|
|
47
|
+
raise FormatError, "invalid WMF HeaderSize" unless header_size == 9
|
|
48
|
+
raise FormatError, "invalid WMF Version" unless [0x100, 0x300].include?(version)
|
|
49
|
+
raise FormatError, "WMF on-disk placeable handle must be zero" if type == 2 && placeable && !placeable.handle.zero?
|
|
50
|
+
|
|
51
|
+
Model::Wmf::Header.new(type: type, version: version, size_words: size, number_of_objects: objects,
|
|
52
|
+
max_record_words: max_record, number_of_members: members, placeable: placeable)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def read_records(bytes, offset, limit, errors)
|
|
56
|
+
records = []
|
|
57
|
+
while offset < limit
|
|
58
|
+
ResourceLimits.check!(:record_count, Binary::MAX_RECORDS, records.length + 1) do
|
|
59
|
+
Evidence.build(401, bytes, offset)
|
|
60
|
+
end
|
|
61
|
+
parse_error(offset, nil, "truncated record header") if limit - offset < 6
|
|
62
|
+
words, function = bytes.byteslice(offset, 6).unpack("Vv")
|
|
63
|
+
parse_error(offset, function, "invalid record length #{words}") if words < 3 || words > (limit - offset) / 2
|
|
64
|
+
raw = bytes.byteslice(offset, words * 2)
|
|
65
|
+
record, reason = Recovery.record(raw, function, offset)
|
|
66
|
+
errors << Evidence.recovered_error(reason, bytes, offset, function) if reason
|
|
67
|
+
record = (record || decode_record(raw, function, offset)).declare_size(words)
|
|
68
|
+
records << record
|
|
69
|
+
offset += raw.bytesize
|
|
70
|
+
return records if record.is_a?(Model::Wmf::Records::Eof)
|
|
71
|
+
end
|
|
72
|
+
parse_error(offset, nil, "missing EOF record")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def decode_record(raw, function, offset)
|
|
76
|
+
return Model::Wmf::Records::Eof.new(offset: offset) if function.zero?
|
|
77
|
+
|
|
78
|
+
return decode_prefix_record(raw, function, offset) if Binary::PREFIX_FUNCTIONS.include?(function & 0xFF)
|
|
79
|
+
return decode_object(raw, function, offset) if Binary::OBJECT_RECORDS.key?(function & 0xFF)
|
|
80
|
+
if Binary::TEXT_FUNCTIONS.include?(function & 0xFF)
|
|
81
|
+
return TextCodec.decode(raw, function, offset) { |message| parse_error(offset, function, message) }
|
|
82
|
+
end
|
|
83
|
+
return decode_point_array(raw, function, offset) if Binary::POINT_ARRAY_RECORDS.key?(function & 0xFF)
|
|
84
|
+
|
|
85
|
+
record_class = Binary::FIXED_RECORDS[function & 0xFF]
|
|
86
|
+
return Model::Wmf::Records::UnknownRecord.new(type_id: function, raw_bytes: raw, offset: offset) unless record_class
|
|
87
|
+
|
|
88
|
+
fields = record_class::WIRE_FIELDS
|
|
89
|
+
parse_error(offset, function, "invalid fixed record length") unless raw.bytesize == 6 + (fields.length * 2)
|
|
90
|
+
values = raw.byteslice(6..).unpack("s<*")
|
|
91
|
+
record_class.new(**fields.zip(values).to_h, type_id: function, offset: offset)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def decode_prefix_record(raw, function, offset)
|
|
95
|
+
return decode_font(raw, function, offset) if function & 0xFF == 0xFB
|
|
96
|
+
return decode_state(raw, function, offset) if Binary::STATE_RECORDS.key?(function & 0xFF)
|
|
97
|
+
|
|
98
|
+
codec = [PaletteCodec, RegionCodec, BrushCodec, BitmapCodec, GeometryCodec, DcEscapeCodec]
|
|
99
|
+
.find { |candidate| candidate::RECORDS.key?(function & 0xFF) }
|
|
100
|
+
codec.decode(raw, function, offset) { |message| parse_error(offset, function, message) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def decode_state(raw, function, offset)
|
|
104
|
+
record_class = Binary::STATE_RECORDS.fetch(function & 0xFF)
|
|
105
|
+
prefix_size = 6 + record_class::PAYLOAD_BYTES
|
|
106
|
+
parse_error(offset, function, "invalid state record length") if raw.bytesize < prefix_size
|
|
107
|
+
values = raw.byteslice(6, record_class::PAYLOAD_BYTES).unpack(record_class::WIRE_FORMAT)
|
|
108
|
+
fields = record_class::WIRE_FIELDS.zip(values).to_h
|
|
109
|
+
record_class.new(**fields, type_id: function, offset: offset, suffix: raw.byteslice(prefix_size..))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def decode_font(raw, function, offset)
|
|
113
|
+
record_class = Model::Wmf::Records::CreateFontIndirect
|
|
114
|
+
prefix_size = 6 + record_class::PAYLOAD_BYTES
|
|
115
|
+
parse_error(offset, function, "invalid font record length") if raw.bytesize < prefix_size
|
|
116
|
+
values = raw.byteslice(6, record_class::PAYLOAD_BYTES).unpack(record_class::WIRE_FORMAT)
|
|
117
|
+
fields = record_class::WIRE_FIELDS.zip(values).to_h
|
|
118
|
+
record_class.new(**fields, type_id: function, offset: offset, suffix: raw.byteslice(prefix_size..))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def decode_object(raw, function, offset)
|
|
122
|
+
record_class = Binary::OBJECT_RECORDS.fetch(function & 0xFF)
|
|
123
|
+
parse_error(offset, function, "invalid object record length") unless raw.bytesize == 6 + record_class::PAYLOAD_BYTES
|
|
124
|
+
values = raw.byteslice(6..).unpack(record_class::WIRE_FORMAT)
|
|
125
|
+
record_class.new(**record_class::WIRE_FIELDS.zip(values).to_h, type_id: function, offset: offset)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def decode_point_array(raw, function, offset)
|
|
129
|
+
parse_error(offset, function, "missing point array count") if raw.bytesize < 8
|
|
130
|
+
record_class = Binary::POINT_ARRAY_RECORDS.fetch(function & 0xFF)
|
|
131
|
+
if record_class == Model::Wmf::Records::PolyPolygon
|
|
132
|
+
polygon_count = raw.byteslice(6, 2).unpack1("v")
|
|
133
|
+
points_start = 8 + (polygon_count * 2)
|
|
134
|
+
parse_error(offset, function, "truncated polygon counts") if raw.bytesize < points_start
|
|
135
|
+
counts = raw.byteslice(8, polygon_count * 2).unpack("v*")
|
|
136
|
+
point_count = counts.sum
|
|
137
|
+
else
|
|
138
|
+
point_count = raw.byteslice(6, 2).unpack1("s<")
|
|
139
|
+
minimum = record_class == Model::Wmf::Records::Polygon ? 2 : 0
|
|
140
|
+
parse_error(offset, function, "invalid point count") if point_count < minimum
|
|
141
|
+
points_start = 8
|
|
142
|
+
end
|
|
143
|
+
parse_error(offset, function, "point array length mismatch") unless raw.bytesize == points_start + (point_count * 4)
|
|
144
|
+
|
|
145
|
+
points = raw.byteslice(points_start..).unpack("s<*").each_slice(2).map do |x, y|
|
|
146
|
+
Model::Wmf::Records::PointS.new(x: x, y: y)
|
|
147
|
+
end
|
|
148
|
+
fields = { points: points, type_id: function, offset: offset }
|
|
149
|
+
fields[:points_per_polygon] = counts if counts
|
|
150
|
+
record_class.new(**fields)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def parse_error(offset, function, message)
|
|
154
|
+
raise ParseError.new(offset: offset, record_code: function, message: "WMF #{message} at offset #{offset}")
|
|
10
155
|
end
|
|
11
156
|
end
|
|
12
157
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Wmf
|
|
5
|
+
# Record shapes the specification rules out but real producers write and readers (LibreOffice) import. Each is
|
|
6
|
+
# framed by its own declared RecordSize inside the input, so the record is kept and the departure is recorded in
|
|
7
|
+
# Metafile#errors instead of raising. Both backends call .record on the same raw record bytes, so the models
|
|
8
|
+
# are identical. Anything not listed here, and any shape that cannot be framed safely, is left to the strict
|
|
9
|
+
# decoders, which reject it.
|
|
10
|
+
#
|
|
11
|
+
# 406 an EOF record longer than three words: it still ends the stream; the surplus is ignored
|
|
12
|
+
# 801 an object record (pen, brush, select, delete) with bytes after its payload: the payload is read, the
|
|
13
|
+
# surplus is ignored; a shorter record is still rejected
|
|
14
|
+
# 701 a font record shorter than 32 face-name bytes but holding the 18 fixed bytes: the face name is
|
|
15
|
+
# zero-padded; a record without the fixed bytes is still rejected
|
|
16
|
+
# 1003 a text record with a negative StringLength: no string can be framed, so the record is kept verbatim
|
|
17
|
+
# as an UnknownRecord; a truncated text record or rectangle is still rejected first
|
|
18
|
+
# 903 a META_POLYGON with NumberOfPoints 0 and no point bytes (PowerPoint, seen in Apache POI's
|
|
19
|
+
# empty-polygon-close.wmf): kept as a Polygon without points; 1 and negative counts are still rejected
|
|
20
|
+
# 1203 a META_CREATEREGION whose ObjectCount is two bytes wide (libUEMF, the library Inkscape writes WMF with,
|
|
21
|
+
# declares it uint16, so every later field sits two bytes early) and whose RegionSize, read there, is exactly
|
|
22
|
+
# the body: the region is kept verbatim as an UnknownRecord (it still takes its object-table slot, from the
|
|
23
|
+
# low byte of its function); any other invalid header is still rejected
|
|
24
|
+
module Recovery
|
|
25
|
+
EOF_BYTES = 6
|
|
26
|
+
FONT_FIXED_BYTES = 18
|
|
27
|
+
# function => [offset of StringLength, shortest record]
|
|
28
|
+
TEXT_COUNTS = { 0x21 => [6, 12], 0x32 => [10, 14] }.freeze
|
|
29
|
+
CLIP_OR_OPAQUE = 6
|
|
30
|
+
RECTANGLE_BYTES = 8
|
|
31
|
+
POLYGON = 0x24
|
|
32
|
+
REGION = 0xFF
|
|
33
|
+
POLYGON_EMPTY_BYTES = 8
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# [record, reason] when `raw` (one whole record) is a recoverable shape, otherwise nil.
|
|
38
|
+
def record(raw, function, offset)
|
|
39
|
+
low = function & 0xFF
|
|
40
|
+
return [Model::Wmf::Records::Eof.new(offset: offset), 406] if function.zero? && raw.bytesize > EOF_BYTES
|
|
41
|
+
return object(raw, function, offset) if Binary::OBJECT_RECORDS.key?(low)
|
|
42
|
+
return font(raw, function, offset) if low == 0xFB
|
|
43
|
+
return polygon(raw, function, offset) if low == POLYGON
|
|
44
|
+
return region(raw, function, offset) if low == REGION
|
|
45
|
+
|
|
46
|
+
text(raw, function, offset) if TEXT_COUNTS.key?(low)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def object(raw, function, offset)
|
|
50
|
+
klass = Binary::OBJECT_RECORDS.fetch(function & 0xFF)
|
|
51
|
+
return unless raw.bytesize > 6 + klass::PAYLOAD_BYTES
|
|
52
|
+
|
|
53
|
+
values = raw.byteslice(6, klass::PAYLOAD_BYTES).unpack(klass::WIRE_FORMAT)
|
|
54
|
+
[klass.new(**klass::WIRE_FIELDS.zip(values).to_h, type_id: function, offset: offset), 801]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def font(raw, function, offset)
|
|
58
|
+
klass = Model::Wmf::Records::CreateFontIndirect
|
|
59
|
+
return unless raw.bytesize.between?(6 + FONT_FIXED_BYTES, 6 + klass::PAYLOAD_BYTES - 1)
|
|
60
|
+
|
|
61
|
+
values = raw.byteslice(6..).ljust(klass::PAYLOAD_BYTES, "\0".b).unpack(klass::WIRE_FORMAT)
|
|
62
|
+
[klass.new(**klass::WIRE_FIELDS.zip(values).to_h, type_id: function, offset: offset), 701]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def polygon(raw, function, offset)
|
|
66
|
+
return unless raw.bytesize == POLYGON_EMPTY_BYTES && raw.byteslice(6, 2).unpack1("s<").zero?
|
|
67
|
+
|
|
68
|
+
[Model::Wmf::Records::Polygon.new(points: [], type_id: function, offset: offset), 903]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def region(raw, function, offset)
|
|
72
|
+
body = raw.byteslice(6..)
|
|
73
|
+
return unless body.bytesize >= RegionCodec::HEADER_BYTES && RegionCodec.header_invalid?(body)
|
|
74
|
+
|
|
75
|
+
kind, size, count = body.unpack("@2v@6s<@8s<")
|
|
76
|
+
return unless kind == RegionCodec::KIND && size == body.bytesize && !count.negative?
|
|
77
|
+
|
|
78
|
+
[Model::Wmf::Records::UnknownRecord.new(type_id: function, raw_bytes: raw, offset: offset), 1203]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def text(raw, function, offset)
|
|
82
|
+
count_at, minimum = TEXT_COUNTS.fetch(function & 0xFF)
|
|
83
|
+
return if raw.bytesize < minimum
|
|
84
|
+
return if function & 0xFF == 0x32 && rectangle_truncated?(raw, minimum)
|
|
85
|
+
return unless raw.byteslice(count_at, 2).unpack1("s<").negative?
|
|
86
|
+
|
|
87
|
+
[Model::Wmf::Records::UnknownRecord.new(type_id: function, raw_bytes: raw, offset: offset), 1003]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def rectangle_truncated?(raw, minimum)
|
|
91
|
+
raw.byteslice(12, 2).unpack1("v").anybits?(CLIP_OR_OPAQUE) && raw.bytesize < minimum + RECTANGLE_BYTES
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Wmf
|
|
5
|
+
module RegionCodec
|
|
6
|
+
RECORDS = {
|
|
7
|
+
0xFF => Model::Wmf::Records::CreateRegion,
|
|
8
|
+
0x28 => Model::Wmf::Records::FillRegion,
|
|
9
|
+
0x29 => Model::Wmf::Records::FrameRegion,
|
|
10
|
+
0x2A => Model::Wmf::Records::InvertRegion,
|
|
11
|
+
0x2B => Model::Wmf::Records::PaintRegion,
|
|
12
|
+
0x2C => Model::Wmf::Records::SelectClipRegion,
|
|
13
|
+
0x20 => Model::Wmf::Records::OffsetClipRgn,
|
|
14
|
+
0x15 => Model::Wmf::Records::ExcludeClipRect,
|
|
15
|
+
0x16 => Model::Wmf::Records::IntersectClipRect
|
|
16
|
+
}.freeze
|
|
17
|
+
FUNCTIONS = RECORDS.invert.freeze
|
|
18
|
+
HEADER_BYTES = 22
|
|
19
|
+
KIND = 6
|
|
20
|
+
BOUNDS_FIELDS = %i[left top right bottom].freeze
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def decode(raw, function, offset, &)
|
|
25
|
+
klass = RECORDS.fetch(function & 0xFF)
|
|
26
|
+
if klass == Model::Wmf::Records::CreateRegion
|
|
27
|
+
fields, size = decode_region(raw.byteslice(6..), &)
|
|
28
|
+
else
|
|
29
|
+
size = klass::WIRE_FIELDS.length * 2
|
|
30
|
+
yield "truncated region/clipping prefix" if raw.bytesize < 6 + size
|
|
31
|
+
fields = klass::WIRE_FIELDS.zip(raw.byteslice(6, size).unpack(klass::WIRE_FORMAT)).to_h
|
|
32
|
+
end
|
|
33
|
+
klass.new(**fields, type_id: function, offset: offset, suffix: raw.byteslice((6 + size)..))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def decode_region(body, &)
|
|
37
|
+
yield "truncated region header" if body.bytesize < HEADER_BYTES
|
|
38
|
+
next_in_chain, _kind, object_count, size, count, max_scan, *bounds = body.unpack("v2Vs<7")
|
|
39
|
+
yield "invalid region header" if header_invalid?(body)
|
|
40
|
+
preflight_scans(body, size, count, max_scan, &)
|
|
41
|
+
cursor = 22
|
|
42
|
+
scans = Array.new(count) do
|
|
43
|
+
points, top, bottom = body.byteslice(cursor, 6).unpack("v3")
|
|
44
|
+
lines = body.byteslice(cursor + 6, points * 2).unpack("v*").each_slice(2).map do |left, right|
|
|
45
|
+
Model::Wmf::Records::RegionScanLine.new(left: left, right: right)
|
|
46
|
+
end
|
|
47
|
+
cursor += 8 + (points * 2)
|
|
48
|
+
Model::Wmf::Records::RegionScan.new(top: top, bottom: bottom, lines: lines)
|
|
49
|
+
end
|
|
50
|
+
[{ next_in_chain: next_in_chain, object_count: object_count,
|
|
51
|
+
bounds: Model::Wmf::Records::RegionBounds.new(**BOUNDS_FIELDS.zip(bounds).to_h), scans: scans }, size]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def header_fields(body)
|
|
55
|
+
body.unpack("@2v@8s<s<")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The spec's header checks, shared with Wmf::Recovery so a recovered shape is exactly one this rejects.
|
|
59
|
+
def header_invalid?(body)
|
|
60
|
+
kind, size, count = header_fields(body)
|
|
61
|
+
max_scan = body.unpack1("@12s<")
|
|
62
|
+
kind != KIND || size < HEADER_BYTES || size > body.bytesize || count.negative? || max_scan.negative? ||
|
|
63
|
+
count > (size - HEADER_BYTES) / 8
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def preflight_scans(body, size, count, max_scan)
|
|
67
|
+
cursor = 22
|
|
68
|
+
observed_max = 0
|
|
69
|
+
count.times do
|
|
70
|
+
yield "truncated region scan" if size - cursor < 8
|
|
71
|
+
points = body.byteslice(cursor, 2).unpack1("v")
|
|
72
|
+
scan_size = 8 + (points * 2)
|
|
73
|
+
if points.odd? || scan_size > size - cursor || body.byteslice(cursor + scan_size - 2, 2).unpack1("v") != points
|
|
74
|
+
yield "invalid region scan"
|
|
75
|
+
end
|
|
76
|
+
observed_max = [observed_max, points].max
|
|
77
|
+
cursor += scan_size
|
|
78
|
+
end
|
|
79
|
+
yield "invalid region scan size or maximum" unless cursor == size && observed_max == max_scan
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def byte_size(record)
|
|
83
|
+
size = if record.is_a?(Model::Wmf::Records::CreateRegion)
|
|
84
|
+
22 + record.scans.sum { |scan| 8 + (scan.lines.length * 4) }
|
|
85
|
+
else
|
|
86
|
+
record.class::WIRE_FIELDS.length * 2
|
|
87
|
+
end
|
|
88
|
+
6 + size + record.suffix.bytesize
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def encode(record)
|
|
92
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
93
|
+
raise SerializeError, "WMF region RecordFunction mismatch" unless function & 0xFF == FUNCTIONS[record.class]
|
|
94
|
+
unless record.suffix.is_a?(String) && record.suffix.bytesize.even?
|
|
95
|
+
raise SerializeError, "WMF region suffix must be a String with even length"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
payload = encode_payload(record) + record.suffix.b
|
|
99
|
+
[3 + (payload.bytesize / 2), function].pack("Vv") + payload
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def encode_payload(record)
|
|
103
|
+
return encode_region(record) if record.is_a?(Model::Wmf::Records::CreateRegion)
|
|
104
|
+
|
|
105
|
+
values = record.class::WIRE_FIELDS.zip(record.class::WIRE_TYPES).map do |field, type|
|
|
106
|
+
Binary.public_send(type, record.public_send(field), field)
|
|
107
|
+
end
|
|
108
|
+
values.pack(record.class::WIRE_FORMAT)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def encode_region(record)
|
|
112
|
+
unless record.bounds.is_a?(Model::Wmf::Records::RegionBounds) && record.scans.is_a?(Array)
|
|
113
|
+
raise SerializeError, "WMF region requires RegionBounds and an array of scans"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
count = Binary.integer(record.scans.length, 0..0x7FFF, "scan count")
|
|
117
|
+
size, max_scan = region_layout(record.scans)
|
|
118
|
+
bounds = BOUNDS_FIELDS.map { |field| Binary.int16(record.bounds.public_send(field), field) }
|
|
119
|
+
header = [Binary.uint16(record.next_in_chain, "next_in_chain"), 6, Binary.uint32(record.object_count, "object_count"),
|
|
120
|
+
size, count, max_scan, *bounds].pack("v2Vs<7")
|
|
121
|
+
header + record.scans.map { |scan| encode_scan(scan) }.join
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def region_layout(scans)
|
|
125
|
+
size = 22
|
|
126
|
+
max_scan = 0
|
|
127
|
+
scans.each do |scan|
|
|
128
|
+
unless scan.is_a?(Model::Wmf::Records::RegionScan) && scan.lines.is_a?(Array)
|
|
129
|
+
raise SerializeError, "WMF region scan requires RegionScan with an array of lines"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
points = Binary.integer(scan.lines.length * 2, 0..0x7FFF, "scan point count")
|
|
133
|
+
size = Binary.integer(size + 8 + (points * 2), 22..0x7FFF, "region size")
|
|
134
|
+
max_scan = [max_scan, points].max
|
|
135
|
+
end
|
|
136
|
+
[size, max_scan]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def encode_scan(scan)
|
|
140
|
+
points = scan.lines.length * 2
|
|
141
|
+
prefix = [points, Binary.uint16(scan.top, "scan top"), Binary.uint16(scan.bottom, "scan bottom")].pack("v3")
|
|
142
|
+
lines = scan.lines.map do |line|
|
|
143
|
+
raise SerializeError, "WMF region line must be a RegionScanLine" unless line.is_a?(Model::Wmf::Records::RegionScanLine)
|
|
144
|
+
|
|
145
|
+
[Binary.uint16(line.left, "scan left"), Binary.uint16(line.right, "scan right")].pack("v2")
|
|
146
|
+
end
|
|
147
|
+
prefix + lines.join + [points].pack("v")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
data/lib/emf/wmf/serializer.rb
CHANGED
|
@@ -5,8 +5,170 @@ module Emf
|
|
|
5
5
|
module Serializer
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
-
def call(
|
|
9
|
-
raise SerializeError, "WMF
|
|
8
|
+
def call(metafile)
|
|
9
|
+
raise SerializeError, "expected a WMF metafile" unless metafile.format == :wmf && metafile.header.is_a?(Model::Wmf::Header)
|
|
10
|
+
raise SerializeError, "WMF record limit exceeded" if metafile.records.length > Binary::MAX_RECORDS
|
|
11
|
+
|
|
12
|
+
validate_eof(metafile.records)
|
|
13
|
+
records = metafile.records.map { |record| serialize_record(record) }
|
|
14
|
+
capacity = Binary.uint16(metafile.header.number_of_objects, "NumberOfObjects")
|
|
15
|
+
Binary.validate_objects(metafile.records, capacity) do |_record, message|
|
|
16
|
+
raise SerializeError, "WMF #{message}"
|
|
17
|
+
end
|
|
18
|
+
size = 9 + records.sum { |bytes| bytes.bytesize / 2 }
|
|
19
|
+
raise SerializeError, "WMF input too large" if (size * 2) + metafile.trailing.bytesize > Binary::MAX_INPUT_BYTES
|
|
20
|
+
|
|
21
|
+
header = serialize_header(metafile.header, size, records.map { |bytes| bytes.bytesize / 2 }.max)
|
|
22
|
+
placeable = serialize_placeable(metafile.header)
|
|
23
|
+
bytes = placeable + header + records.join + metafile.trailing
|
|
24
|
+
raise SerializeError, "WMF input too large" if bytes.bytesize > Binary::MAX_INPUT_BYTES
|
|
25
|
+
|
|
26
|
+
bytes
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate_eof(records)
|
|
30
|
+
return if records.last.is_a?(Model::Wmf::Records::Eof) && records.one?(Model::Wmf::Records::Eof)
|
|
31
|
+
|
|
32
|
+
raise SerializeError, "WMF requires one final EOF record"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def serialize_header(header, size, max_record)
|
|
36
|
+
raise SerializeError, "invalid WMF Type" unless [1, 2].include?(header.type)
|
|
37
|
+
raise SerializeError, "invalid WMF Version" unless [0x100, 0x300].include?(header.version)
|
|
38
|
+
|
|
39
|
+
[header.type, 9, header.version, size, Binary.uint16(header.number_of_objects, "NumberOfObjects"),
|
|
40
|
+
max_record, Binary.uint16(header.number_of_members, "NumberOfMembers")].pack("v3VvVv")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def serialize_placeable(header)
|
|
44
|
+
placeable = header.placeable
|
|
45
|
+
return "".b unless placeable
|
|
46
|
+
raise SerializeError, "WMF on-disk placeable handle must be zero" if header.type == 2 && !placeable.handle.zero?
|
|
47
|
+
|
|
48
|
+
bytes = [Binary::PLACEABLE_KEY, Binary.uint16(placeable.handle, "handle"),
|
|
49
|
+
Binary.int16(placeable.left, "left"), Binary.int16(placeable.top, "top"),
|
|
50
|
+
Binary.int16(placeable.right, "right"), Binary.int16(placeable.bottom, "bottom"),
|
|
51
|
+
Binary.uint16(placeable.inch, "inch"), 0].pack("Vvs<4vV")
|
|
52
|
+
bytes + [Binary.checksum(bytes)].pack("v")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def serialize_record(record)
|
|
56
|
+
codec = Binary.prefix_codec(record)
|
|
57
|
+
return codec.encode(record) if codec
|
|
58
|
+
|
|
59
|
+
case record
|
|
60
|
+
when Model::Wmf::Records::Eof then [3, 0].pack("Vv")
|
|
61
|
+
when Model::Wmf::Records::PointRecord, Model::Wmf::Records::RectangleRecord, Model::Wmf::Records::OffsetRecord
|
|
62
|
+
serialize_fixed(record)
|
|
63
|
+
when Model::Wmf::Records::StateRecord then serialize_state(record)
|
|
64
|
+
when Model::Wmf::Records::CreateFontIndirect then serialize_font(record)
|
|
65
|
+
when Model::Wmf::Records::ObjectRecord then serialize_object(record)
|
|
66
|
+
when Model::Wmf::Records::TextOut then TextCodec.encode(record)
|
|
67
|
+
when Model::Wmf::Records::PointArrayRecord then serialize_point_array(record)
|
|
68
|
+
when Model::Wmf::Records::UnknownRecord then serialize_unknown(record)
|
|
69
|
+
else raise SerializeError, "unsupported WMF record #{record.class}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def serialize_fixed(record)
|
|
74
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
75
|
+
expected = Binary::FIXED_FUNCTIONS[record.class]
|
|
76
|
+
raise SerializeError, "WMF fixed RecordFunction mismatch" unless function & 0xFF == expected
|
|
77
|
+
|
|
78
|
+
values = record.class::WIRE_FIELDS.map { |field| Binary.int16(record.public_send(field), field) }
|
|
79
|
+
[3 + values.length, function, *values].pack("Vvs<*")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def serialize_state(record)
|
|
83
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
84
|
+
expected = Binary::STATE_FUNCTIONS[record.class]
|
|
85
|
+
raise SerializeError, "WMF state RecordFunction mismatch" unless function & 0xFF == expected
|
|
86
|
+
unless record.suffix.is_a?(String) && record.suffix.bytesize.even?
|
|
87
|
+
raise SerializeError, "WMF state suffix must be a String with even length"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
values = record.class::WIRE_FIELDS.zip(record.class::WIRE_TYPES).map do |field, type|
|
|
91
|
+
Binary.public_send(type, record.public_send(field), field)
|
|
92
|
+
end
|
|
93
|
+
payload = values.pack(record.class::WIRE_FORMAT) + record.suffix.b
|
|
94
|
+
[3 + (payload.bytesize / 2), function].pack("Vv") + payload
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def serialize_font(record)
|
|
98
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
99
|
+
raise SerializeError, "WMF font RecordFunction mismatch" unless function & 0xFF == 0xFB
|
|
100
|
+
unless record.face_name.is_a?(String) && record.face_name.bytesize == 32
|
|
101
|
+
raise SerializeError, "WMF font face_name must be a String with 32 bytes"
|
|
102
|
+
end
|
|
103
|
+
unless record.suffix.is_a?(String) && record.suffix.bytesize.even?
|
|
104
|
+
raise SerializeError, "WMF font suffix must be a String with even length"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
values = record.class::SIGNED_FIELDS.map { |field| Binary.int16(record.public_send(field), field) }
|
|
108
|
+
values += record.class::BYTE_FIELDS.map { |field| Binary.uint8(record.public_send(field), field) }
|
|
109
|
+
payload = [*values, record.face_name.b].pack(record.class::WIRE_FORMAT) + record.suffix.b
|
|
110
|
+
[3 + (payload.bytesize / 2), function].pack("Vv") + payload
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def serialize_object(record)
|
|
114
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
115
|
+
expected = Binary::OBJECT_FUNCTIONS[record.class]
|
|
116
|
+
raise SerializeError, "WMF object RecordFunction mismatch" unless function & 0xFF == expected
|
|
117
|
+
|
|
118
|
+
values = record.class::WIRE_FIELDS.zip(record.class::WIRE_TYPES).map do |field, type|
|
|
119
|
+
Binary.public_send(type, record.public_send(field), field)
|
|
120
|
+
end
|
|
121
|
+
payload = values.pack(record.class::WIRE_FORMAT)
|
|
122
|
+
[3 + (payload.bytesize / 2), function].pack("Vv") + payload
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def serialize_point_array(record)
|
|
126
|
+
function = Binary.uint16(record.type_id, "RecordFunction")
|
|
127
|
+
expected = Binary::POINT_ARRAY_FUNCTIONS[record.class]
|
|
128
|
+
raise SerializeError, "WMF point array RecordFunction mismatch" unless function & 0xFF == expected
|
|
129
|
+
raise SerializeError, "WMF points must be an array" unless record.points.is_a?(Array)
|
|
130
|
+
|
|
131
|
+
counts = serialize_point_counts(record)
|
|
132
|
+
coordinates = record.points.flat_map do |point|
|
|
133
|
+
raise SerializeError, "WMF point must be a PointS" unless point.is_a?(Model::Wmf::Records::PointS)
|
|
134
|
+
|
|
135
|
+
[Binary.int16(point.x, "x"), Binary.int16(point.y, "y")]
|
|
136
|
+
end
|
|
137
|
+
payload = counts + coordinates.pack("s<*")
|
|
138
|
+
[3 + (payload.bytesize / 2), function].pack("Vv") + payload
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def serialize_point_counts(record)
|
|
142
|
+
unless record.is_a?(Model::Wmf::Records::PolyPolygon)
|
|
143
|
+
count = Binary.int16(record.points.length, "NumberOfPoints")
|
|
144
|
+
raise SerializeError, "WMF Polygon requires at least two points" if record.is_a?(Model::Wmf::Records::Polygon) && count < 2
|
|
145
|
+
|
|
146
|
+
return [count].pack("s<")
|
|
147
|
+
end
|
|
148
|
+
counts = record.points_per_polygon
|
|
149
|
+
raise SerializeError, "WMF polygon counts must be an array" unless counts.is_a?(Array)
|
|
150
|
+
|
|
151
|
+
polygon_count = Binary.uint16(counts.length, "NumberOfPolygons")
|
|
152
|
+
counts.each { |count| Binary.uint16(count, "PointsPerPolygon") }
|
|
153
|
+
raise SerializeError, "WMF polygon point count mismatch" unless counts.sum == record.points.length
|
|
154
|
+
|
|
155
|
+
[polygon_count, *counts].pack("v*")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# A text record with a negative StringLength, which the parser keeps verbatim (Recovery).
|
|
159
|
+
def kept_verbatim?(raw, function)
|
|
160
|
+
Recovery.record(raw, function, nil)&.last == 1003
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def serialize_unknown(record)
|
|
164
|
+
raw = record.raw_bytes
|
|
165
|
+
words, function = raw.unpack("Vv")
|
|
166
|
+
unless raw.bytesize >= 6 && raw.bytesize.even? && words == raw.bytesize / 2 && function == record.type_id &&
|
|
167
|
+
!function.zero? && (!Binary::TYPED_FUNCTIONS.include?(function & 0xFF) || kept_verbatim?(raw, function))
|
|
168
|
+
raise SerializeError, "invalid raw WMF record"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
raw
|
|
10
172
|
end
|
|
11
173
|
end
|
|
12
174
|
end
|