emf 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.adoc +224 -19
- data/exe/emf +12 -0
- data/lib/emf/backend.rb +60 -0
- data/lib/emf/binary/class_field_accessors.rb +49 -0
- data/lib/emf/binary/float32_le.rb +54 -0
- data/lib/emf/binary/types.rb +40 -6
- data/lib/emf/conformance/context.rb +36 -0
- data/lib/emf/conformance/diagnostic.rb +18 -0
- data/lib/emf/conformance/manifest/emf_bitmap_misc.yml +622 -0
- data/lib/emf/conformance/manifest/emf_control_buffers.yml +61 -0
- data/lib/emf/conformance/manifest/emf_core.yml +117 -0
- data/lib/emf/conformance/manifest/emf_corpus_rules.yml +21 -0
- data/lib/emf/conformance/manifest/emf_objects_geometry.yml +229 -0
- data/lib/emf/conformance/manifest/emf_plus.yml +357 -0
- data/lib/emf/conformance/manifest/emf_plus_core.yml +33 -0
- data/lib/emf/conformance/manifest/emf_state_path.yml +403 -0
- data/lib/emf/conformance/manifest/emf_text.yml +97 -0
- data/lib/emf/conformance/manifest/wmf.yml +874 -0
- data/lib/emf/conformance/manifest.rb +79 -0
- data/lib/emf/conformance/profile.rb +35 -0
- data/lib/emf/conformance/record_types.rb +24 -0
- data/lib/emf/conformance/registry.rb +76 -0
- data/lib/emf/conformance/report.rb +83 -0
- data/lib/emf/conformance/rule.rb +16 -0
- data/lib/emf/conformance/rules/emf_bitmap.rb +357 -0
- data/lib/emf/conformance/rules/emf_comment_escape.rb +309 -0
- data/lib/emf/conformance/rules/emf_control_buffers.rb +137 -0
- data/lib/emf/conformance/rules/emf_corpus_named.rb +83 -0
- data/lib/emf/conformance/rules/emf_framing.rb +94 -0
- data/lib/emf/conformance/rules/emf_geometry.rb +282 -0
- data/lib/emf/conformance/rules/emf_header.rb +114 -0
- data/lib/emf/conformance/rules/emf_objects.rb +291 -0
- data/lib/emf/conformance/rules/emf_parse_recovery.rb +97 -0
- data/lib/emf/conformance/rules/emf_path_clip.rb +165 -0
- data/lib/emf/conformance/rules/emf_plus.rb +75 -0
- data/lib/emf/conformance/rules/emf_plus_common.rb +58 -0
- data/lib/emf/conformance/rules/emf_plus_drawing.rb +78 -0
- data/lib/emf/conformance/rules/emf_plus_effects.rb +104 -0
- data/lib/emf/conformance/rules/emf_plus_fields.rb +169 -0
- data/lib/emf/conformance/rules/emf_plus_fields_b.rb +102 -0
- data/lib/emf/conformance/rules/emf_plus_framing.rb +172 -0
- data/lib/emf/conformance/rules/emf_plus_objects.rb +97 -0
- data/lib/emf/conformance/rules/emf_plus_payloads.rb +320 -0
- data/lib/emf/conformance/rules/emf_plus_properties.rb +96 -0
- data/lib/emf/conformance/rules/emf_plus_state.rb +77 -0
- data/lib/emf/conformance/rules/emf_state.rb +244 -0
- data/lib/emf/conformance/rules/emf_text.rb +221 -0
- data/lib/emf/conformance/rules/emf_transform.rb +85 -0
- data/lib/emf/conformance/rules/wmf_buffers.rb +259 -0
- data/lib/emf/conformance/rules/wmf_framing.rb +146 -0
- data/lib/emf/conformance/rules/wmf_layout_records.rb +112 -0
- data/lib/emf/conformance/rules/wmf_objects.rb +168 -0
- data/lib/emf/conformance/rules/wmf_records.rb +191 -0
- data/lib/emf/conformance/rules/wmf_rejections.rb +184 -0
- data/lib/emf/conformance/runner.rb +61 -0
- data/lib/emf/conformance/ternary_raster_operations.rb +52 -0
- data/lib/emf/conformance.rb +31 -0
- data/lib/emf/emf_plus/effects/blur.rb +24 -0
- data/lib/emf/emf_plus/effects/brightness_contrast.rb +22 -0
- data/lib/emf/emf_plus/effects/color_balance.rb +24 -0
- data/lib/emf/emf_plus/effects/color_curve.rb +24 -0
- data/lib/emf/emf_plus/effects/color_lookup_table.rb +26 -0
- data/lib/emf/emf_plus/effects/color_matrix.rb +23 -0
- data/lib/emf/emf_plus/effects/effect.rb +50 -0
- data/lib/emf/emf_plus/effects/hue_saturation_lightness.rb +24 -0
- data/lib/emf/emf_plus/effects/levels.rb +24 -0
- data/lib/emf/emf_plus/effects/opaque.rb +19 -0
- data/lib/emf/emf_plus/effects/red_eye_correction.rb +35 -0
- data/lib/emf/emf_plus/effects/sharpen.rb +24 -0
- data/lib/emf/emf_plus/effects/tint.rb +22 -0
- data/lib/emf/emf_plus/effects.rb +45 -0
- data/lib/emf/emf_plus/frame.rb +41 -0
- data/lib/emf/emf_plus/links/effect_link.rb +16 -0
- data/lib/emf/emf_plus/links/get_dc_span.rb +18 -0
- data/lib/emf/emf_plus/links/link_error.rb +15 -0
- data/lib/emf/emf_plus/links.rb +77 -0
- data/lib/emf/emf_plus/object_chain.rb +108 -0
- data/lib/emf/emf_plus/object_table.rb +182 -0
- data/lib/emf/emf_plus/objects/brush.rb +296 -0
- data/lib/emf/emf_plus/objects/custom_line_cap.rb +106 -0
- data/lib/emf/emf_plus/objects/font.rb +47 -0
- data/lib/emf/emf_plus/objects/image.rb +68 -0
- data/lib/emf/emf_plus/objects/image_attributes.rb +28 -0
- data/lib/emf/emf_plus/objects/opaque.rb +19 -0
- data/lib/emf/emf_plus/objects/parts.rb +103 -0
- data/lib/emf/emf_plus/objects/path.rb +21 -0
- data/lib/emf/emf_plus/objects/pen.rb +132 -0
- data/lib/emf/emf_plus/objects/region.rb +39 -0
- data/lib/emf/emf_plus/objects/string_format.rb +55 -0
- data/lib/emf/emf_plus/objects/typed.rb +50 -0
- data/lib/emf/emf_plus/objects.rb +46 -0
- data/lib/emf/emf_plus/parser.rb +64 -2
- data/lib/emf/emf_plus/records/base.rb +107 -0
- data/lib/emf/emf_plus/records/begin_container.rb +31 -0
- data/lib/emf/emf_plus/records/begin_container_no_params.rb +20 -0
- data/lib/emf/emf_plus/records/body.rb +16 -0
- data/lib/emf/emf_plus/records/clear.rb +22 -0
- data/lib/emf/emf_plus/records/clip_transform_flags.rb +26 -0
- data/lib/emf/emf_plus/records/comment.rb +21 -0
- data/lib/emf/emf_plus/records/draw_arc.rb +33 -0
- data/lib/emf/emf_plus/records/draw_beziers.rb +31 -0
- data/lib/emf/emf_plus/records/draw_closed_curve.rb +34 -0
- data/lib/emf/emf_plus/records/draw_curve.rb +38 -0
- data/lib/emf/emf_plus/records/draw_driver_string.rb +61 -0
- data/lib/emf/emf_plus/records/draw_ellipse.rb +28 -0
- data/lib/emf/emf_plus/records/draw_image.rb +42 -0
- data/lib/emf/emf_plus/records/draw_image_points.rb +62 -0
- data/lib/emf/emf_plus/records/draw_lines.rb +34 -0
- data/lib/emf/emf_plus/records/draw_path.rb +23 -0
- data/lib/emf/emf_plus/records/draw_pie.rb +33 -0
- data/lib/emf/emf_plus/records/draw_rects.rb +30 -0
- data/lib/emf/emf_plus/records/draw_string.rb +41 -0
- data/lib/emf/emf_plus/records/end_container.rb +20 -0
- data/lib/emf/emf_plus/records/end_of_file.rb +17 -0
- data/lib/emf/emf_plus/records/fill_brush.rb +21 -0
- data/lib/emf/emf_plus/records/fill_closed_curve.rb +38 -0
- data/lib/emf/emf_plus/records/fill_ellipse.rb +29 -0
- data/lib/emf/emf_plus/records/fill_path.rb +23 -0
- data/lib/emf/emf_plus/records/fill_pie.rb +34 -0
- data/lib/emf/emf_plus/records/fill_polygon.rb +32 -0
- data/lib/emf/emf_plus/records/fill_rects.rb +31 -0
- data/lib/emf/emf_plus/records/fill_region.rb +23 -0
- data/lib/emf/emf_plus/records/get_dc.rb +18 -0
- data/lib/emf/emf_plus/records/header.rb +35 -0
- data/lib/emf/emf_plus/records/multiply_world_transform.rb +24 -0
- data/lib/emf/emf_plus/records/object.rb +32 -0
- data/lib/emf/emf_plus/records/offset_clip.rb +24 -0
- data/lib/emf/emf_plus/records/primitives/argb.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/bitmap.rb +32 -0
- data/lib/emf/emf_plus/records/primitives/bitmap_data.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/blend_colors.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/blend_factors.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/bounded_array.rb +38 -0
- data/lib/emf/emf_plus/records/primitives/byte_reader.rb +25 -0
- data/lib/emf/emf_plus/records/primitives/character_range.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/compressed_image.rb +17 -0
- data/lib/emf/emf_plus/records/primitives/focus_scale_data.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/graphics_version.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/language_identifier.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/metafile.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/palette.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/path.rb +89 -0
- data/lib/emf/emf_plus/records/primitives/path_point_type.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/path_point_type_rle.rb +23 -0
- data/lib/emf/emf_plus/records/primitives/point.rb +18 -0
- data/lib/emf/emf_plus/records/primitives/point_array.rb +42 -0
- data/lib/emf/emf_plus/records/primitives/point_f.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/point_r.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/rect.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/rect_array.rb +24 -0
- data/lib/emf/emf_plus/records/primitives/rect_f.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/region_node.rb +68 -0
- data/lib/emf/emf_plus/records/primitives/region_node_child_nodes.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/region_node_path.rb +37 -0
- data/lib/emf/emf_plus/records/primitives/relative_coordinate.rb +32 -0
- data/lib/emf/emf_plus/records/primitives/string_format_data.rb +23 -0
- data/lib/emf/emf_plus/records/primitives/transform_matrix.rb +23 -0
- data/lib/emf/emf_plus/records/primitives.rb +41 -0
- data/lib/emf/emf_plus/records/reset_clip.rb +17 -0
- data/lib/emf/emf_plus/records/reset_world_transform.rb +17 -0
- data/lib/emf/emf_plus/records/restore.rb +20 -0
- data/lib/emf/emf_plus/records/rotate_world_transform.rb +24 -0
- data/lib/emf/emf_plus/records/save.rb +20 -0
- data/lib/emf/emf_plus/records/scale_world_transform.rb +26 -0
- data/lib/emf/emf_plus/records/serializable_object.rb +32 -0
- data/lib/emf/emf_plus/records/set_anti_alias_mode.rb +25 -0
- data/lib/emf/emf_plus/records/set_clip_path.rb +20 -0
- data/lib/emf/emf_plus/records/set_clip_rect.rb +24 -0
- data/lib/emf/emf_plus/records/set_clip_region.rb +20 -0
- data/lib/emf/emf_plus/records/set_compositing_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_compositing_quality.rb +21 -0
- data/lib/emf/emf_plus/records/set_interpolation_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_page_transform.rb +25 -0
- data/lib/emf/emf_plus/records/set_pixel_offset_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_rendering_origin.rb +22 -0
- data/lib/emf/emf_plus/records/set_text_contrast.rb +24 -0
- data/lib/emf/emf_plus/records/set_text_rendering_hint.rb +21 -0
- data/lib/emf/emf_plus/records/set_ts_clip.rb +40 -0
- data/lib/emf/emf_plus/records/set_ts_graphics.rb +88 -0
- data/lib/emf/emf_plus/records/set_world_transform.rb +22 -0
- data/lib/emf/emf_plus/records/shape_flags.rb +23 -0
- data/lib/emf/emf_plus/records/translate_world_transform.rb +26 -0
- data/lib/emf/emf_plus/records/unknown.rb +20 -0
- data/lib/emf/emf_plus/records.rb +107 -0
- data/lib/emf/emf_plus/serializer.rb +9 -2
- data/lib/emf/emf_plus/stream/conformance.rb +68 -0
- data/lib/emf/emf_plus/stream/editor.rb +21 -0
- data/lib/emf/emf_plus/stream/emf_gap.rb +20 -0
- data/lib/emf/emf_plus/stream/link_index.rb +30 -0
- data/lib/emf/emf_plus/stream/rebuild.rb +47 -0
- data/lib/emf/emf_plus/stream/record_handle.rb +26 -0
- data/lib/emf/emf_plus/stream/segment.rb +70 -0
- data/lib/emf/emf_plus/stream.rb +153 -0
- data/lib/emf/emf_plus.rb +9 -0
- data/lib/emf/emr/binary/dib_header.rb +142 -0
- data/lib/emf/emr/binary/ext_text_out_fields.rb +43 -0
- data/lib/emf/emr/binary/extended_pen_buffers.rb +23 -0
- data/lib/emf/emr/binary/fast_fill.rb +1164 -0
- data/lib/emf/emr/binary/font.rb +159 -0
- data/lib/emf/emr/binary/lazy_utf16_array.rb +82 -0
- data/lib/emf/emr/binary/pack_plan.rb +144 -0
- data/lib/emf/emr/binary/pack_plan_check.rb +75 -0
- data/lib/emf/emr/binary/pack_plan_exact.rb +130 -0
- data/lib/emf/emr/binary/pack_plan_generate.rb +116 -0
- data/lib/emf/emr/binary/pack_plan_values.rb +92 -0
- data/lib/emf/emr/binary/palette_body.rb +19 -0
- data/lib/emf/emr/binary/palette_entry.rb +18 -0
- data/lib/emf/emr/binary/record.rb +33 -0
- data/lib/emf/emr/binary/records/alpha_blend.rb +43 -0
- data/lib/emf/emr/binary/records/angle_arc.rb +2 -2
- data/lib/emf/emr/binary/records/bit_blt.rb +14 -0
- data/lib/emf/emr/binary/records/color_correct_palette.rb +55 -1
- data/lib/emf/emr/binary/records/colormatch_to_target_w.rb +76 -1
- data/lib/emf/emr/binary/records/create_colorspace.rb +67 -1
- data/lib/emf/emr/binary/records/create_colorspace_w.rb +90 -1
- data/lib/emf/emr/binary/records/create_dib_pattern_brush_pt.rb +14 -0
- data/lib/emf/emr/binary/records/create_font_indirect_w.rb +6 -2
- data/lib/emf/emr/binary/records/create_mono_brush.rb +14 -0
- data/lib/emf/emr/binary/records/create_palette.rb +19 -1
- data/lib/emf/emr/binary/records/delete_colorspace.rb +22 -1
- data/lib/emf/emr/binary/records/draw_escape.rb +45 -1
- data/lib/emf/emr/binary/records/ellipse.rb +1 -1
- data/lib/emf/emr/binary/records/eof.rb +9 -0
- data/lib/emf/emr/binary/records/ext_create_pen.rb +80 -1
- data/lib/emf/emr/binary/records/ext_escape.rb +45 -1
- data/lib/emf/emr/binary/records/ext_flood_fill.rb +9 -2
- data/lib/emf/emr/binary/records/ext_select_clip_rgn.rb +9 -1
- data/lib/emf/emr/binary/records/ext_text_out_a.rb +16 -3
- data/lib/emf/emr/binary/records/ext_text_out_w.rb +14 -3
- data/lib/emf/emr/binary/records/fill_rgn.rb +11 -2
- data/lib/emf/emr/binary/records/force_ufi_mapping.rb +38 -1
- data/lib/emf/emr/binary/records/frame_rgn.rb +12 -2
- data/lib/emf/emr/binary/records/gls_bounded_record.rb +61 -1
- data/lib/emf/emr/binary/records/gls_record.rb +42 -1
- data/lib/emf/emr/binary/records/gradient_fill.rb +7 -0
- data/lib/emf/emr/binary/records/invert_rgn.rb +11 -3
- data/lib/emf/emr/binary/records/mask_blt.rb +66 -0
- data/lib/emf/emr/binary/records/named_escape.rb +78 -1
- data/lib/emf/emr/binary/records/paint_rgn.rb +11 -3
- data/lib/emf/emr/binary/records/pixel_format.rb +76 -1
- data/lib/emf/emr/binary/records/plg_blt.rb +66 -0
- data/lib/emf/emr/binary/records/poly_textout_a.rb +128 -1
- data/lib/emf/emr/binary/records/poly_textout_w.rb +128 -1
- data/lib/emf/emr/binary/records/raw.rb +3 -0
- data/lib/emf/emr/binary/records/realize_palette.rb +1 -0
- data/lib/emf/emr/binary/records/rectangle.rb +1 -1
- data/lib/emf/emr/binary/records/resize_palette.rb +16 -2
- data/lib/emf/emr/binary/records/round_rect.rb +2 -2
- data/lib/emf/emr/binary/records/select_palette.rb +1 -0
- data/lib/emf/emr/binary/records/set_color_adjustment.rb +136 -0
- data/lib/emf/emr/binary/records/set_colorspace.rb +22 -1
- data/lib/emf/emr/binary/records/set_dibits_to_device.rb +43 -0
- data/lib/emf/emr/binary/records/set_icm_profile_a.rb +71 -1
- data/lib/emf/emr/binary/records/set_icm_profile_w.rb +71 -1
- data/lib/emf/emr/binary/records/set_linked_ufis.rb +49 -1
- data/lib/emf/emr/binary/records/set_miter_limit.rb +1 -1
- data/lib/emf/emr/binary/records/set_palette_entries.rb +20 -2
- data/lib/emf/emr/binary/records/set_text_justification.rb +8 -2
- data/lib/emf/emr/binary/records/small_text_out.rb +55 -4
- data/lib/emf/emr/binary/records/stretch_blt.rb +14 -0
- data/lib/emf/emr/binary/records/stretch_dibits.rb +14 -0
- data/lib/emf/emr/binary/records/transparent_blt.rb +43 -0
- data/lib/emf/emr/binary/records.rb +4 -2
- data/lib/emf/emr/binary/region_data.rb +33 -0
- data/lib/emf/emr/binary/state_record_body.rb +33 -0
- data/lib/emf/emr/binary/thin_shell.rb +226 -0
- data/lib/emf/emr/binary/value_view/layout.rb +101 -0
- data/lib/emf/emr/binary/value_view/reader_source.rb +88 -0
- data/lib/emf/emr/binary/value_view.rb +173 -0
- data/lib/emf/emr/binary/variable_buffers.rb +87 -0
- data/lib/emf/emr/binary.rb +4 -0
- data/lib/emf/emr/filled_record.rb +77 -0
- data/lib/emf/emr/parser.rb +239 -50
- data/lib/emf/emr/serializer.rb +4 -5
- data/lib/emf/emr.rb +1 -0
- data/lib/emf/error.rb +24 -1
- data/lib/emf/model/diagnostic.rb +41 -0
- data/lib/emf/model/emr/header.rb +40 -1
- data/lib/emf/model/emr/records.rb +142 -6
- data/lib/emf/model/emr/wire_values.rb +43 -0
- data/lib/emf/model/emr.rb +1 -0
- data/lib/emf/model/geometry/color.rb +19 -1
- data/lib/emf/model/metafile.rb +22 -2
- data/lib/emf/model/wmf/bitmap_records.rb +155 -0
- data/lib/emf/model/wmf/brush_records.rb +45 -0
- data/lib/emf/model/wmf/dc_escape_records.rb +67 -0
- data/lib/emf/model/wmf/font_record.rb +40 -0
- data/lib/emf/model/wmf/geometry_records.rb +107 -0
- data/lib/emf/model/wmf/header.rb +36 -0
- data/lib/emf/model/wmf/object_records.rb +71 -0
- data/lib/emf/model/wmf/palette_records.rb +68 -0
- data/lib/emf/model/wmf/records.rb +247 -0
- data/lib/emf/model/wmf/region_records.rb +112 -0
- data/lib/emf/model/wmf/state_records.rb +188 -0
- data/lib/emf/model/wmf/text_records.rb +36 -0
- data/lib/emf/model/wmf.rb +11 -0
- data/lib/emf/model.rb +2 -0
- data/lib/emf/native/bitmap_fields.rb +58 -0
- data/lib/emf/native/diagnostic_contract.rb +39 -0
- data/lib/emf/native/diagnostics.rb +81 -0
- data/lib/emf/native/emf_plus_loader.rb +35 -0
- data/lib/emf/native/emf_plus_snapshot.rb +96 -0
- data/lib/emf/native/emf_plus_source.rb +20 -0
- data/lib/emf/native/emf_plus_stream.rb +150 -0
- data/lib/emf/native/fields.rb +369 -0
- data/lib/emf/native/font_fields.rb +87 -0
- data/lib/emf/native/gradient_fields.rb +23 -0
- data/lib/emf/native/header.rb +35 -0
- data/lib/emf/native/header_errors.rb +30 -0
- data/lib/emf/native/hydrator.rb +151 -0
- data/lib/emf/native/lazy_wire.rb +100 -0
- data/lib/emf/native/legacy_state_fields.rb +22 -0
- data/lib/emf/native/library_path.rb +68 -0
- data/lib/emf/native/loader.rb +120 -0
- data/lib/emf/native/palette_fields.rb +45 -0
- data/lib/emf/native/poly_text_fields.rb +116 -0
- data/lib/emf/native/resource_failure.rb +28 -0
- data/lib/emf/native/small_text_fields.rb +27 -0
- data/lib/emf/native/snapshot.rb +102 -0
- data/lib/emf/native/wmf_bitmap_fields.rb +58 -0
- data/lib/emf/native/wmf_brush_fields.rb +62 -0
- data/lib/emf/native/wmf_fields.rb +117 -0
- data/lib/emf/native/wmf_hydrator.rb +143 -0
- data/lib/emf/native/wmf_loading.rb +49 -0
- data/lib/emf/native/wmf_palette_fields.rb +60 -0
- data/lib/emf/native/wmf_region_fields.rb +92 -0
- data/lib/emf/native/wmf_rejection.rb +84 -0
- data/lib/emf/native/wmf_remaining_fields.rb +70 -0
- data/lib/emf/native/wmf_snapshot.rb +51 -0
- data/lib/emf/native/wmf_text_fields.rb +56 -0
- data/lib/emf/native.rb +61 -0
- data/lib/emf/parse_evidence.rb +57 -0
- data/lib/emf/resource_budget/bitmap.rb +66 -0
- data/lib/emf/resource_budget/gradient.rb +30 -0
- data/lib/emf/resource_budget/poly_text.rb +85 -0
- data/lib/emf/resource_budget.rb +382 -0
- data/lib/emf/resource_limits/emf_records.rb +130 -0
- data/lib/emf/resource_limits.rb +58 -0
- data/lib/emf/version.rb +1 -1
- data/lib/emf/visitors/dump.rb +4 -0
- data/lib/emf/visitors/stats.rb +6 -5
- data/lib/emf/wmf/binary.rb +150 -1
- data/lib/emf/wmf/bitmap_codec.rb +118 -0
- data/lib/emf/wmf/brush_codec.rb +102 -0
- data/lib/emf/wmf/dc_escape_codec.rb +75 -0
- data/lib/emf/wmf/dib.rb +187 -0
- data/lib/emf/wmf/embedded_emf.rb +137 -0
- data/lib/emf/wmf/escape_views.rb +89 -0
- data/lib/emf/wmf/evidence.rb +255 -0
- data/lib/emf/wmf/geometry_codec.rb +49 -0
- data/lib/emf/wmf/palette_codec.rb +85 -0
- data/lib/emf/wmf/parser.rb +147 -2
- data/lib/emf/wmf/recovery.rb +95 -0
- data/lib/emf/wmf/region_codec.rb +151 -0
- data/lib/emf/wmf/serializer.rb +164 -2
- data/lib/emf/wmf/text_codec.rb +123 -0
- data/lib/emf/wmf.rb +7 -0
- data/lib/emf.rb +13 -9
- metadata +287 -10
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module EmfPlus
|
|
5
|
+
# Joins continued EmfPlusObject chunks into logical objects. A chain ends
|
|
6
|
+
# when TotalObjectSize bytes of ObjectData are accumulated, not when the C
|
|
7
|
+
# flag clears: Windows leaves C set on the final chunk. A final chunk with C
|
|
8
|
+
# clear (the layout the spec describes) also ends the chain.
|
|
9
|
+
module ObjectChain
|
|
10
|
+
# One object, possibly spread over several records. Chunk boundaries and
|
|
11
|
+
# source records are kept so `split` can return them unchanged.
|
|
12
|
+
class LogicalObject
|
|
13
|
+
attr_reader :object_type, :emf_object_id, :data, :chunk_sizes, :offset
|
|
14
|
+
|
|
15
|
+
def initialize(chunks)
|
|
16
|
+
@chunks = chunks.freeze
|
|
17
|
+
@object_type = chunks.first.object_type
|
|
18
|
+
@offset = chunks.first.offset
|
|
19
|
+
@emf_object_id = chunks.first.emf_object_id
|
|
20
|
+
@data = chunks.map(&:object_data).join.b.freeze
|
|
21
|
+
@chunk_sizes = chunks.map { |chunk| chunk.object_data.bytesize }.freeze
|
|
22
|
+
@chunk_flags = chunks.map(&:flags).freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def total_size = data.bytesize
|
|
26
|
+
|
|
27
|
+
# Joined ObjectData decoded by type; works for chained objects, which
|
|
28
|
+
# no single Records::Object chunk can describe.
|
|
29
|
+
def body = @body ||= Objects.decode(self)
|
|
30
|
+
|
|
31
|
+
# With the original chunk sizes the source records come back as read:
|
|
32
|
+
# padding, offsets and comment indexes included. Other sizes re-chunk
|
|
33
|
+
# the data with zero padding, every chunk continued.
|
|
34
|
+
def split(sizes = chunk_sizes)
|
|
35
|
+
return @chunks.dup if sizes == chunk_sizes
|
|
36
|
+
|
|
37
|
+
check_sizes(sizes)
|
|
38
|
+
offset = 0
|
|
39
|
+
sizes.each_with_index.map do |size, index|
|
|
40
|
+
chunk = data.byteslice(offset, size)
|
|
41
|
+
offset += size
|
|
42
|
+
Records.decode(rechunked_frame(chunk, @chunks.fetch(index, @chunks.last)))
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def check_sizes(sizes)
|
|
49
|
+
return if sizes.any? && sizes.all? { |size| size.is_a?(Integer) && size.positive? } && sizes.sum == total_size
|
|
50
|
+
|
|
51
|
+
raise ArgumentError, "split sizes #{sizes.inspect} must be positive and sum to #{total_size}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def rechunked_frame(chunk, source)
|
|
55
|
+
flags = @chunk_flags.first | Records::Object::CONTINUED_FLAG
|
|
56
|
+
Frame.new(type: Records::Object::TYPE, flags: flags, size: 0, data_size: 0, data: "".b,
|
|
57
|
+
offset: source.offset, comment_index: source.comment_index)
|
|
58
|
+
.with_data([total_size].pack("V") + chunk)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module_function
|
|
63
|
+
|
|
64
|
+
# Returns the records with each object (single or chained) as a
|
|
65
|
+
# LogicalObject; every other record passes through unchanged.
|
|
66
|
+
def call(records)
|
|
67
|
+
entries = []
|
|
68
|
+
queue = records.dup
|
|
69
|
+
until queue.empty?
|
|
70
|
+
record = queue.shift
|
|
71
|
+
entries << (record.is_a?(Records::Object) ? LogicalObject.new(take_chain(record, queue)) : record)
|
|
72
|
+
end
|
|
73
|
+
entries
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def take_chain(first, queue)
|
|
77
|
+
return [first] unless first.continued?
|
|
78
|
+
|
|
79
|
+
chunks = [first]
|
|
80
|
+
accumulated = first.object_data.bytesize
|
|
81
|
+
while accumulated < first.total_object_size
|
|
82
|
+
chunk = next_chunk(first, chunks.last, queue.shift)
|
|
83
|
+
chunks << chunk
|
|
84
|
+
accumulated += chunk.object_data.bytesize
|
|
85
|
+
break unless chunk.continued?
|
|
86
|
+
end
|
|
87
|
+
unless accumulated == first.total_object_size
|
|
88
|
+
fail_chain(first,
|
|
89
|
+
"chain holds #{accumulated} bytes, TotalObjectSize is #{first.total_object_size}")
|
|
90
|
+
end
|
|
91
|
+
chunks
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def next_chunk(first, previous, candidate)
|
|
95
|
+
matches = candidate.is_a?(Records::Object) &&
|
|
96
|
+
candidate.object_type == first.object_type && candidate.emf_object_id == first.emf_object_id &&
|
|
97
|
+
(!candidate.continued? || candidate.total_object_size == first.total_object_size)
|
|
98
|
+
fail_chain(previous, "continued EmfPlusObject chain not completed by a matching chunk") unless matches
|
|
99
|
+
candidate
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fail_chain(record, message)
|
|
103
|
+
raise ParseError.new(offset: record.offset, record_code: record.type,
|
|
104
|
+
message: "#{message} (chain started near offset #{record.offset})")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module EmfPlus
|
|
5
|
+
# An object ID outside the 64-slot table (0..63).
|
|
6
|
+
class ObjectIdError < Emf::Error; end
|
|
7
|
+
|
|
8
|
+
# The EMF+ object table (MS-EMFPLUS 2.3.5.1): 64 slots, IDs 0..63, over a
|
|
9
|
+
# sequence of framed records. A stream index is a record's position in that
|
|
10
|
+
# sequence. A new EmfPlusObject with an existing ID replaces the slot from
|
|
11
|
+
# its position on, so lookups are time-indexed: `lookup(id, at: n)` returns
|
|
12
|
+
# the object defined most recently before position n, and ID reuse never
|
|
13
|
+
# changes what an earlier draw resolved to.
|
|
14
|
+
#
|
|
15
|
+
# An object's position is that of its last chunk, the point where it is
|
|
16
|
+
# complete. Problems found while building are collected in `errors` and
|
|
17
|
+
# never raised; a record or chain that cannot be placed keeps its records.
|
|
18
|
+
class ObjectTable
|
|
19
|
+
SLOTS = 64
|
|
20
|
+
|
|
21
|
+
Diagnostic = Struct.new(:kind, :stream_index, :id, :message, :records, keyword_init: true)
|
|
22
|
+
Resolution = Struct.new(:role, :raw_id, :object, :diagnostic, :solid_color, keyword_init: true)
|
|
23
|
+
Placement = Struct.new(:stream_index, :logical)
|
|
24
|
+
private_constant :Placement
|
|
25
|
+
|
|
26
|
+
# Reads chain records off a queue of [record, stream index] pairs and
|
|
27
|
+
# remembers them, so a failed chain can hand back everything it consumed.
|
|
28
|
+
class Tape
|
|
29
|
+
attr_reader :taken
|
|
30
|
+
|
|
31
|
+
def initialize(queue)
|
|
32
|
+
@queue = queue
|
|
33
|
+
@taken = []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def shift
|
|
37
|
+
pair = @queue.shift
|
|
38
|
+
@taken << pair if pair
|
|
39
|
+
pair&.first
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
private_constant :Tape
|
|
43
|
+
|
|
44
|
+
# What each draw record references: [role, object type, reader for the
|
|
45
|
+
# ID, reader for the S flag or nil].
|
|
46
|
+
module References
|
|
47
|
+
PEN = [:pen, Objects::PEN, :pen_id, nil].freeze
|
|
48
|
+
BRUSH = [:brush, Objects::BRUSH, :brush_id, :solid_color?].freeze
|
|
49
|
+
FONT = [:font, Objects::FONT, :font_object_id, nil].freeze
|
|
50
|
+
FORMAT = [:string_format, Objects::STRING_FORMAT, :format_id, nil].freeze
|
|
51
|
+
IMAGE = [:image, Objects::IMAGE, :image_object_id, nil].freeze
|
|
52
|
+
IMAGE_ATTRIBUTES = [:image_attributes, Objects::IMAGE_ATTRIBUTES, :image_attributes_id, nil].freeze
|
|
53
|
+
PATH = [:path, Objects::PATH, :path_id, nil].freeze
|
|
54
|
+
REGION = [:region, Objects::REGION, :region_id, nil].freeze
|
|
55
|
+
CLIP_PATH = [:path, Objects::PATH, :emf_object_id, nil].freeze
|
|
56
|
+
CLIP_REGION = [:region, Objects::REGION, :emf_object_id, nil].freeze
|
|
57
|
+
|
|
58
|
+
BY_RECORD = {
|
|
59
|
+
Records::DrawRects => [PEN], Records::DrawLines => [PEN], Records::DrawEllipse => [PEN],
|
|
60
|
+
Records::DrawPie => [PEN], Records::DrawArc => [PEN], Records::DrawClosedCurve => [PEN],
|
|
61
|
+
Records::DrawCurve => [PEN], Records::DrawBeziers => [PEN], Records::DrawPath => [PATH, PEN],
|
|
62
|
+
Records::FillRects => [BRUSH], Records::FillPolygon => [BRUSH], Records::FillEllipse => [BRUSH],
|
|
63
|
+
Records::FillPie => [BRUSH], Records::FillClosedCurve => [BRUSH], Records::FillPath => [PATH, BRUSH],
|
|
64
|
+
Records::FillRegion => [REGION, BRUSH],
|
|
65
|
+
Records::DrawString => [FONT, BRUSH, FORMAT], Records::DrawDriverString => [FONT, BRUSH],
|
|
66
|
+
Records::DrawImage => [IMAGE, IMAGE_ATTRIBUTES], Records::DrawImagePoints => [IMAGE, IMAGE_ATTRIBUTES],
|
|
67
|
+
Records::SetClipPath => [CLIP_PATH], Records::SetClipRegion => [CLIP_REGION]
|
|
68
|
+
}.freeze
|
|
69
|
+
end
|
|
70
|
+
private_constant :References
|
|
71
|
+
|
|
72
|
+
attr_reader :errors
|
|
73
|
+
|
|
74
|
+
def initialize(records)
|
|
75
|
+
@placements = Hash.new { |hash, id| hash[id] = [] }
|
|
76
|
+
@errors = []
|
|
77
|
+
build(records.to_a)
|
|
78
|
+
@placements.default_proc = nil
|
|
79
|
+
@placements.each_value(&:freeze)
|
|
80
|
+
@errors.freeze
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The LogicalObject in slot `id` as of stream position `at` (objects
|
|
84
|
+
# defined at earlier positions only), or nil when the slot is empty then.
|
|
85
|
+
def lookup(id, at:) # rubocop:disable Naming/MethodParameterName
|
|
86
|
+
check_id!(id)
|
|
87
|
+
placements = @placements.fetch(id, [])
|
|
88
|
+
placements.reverse_each { |placement| return placement.logical if placement.stream_index < at }
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# All 64 slots as of stream position `at`.
|
|
93
|
+
def at(stream_index)
|
|
94
|
+
Array.new(SLOTS) { |id| lookup(id, at: stream_index) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# One Resolution per object reference the record carries, resolved as of
|
|
98
|
+
# its stream position `at`: the slot's object, or nil with a Diagnostic
|
|
99
|
+
# (empty slot, wrong object type, ID outside the table). A solid-colour
|
|
100
|
+
# BrushId is kept raw and not looked up. A record with no references
|
|
101
|
+
# gives an empty array.
|
|
102
|
+
def resolve(record, at:) # rubocop:disable Naming/MethodParameterName
|
|
103
|
+
References::BY_RECORD.fetch(record.class, []).map { |reference| resolution(record, reference, at) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def build(records)
|
|
109
|
+
queue = records.each_with_index.to_a
|
|
110
|
+
until queue.empty?
|
|
111
|
+
record, index = queue.shift
|
|
112
|
+
place_chain(record, index, queue) if record.is_a?(Records::Object)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def place_chain(first, first_index, queue)
|
|
117
|
+
tape = Tape.new(queue)
|
|
118
|
+
chunks = ObjectChain.take_chain(first, tape)
|
|
119
|
+
place(ObjectChain::LogicalObject.new(chunks), tape.taken.last&.last || first_index)
|
|
120
|
+
rescue ParseError => e
|
|
121
|
+
unplaced_chain(first, first_index, queue, tape.taken, e)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def place(logical, index)
|
|
125
|
+
id = logical.emf_object_id
|
|
126
|
+
if id < SLOTS
|
|
127
|
+
@placements[id] << Placement.new(index, logical)
|
|
128
|
+
else
|
|
129
|
+
add_error(:object_id_out_of_range, index, id, "object ID #{id} is outside 0..#{SLOTS - 1}", logical.split)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def unplaced_chain(first, first_index, queue, taken, error)
|
|
134
|
+
stray = taken.last
|
|
135
|
+
queue.unshift(taken.pop) if stray && !chunk_of?(first, stray.first)
|
|
136
|
+
records = [first] + taken.map(&:first)
|
|
137
|
+
add_error(:incomplete_chain, taken.last&.last || first_index, first.emf_object_id, error.message, records)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def chunk_of?(first, record)
|
|
141
|
+
record.is_a?(Records::Object) && record.object_type == first.object_type &&
|
|
142
|
+
record.emf_object_id == first.emf_object_id
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def add_error(kind, index, id, message, records)
|
|
146
|
+
@errors << Diagnostic.new(kind: kind, stream_index: index, id: id, message: message, records: records).freeze
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def check_id!(id)
|
|
150
|
+
return if id.is_a?(Integer) && id >= 0 && id < SLOTS
|
|
151
|
+
|
|
152
|
+
raise ObjectIdError, "object ID #{id.inspect} is outside 0..#{SLOTS - 1}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def resolution(record, (role, type, id_reader, solid_reader), position)
|
|
156
|
+
raw_id = record.public_send(id_reader).to_i
|
|
157
|
+
return Resolution.new(role: role, raw_id: raw_id, solid_color: true) if solid_reader && record.public_send(solid_reader)
|
|
158
|
+
|
|
159
|
+
found = lookup(raw_id, at: position)
|
|
160
|
+
kind = problem(found, type)
|
|
161
|
+
return Resolution.new(role: role, raw_id: raw_id, object: found) unless kind
|
|
162
|
+
|
|
163
|
+
diagnostic = Diagnostic.new(kind: kind, stream_index: position, id: raw_id, records: [record],
|
|
164
|
+
message: "#{role} reference #{raw_id}: #{kind.to_s.tr('_', ' ')}").freeze
|
|
165
|
+
Resolution.new(role: role, raw_id: raw_id, diagnostic: diagnostic)
|
|
166
|
+
rescue ObjectIdError
|
|
167
|
+
Resolution.new(role: role, raw_id: raw_id, diagnostic: out_of_range(record, role, raw_id, position))
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def out_of_range(record, role, raw_id, position)
|
|
171
|
+
Diagnostic.new(kind: :object_id_out_of_range, stream_index: position, id: raw_id, records: [record],
|
|
172
|
+
message: "#{role} reference #{raw_id}: object ID outside 0..#{SLOTS - 1}").freeze
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def problem(found, type)
|
|
176
|
+
return :empty_slot unless found
|
|
177
|
+
|
|
178
|
+
:wrong_type unless found.object_type == type
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "emf/emf_plus/records/primitives/graphics_version"
|
|
4
|
+
require "emf/emf_plus/records/primitives/argb"
|
|
5
|
+
require "emf/emf_plus/records/primitives/point_f"
|
|
6
|
+
require "emf/emf_plus/records/primitives/rect_f"
|
|
7
|
+
require "emf/emf_plus/records/primitives/transform_matrix"
|
|
8
|
+
require "emf/emf_plus/records/primitives/blend_colors"
|
|
9
|
+
require "emf/emf_plus/records/primitives/blend_factors"
|
|
10
|
+
require "emf/emf_plus/records/primitives/focus_scale_data"
|
|
11
|
+
|
|
12
|
+
module Emf
|
|
13
|
+
module EmfPlus
|
|
14
|
+
module Objects
|
|
15
|
+
# EmfPlusBrush (2.2.1.1): Version, Type, then the BrushData the Type
|
|
16
|
+
# selects. Every count is checked against the bytes left before BinData
|
|
17
|
+
# allocates, so a hostile count cannot hang or exhaust memory.
|
|
18
|
+
#
|
|
19
|
+
# The nested EmfPlusPath of a path gradient boundary (BoundaryPathData)
|
|
20
|
+
# and the EmfPlusImage of a texture brush decode through the Path and
|
|
21
|
+
# Image objects. A nested body that does not parse stays raw bytes, so
|
|
22
|
+
# the brush still round-trips.
|
|
23
|
+
class Brush < Typed
|
|
24
|
+
SOLID = 0
|
|
25
|
+
HATCH = 1
|
|
26
|
+
TEXTURE = 2
|
|
27
|
+
PATH_GRADIENT = 3
|
|
28
|
+
LINEAR_GRADIENT = 4
|
|
29
|
+
|
|
30
|
+
DATA_PATH = 0x01
|
|
31
|
+
DATA_TRANSFORM = 0x02
|
|
32
|
+
DATA_PRESET_COLORS = 0x04
|
|
33
|
+
DATA_BLEND_FACTORS_H = 0x08
|
|
34
|
+
DATA_BLEND_FACTORS_V = 0x10
|
|
35
|
+
DATA_FOCUS_SCALES = 0x40
|
|
36
|
+
|
|
37
|
+
P = Records::Primitives
|
|
38
|
+
private_constant :P
|
|
39
|
+
|
|
40
|
+
class BrushHeader < Records::Body
|
|
41
|
+
endian :little
|
|
42
|
+
graphics_version :version
|
|
43
|
+
uint32 :brush_type
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# EmfPlusSolidBrushData (2.2.2.43).
|
|
47
|
+
class SolidBrushData < Records::Body
|
|
48
|
+
endian :little
|
|
49
|
+
argb :solid_color
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# EmfPlusHatchBrushData (2.2.2.20).
|
|
53
|
+
class HatchBrushData < Records::Body
|
|
54
|
+
endian :little
|
|
55
|
+
uint32 :hatch_style
|
|
56
|
+
argb :fore_color
|
|
57
|
+
argb :back_color
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# EmfPlusTextureBrushData (2.2.2.45) without OptionalData.
|
|
61
|
+
class TextureBrushData < Records::Body
|
|
62
|
+
endian :little
|
|
63
|
+
uint32 :brush_data_flags
|
|
64
|
+
int32 :wrap_mode
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# EmfPlusLinearGradientBrushData (2.2.2.24) without OptionalData.
|
|
68
|
+
class LinearGradientBrushData < Records::Body
|
|
69
|
+
endian :little
|
|
70
|
+
uint32 :brush_data_flags
|
|
71
|
+
int32 :wrap_mode
|
|
72
|
+
rect_f :rect_f
|
|
73
|
+
argb :start_color
|
|
74
|
+
argb :end_color
|
|
75
|
+
uint32 :reserved1
|
|
76
|
+
uint32 :reserved2
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# EmfPlusPathGradientBrushData (2.2.2.29) up to SurroundingColorCount.
|
|
80
|
+
class PathGradientBrushData < Records::Body
|
|
81
|
+
endian :little
|
|
82
|
+
uint32 :brush_data_flags
|
|
83
|
+
int32 :wrap_mode
|
|
84
|
+
argb :center_color
|
|
85
|
+
point_f :center_point_f
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class SurroundingColors < Records::Body
|
|
89
|
+
endian :little
|
|
90
|
+
uint32 :surrounding_color_count
|
|
91
|
+
array :surrounding_color, type: :argb, initial_length: :surrounding_color_count
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# EmfPlusBoundaryPathData (2.2.2.6).
|
|
95
|
+
class BoundaryPathData < Records::Body
|
|
96
|
+
endian :little
|
|
97
|
+
int32 :boundary_path_size
|
|
98
|
+
string :boundary_path_data, read_length: :boundary_path_size
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# EmfPlusBoundaryPointData (2.2.2.7).
|
|
102
|
+
class BoundaryPointData < Records::Body
|
|
103
|
+
endian :little
|
|
104
|
+
int32 :boundary_point_count
|
|
105
|
+
array :boundary_point_data, type: :point_f, initial_length: :boundary_point_count
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Malformed < StandardError; end
|
|
109
|
+
private_constant :Malformed
|
|
110
|
+
|
|
111
|
+
module Nested
|
|
112
|
+
Source = Struct.new(:object_type, :data, :offset)
|
|
113
|
+
|
|
114
|
+
module_function
|
|
115
|
+
|
|
116
|
+
# The decoded nested object, or nil when its bytes do not parse.
|
|
117
|
+
def decode(klass, object_type, data)
|
|
118
|
+
klass.new(Source.new(object_type, data, 0))
|
|
119
|
+
rescue Emf::ParseError
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
private_constant :Nested
|
|
124
|
+
|
|
125
|
+
# BoundaryPathData with its EmfPlusPath decoded. Size and bytes follow
|
|
126
|
+
# the path, so an edit to it re-serializes with the right size.
|
|
127
|
+
class BoundaryPath
|
|
128
|
+
attr_reader :path
|
|
129
|
+
|
|
130
|
+
def initialize(record)
|
|
131
|
+
@raw = record.boundary_path_data.to_s.b.freeze
|
|
132
|
+
@path = Nested.decode(Path, PATH, @raw)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def boundary_path_data = path ? path.to_binary : @raw
|
|
136
|
+
def boundary_path_size = boundary_path_data.bytesize
|
|
137
|
+
def to_binary_s = [boundary_path_size].pack("l<") + boundary_path_data
|
|
138
|
+
def num_bytes = 4 + boundary_path_size
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Ordered pieces of the object; strings are raw nested objects.
|
|
142
|
+
class Body
|
|
143
|
+
attr_reader :parts
|
|
144
|
+
|
|
145
|
+
def initialize(parts) = @parts = parts
|
|
146
|
+
|
|
147
|
+
def version = parts.fetch(:header).version
|
|
148
|
+
|
|
149
|
+
def num_bytes = parts.each_value.sum { |part| bytes(part).bytesize }
|
|
150
|
+
|
|
151
|
+
def to_binary_s = parts.each_value.map { |part| bytes(part) }.join.b
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def bytes(part)
|
|
156
|
+
case part
|
|
157
|
+
when String then part
|
|
158
|
+
when Typed then part.to_binary
|
|
159
|
+
else part.to_binary_s
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Walks the data once, reading each piece the flags select.
|
|
165
|
+
class Reader
|
|
166
|
+
def initialize(data)
|
|
167
|
+
@data = data
|
|
168
|
+
@pos = 0
|
|
169
|
+
@parts = {}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def read
|
|
173
|
+
take(:header, BrushHeader)
|
|
174
|
+
read_brush_data(@parts.fetch(:header).brush_type)
|
|
175
|
+
Body.new(@parts)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def read_brush_data(brush_type)
|
|
181
|
+
case brush_type
|
|
182
|
+
when SOLID then take(:data, SolidBrushData)
|
|
183
|
+
when HATCH then take(:data, HatchBrushData)
|
|
184
|
+
when TEXTURE then read_texture
|
|
185
|
+
when PATH_GRADIENT then read_path_gradient
|
|
186
|
+
when LINEAR_GRADIENT then read_linear_gradient
|
|
187
|
+
else take_rest(:unknown_data)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def read_texture
|
|
192
|
+
take(:data, TextureBrushData)
|
|
193
|
+
take(:transform, P::TransformMatrix) if flag?(DATA_TRANSFORM)
|
|
194
|
+
take_rest(:image)
|
|
195
|
+
decode_image
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def decode_image
|
|
199
|
+
raw = @parts[:image]
|
|
200
|
+
return unless raw
|
|
201
|
+
|
|
202
|
+
@parts[:image] = Nested.decode(Image, IMAGE, raw) || raw
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def read_linear_gradient
|
|
206
|
+
take(:data, LinearGradientBrushData)
|
|
207
|
+
take(:transform, P::TransformMatrix) if flag?(DATA_TRANSFORM)
|
|
208
|
+
if flag?(DATA_PRESET_COLORS)
|
|
209
|
+
take_counted(:blend_colors, P::BlendColors, 8)
|
|
210
|
+
else
|
|
211
|
+
# The spec lists the vertical pattern before the horizontal one.
|
|
212
|
+
take_counted(:blend_factors_v, P::BlendFactors, 8) if flag?(DATA_BLEND_FACTORS_V)
|
|
213
|
+
take_counted(:blend_factors_h, P::BlendFactors, 8) if flag?(DATA_BLEND_FACTORS_H)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def read_path_gradient
|
|
218
|
+
take(:data, PathGradientBrushData)
|
|
219
|
+
take_counted(:surrounding_colors, SurroundingColors, 4)
|
|
220
|
+
flag?(DATA_PATH) ? read_boundary_path : take_counted(:boundary_points, BoundaryPointData, 8, signed: true)
|
|
221
|
+
read_path_gradient_optional
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def read_boundary_path
|
|
225
|
+
take_counted(:boundary_path, BoundaryPathData, 1, signed: true)
|
|
226
|
+
@parts[:boundary_path] = BoundaryPath.new(@parts[:boundary_path])
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def read_path_gradient_optional
|
|
230
|
+
take(:transform, P::TransformMatrix) if flag?(DATA_TRANSFORM)
|
|
231
|
+
if flag?(DATA_PRESET_COLORS)
|
|
232
|
+
take_counted(:blend_colors, P::BlendColors, 8)
|
|
233
|
+
elsif flag?(DATA_BLEND_FACTORS_H)
|
|
234
|
+
take_counted(:blend_factors_h, P::BlendFactors, 8)
|
|
235
|
+
end
|
|
236
|
+
take(:focus_scales, P::FocusScaleData) if flag?(DATA_FOCUS_SCALES)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def flag?(mask) = @parts.fetch(:data).brush_data_flags.anybits?(mask)
|
|
240
|
+
|
|
241
|
+
def take(name, klass)
|
|
242
|
+
record = klass.new
|
|
243
|
+
raise Malformed, "#{name} needs #{record.num_bytes} bytes, #{remaining} left" if record.num_bytes > remaining
|
|
244
|
+
|
|
245
|
+
@parts[name] = record.read(@data.byteslice(@pos..))
|
|
246
|
+
@pos += record.num_bytes
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# `item_size` bytes per counted element after the 4-byte count.
|
|
250
|
+
def take_counted(name, klass, item_size, signed: false)
|
|
251
|
+
raise Malformed, "#{name} count needs 4 bytes, #{remaining} left" if remaining < 4
|
|
252
|
+
|
|
253
|
+
count = @data.byteslice(@pos, 4).unpack1(signed ? "l<" : "V")
|
|
254
|
+
raise Malformed, "#{name} count #{count} is negative" if count.negative?
|
|
255
|
+
|
|
256
|
+
needed = 4 + (count * item_size)
|
|
257
|
+
raise Malformed, "#{name} of #{count} entries needs #{needed} bytes, #{remaining} left" if needed > remaining
|
|
258
|
+
|
|
259
|
+
@parts[name] = klass.read(@data.byteslice(@pos..))
|
|
260
|
+
@pos += @parts[name].num_bytes
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def take_rest(name)
|
|
264
|
+
rest = @data.byteslice(@pos..)
|
|
265
|
+
@parts[name] = rest.freeze unless rest.empty?
|
|
266
|
+
@pos += rest.bytesize
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def remaining = @data.bytesize - @pos
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def brush_type = body.parts.fetch(:header).brush_type
|
|
273
|
+
def data = body.parts[:data]
|
|
274
|
+
def brush_data_flags = data.brush_data_flags
|
|
275
|
+
def transform = body.parts[:transform]
|
|
276
|
+
def surrounding_colors = body.parts[:surrounding_colors]
|
|
277
|
+
def boundary_path = body.parts[:boundary_path]
|
|
278
|
+
def boundary_points = body.parts[:boundary_points]
|
|
279
|
+
def blend_colors = body.parts[:blend_colors]
|
|
280
|
+
def blend_factors_h = body.parts[:blend_factors_h]
|
|
281
|
+
def blend_factors_v = body.parts[:blend_factors_v]
|
|
282
|
+
def focus_scales = body.parts[:focus_scales]
|
|
283
|
+
def image = body.parts[:image]
|
|
284
|
+
def unknown_data = body.parts[:unknown_data]
|
|
285
|
+
|
|
286
|
+
private
|
|
287
|
+
|
|
288
|
+
def read_body(data, logical)
|
|
289
|
+
Reader.new(data).read
|
|
290
|
+
rescue IOError, BinData::ValidityError, Malformed => e
|
|
291
|
+
fail_parse(logical, e.message)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "emf/binary/float32_le"
|
|
4
|
+
require "emf/emf_plus/records/primitives/graphics_version"
|
|
5
|
+
require "emf/emf_plus/records/primitives/point_f"
|
|
6
|
+
require "emf/emf_plus/records/primitives/path"
|
|
7
|
+
|
|
8
|
+
module Emf
|
|
9
|
+
module EmfPlus
|
|
10
|
+
module Objects
|
|
11
|
+
# EmfPlusCustomLineCap (2.2.1.2): Version, Type, then the default
|
|
12
|
+
# (2.2.2.13) or adjustable arrow (2.2.2.12) data the Type selects.
|
|
13
|
+
class CustomLineCap < Typed
|
|
14
|
+
DEFAULT = 0
|
|
15
|
+
ADJUSTABLE_ARROW = 1
|
|
16
|
+
|
|
17
|
+
DATA_FILL_PATH = 0x01
|
|
18
|
+
DATA_LINE_PATH = 0x02
|
|
19
|
+
|
|
20
|
+
class CustomLineCapHeader < Records::Body
|
|
21
|
+
endian :little
|
|
22
|
+
graphics_version :version
|
|
23
|
+
uint32 :cap_type
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# EmfPlusCustomLineCapData (2.2.2.13) without OptionalData.
|
|
27
|
+
class CustomLineCapDefaultData < Records::Body
|
|
28
|
+
endian :little
|
|
29
|
+
uint32 :custom_line_cap_data_flags
|
|
30
|
+
uint32 :base_cap
|
|
31
|
+
float32_le :base_inset
|
|
32
|
+
uint32 :stroke_start_cap
|
|
33
|
+
uint32 :stroke_end_cap
|
|
34
|
+
uint32 :stroke_join
|
|
35
|
+
float32_le :stroke_miter_limit
|
|
36
|
+
float32_le :width_scale
|
|
37
|
+
point_f :fill_hot_spot
|
|
38
|
+
point_f :stroke_hot_spot
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# EmfPlusCustomLineCapArrowData (2.2.2.12).
|
|
42
|
+
class CustomLineCapArrowData < Records::Body
|
|
43
|
+
endian :little
|
|
44
|
+
float32_le :width
|
|
45
|
+
float32_le :height
|
|
46
|
+
float32_le :middle_inset
|
|
47
|
+
uint32 :fill_state
|
|
48
|
+
uint32 :line_start_cap
|
|
49
|
+
uint32 :line_end_cap
|
|
50
|
+
uint32 :line_join
|
|
51
|
+
float32_le :line_miter_limit
|
|
52
|
+
float32_le :width_scale
|
|
53
|
+
point_f :fill_hot_spot
|
|
54
|
+
point_f :line_hot_spot
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
P = Records::Primitives
|
|
58
|
+
private_constant :P
|
|
59
|
+
|
|
60
|
+
# Walks the data once, reading the optional paths the flags select.
|
|
61
|
+
class Reader
|
|
62
|
+
def initialize(data)
|
|
63
|
+
@cursor = Parts::Cursor.new(data)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def read
|
|
67
|
+
@cursor.take(:header, CustomLineCapHeader)
|
|
68
|
+
read_cap_data(@cursor.parts.fetch(:header).cap_type)
|
|
69
|
+
Parts::Body.new(@cursor.parts)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def read_cap_data(cap_type)
|
|
75
|
+
case cap_type
|
|
76
|
+
when DEFAULT then read_default
|
|
77
|
+
when ADJUSTABLE_ARROW then @cursor.take(:data, CustomLineCapArrowData)
|
|
78
|
+
else @cursor.take_rest(:unknown_data)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def read_default
|
|
83
|
+
@cursor.take(:data, CustomLineCapDefaultData)
|
|
84
|
+
flags = @cursor.parts.fetch(:data).custom_line_cap_data_flags
|
|
85
|
+
@cursor.take_sized(:fill_path) { |bytes| P::Path.new.read(bytes) } if flags.anybits?(DATA_FILL_PATH)
|
|
86
|
+
@cursor.take_sized(:line_path) { |bytes| P::Path.new.read(bytes) } if flags.anybits?(DATA_LINE_PATH)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def cap_type = body.parts.fetch(:header).cap_type
|
|
91
|
+
def data = body.parts[:data]
|
|
92
|
+
def fill_path = body.parts[:fill_path]&.inner
|
|
93
|
+
def line_path = body.parts[:line_path]&.inner
|
|
94
|
+
def unknown_data = body.parts[:unknown_data]
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def read_body(data, logical)
|
|
99
|
+
Reader.new(data).read
|
|
100
|
+
rescue IOError, BinData::ValidityError, Parts::Malformed => e
|
|
101
|
+
fail_parse(logical, e.message)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|