pdfrb 0.3.0 → 0.6.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/.rubocop.yml +1 -1
- data/Gemfile +1 -0
- data/PROPOSAL.canvas-clip.md +92 -0
- data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
- data/PROPOSAL.canvas-rich-text.md +103 -0
- data/PROPOSAL.canvas-text-lines.md +77 -0
- data/PROPOSAL.canvas-transparency.md +106 -0
- data/PROPOSAL.external-font-metrics.md +129 -0
- data/PROPOSAL.font-embedding-fontfile2.md +88 -0
- data/PROPOSAL.font-resolver-ttc.md +105 -0
- data/PROPOSAL.font-resolver.md +96 -0
- data/PROPOSAL.font-subsetting.md +98 -0
- data/PROPOSAL.gradient-shadings.md +110 -0
- data/PROPOSAL.page-mediabox-setter.md +66 -0
- data/PROPOSAL.per-glyph-width.md +91 -0
- data/PROPOSAL.stringio-require.md +53 -0
- data/PROPOSAL.struct-tree-root.md +102 -0
- data/PROPOSAL.text-measurement.md +93 -0
- data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
- data/RELEASE.md +48 -0
- data/lib/pdfrb/action/base.rb +21 -0
- data/lib/pdfrb/action/types.rb +178 -0
- data/lib/pdfrb/action.rb +55 -0
- data/lib/pdfrb/annotation/base.rb +79 -0
- data/lib/pdfrb/annotation/types.rb +222 -0
- data/lib/pdfrb/annotation.rb +78 -0
- data/lib/pdfrb/appearance/generator.rb +216 -0
- data/lib/pdfrb/appearance.rb +11 -0
- data/lib/pdfrb/arlington/object_definition.rb +2 -2
- data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
- data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
- data/lib/pdfrb/arlington/predicate.rb +1 -5
- data/lib/pdfrb/cli/batch.rb +18 -0
- data/lib/pdfrb/cli/debug_info.rb +18 -0
- data/lib/pdfrb/cli/files.rb +16 -0
- data/lib/pdfrb/cli/fonts.rb +16 -0
- data/lib/pdfrb/cli/form.rb +18 -0
- data/lib/pdfrb/cli/image2pdf.rb +18 -0
- data/lib/pdfrb/cli/images.rb +34 -0
- data/lib/pdfrb/cli/info.rb +20 -0
- data/lib/pdfrb/cli/inspect.rb +18 -0
- data/lib/pdfrb/cli/merge.rb +18 -0
- data/lib/pdfrb/cli/modify.rb +18 -0
- data/lib/pdfrb/cli/optimize.rb +18 -0
- data/lib/pdfrb/cli/split.rb +21 -0
- data/lib/pdfrb/cli/watermark.rb +18 -0
- data/lib/pdfrb/cli.rb +291 -7
- data/lib/pdfrb/color/color_space.rb +259 -0
- data/lib/pdfrb/color/icc_profile.rb +90 -0
- data/lib/pdfrb/color.rb +12 -0
- data/lib/pdfrb/compare/comparator.rb +244 -0
- data/lib/pdfrb/compare/report.rb +78 -0
- data/lib/pdfrb/compare.rb +42 -0
- data/lib/pdfrb/composer.rb +161 -0
- data/lib/pdfrb/conformance/pdf_a.rb +317 -56
- data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
- data/lib/pdfrb/conformance/pdf_x.rb +247 -0
- data/lib/pdfrb/conformance/rule.rb +78 -0
- data/lib/pdfrb/conformance/structure_elements.rb +174 -0
- data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
- data/lib/pdfrb/conformance.rb +7 -3
- data/lib/pdfrb/content/canvas.rb +217 -72
- data/lib/pdfrb/content/color_space.rb +157 -0
- data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
- data/lib/pdfrb/content/operator.rb +21 -1
- data/lib/pdfrb/content/operators/clipping.rb +36 -0
- data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
- data/lib/pdfrb/content/operators.rb +21 -0
- data/lib/pdfrb/content/shading/base.rb +139 -0
- data/lib/pdfrb/content/shading.rb +17 -0
- data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
- data/lib/pdfrb/content/tiling_pattern.rb +78 -0
- data/lib/pdfrb/content/transformation_matrix.rb +61 -0
- data/lib/pdfrb/content.rb +7 -26
- data/lib/pdfrb/destination/fit.rb +185 -0
- data/lib/pdfrb/destination.rb +13 -0
- data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
- data/lib/pdfrb/digital_signature/signing.rb +107 -11
- data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
- data/lib/pdfrb/digital_signature/verification.rb +88 -9
- data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
- data/lib/pdfrb/digital_signature.rb +26 -2
- data/lib/pdfrb/document/annotations.rb +120 -3
- data/lib/pdfrb/document/associated_files.rb +104 -0
- data/lib/pdfrb/document/colors.rb +95 -0
- data/lib/pdfrb/document/destinations.rb +127 -5
- data/lib/pdfrb/document/display.rb +84 -0
- data/lib/pdfrb/document/fonts.rb +417 -45
- data/lib/pdfrb/document/form.rb +338 -0
- data/lib/pdfrb/document/graphics_state.rb +91 -0
- data/lib/pdfrb/document/images.rb +4 -4
- data/lib/pdfrb/document/info.rb +102 -0
- data/lib/pdfrb/document/layers.rb +62 -0
- data/lib/pdfrb/document/metadata.rb +12 -5
- data/lib/pdfrb/document/output_intents.rb +91 -0
- data/lib/pdfrb/document/page_labels.rb +80 -0
- data/lib/pdfrb/document/pages.rb +68 -19
- data/lib/pdfrb/document/portfolio.rb +119 -0
- data/lib/pdfrb/document/shadings.rb +159 -0
- data/lib/pdfrb/document/stamps.rb +68 -0
- data/lib/pdfrb/document/structure.rb +242 -0
- data/lib/pdfrb/document.rb +73 -4
- data/lib/pdfrb/encryption/identity.rb +13 -0
- data/lib/pdfrb/encryption/password_verification.rb +53 -8
- data/lib/pdfrb/encryption/security_handler.rb +8 -5
- data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
- data/lib/pdfrb/encryption.rb +17 -0
- data/lib/pdfrb/error.rb +2 -2
- data/lib/pdfrb/filter/brotli_decode.rb +32 -0
- data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
- data/lib/pdfrb/filter/crypt.rb +2 -2
- data/lib/pdfrb/filter/dct_decode.rb +18 -0
- data/lib/pdfrb/filter/flate_decode.rb +4 -18
- data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
- data/lib/pdfrb/filter/jpx_decode.rb +18 -0
- data/lib/pdfrb/filter/lzw_decode.rb +3 -2
- data/lib/pdfrb/filter/png_predictor.rb +94 -0
- data/lib/pdfrb/filter.rb +26 -13
- data/lib/pdfrb/font/afm_parser.rb +50 -26
- data/lib/pdfrb/font/cmap/writer.rb +9 -0
- data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
- data/lib/pdfrb/font/encoding.rb +32 -3
- data/lib/pdfrb/font/metrics_helper.rb +121 -0
- data/lib/pdfrb/font/true_type/file.rb +10 -1
- data/lib/pdfrb/font/true_type/glyf.rb +82 -0
- data/lib/pdfrb/font/true_type/kern.rb +58 -0
- data/lib/pdfrb/font/true_type/loca.rb +42 -0
- data/lib/pdfrb/font/true_type/maxp.rb +44 -0
- data/lib/pdfrb/font/true_type/name.rb +53 -0
- data/lib/pdfrb/font/true_type/post.rb +93 -0
- data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
- data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
- data/lib/pdfrb/font/true_type.rb +7 -0
- data/lib/pdfrb/font.rb +2 -0
- data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
- data/lib/pdfrb/font_loader/from_file.rb +20 -0
- data/lib/pdfrb/font_loader/standard14.rb +20 -0
- data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
- data/lib/pdfrb/font_loader.rb +4 -0
- data/lib/pdfrb/font_resolver.rb +138 -0
- data/lib/pdfrb/image/audit.rb +94 -0
- data/lib/pdfrb/image/downsampler.rb +124 -0
- data/lib/pdfrb/image.rb +13 -0
- data/lib/pdfrb/image_loader/jpeg.rb +39 -58
- data/lib/pdfrb/image_loader/png.rb +31 -257
- data/lib/pdfrb/layout/bidi.rb +198 -0
- data/lib/pdfrb/layout/box.rb +49 -0
- data/lib/pdfrb/layout/box_fitter.rb +54 -0
- data/lib/pdfrb/layout/column_box.rb +71 -0
- data/lib/pdfrb/layout/container_box.rb +45 -0
- data/lib/pdfrb/layout/frame.rb +67 -0
- data/lib/pdfrb/layout/image_box.rb +44 -0
- data/lib/pdfrb/layout/inline_box.rb +25 -0
- data/lib/pdfrb/layout/line.rb +55 -0
- data/lib/pdfrb/layout/list_box.rb +87 -0
- data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
- data/lib/pdfrb/layout/page_style.rb +65 -0
- data/lib/pdfrb/layout/style.rb +62 -0
- data/lib/pdfrb/layout/table_box.rb +66 -0
- data/lib/pdfrb/layout/text_box.rb +38 -0
- data/lib/pdfrb/layout/text_fragment.rb +45 -0
- data/lib/pdfrb/layout/text_layouter.rb +123 -0
- data/lib/pdfrb/layout.rb +28 -0
- data/lib/pdfrb/linearization/hint_stream.rb +93 -0
- data/lib/pdfrb/linearization/writer.rb +259 -0
- data/lib/pdfrb/linearization.rb +21 -0
- data/lib/pdfrb/model/cos/dictionary.rb +12 -0
- data/lib/pdfrb/model/pdf_array.rb +2 -2
- data/lib/pdfrb/model/type/action.rb +42 -0
- data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
- data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
- data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
- data/lib/pdfrb/model/type/action_goto.rb +34 -0
- data/lib/pdfrb/model/type/action_hide.rb +26 -0
- data/lib/pdfrb/model/type/action_import_data.rb +18 -0
- data/lib/pdfrb/model/type/action_java_script.rb +18 -0
- data/lib/pdfrb/model/type/action_launch.rb +22 -0
- data/lib/pdfrb/model/type/action_movie.rb +22 -0
- data/lib/pdfrb/model/type/action_named.rb +23 -0
- data/lib/pdfrb/model/type/action_nop.rb +12 -0
- data/lib/pdfrb/model/type/action_rendition.rb +28 -0
- data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
- data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
- data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
- data/lib/pdfrb/model/type/action_set_state.rb +14 -0
- data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
- data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
- data/lib/pdfrb/model/type/action_thread.rb +23 -0
- data/lib/pdfrb/model/type/action_trans.rb +30 -0
- data/lib/pdfrb/model/type/action_uri.rb +30 -0
- data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
- data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
- data/lib/pdfrb/model/type/annotation.rb +49 -10
- data/lib/pdfrb/model/type/appearance.rb +41 -0
- data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
- data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
- data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
- data/lib/pdfrb/model/type/border_effect.rb +25 -0
- data/lib/pdfrb/model/type/border_styling.rb +38 -0
- data/lib/pdfrb/model/type/box_color_info.rb +21 -0
- data/lib/pdfrb/model/type/box_style.rb +19 -0
- data/lib/pdfrb/model/type/button.rb +17 -0
- data/lib/pdfrb/model/type/cal_gray.rb +17 -0
- data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
- data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
- data/lib/pdfrb/model/type/catalog.rb +99 -16
- data/lib/pdfrb/model/type/choice.rb +21 -0
- data/lib/pdfrb/model/type/cid_font.rb +52 -1
- data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
- data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
- data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
- data/lib/pdfrb/model/type/collection.rb +27 -0
- data/lib/pdfrb/model/type/collection_field.rb +24 -0
- data/lib/pdfrb/model/type/collection_item.rb +19 -0
- data/lib/pdfrb/model/type/collection_schema.rb +25 -0
- data/lib/pdfrb/model/type/collection_sort.rb +13 -0
- data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
- data/lib/pdfrb/model/type/device_n.rb +37 -0
- data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
- data/lib/pdfrb/model/type/device_n_process.rb +13 -0
- data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
- data/lib/pdfrb/model/type/document_security_store.rb +55 -0
- data/lib/pdfrb/model/type/embedded_file.rb +19 -0
- data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
- data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
- data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
- data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
- data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
- data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
- data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
- data/lib/pdfrb/model/type/file_specification.rb +43 -0
- data/lib/pdfrb/model/type/file_trailer.rb +54 -0
- data/lib/pdfrb/model/type/font.rb +73 -2
- data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
- data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
- data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
- data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
- data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
- data/lib/pdfrb/model/type/font_encoding.rb +47 -0
- data/lib/pdfrb/model/type/font_file.rb +28 -0
- data/lib/pdfrb/model/type/font_file2.rb +32 -0
- data/lib/pdfrb/model/type/font_file3.rb +23 -0
- data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
- data/lib/pdfrb/model/type/font_true_type.rb +1 -1
- data/lib/pdfrb/model/type/font_type0.rb +1 -1
- data/lib/pdfrb/model/type/font_type1.rb +1 -1
- data/lib/pdfrb/model/type/font_type3.rb +1 -1
- data/lib/pdfrb/model/type/form_field.rb +73 -0
- data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
- data/lib/pdfrb/model/type/function.rb +42 -0
- data/lib/pdfrb/model/type/function_exponential.rb +31 -0
- data/lib/pdfrb/model/type/function_postscript.rb +25 -0
- data/lib/pdfrb/model/type/function_sampled.rb +55 -0
- data/lib/pdfrb/model/type/function_stitching.rb +33 -0
- data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
- data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
- data/lib/pdfrb/model/type/halftone.rb +21 -0
- data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
- data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
- data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
- data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
- data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
- data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
- data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
- data/lib/pdfrb/model/type/icon_fit.rb +31 -0
- data/lib/pdfrb/model/type/indexed.rb +32 -0
- data/lib/pdfrb/model/type/info.rb +44 -0
- data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
- data/lib/pdfrb/model/type/interactive_form.rb +36 -0
- data/lib/pdfrb/model/type/interior_color.rb +12 -0
- data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
- data/lib/pdfrb/model/type/lab.rb +27 -0
- data/lib/pdfrb/model/type/line_annotation.rb +44 -0
- data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
- data/lib/pdfrb/model/type/link_annotation.rb +41 -0
- data/lib/pdfrb/model/type/link_appearance.rb +9 -0
- data/lib/pdfrb/model/type/mark_information.rb +23 -0
- data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
- data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
- data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
- data/lib/pdfrb/model/type/measure.rb +39 -0
- data/lib/pdfrb/model/type/media_clip.rb +28 -0
- data/lib/pdfrb/model/type/media_offset.rb +106 -0
- data/lib/pdfrb/model/type/metadata.rb +27 -3
- data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
- data/lib/pdfrb/model/type/movie.rb +28 -0
- data/lib/pdfrb/model/type/names.rb +51 -0
- data/lib/pdfrb/model/type/namespace.rb +27 -0
- data/lib/pdfrb/model/type/object_reference.rb +31 -0
- data/lib/pdfrb/model/type/object_stream.rb +38 -0
- data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
- data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
- data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
- data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
- data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
- data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
- data/lib/pdfrb/model/type/outline.rb +40 -6
- data/lib/pdfrb/model/type/outline_item.rb +45 -0
- data/lib/pdfrb/model/type/output_intent.rb +30 -0
- data/lib/pdfrb/model/type/page.rb +127 -0
- data/lib/pdfrb/model/type/page_label.rb +68 -0
- data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
- data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
- data/lib/pdfrb/model/type/pattern.rb +16 -0
- data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
- data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
- data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
- data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
- data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
- data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
- data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
- data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
- data/lib/pdfrb/model/type/rendition.rb +31 -0
- data/lib/pdfrb/model/type/resources.rb +59 -14
- data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
- data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
- data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
- data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
- data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
- data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
- data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
- data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
- data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
- data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
- data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
- data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
- data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
- data/lib/pdfrb/model/type/separation.rb +25 -0
- data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
- data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
- data/lib/pdfrb/model/type/signature.rb +65 -0
- data/lib/pdfrb/model/type/signature_build.rb +86 -0
- data/lib/pdfrb/model/type/signature_field.rb +17 -0
- data/lib/pdfrb/model/type/software_identifier.rb +19 -0
- data/lib/pdfrb/model/type/sound.rb +38 -0
- data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
- data/lib/pdfrb/model/type/square_annotation.rb +24 -0
- data/lib/pdfrb/model/type/square_circle.rb +13 -0
- data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
- data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
- data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
- data/lib/pdfrb/model/type/struct_elem.rb +75 -0
- data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
- data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
- data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
- data/lib/pdfrb/model/type/text_annotation.rb +25 -0
- data/lib/pdfrb/model/type/text_appearance.rb +9 -0
- data/lib/pdfrb/model/type/text_field.rb +21 -0
- data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
- data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
- data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
- data/lib/pdfrb/model/type/three_d_background.rb +28 -0
- data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
- data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
- data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
- data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
- data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
- data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
- data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
- data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
- data/lib/pdfrb/model/type/three_d_node.rb +37 -0
- data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
- data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
- data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
- data/lib/pdfrb/model/type/three_d_units.rb +18 -0
- data/lib/pdfrb/model/type/three_d_view.rb +40 -0
- data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
- data/lib/pdfrb/model/type/transition.rb +68 -0
- data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
- data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
- data/lib/pdfrb/model/type/uri_dict.rb +18 -0
- data/lib/pdfrb/model/type/url_alias.rb +19 -0
- data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
- data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
- data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
- data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
- data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
- data/lib/pdfrb/model/type/xobject_form.rb +41 -0
- data/lib/pdfrb/model/type/xobject_image.rb +56 -0
- data/lib/pdfrb/model/type/xref_stream.rb +37 -0
- data/lib/pdfrb/model/type.rb +286 -6
- data/lib/pdfrb/serializer.rb +2 -2
- data/lib/pdfrb/source/tokenizer.rb +0 -2
- data/lib/pdfrb/task/benchmark.rb +83 -0
- data/lib/pdfrb/task/extract_text.rb +1 -1
- data/lib/pdfrb/task/generate_corpus.rb +130 -0
- data/lib/pdfrb/task/memory_profile.rb +84 -0
- data/lib/pdfrb/task/optimize.rb +97 -7
- data/lib/pdfrb/task.rb +3 -3
- data/lib/pdfrb/test_utils.rb +112 -0
- data/lib/pdfrb/validator.rb +82 -0
- data/lib/pdfrb/version.rb +1 -1
- data/lib/pdfrb/writer.rb +117 -27
- data/lib/pdfrb/xmp/packet.rb +122 -0
- data/lib/pdfrb/xmp/schemas.rb +96 -0
- data/lib/pdfrb/xmp.rb +44 -0
- data/lib/pdfrb/xref_section.rb +2 -2
- data/lib/pdfrb.rb +24 -54
- data/pdfrb.gemspec +45 -0
- data/script/apply_fixes.sh +843 -0
- data/script/make_jpeg_fixture.rb +53 -0
- data/script/make_png_fixture.rb +25 -0
- metadata +311 -2
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Lint/MissingSuper
|
|
4
|
+
module Pdfrb
|
|
5
|
+
module Color
|
|
6
|
+
# Color space value objects (ISO 32000-2 §8.6). Each color space
|
|
7
|
+
# knows how to serialize to its PDF representation (a name for
|
|
8
|
+
# device spaces, an array for CIE-based and special spaces).
|
|
9
|
+
#
|
|
10
|
+
# Hierarchy:
|
|
11
|
+
# Base
|
|
12
|
+
# Device (DeviceGray, DeviceRGB, DeviceCMYK)
|
|
13
|
+
# CIEBased (CalGray, CalRGB, Lab, ICCBased)
|
|
14
|
+
# Special (Indexed, Separation, DeviceN, Pattern)
|
|
15
|
+
#
|
|
16
|
+
# OCP: new color space = subclass + register_as.
|
|
17
|
+
class ColorSpace
|
|
18
|
+
class << self
|
|
19
|
+
def registry
|
|
20
|
+
@registry ||= {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def register(name, klass)
|
|
24
|
+
registry[name.to_sym] = klass
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def [](name)
|
|
28
|
+
registry[name.to_sym]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def names
|
|
32
|
+
registry.keys
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Subclass hook: the PDF name or array-head symbol.
|
|
36
|
+
def pdf_name
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Register this class under its pdf_name.
|
|
41
|
+
def register_as(name = pdf_name)
|
|
42
|
+
ColorSpace.register(name, self)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Device color spaces — simple names, no parameters.
|
|
49
|
+
class DeviceColorSpace < ColorSpace
|
|
50
|
+
def to_pdf
|
|
51
|
+
self.class.pdf_name
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class DeviceGray < DeviceColorSpace
|
|
56
|
+
class << self
|
|
57
|
+
def pdf_name; :DeviceGray; end
|
|
58
|
+
end
|
|
59
|
+
register_as
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class DeviceRGB < DeviceColorSpace
|
|
63
|
+
class << self
|
|
64
|
+
def pdf_name; :DeviceRGB; end
|
|
65
|
+
end
|
|
66
|
+
register_as
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class DeviceCMYK < DeviceColorSpace
|
|
70
|
+
class << self
|
|
71
|
+
def pdf_name; :DeviceCMYK; end
|
|
72
|
+
end
|
|
73
|
+
register_as
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# CIE-based color spaces — arrays with calibration parameters.
|
|
77
|
+
class CIEBasedColorSpace < ColorSpace
|
|
78
|
+
def to_pdf
|
|
79
|
+
Pdfrb::Model::PdfArray.new([self.class.pdf_name, parameters_array].compact.flatten)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def parameters_array
|
|
83
|
+
[]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class CalGray < CIEBasedColorSpace
|
|
88
|
+
attr_reader :white_point, :black_point, :gamma
|
|
89
|
+
|
|
90
|
+
def initialize(white_point:, black_point: nil, gamma: nil)
|
|
91
|
+
@white_point = white_point
|
|
92
|
+
@black_point = black_point
|
|
93
|
+
@gamma = gamma
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class << self
|
|
97
|
+
def pdf_name; :CalGray; end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parameters_array
|
|
101
|
+
dict = { WhitePoint: Pdfrb::Model::PdfArray.new(@white_point) }
|
|
102
|
+
dict[:BlackPoint] = Pdfrb::Model::PdfArray.new(@black_point) if @black_point
|
|
103
|
+
dict[:Gamma] = @gamma if @gamma
|
|
104
|
+
dict
|
|
105
|
+
end
|
|
106
|
+
register_as
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class CalRGB < CIEBasedColorSpace
|
|
110
|
+
attr_reader :white_point, :gamma, :matrix, :black_point
|
|
111
|
+
|
|
112
|
+
def initialize(white_point:, gamma: nil, matrix: nil, black_point: nil)
|
|
113
|
+
@white_point = white_point
|
|
114
|
+
@gamma = gamma
|
|
115
|
+
@matrix = matrix
|
|
116
|
+
@black_point = black_point
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class << self
|
|
120
|
+
def pdf_name; :CalRGB; end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def parameters_array
|
|
124
|
+
dict = { WhitePoint: Pdfrb::Model::PdfArray.new(@white_point) }
|
|
125
|
+
dict[:Gamma] = Pdfrb::Model::PdfArray.new(@gamma) if @gamma
|
|
126
|
+
dict[:Matrix] = Pdfrb::Model::PdfArray.new(@matrix) if @matrix
|
|
127
|
+
dict[:BlackPoint] = Pdfrb::Model::PdfArray.new(@black_point) if @black_point
|
|
128
|
+
dict
|
|
129
|
+
end
|
|
130
|
+
register_as
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class Lab < CIEBasedColorSpace
|
|
134
|
+
attr_reader :white_point, :black_point, :range
|
|
135
|
+
|
|
136
|
+
def initialize(white_point:, black_point: nil, range: nil)
|
|
137
|
+
@white_point = white_point
|
|
138
|
+
@black_point = black_point
|
|
139
|
+
@range = range
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class << self
|
|
143
|
+
def pdf_name; :Lab; end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def parameters_array
|
|
147
|
+
dict = { WhitePoint: Pdfrb::Model::PdfArray.new(@white_point) }
|
|
148
|
+
dict[:BlackPoint] = Pdfrb::Model::PdfArray.new(@black_point) if @black_point
|
|
149
|
+
dict[:Range] = Pdfrb::Model::PdfArray.new(@range) if @range
|
|
150
|
+
dict
|
|
151
|
+
end
|
|
152
|
+
register_as
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# ICCBased is special — references an ICC profile stream.
|
|
156
|
+
# Delegates to Pdfrb::Color::ICCProfile for stream creation.
|
|
157
|
+
class ICCBased < CIEBasedColorSpace
|
|
158
|
+
attr_reader :icc_stream_ref
|
|
159
|
+
|
|
160
|
+
def initialize(icc_stream_ref)
|
|
161
|
+
@icc_stream_ref = icc_stream_ref
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class << self
|
|
165
|
+
def pdf_name; :ICCBased; end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def to_pdf
|
|
169
|
+
Pdfrb::Model::PdfArray.new([:ICCBased, @icc_stream_ref])
|
|
170
|
+
end
|
|
171
|
+
register_as
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Indexed color space — maps small integer codes to colors in a base space.
|
|
175
|
+
class Indexed < ColorSpace
|
|
176
|
+
attr_reader :base, :hival, :lookup
|
|
177
|
+
|
|
178
|
+
def initialize(base:, hival:, lookup:)
|
|
179
|
+
@base = base
|
|
180
|
+
@hival = hival
|
|
181
|
+
@lookup = lookup
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class << self
|
|
185
|
+
def pdf_name; :Indexed; end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def to_pdf
|
|
189
|
+
Pdfrb::Model::PdfArray.new([:Indexed, @base, @hival, @lookup])
|
|
190
|
+
end
|
|
191
|
+
register_as
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Separation color space — a single named ink (e.g. "PANTONE 185 C").
|
|
195
|
+
class Separation < ColorSpace
|
|
196
|
+
attr_reader :name, :alternate, :tint_transform
|
|
197
|
+
|
|
198
|
+
def initialize(name:, alternate:, tint_transform:)
|
|
199
|
+
@name = name
|
|
200
|
+
@alternate = alternate
|
|
201
|
+
@tint_transform = tint_transform
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
class << self
|
|
205
|
+
def pdf_name; :Separation; end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def to_pdf
|
|
209
|
+
Pdfrb::Model::PdfArray.new([:Separation, @name, @alternate, @tint_transform])
|
|
210
|
+
end
|
|
211
|
+
register_as
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# DeviceN color space — multiple named inks.
|
|
215
|
+
class DeviceN < ColorSpace
|
|
216
|
+
attr_reader :names, :alternate, :tint_transform
|
|
217
|
+
|
|
218
|
+
def initialize(names:, alternate:, tint_transform:)
|
|
219
|
+
@names = names
|
|
220
|
+
@alternate = alternate
|
|
221
|
+
@tint_transform = tint_transform
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class << self
|
|
225
|
+
def pdf_name; :DeviceN; end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def to_pdf
|
|
229
|
+
Pdfrb::Model::PdfArray.new([
|
|
230
|
+
:DeviceN,
|
|
231
|
+
Pdfrb::Model::PdfArray.new(@names),
|
|
232
|
+
@alternate,
|
|
233
|
+
@tint_transform,
|
|
234
|
+
])
|
|
235
|
+
end
|
|
236
|
+
register_as
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Pattern color space — for tiling and shading patterns.
|
|
240
|
+
class Pattern < ColorSpace
|
|
241
|
+
attr_reader :base
|
|
242
|
+
|
|
243
|
+
def initialize(base: nil)
|
|
244
|
+
@base = base
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
class << self
|
|
248
|
+
def pdf_name; :Pattern; end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def to_pdf
|
|
252
|
+
@base ? Pdfrb::Model::PdfArray.new([:Pattern, @base]) : :Pattern
|
|
253
|
+
end
|
|
254
|
+
register_as
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# rubocop:enable Lint/MissingSuper
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Color
|
|
5
|
+
# ICC profile stream model. An ICCBased color space is a
|
|
6
|
+
# [/ICCBased <stream-ref>] array where the stream carries the
|
|
7
|
+
# raw ICC profile bytes and the dictionary declares /N (number
|
|
8
|
+
# of components), /Alternate (fallback space), and /Range.
|
|
9
|
+
#
|
|
10
|
+
# This class parses the ICC header to extract metadata (version,
|
|
11
|
+
# device class, color space, component count) and builds the
|
|
12
|
+
# PDF stream dictionary.
|
|
13
|
+
#
|
|
14
|
+
# ICC spec: ISO 15076-1 / ICC.1:2010. PDF integration: ISO 32000-2 §8.6.5.5.
|
|
15
|
+
class ICCProfile
|
|
16
|
+
HEADER_SIZE = 128
|
|
17
|
+
SIGNATURE_OFFSET = 36
|
|
18
|
+
|
|
19
|
+
attr_reader :raw_data, :version, :device_class, :color_space,
|
|
20
|
+
:component_count, :alternate
|
|
21
|
+
|
|
22
|
+
def initialize(raw_data, alternate: nil)
|
|
23
|
+
@raw_data = raw_data.b
|
|
24
|
+
parse_header
|
|
25
|
+
@alternate = alternate || derive_alternate
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.read(path)
|
|
29
|
+
new(File.binread(path))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def pdf_color_space_array
|
|
33
|
+
[:ICCBased, :__icc_stream_ref__]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stream_dictionary_fields
|
|
37
|
+
{
|
|
38
|
+
N: @component_count,
|
|
39
|
+
Alternate: @alternate,
|
|
40
|
+
}.freeze
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def valid?
|
|
44
|
+
@raw_data.bytesize >= HEADER_SIZE && signature_valid?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def parse_header
|
|
50
|
+
return unless @raw_data.bytesize >= HEADER_SIZE
|
|
51
|
+
|
|
52
|
+
major = @raw_data.getbyte(8)
|
|
53
|
+
minor_bugfix = @raw_data.getbyte(9)
|
|
54
|
+
@version = format("%d.%d.%d", major, (minor_bugfix >> 4) & 0x0F, minor_bugfix & 0x0F)
|
|
55
|
+
@device_class = read_str(12, 4)
|
|
56
|
+
@color_space = read_str(16, 4)
|
|
57
|
+
@component_count = component_count_for(@color_space)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def signature_valid?
|
|
61
|
+
read_str(SIGNATURE_OFFSET, 4) == "acsp"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def derive_alternate
|
|
65
|
+
case @color_space
|
|
66
|
+
when "CMYK" then :DeviceCMYK
|
|
67
|
+
when "GRAY" then :DeviceGray
|
|
68
|
+
when "LAB " then :Lab
|
|
69
|
+
else :DeviceRGB # RGB and unknown fall back to DeviceRGB
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def component_count_for(cs)
|
|
74
|
+
case cs
|
|
75
|
+
when "CMYK" then 4
|
|
76
|
+
when "GRAY" then 1
|
|
77
|
+
else 3 # RGB, LAB, and unknown all use 3 components
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def read_u32(offset)
|
|
82
|
+
@raw_data.byteslice(offset, 4)&.unpack1("N")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def read_str(offset, len)
|
|
86
|
+
@raw_data.byteslice(offset, len)&.force_encoding(Encoding::US_ASCII) || ""
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/pdfrb/color.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
# Color spaces and color management. Provides domain types for
|
|
5
|
+
# device color spaces (DeviceGray/RGB/CMYK), CIE-based spaces
|
|
6
|
+
# (CalGray, CalRGB, Lab, ICCBased), and special spaces (Indexed,
|
|
7
|
+
# Separation, DeviceN, Pattern).
|
|
8
|
+
module Color
|
|
9
|
+
autoload :ICCProfile, "pdfrb/color/icc_profile"
|
|
10
|
+
autoload :ColorSpace, "pdfrb/color/color_space"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Compare
|
|
5
|
+
# Compares two Pdfrb::Documents at the structural level.
|
|
6
|
+
# Walks pages, extracts text, inventories fonts/images,
|
|
7
|
+
# inspects outlines and metadata. Produces a Report.
|
|
8
|
+
class Comparator
|
|
9
|
+
def compare(left, right)
|
|
10
|
+
Report.new(
|
|
11
|
+
page_count_delta: page_count_delta(left, right),
|
|
12
|
+
per_page_text_diffs: per_page_text_diffs(left, right),
|
|
13
|
+
font_diff: font_diff(left, right),
|
|
14
|
+
image_count_delta: image_count_delta(left, right),
|
|
15
|
+
outline_diff: outline_diff(left, right),
|
|
16
|
+
metadata_diff: metadata_diff(left, right),
|
|
17
|
+
structural_diffs: structural_diffs(left, right)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def page_count_delta(left, right)
|
|
24
|
+
right.pages.count - left.pages.count
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def per_page_text_diffs(left, right)
|
|
28
|
+
left_text = extract_text_per_page(left)
|
|
29
|
+
right_text = extract_text_per_page(right)
|
|
30
|
+
|
|
31
|
+
max = [left_text.length, right_text.length].max
|
|
32
|
+
(0...max).filter_map do |i|
|
|
33
|
+
lt = left_text[i] || ""
|
|
34
|
+
rt = right_text[i] || ""
|
|
35
|
+
sim = text_similarity(lt, rt)
|
|
36
|
+
next nil if sim >= 1.0 && lt == rt
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
page: i,
|
|
40
|
+
similarity: sim,
|
|
41
|
+
left_length: lt.length,
|
|
42
|
+
right_length: rt.length,
|
|
43
|
+
left_missing: chars_only_in(rt, lt),
|
|
44
|
+
right_missing: chars_only_in(lt, rt),
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def extract_text_per_page(doc)
|
|
50
|
+
Pdfrb::Task::ExtractText.call(doc)
|
|
51
|
+
rescue StandardError
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def text_similarity(a, b)
|
|
56
|
+
return 1.0 if a == b
|
|
57
|
+
return 0.0 if a.empty? || b.empty?
|
|
58
|
+
|
|
59
|
+
max_len = [a.length, b.length].max
|
|
60
|
+
lev = levenshtein(a, b)
|
|
61
|
+
1.0 - (lev.to_f / max_len)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def levenshtein(a, b)
|
|
65
|
+
m = a.length
|
|
66
|
+
n = b.length
|
|
67
|
+
return n if m.zero?
|
|
68
|
+
return m if n.zero?
|
|
69
|
+
|
|
70
|
+
d = Array.new(m + 1) { |i| [i] + Array.new(n, 0) }
|
|
71
|
+
d[0] = (0..n).to_a
|
|
72
|
+
|
|
73
|
+
(1..m).each do |i|
|
|
74
|
+
(1..n).each do |j|
|
|
75
|
+
cost = a[i - 1] == b[j - 1] ? 0 : 1
|
|
76
|
+
d[i][j] = [d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost].min
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
d[m][n]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def chars_only_in(a, b)
|
|
83
|
+
(a.chars.to_a - b.chars.to_a).uniq.first(20).join
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def font_diff(left, right)
|
|
87
|
+
left_fonts = inventory_fonts(left)
|
|
88
|
+
right_fonts = inventory_fonts(right)
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
added: (right_fonts - left_fonts).sort,
|
|
92
|
+
removed: (left_fonts - right_fonts).sort,
|
|
93
|
+
common: (left_fonts & right_fonts).sort,
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def inventory_fonts(doc)
|
|
98
|
+
fonts = Set.new
|
|
99
|
+
doc.each_indirect_object do |obj|
|
|
100
|
+
next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
101
|
+
next unless obj[:Type] == :Font
|
|
102
|
+
|
|
103
|
+
name = obj[:BaseFont] || obj[:Name] || "Unknown"
|
|
104
|
+
subtype = obj[:Subtype] || "Unknown"
|
|
105
|
+
fonts << "#{subtype}:#{name}"
|
|
106
|
+
end
|
|
107
|
+
fonts.to_a
|
|
108
|
+
rescue StandardError
|
|
109
|
+
[]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def image_count_delta(left, right)
|
|
113
|
+
right_count = count_images(right)
|
|
114
|
+
left_count = count_images(left)
|
|
115
|
+
right_count - left_count
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def count_images(doc)
|
|
119
|
+
count = 0
|
|
120
|
+
doc.each_indirect_object do |obj|
|
|
121
|
+
next unless obj.is_a?(Pdfrb::Model::Cos::Stream)
|
|
122
|
+
next unless obj[:Subtype] == :Image
|
|
123
|
+
|
|
124
|
+
count += 1
|
|
125
|
+
end
|
|
126
|
+
count
|
|
127
|
+
rescue StandardError
|
|
128
|
+
0
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def outline_diff(left, right)
|
|
132
|
+
left_titles = outline_titles(left)
|
|
133
|
+
right_titles = outline_titles(right)
|
|
134
|
+
|
|
135
|
+
{
|
|
136
|
+
added: (right_titles - left_titles),
|
|
137
|
+
removed: (left_titles - right_titles),
|
|
138
|
+
common: (left_titles & right_titles),
|
|
139
|
+
}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def outline_titles(doc)
|
|
143
|
+
titles = Set.new
|
|
144
|
+
outlines_ref = doc.catalog&.value&.dig(:Outlines)
|
|
145
|
+
return titles.to_a unless outlines_ref
|
|
146
|
+
|
|
147
|
+
outlines = doc.object(outlines_ref)
|
|
148
|
+
return titles.to_a unless outlines
|
|
149
|
+
|
|
150
|
+
walk_outline(outlines, doc) { |title| titles << title }
|
|
151
|
+
titles.to_a
|
|
152
|
+
rescue StandardError
|
|
153
|
+
[]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def walk_outline(outlines, doc, &)
|
|
157
|
+
current = outlines[:First]
|
|
158
|
+
while current
|
|
159
|
+
entry = doc.object(current)
|
|
160
|
+
break unless entry
|
|
161
|
+
|
|
162
|
+
title = entry[:Title]
|
|
163
|
+
yield(title.to_s) if title
|
|
164
|
+
|
|
165
|
+
if entry[:First]
|
|
166
|
+
child = doc.object(entry[:First])
|
|
167
|
+
walk_outline(child, doc, &) if child
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
current = entry[:Next]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def metadata_diff(left, right)
|
|
175
|
+
left_info = extract_info(left)
|
|
176
|
+
right_info = extract_info(right)
|
|
177
|
+
|
|
178
|
+
diff = {}
|
|
179
|
+
(left_info.keys | right_info.keys).each do |key|
|
|
180
|
+
lv = left_info[key]
|
|
181
|
+
rv = right_info[key]
|
|
182
|
+
next if lv == rv
|
|
183
|
+
|
|
184
|
+
diff[key] = { left: lv, right: rv }
|
|
185
|
+
end
|
|
186
|
+
diff
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def extract_info(doc)
|
|
190
|
+
trailer = doc.trailer || {}
|
|
191
|
+
info_ref = trailer[:Info]
|
|
192
|
+
return {} unless info_ref
|
|
193
|
+
|
|
194
|
+
info = doc.object(info_ref)
|
|
195
|
+
return {} unless info
|
|
196
|
+
|
|
197
|
+
result = {}
|
|
198
|
+
info.value.each do |k, v|
|
|
199
|
+
next if k == :Type
|
|
200
|
+
|
|
201
|
+
result[k] = v.to_s
|
|
202
|
+
end
|
|
203
|
+
result
|
|
204
|
+
rescue StandardError
|
|
205
|
+
{}
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def structural_diffs(left, right)
|
|
209
|
+
diffs = []
|
|
210
|
+
|
|
211
|
+
lc = left.catalog
|
|
212
|
+
rc = right.catalog
|
|
213
|
+
if lc && rc
|
|
214
|
+
left_keys = lc.value.keys.to_set
|
|
215
|
+
right_keys = rc.value.keys.to_set
|
|
216
|
+
|
|
217
|
+
added = right_keys - left_keys
|
|
218
|
+
removed = left_keys - right_keys
|
|
219
|
+
unless added.empty? && removed.empty?
|
|
220
|
+
diffs << {
|
|
221
|
+
location: "catalog",
|
|
222
|
+
keys_added: added.to_a.sort,
|
|
223
|
+
keys_removed: removed.to_a.sort,
|
|
224
|
+
}
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
lt = left.trailer || {}
|
|
229
|
+
rt = right.trailer || {}
|
|
230
|
+
left_tkeys = lt.keys.to_set - [:Size, :Root, :Prev]
|
|
231
|
+
right_tkeys = rt.keys.to_set - [:Size, :Root, :Prev]
|
|
232
|
+
unless (left_tkeys - right_tkeys).empty? && (right_tkeys - left_tkeys).empty?
|
|
233
|
+
diffs << {
|
|
234
|
+
location: "trailer",
|
|
235
|
+
keys_added: (right_tkeys - left_tkeys).to_a.sort,
|
|
236
|
+
keys_removed: (left_tkeys - right_tkeys).to_a.sort,
|
|
237
|
+
}
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
diffs
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Compare
|
|
5
|
+
# Result of comparing two PDFs. Immutable value object.
|
|
6
|
+
class Report
|
|
7
|
+
attr_reader :page_count_delta, :per_page_text_diffs,
|
|
8
|
+
:font_diff, :image_count_delta,
|
|
9
|
+
:outline_diff, :metadata_diff, :structural_diffs
|
|
10
|
+
|
|
11
|
+
def initialize(page_count_delta:, per_page_text_diffs:, font_diff:,
|
|
12
|
+
image_count_delta:, outline_diff:, metadata_diff:,
|
|
13
|
+
structural_diffs:)
|
|
14
|
+
@page_count_delta = page_count_delta
|
|
15
|
+
@per_page_text_diffs = per_page_text_diffs.freeze
|
|
16
|
+
@font_diff = font_diff.freeze
|
|
17
|
+
@image_count_delta = image_count_delta
|
|
18
|
+
@outline_diff = outline_diff.freeze
|
|
19
|
+
@metadata_diff = metadata_diff.freeze
|
|
20
|
+
@structural_diffs = structural_diffs.freeze
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def equivalent?
|
|
25
|
+
return false unless @page_count_delta.zero?
|
|
26
|
+
return false unless @per_page_text_diffs.empty?
|
|
27
|
+
return false unless @font_diff[:added].empty? && @font_diff[:removed].empty?
|
|
28
|
+
return false unless @image_count_delta.zero?
|
|
29
|
+
return false unless @structural_diffs.empty?
|
|
30
|
+
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def similarity
|
|
35
|
+
total_checks = 0
|
|
36
|
+
passing = 0
|
|
37
|
+
|
|
38
|
+
total_checks += 1
|
|
39
|
+
passing += 1 if @page_count_delta.zero?
|
|
40
|
+
|
|
41
|
+
total_checks += @per_page_text_diffs.length + 1
|
|
42
|
+
passing += @per_page_text_diffs.count { |d| d[:similarity] > 0.95 } + 1
|
|
43
|
+
|
|
44
|
+
total_checks += 1
|
|
45
|
+
passing += 1 if @font_diff[:added].empty? && @font_diff[:removed].empty?
|
|
46
|
+
|
|
47
|
+
total_checks += 1
|
|
48
|
+
passing += 1 if @image_count_delta.zero?
|
|
49
|
+
|
|
50
|
+
total_checks + 1
|
|
51
|
+
passing + (@structural_diffs.empty? ? 1 : 0)
|
|
52
|
+
(passing.to_f / (total_checks + 1)).round(4)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_h
|
|
56
|
+
{
|
|
57
|
+
equivalent: equivalent?,
|
|
58
|
+
similarity: similarity,
|
|
59
|
+
page_count_delta: @page_count_delta,
|
|
60
|
+
per_page_text_diffs: @per_page_text_diffs,
|
|
61
|
+
font_diff: @font_diff,
|
|
62
|
+
image_count_delta: @image_count_delta,
|
|
63
|
+
outline_diff: @outline_diff,
|
|
64
|
+
metadata_diff: @metadata_diff,
|
|
65
|
+
structural_diffs: @structural_diffs,
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def summary
|
|
70
|
+
status = equivalent? ? "EQUIVALENT" : "DIFFERENT"
|
|
71
|
+
sim = (similarity * 100).round(1)
|
|
72
|
+
"#{status} (#{sim}% similarity, page_delta=#{@page_count_delta}, " \
|
|
73
|
+
"text_diffs=#{@per_page_text_diffs.length}, " \
|
|
74
|
+
"font_changes=#{@font_diff[:added].length + @font_diff[:removed].length})"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|