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,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
# Native reports header truncation by byte position; the Ruby parser names the
|
|
6
|
+
# header's own nSize. When Ruby would reject the same input with a FormatError,
|
|
7
|
+
# report Ruby's wording, including an nSize too small for the fields it implies.
|
|
8
|
+
module HeaderErrors
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def translate(error, input)
|
|
12
|
+
return error if input.bytesize < 8
|
|
13
|
+
|
|
14
|
+
size = input.unpack1("@4V")
|
|
15
|
+
return FormatError.new("EMF header nSize too small: #{size}") if size < 8
|
|
16
|
+
return FormatError.new("truncated EMF header (claimed #{size}, got #{input.bytesize})") if input.bytesize < size
|
|
17
|
+
return FormatError.new("EMF header nSize #{size} cannot hold its fields") unless ruby_header_fits?(input, size)
|
|
18
|
+
|
|
19
|
+
error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ruby_header_fits?(input, size)
|
|
23
|
+
Emr::Binary::Header.read(input.byteslice(0, size))
|
|
24
|
+
true
|
|
25
|
+
rescue IOError
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "fields"
|
|
4
|
+
require_relative "font_fields"
|
|
5
|
+
require_relative "gradient_fields"
|
|
6
|
+
require_relative "bitmap_fields"
|
|
7
|
+
require_relative "small_text_fields"
|
|
8
|
+
require_relative "poly_text_fields"
|
|
9
|
+
require_relative "lazy_wire"
|
|
10
|
+
require_relative "diagnostics"
|
|
11
|
+
require_relative "emf_plus_source"
|
|
12
|
+
|
|
13
|
+
module Emf
|
|
14
|
+
module Native
|
|
15
|
+
class Hydrator
|
|
16
|
+
SUPPORTED_TYPES = [*(2..68), *(70..98), 114, 115, 116, 118, 120].freeze
|
|
17
|
+
OPAQUE_BODY_TYPES = [23, 96, 97, *(99..106), *(109..113), 119, 121, 122].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(snapshot, input)
|
|
20
|
+
@snapshot = snapshot
|
|
21
|
+
@input = input
|
|
22
|
+
# Filled wires read their fields from this frozen view, as Ruby-parsed wires do.
|
|
23
|
+
@data = input.frozen? ? input : input.dup.freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call
|
|
27
|
+
diagnostics = Diagnostics.new(@snapshot, @input)
|
|
28
|
+
plus_candidates = []
|
|
29
|
+
records = hydrate_records(diagnostics, plus_candidates)
|
|
30
|
+
emf_plus = emf_plus_stream(diagnostics, plus_candidates)
|
|
31
|
+
Model::Metafile.new(
|
|
32
|
+
format: emf_plus.empty? ? :emf : :emf_plus, header: Header.new.call(@snapshot.header, @snapshot.raw_header), records: records,
|
|
33
|
+
errors: diagnostics.errors, emf_plus: emf_plus.empty? ? nil : emf_plus,
|
|
34
|
+
trailing: @snapshot.trailing + @snapshot.unframed, emf_plus_source: (EmfPlusSource.call(@input) unless emf_plus.empty?)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# One pass over the snapshot table. Records the Ruby parser reads from
|
|
41
|
+
# bytes alone become lazy wires straight from the table's own type,
|
|
42
|
+
# family and span; the rest take the field path.
|
|
43
|
+
def hydrate_records(diagnostics, plus_candidates)
|
|
44
|
+
table = @snapshot.table
|
|
45
|
+
recovering = diagnostics.recovering?
|
|
46
|
+
limit = @input.bytesize
|
|
47
|
+
Array.new(@snapshot.record_count) do |index|
|
|
48
|
+
base = index * 5
|
|
49
|
+
offset = table[base + 1]
|
|
50
|
+
length = table[base + 2]
|
|
51
|
+
type_family = table[base + 3]
|
|
52
|
+
plus_candidates << index if (recovered = recovering && diagnostics.recovered?(offset)) || type_family >> 32 == 4
|
|
53
|
+
adapter = diagnostics.adapter_at(offset) if recovered
|
|
54
|
+
next adapter if adapter
|
|
55
|
+
|
|
56
|
+
raise FormatError, "invalid native record span" unless length >= 8 && offset >= 0 && offset <= limit - length
|
|
57
|
+
|
|
58
|
+
filled = FilledWire.for(type_family, offset, length, @data)
|
|
59
|
+
next Model::Emr::Records::WireAdapter.lazy(source: filled, offset: offset) if filled
|
|
60
|
+
|
|
61
|
+
hydrate_record(@snapshot.record(index), offset, length)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def hydrate_record(record, offset, length)
|
|
66
|
+
raw = @input.byteslice(offset, length)
|
|
67
|
+
|
|
68
|
+
if record.fetch(:family) == 90
|
|
69
|
+
wire = FontFields.new(record.fetch(:payload), raw).call
|
|
70
|
+
return Model::Emr::Records::WireAdapter.new(wire: wire, offset: offset)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
klass, fields = wire_fields(record, raw)
|
|
74
|
+
Model::Emr::Records::WireAdapter.lazy(source: LazyWire.new(klass, fields, raw, record), offset: offset)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def wire_fields(record, raw)
|
|
78
|
+
payload = record.fetch(:payload)
|
|
79
|
+
family = record.fetch(:family)
|
|
80
|
+
case family
|
|
81
|
+
when 93..103 then BitmapFields.call(payload, family, raw)
|
|
82
|
+
when 0 then raw_fields(record, raw)
|
|
83
|
+
when 1 then Fields.eof(payload, raw)
|
|
84
|
+
when 3, 45..53 then Fields.point_array(payload, record.fetch(:family))
|
|
85
|
+
when 54..57 then Fields.multi_point_array(payload, record.fetch(:family))
|
|
86
|
+
when 58, 59 then Fields.poly_draw(payload, record.fetch(:family))
|
|
87
|
+
when 4 then Fields.comment(payload, raw)
|
|
88
|
+
when 33
|
|
89
|
+
klass, fields = Fields.call(payload, 33)
|
|
90
|
+
[klass, fields.merge(trailing: raw.byteslice(12..))]
|
|
91
|
+
when 2, 5..44, 60..86, 91, 92, 104..106 then scalar_fields(record, raw)
|
|
92
|
+
else raise FormatError, "unsupported native snapshot record family #{record.fetch(:family)}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def scalar_fields(record, raw)
|
|
97
|
+
family = record.fetch(:family)
|
|
98
|
+
return PolyTextFields.call(record.fetch(:payload), family, raw, record.fetch(:type)) if (104..106).cover?(family)
|
|
99
|
+
|
|
100
|
+
return PaletteFields.call(record.fetch(:payload), family, raw) if (82..86).cover?(family)
|
|
101
|
+
|
|
102
|
+
return Fields.extended_pen(record.fetch(:payload), raw) if family == 81
|
|
103
|
+
|
|
104
|
+
return Fields.region(record.fetch(:payload), family, raw) if (76..80).cover?(family)
|
|
105
|
+
|
|
106
|
+
return Fields.ext_text(record.fetch(:payload), family, raw) if [74, 75].include?(family)
|
|
107
|
+
|
|
108
|
+
if [91, 92].include?(family)
|
|
109
|
+
klass, fields = Fields.call(record.fetch(:payload), family)
|
|
110
|
+
fixed = family == 91 ? 24 : 16
|
|
111
|
+
raise FormatError, "invalid native state record length" if raw.bytesize < fixed
|
|
112
|
+
|
|
113
|
+
return [klass, fields.merge(trailing: raw.byteslice(fixed..))]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
Fields.call(record.fetch(:payload), family)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def raw_fields(record, raw)
|
|
120
|
+
raise FormatError, "invalid native typed payload length" unless record.fetch(:payload).empty?
|
|
121
|
+
|
|
122
|
+
type = record.fetch(:type)
|
|
123
|
+
return SmallTextFields.call(raw) if type == 108
|
|
124
|
+
|
|
125
|
+
# Body-backed public classes also preserve malformed recognized records.
|
|
126
|
+
# Their family-0 snapshot carries no semantic fields; diagnostics report recovery.
|
|
127
|
+
return [Emr::Binary::Records.lookup(type), { body: raw.byteslice(8..) }] if OPAQUE_BODY_TYPES.include?(type)
|
|
128
|
+
|
|
129
|
+
if !SUPPORTED_TYPES.include?(type) && Emr::Binary::Records.lookup(type) != Emr::Binary::Records::Raw
|
|
130
|
+
raise FormatError, "native backend does not support typed EMR record #{type}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
[Emr::Binary::Records::Raw, { payload: raw.byteslice(8..) }]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Only recovered records and COMMENTs can carry EMF+ bytes, in record order.
|
|
137
|
+
def emf_plus_stream(diagnostics, candidates)
|
|
138
|
+
table = @snapshot.table
|
|
139
|
+
candidates.filter_map do |index|
|
|
140
|
+
offset = table[(index * 5) + 1]
|
|
141
|
+
next diagnostics.emf_plus_at(offset) if diagnostics.recovered?(offset)
|
|
142
|
+
next unless table[(index * 5) + 3] >> 32 == 4
|
|
143
|
+
|
|
144
|
+
_klass, fields = Fields.comment(@snapshot.payload(index), @input.byteslice(offset, table[(index * 5) + 2]))
|
|
145
|
+
data = fields.fetch(:data)
|
|
146
|
+
data.byteslice(4..) if fields.fetch(:cb_data) >= 4 && data.start_with?("EMF+")
|
|
147
|
+
end.join.b
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
# Owns validated Ruby values, never a native document or its hydrator.
|
|
6
|
+
class LazyWire
|
|
7
|
+
XFORM_LEAVES = %i[m11 m12 m21 m22 dx dy].each_with_index.map { |name, index| [[:xform, name], 8 + (index * 4)] }.freeze
|
|
8
|
+
TEXT_SCALE_LEAVES = [[[:ex_scale], 28], [[:ey_scale], 32]].freeze
|
|
9
|
+
|
|
10
|
+
# Whole-record byte offsets of each float32 leaf, header included.
|
|
11
|
+
FLOAT_LEAVES = {
|
|
12
|
+
Emr::Binary::Records::SetWorldTransform => XFORM_LEAVES,
|
|
13
|
+
Emr::Binary::Records::ModifyWorldTransform => XFORM_LEAVES,
|
|
14
|
+
Emr::Binary::Records::AngleArc => [[[:start_angle], 20], [[:sweep_angle], 24]],
|
|
15
|
+
Emr::Binary::Records::SetMiterLimit => [[[:miter_limit], 8]],
|
|
16
|
+
Emr::Binary::Records::ExtTextOutA => TEXT_SCALE_LEAVES,
|
|
17
|
+
Emr::Binary::Records::ExtTextOutW => TEXT_SCALE_LEAVES,
|
|
18
|
+
Emr::Binary::Records::SmallTextOut => TEXT_SCALE_LEAVES
|
|
19
|
+
}.freeze
|
|
20
|
+
private_constant :XFORM_LEAVES, :TEXT_SCALE_LEAVES, :FLOAT_LEAVES
|
|
21
|
+
|
|
22
|
+
def initialize(klass, fields, bytes, record)
|
|
23
|
+
@klass = klass
|
|
24
|
+
@fields = fields.merge(i_type: record.fetch(:type), n_size: bytes.bytesize)
|
|
25
|
+
@bytes = bytes
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def type_id
|
|
29
|
+
@fields.fetch(:i_type)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_binary_s
|
|
33
|
+
@bytes
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def append_to(out)
|
|
37
|
+
out << to_binary_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def wire_class
|
|
41
|
+
@klass
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# No byte reader: these fields come from the hydrator, which may
|
|
45
|
+
# disagree with the raw bytes (recovered records).
|
|
46
|
+
def value_source
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def materialize
|
|
51
|
+
@klass.new(**@fields).tap { |wire| restore_float_bits(wire) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def suffix(wire)
|
|
55
|
+
Model::Emr::Records::WireAdapter.suffix_of(@bytes, wire)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# The constructor only receives Floats, and Ruby has already canonicalised
|
|
61
|
+
# any NaN payload on the way in; the record bytes still hold the original.
|
|
62
|
+
def restore_float_bits(wire)
|
|
63
|
+
FLOAT_LEAVES.fetch(@klass, []).each do |path, offset|
|
|
64
|
+
path.inject(wire) { |node, name| node.public_send(name) }
|
|
65
|
+
.assign_bits(@bytes.byteslice(offset, 4).unpack1("V"))
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Same contract as LazyWire for a fixed-layout record whose bytes alone
|
|
71
|
+
# define the wire: no per-record field Hash is built at parse time, and
|
|
72
|
+
# materialising reads the record bytes with the same FastFill the Ruby
|
|
73
|
+
# backend parses with. Float leaves keep their original bits there.
|
|
74
|
+
class FilledWire < Emr::FilledRecord
|
|
75
|
+
# Native records have always answered a plain Integer type id.
|
|
76
|
+
attr_reader :type_id
|
|
77
|
+
|
|
78
|
+
# Snapshot families whose records the Ruby backend's fillers read from
|
|
79
|
+
# bytes alone; any other family keeps its own validation in the Hydrator.
|
|
80
|
+
FAMILIES = Array.new(87) { |family| [2, 3, *5..32, *35..57, *60..86].include?(family) }.freeze
|
|
81
|
+
# The filler of each EMR type, indexed by type; types past the table have none.
|
|
82
|
+
FILLERS = Array.new(128) { |type| Emr::Binary::FastFill.for(type) || Emr::Binary::FastFill.for_points(type) }.freeze
|
|
83
|
+
private_constant :FAMILIES, :FILLERS
|
|
84
|
+
|
|
85
|
+
# The source for the fixed-layout or point-array record the snapshot
|
|
86
|
+
# places at `offset` of the frozen `data`, or nil. `type_family` is the
|
|
87
|
+
# snapshot table word, type | family << 32. The snapshot takes the type
|
|
88
|
+
# and `length` from the record's own header, so they are not read again.
|
|
89
|
+
# Recovered (family 0, including a count that overruns the bytes)
|
|
90
|
+
# records keep the field path. The caller has bounds-checked the span.
|
|
91
|
+
def self.for(type_family, offset, length, data)
|
|
92
|
+
type = type_family & 0xFFFF_FFFF
|
|
93
|
+
filler = FILLERS[type]
|
|
94
|
+
return unless filler && FAMILIES[type_family >> 32] && length >= filler.size
|
|
95
|
+
|
|
96
|
+
new(filler, data, offset, length, type)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
module LegacyStateFields
|
|
6
|
+
def self.call(payload, family)
|
|
7
|
+
raise FormatError, "invalid native opaque payload length" if payload.bytesize < 8
|
|
8
|
+
|
|
9
|
+
count = payload.unpack1("Q<")
|
|
10
|
+
raise FormatError, "invalid native opaque payload length" unless payload.bytesize == 8 + count
|
|
11
|
+
|
|
12
|
+
body = payload.byteslice(8..)
|
|
13
|
+
semantic_family = family == 68 ? 91 : 92
|
|
14
|
+
length = semantic_family == 91 ? 16 : 8
|
|
15
|
+
return [Emr::Binary::Records::Raw, { payload: body }] if count < length
|
|
16
|
+
|
|
17
|
+
klass, fields = Fields.call(body.byteslice(0, length), semantic_family)
|
|
18
|
+
[klass, fields.merge(trailing: body.byteslice(length..))]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Emf
|
|
7
|
+
module Native
|
|
8
|
+
class LibraryPath
|
|
9
|
+
def initialize(directory = __dir__)
|
|
10
|
+
@directory = directory
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
override = ENV.fetch("EMF_NATIVE_LIBRARY", nil)
|
|
15
|
+
return override unless override.nil?
|
|
16
|
+
|
|
17
|
+
manifest_path = File.join(@directory, "platforms.json")
|
|
18
|
+
return unless File.exist?(manifest_path)
|
|
19
|
+
|
|
20
|
+
manifest = JSON.parse(File.read(manifest_path))
|
|
21
|
+
validate_manifest(manifest)
|
|
22
|
+
bundled_path(manifest)
|
|
23
|
+
rescue JSON::ParserError, SystemCallError => e
|
|
24
|
+
raise Unavailable, "native backend manifest could not load: #{e.message}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def validate_manifest(manifest)
|
|
30
|
+
unless manifest.is_a?(Hash) && component?(manifest["platform"]) && component?(manifest["library"]) &&
|
|
31
|
+
manifest["sha256"].is_a?(String) && manifest["sha256"].match?(/\A[0-9a-f]{64}\z/)
|
|
32
|
+
raise Unavailable, "native backend manifest is invalid"
|
|
33
|
+
end
|
|
34
|
+
raise Unavailable, "native backend manifest platform mismatch" unless compatible_platform?(manifest["platform"])
|
|
35
|
+
return if manifest["abi_version"].is_a?(Integer) && manifest["abi_version"] == 1
|
|
36
|
+
|
|
37
|
+
raise Unavailable, "native backend manifest ABI version mismatch (expected 1)"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The bundled library's platform is RubyGems' to match; without
|
|
41
|
+
# RubyGems (ruby --disable-gems) only EMF_NATIVE_LIBRARY can name one.
|
|
42
|
+
def compatible_platform?(platform)
|
|
43
|
+
raise Unavailable, "native backend needs RubyGems to match the bundled platform" unless defined?(::Gem)
|
|
44
|
+
|
|
45
|
+
require "rubygems/platform"
|
|
46
|
+
case Gem::Platform.local
|
|
47
|
+
when Gem::Platform.new(platform) then true
|
|
48
|
+
else false
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def component?(value)
|
|
53
|
+
value.is_a?(String) && value.match?(/\A[a-zA-Z0-9_][a-zA-Z0-9_.-]*\z/)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def bundled_path(manifest)
|
|
57
|
+
root = File.realpath(@directory)
|
|
58
|
+
path = File.join(root, manifest.fetch("platform"), manifest.fetch("library"))
|
|
59
|
+
unless File.realpath(path).start_with?("#{root}#{File::SEPARATOR}")
|
|
60
|
+
raise Unavailable, "native backend library escapes its installation directory"
|
|
61
|
+
end
|
|
62
|
+
raise Unavailable, "native backend library checksum mismatch" unless Digest::SHA256.file(path).hexdigest == manifest.fetch("sha256")
|
|
63
|
+
|
|
64
|
+
path
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fiddle"
|
|
4
|
+
require_relative "wmf_loading"
|
|
5
|
+
require_relative "resource_failure"
|
|
6
|
+
|
|
7
|
+
module Emf
|
|
8
|
+
module Native
|
|
9
|
+
class Loader
|
|
10
|
+
include WmfLoading
|
|
11
|
+
include ResourceFailure
|
|
12
|
+
|
|
13
|
+
POINTER = Fiddle::TYPE_VOIDP
|
|
14
|
+
UINT64 = Fiddle::TYPE_LONG_LONG
|
|
15
|
+
# Version 1 status values from emf-ffi/include/emf.h.
|
|
16
|
+
STATUS_ERRORS = { 2 => FormatError,
|
|
17
|
+
4 => SerializeError }.freeze
|
|
18
|
+
SIGNATURES = {
|
|
19
|
+
emf_parse: [[POINTER, UINT64, POINTER, POINTER, POINTER], Fiddle::TYPE_INT],
|
|
20
|
+
emf_document_snapshot: [[POINTER, POINTER, POINTER], Fiddle::TYPE_INT],
|
|
21
|
+
emf_record_bytes: [[POINTER, UINT64, POINTER, POINTER], Fiddle::TYPE_INT],
|
|
22
|
+
emf_buffer_view: [[POINTER, POINTER, POINTER], Fiddle::TYPE_INT],
|
|
23
|
+
emf_error_view: [[POINTER, POINTER], Fiddle::TYPE_INT],
|
|
24
|
+
emf_document_free: [[POINTER], Fiddle::TYPE_VOID],
|
|
25
|
+
emf_buffer_free: [[POINTER], Fiddle::TYPE_VOID],
|
|
26
|
+
emf_error_free: [[POINTER], Fiddle::TYPE_VOID]
|
|
27
|
+
}.freeze
|
|
28
|
+
# Present in newer libraries only; an older one keeps the schema 2 snapshot.
|
|
29
|
+
SNAPSHOT_V3 = :emf_document_snapshot_v3
|
|
30
|
+
|
|
31
|
+
def initialize(path)
|
|
32
|
+
raise Unavailable, "native backend requires a 64-bit process" unless Fiddle::SIZEOF_VOIDP == 8
|
|
33
|
+
|
|
34
|
+
@library = Fiddle.dlopen(path)
|
|
35
|
+
@functions = { emf_abi_version: Fiddle::Function.new(@library["emf_abi_version"], [], Fiddle::TYPE_INT) }
|
|
36
|
+
raise Unavailable, "native backend ABI version mismatch (expected 1)" unless invoke(:emf_abi_version) == 1
|
|
37
|
+
|
|
38
|
+
SIGNATURES.each do |name, (arguments, result)|
|
|
39
|
+
@functions[name] = Fiddle::Function.new(@library[name.to_s], arguments, result)
|
|
40
|
+
end
|
|
41
|
+
optional(:emf_error_resource_view, [POINTER, POINTER])
|
|
42
|
+
@snapshot_function = optional(SNAPSHOT_V3, [POINTER, POINTER, POINTER]) ? SNAPSHOT_V3 : :emf_document_snapshot
|
|
43
|
+
rescue Fiddle::DLError => e
|
|
44
|
+
raise Unavailable, "native backend could not load: #{e.message}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def document(bytes)
|
|
48
|
+
output = slot
|
|
49
|
+
checked(:emf_parse, bytes, bytes.bytesize, 0, output)
|
|
50
|
+
yield pointer(output)
|
|
51
|
+
ensure
|
|
52
|
+
invoke(:emf_document_free, pointer(output)) if output && pointer(output) != 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def snapshot(document)
|
|
56
|
+
buffer(@snapshot_function, document)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def record_bytes(document, index)
|
|
60
|
+
buffer(:emf_record_bytes, document, index)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def optional(name, arguments)
|
|
66
|
+
@functions[name] = Fiddle::Function.new(@library[name.to_s], arguments, Fiddle::TYPE_INT)
|
|
67
|
+
rescue Fiddle::DLError
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def invoke(name, *arguments)
|
|
72
|
+
@functions.fetch(name).call(*arguments)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def slot
|
|
76
|
+
Fiddle::Pointer["\0" * 8]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def pointer(output)
|
|
80
|
+
output[0, 8].unpack1("J")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def checked(name, *arguments)
|
|
84
|
+
error = slot
|
|
85
|
+
status = invoke(name, *arguments, error)
|
|
86
|
+
return if status.zero?
|
|
87
|
+
|
|
88
|
+
raise resource_failure(pointer(error)) if status == 3
|
|
89
|
+
|
|
90
|
+
message = error_message(pointer(error), status)
|
|
91
|
+
raise STATUS_ERRORS.fetch(status, Error), message
|
|
92
|
+
ensure
|
|
93
|
+
invoke(:emf_error_free, pointer(error)) if error && pointer(error) != 0
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def error_message(error, status)
|
|
97
|
+
return "native operation failed (status #{status})" if error.zero?
|
|
98
|
+
|
|
99
|
+
info = Fiddle::Pointer[[48, 1].pack("L2") + ("\0" * 40)]
|
|
100
|
+
return "native operation failed (status #{status})" unless invoke(:emf_error_view, error, info).zero?
|
|
101
|
+
|
|
102
|
+
address, length = info[32, 16].unpack("JQ")
|
|
103
|
+
Fiddle::Pointer.new(address)[0, length].force_encoding(Encoding::UTF_8)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def buffer(name, *arguments)
|
|
107
|
+
output = slot
|
|
108
|
+
checked(name, *arguments, output)
|
|
109
|
+
data = slot
|
|
110
|
+
length = slot
|
|
111
|
+
status = invoke(:emf_buffer_view, pointer(output), data, length)
|
|
112
|
+
raise STATUS_ERRORS.fetch(status, Error), "native buffer view failed (status #{status})" unless status.zero?
|
|
113
|
+
|
|
114
|
+
Fiddle::Pointer.new(pointer(data))[0, pointer(length)]
|
|
115
|
+
ensure
|
|
116
|
+
invoke(:emf_buffer_free, pointer(output)) if output && pointer(output) != 0
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
module PaletteFields
|
|
6
|
+
def self.call(payload, family, raw)
|
|
7
|
+
klass = [Emr::Binary::Records::SelectPalette, Emr::Binary::Records::CreatePalette,
|
|
8
|
+
Emr::Binary::Records::SetPaletteEntries, Emr::Binary::Records::ResizePalette,
|
|
9
|
+
Emr::Binary::Records::RealizePalette].fetch(family - 82)
|
|
10
|
+
return array_fields(payload, family, raw, klass) if [83, 84].include?(family)
|
|
11
|
+
|
|
12
|
+
fixed = { 82 => 4, 85 => 8, 86 => 0 }.fetch(family)
|
|
13
|
+
raise FormatError, "invalid native palette payload" unless payload.bytesize == fixed && raw.bytesize >= fixed + 8
|
|
14
|
+
|
|
15
|
+
fields = { trailing: raw.byteslice((fixed + 8)..) }
|
|
16
|
+
fields[:ih_pal] = payload.unpack1("V") if fixed.positive?
|
|
17
|
+
fields[:entry_count] = payload.byteslice(4, 4).unpack1("V") if family == 85
|
|
18
|
+
[klass, fields]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.palette_counts_valid?(family, scalar, count)
|
|
22
|
+
family != 83 || (scalar <= 0xFFFF && count <= 0xFFFF)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.array_fields(payload, family, raw, klass)
|
|
26
|
+
raise FormatError, "invalid native palette array payload" if payload.bytesize < 16
|
|
27
|
+
|
|
28
|
+
handle, scalar, count = payload.unpack("V2Q<")
|
|
29
|
+
fixed = family == 83 ? 16 : 20
|
|
30
|
+
valid = payload.bytesize == 16 + (count * 4) && raw.bytesize >= fixed + (count * 4)
|
|
31
|
+
valid &&= palette_counts_valid?(family, scalar, count)
|
|
32
|
+
raise FormatError, "invalid native palette array length" unless valid
|
|
33
|
+
|
|
34
|
+
ResourceLimits::EmfRecords.check_count(count) if family == 84
|
|
35
|
+
|
|
36
|
+
entries = payload.byteslice(16..).unpack("C*").each_slice(4).map do |red, green, blue, flags|
|
|
37
|
+
{ red: red, green: green, blue: blue, flags: flags }
|
|
38
|
+
end
|
|
39
|
+
fields = { ih_pal: handle, entry_count: count, palette_entries: entries, trailing: raw.byteslice((fixed + (count * 4))..) }
|
|
40
|
+
fields[family == 83 ? :version : :start] = scalar
|
|
41
|
+
[klass, fields]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
# Snapshot families 104/105 encode a 36-byte header and 52-byte entry
|
|
6
|
+
# headers followed by text units and Dx words. Family 106 is twelve words.
|
|
7
|
+
# Source gaps and suffixes remain authoritative for the public body API.
|
|
8
|
+
module PolyTextFields
|
|
9
|
+
def self.call(payload, family, raw, type)
|
|
10
|
+
expected_type = { 104 => 96, 105 => 97, 106 => 23 }.fetch(family)
|
|
11
|
+
valid_header = type == expected_type && raw.bytesize >= 8 && raw.unpack1("V") == type
|
|
12
|
+
reject! unless valid_header && raw.byteslice(4, 4).unpack1("V") == raw.bytesize
|
|
13
|
+
|
|
14
|
+
if family == 106
|
|
15
|
+
reject! unless payload.bytesize == 24 && raw.bytesize >= 32 && payload == raw.byteslice(8, 24)
|
|
16
|
+
else
|
|
17
|
+
TextValidator.new(payload, raw, family == 104 ? 1 : 2).call
|
|
18
|
+
end
|
|
19
|
+
[Emr::Binary::Records.lookup(type), { body: raw.byteslice(8..) }]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.reject!
|
|
23
|
+
raise FormatError, "invalid native PolyTextOut/COLORADJUSTMENT payload"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The source descriptor table is variable width. Validate every semantic
|
|
27
|
+
# field and detached span before constructing the public body-backed wire.
|
|
28
|
+
class TextValidator
|
|
29
|
+
def initialize(payload, raw, width)
|
|
30
|
+
@payload = payload
|
|
31
|
+
@raw = raw
|
|
32
|
+
@width = width
|
|
33
|
+
@cursor = 36
|
|
34
|
+
@descriptor = 40
|
|
35
|
+
@spans = []
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call
|
|
39
|
+
count.times { entry }
|
|
40
|
+
reject! unless @cursor == @payload.bytesize
|
|
41
|
+
|
|
42
|
+
finish = @descriptor
|
|
43
|
+
@spans.sort.each do |start, stop|
|
|
44
|
+
reject! if start < finish
|
|
45
|
+
finish = stop
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def count
|
|
52
|
+
reject! unless @payload.bytesize >= 36 && @raw.bytesize >= 40
|
|
53
|
+
reject! unless @payload.byteslice(0, 28) == @raw.byteslice(8, 28)
|
|
54
|
+
|
|
55
|
+
value = @payload.byteslice(28, 8).unpack1("Q<")
|
|
56
|
+
reject! unless value == @raw.byteslice(36, 4).unpack1("V")
|
|
57
|
+
reject! unless value <= (@payload.bytesize - 36) / 52 && value <= (@raw.bytesize - 40) / 24
|
|
58
|
+
value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def entry
|
|
62
|
+
reject! unless @cursor + 52 <= @payload.bytesize && @descriptor + 24 <= @raw.bytesize
|
|
63
|
+
|
|
64
|
+
options, has_rect = @payload.byteslice(@cursor + 8, 8).unpack("V2")
|
|
65
|
+
size = rectangle(options, has_rect)
|
|
66
|
+
reject! unless @payload.byteslice(@cursor, 8) == @raw.byteslice(@descriptor, 8)
|
|
67
|
+
reject! unless options == @raw.byteslice(@descriptor + 16, 4).unpack1("V")
|
|
68
|
+
|
|
69
|
+
chars, string = @raw.byteslice(@descriptor + 8, 8).unpack("V2")
|
|
70
|
+
dx = @raw.byteslice(@descriptor + size - 4, 4).unpack1("V")
|
|
71
|
+
dx_count = spacing_count(chars, dx, options)
|
|
72
|
+
@cursor += 52
|
|
73
|
+
buffer(string, chars * @width, @width, string.zero? && chars.zero?)
|
|
74
|
+
buffer(dx, dx_count * 4, 4, dx.zero?)
|
|
75
|
+
@descriptor += size
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def rectangle(options, has_rect)
|
|
79
|
+
with_rect = options.nobits?(0x100)
|
|
80
|
+
size = with_rect ? 40 : 24
|
|
81
|
+
reject! unless has_rect == (with_rect ? 1 : 0) && @descriptor + size <= @raw.bytesize
|
|
82
|
+
|
|
83
|
+
bytes = with_rect ? @raw.byteslice(@descriptor + 20, 16) : "\0".b * 16
|
|
84
|
+
reject! unless @payload.byteslice(@cursor + 16, 16) == bytes
|
|
85
|
+
size
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def spacing_count(chars, dx, options)
|
|
89
|
+
text_count, has_dx, dx_count = @payload.byteslice(@cursor + 32, 20).unpack("Q<VQ<")
|
|
90
|
+
reject! unless text_count == chars && has_dx == (dx.zero? ? 0 : 1)
|
|
91
|
+
|
|
92
|
+
multiplier = options.anybits?(0x2000) ? 2 : 1
|
|
93
|
+
expected = dx.zero? ? 0 : chars * multiplier
|
|
94
|
+
reject! unless dx_count == expected
|
|
95
|
+
dx_count
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def buffer(offset, length, alignment, absent)
|
|
99
|
+
reject! unless length <= @payload.bytesize - @cursor
|
|
100
|
+
unless absent
|
|
101
|
+
reject! unless offset >= 40 && (offset % alignment).zero? && offset <= @raw.bytesize
|
|
102
|
+
reject! unless length <= @raw.bytesize - offset
|
|
103
|
+
reject! unless @payload.byteslice(@cursor, length) == @raw.byteslice(offset, length)
|
|
104
|
+
|
|
105
|
+
@spans << [offset, offset + length]
|
|
106
|
+
end
|
|
107
|
+
@cursor += length
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def reject!
|
|
111
|
+
PolyTextFields.reject!
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module Native
|
|
5
|
+
module ResourceFailure
|
|
6
|
+
RESOURCES = { 1 => :input_bytes, 2 => :record_bytes, 3 => :record_count,
|
|
7
|
+
4 => :allocated_bytes, 5 => :work_units, 6 => :output_bytes }.freeze
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def resource_failure(error)
|
|
12
|
+
# Older ABI-1 libraries expose only the original status and message.
|
|
13
|
+
return FormatError.new(error_message(error, 3)) unless @functions.key?(:emf_error_resource_view)
|
|
14
|
+
return Error.new("native resource failure has no error detail") if error.zero?
|
|
15
|
+
|
|
16
|
+
info = Fiddle::Pointer[[32, 1].pack("L2") + ("\0" * 24)]
|
|
17
|
+
return Error.new("native resource failure detail is unreadable") unless invoke(:emf_error_resource_view, error, info).zero?
|
|
18
|
+
|
|
19
|
+
code = info[8, 4].unpack1("L")
|
|
20
|
+
resource = RESOURCES[code]
|
|
21
|
+
return Error.new("native resource failure has unknown resource #{code}") unless resource
|
|
22
|
+
|
|
23
|
+
limit, actual = info[16, 16].unpack("Q2")
|
|
24
|
+
ResourceLimitError.new(resource: resource, limit: limit, actual: actual)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|