pdfrb 0.2.1 → 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/CHANGELOG.md +24 -0
- 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 +312 -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/form_xobject.rb +68 -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 +25 -53
- 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 +312 -2
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Facade for authoring tagged PDF structure trees (PDF/UA §7.9, ISO 32000-2 §14.8).
|
|
6
|
+
#
|
|
7
|
+
# A tagged PDF has:
|
|
8
|
+
# * /StructTreeRoot on the Catalog — root of the structure hierarchy.
|
|
9
|
+
# * /MarkInfo /Marked true on the Catalog — declares the PDF as tagged.
|
|
10
|
+
# * /ParentTree on StructTreeRoot — maps marked-content BDC/EMC
|
|
11
|
+
# references (via /MCID on BDC) back to their parent StructElem.
|
|
12
|
+
#
|
|
13
|
+
# Structure elements form a tree: a document has sections, sections
|
|
14
|
+
# have headings/paragraphs/figures, etc. Each element has a /S
|
|
15
|
+
# (structure type — :H1, :P, :Figure, :Table, ...) and optional
|
|
16
|
+
# /T (title), /Alt (alternate text), /Lang (language).
|
|
17
|
+
class Structure
|
|
18
|
+
attr_reader :document, :root
|
|
19
|
+
|
|
20
|
+
def initialize(document)
|
|
21
|
+
@document = document
|
|
22
|
+
@elements = []
|
|
23
|
+
@current_index = 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Enable tagged PDF: create /StructTreeRoot, set /MarkInfo.
|
|
27
|
+
# Idempotent — returns the existing root if already enabled.
|
|
28
|
+
# @return [Pdfrb::Model::Cos::Dictionary] the StructTreeRoot.
|
|
29
|
+
def enable!
|
|
30
|
+
return @root if @root
|
|
31
|
+
|
|
32
|
+
root_elem = document.add(
|
|
33
|
+
{ Type: :StructTreeRoot },
|
|
34
|
+
type: Pdfrb::Model::Type::StructTreeRoot
|
|
35
|
+
)
|
|
36
|
+
catalog = document.catalog
|
|
37
|
+
catalog.value[:StructTreeRoot] =
|
|
38
|
+
Pdfrb::Model::Reference.new(root_elem.oid, root_elem.gen)
|
|
39
|
+
mark_info = catalog.value[:MarkInfo]
|
|
40
|
+
if mark_info.nil?
|
|
41
|
+
mark_info = document.add({ Marked: true },
|
|
42
|
+
type: Pdfrb::Model::Cos::Dictionary)
|
|
43
|
+
catalog.value[:MarkInfo] =
|
|
44
|
+
Pdfrb::Model::Reference.new(mark_info.oid, mark_info.gen)
|
|
45
|
+
end
|
|
46
|
+
@root = root_elem
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Add a top-level child element to the structure tree.
|
|
50
|
+
# @param type [Symbol] structure type (:Document, :Part, :H1, :P, ...).
|
|
51
|
+
# @param attrs [Hash] additional attributes (:T, :Alt, :Lang, ...).
|
|
52
|
+
# @return [Pdfrb::Model::Cos::Dictionary] the new element.
|
|
53
|
+
def add_element(type, page: nil, mcid: nil, **attrs)
|
|
54
|
+
ensure_root
|
|
55
|
+
elem = create_element(type, **attrs)
|
|
56
|
+
append_child(@root, elem)
|
|
57
|
+
|
|
58
|
+
if page
|
|
59
|
+
page_ref = if page.is_a?(Pdfrb::Model::Reference)
|
|
60
|
+
page
|
|
61
|
+
else
|
|
62
|
+
Pdfrb::Model::Reference.new(page.oid, page.gen)
|
|
63
|
+
end
|
|
64
|
+
elem.value[:Pg] = page_ref
|
|
65
|
+
end
|
|
66
|
+
elem.value[:K] = { MCID: mcid } if mcid
|
|
67
|
+
|
|
68
|
+
@elements << elem
|
|
69
|
+
elem
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Add a child element to a parent element.
|
|
73
|
+
# @param parent [Pdfrb::Model::Cos::Dictionary] the parent StructElem.
|
|
74
|
+
# @param type [Symbol] structure type.
|
|
75
|
+
# @param attrs [Hash] additional attributes.
|
|
76
|
+
# @return [Pdfrb::Model::Cos::Dictionary] the new element.
|
|
77
|
+
def add_child(parent, type, **attrs)
|
|
78
|
+
elem = create_element(type, **attrs)
|
|
79
|
+
append_child(parent, elem)
|
|
80
|
+
@elements << elem
|
|
81
|
+
elem
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Set alternate text on a structure element for screen readers.
|
|
85
|
+
# Required for /Figure elements per PDF/UA-1 Tech Note 001.
|
|
86
|
+
# @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
|
|
87
|
+
# @param text [String] the alt text.
|
|
88
|
+
def set_alt_text(element, text)
|
|
89
|
+
element.value[:Alt] = text.to_s
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Set actual text on a structure element. Overrides the visual
|
|
93
|
+
# text for screen readers (e.g., expanding abbreviations).
|
|
94
|
+
# @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
|
|
95
|
+
# @param text [String] the actual text.
|
|
96
|
+
def set_actual_text(element, text)
|
|
97
|
+
element.value[:ActualText] = text.to_s
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Set language on a structure element (overrides document /Lang).
|
|
101
|
+
# @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
|
|
102
|
+
# @param lang [String] BCP 47 language tag (e.g., "en-US").
|
|
103
|
+
def set_language(element, lang)
|
|
104
|
+
element.value[:Lang] = lang.to_s
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Check if a structure element has alt text (required for /Figure).
|
|
108
|
+
# @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
|
|
109
|
+
# @return [Boolean]
|
|
110
|
+
def has_alt_text?(element)
|
|
111
|
+
alt = element.value[:Alt]
|
|
112
|
+
alt && !alt.to_s.empty?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Validate that all /Figure elements have /Alt text per PDF/UA-1.
|
|
116
|
+
# @return [Array<Hash>] list of violations (element + reason).
|
|
117
|
+
def validate_alt_text!
|
|
118
|
+
violations = []
|
|
119
|
+
each_element do |elem|
|
|
120
|
+
next unless elem.value[:S]&.to_sym == :Figure
|
|
121
|
+
|
|
122
|
+
unless has_alt_text?(elem)
|
|
123
|
+
violations << { element: elem, reason: "Figure missing /Alt text" }
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
violations
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Walk all structure elements depth-first.
|
|
130
|
+
def each_element(&block)
|
|
131
|
+
return enum_for(:each_element) unless block
|
|
132
|
+
|
|
133
|
+
@elements.each(&block)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Define a role mapping: maps a custom structure type name to
|
|
137
|
+
# a standard one. Required for PDF/UA when custom types are used.
|
|
138
|
+
# @param custom [Symbol] the custom type name.
|
|
139
|
+
# @param standard [Symbol] the standard type (:H1, :P, :Figure, ...).
|
|
140
|
+
def map_role(custom, standard)
|
|
141
|
+
ensure_root
|
|
142
|
+
role_map = @root.value[:RoleMap]
|
|
143
|
+
if role_map.nil?
|
|
144
|
+
role_map = {}
|
|
145
|
+
@root.value[:RoleMap] = role_map
|
|
146
|
+
end
|
|
147
|
+
role_map[custom] = standard
|
|
148
|
+
role_map
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def build_parent_tree
|
|
154
|
+
return if @root.nil?
|
|
155
|
+
|
|
156
|
+
page_map = Hash.new { |h, k| h[k] = [] }
|
|
157
|
+
max_page = -1
|
|
158
|
+
each_element_with_page do |elem, page_index, mcid|
|
|
159
|
+
page_map[page_index] << [mcid, elem]
|
|
160
|
+
max_page = page_index if page_index > max_page
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
nums = []
|
|
164
|
+
page_map.keys.sort.each do |page_idx|
|
|
165
|
+
entries = page_map[page_idx]
|
|
166
|
+
nums << page_idx
|
|
167
|
+
nums << entries.length
|
|
168
|
+
entries.each do |_mcid, elem| # rubocop:disable Style/HashEachMethods
|
|
169
|
+
nums << Pdfrb::Model::Reference.new(elem.oid, elem.gen)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@root.value[:ParentTree] = { Nums: nums }
|
|
174
|
+
@root.value[:ParentTreeNextKey] = max_page + 1 if max_page >= 0
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def each_element_with_page
|
|
178
|
+
return enum_for(:each_element_with_page) unless block_given?
|
|
179
|
+
|
|
180
|
+
@elements.each do |elem|
|
|
181
|
+
page_ref = elem.value[:Pg]
|
|
182
|
+
page_index = page_index_for(page_ref)
|
|
183
|
+
mcid = mcid_for(elem)
|
|
184
|
+
yield elem, page_index, mcid
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def page_index_for(page_ref)
|
|
189
|
+
return 0 unless page_ref
|
|
190
|
+
|
|
191
|
+
document.pages.each_with_index do |page, idx|
|
|
192
|
+
page_obj = page.is_a?(Pdfrb::Model::Reference) ? document.object(page) : page
|
|
193
|
+
return idx if page_obj == page_ref || page == page_ref
|
|
194
|
+
end
|
|
195
|
+
0
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def mcid_for(elem)
|
|
199
|
+
k = elem.value[:K]
|
|
200
|
+
k.is_a?(::Hash) ? k[:MCID] || 0 : 0
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def ensure_root
|
|
204
|
+
@root || enable!
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def create_element(type, **attrs)
|
|
208
|
+
dict = { Type: :StructElem, S: type }
|
|
209
|
+
dict[:T] = attrs[:title] if attrs[:title]
|
|
210
|
+
dict[:Alt] = attrs[:alt] if attrs[:alt]
|
|
211
|
+
dict[:Lang] = attrs[:lang] if attrs[:lang]
|
|
212
|
+
document.add(dict, type: Pdfrb::Model::Type::StructElem)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def append_child(parent, child)
|
|
216
|
+
children = parent.value[:K]
|
|
217
|
+
if children.nil?
|
|
218
|
+
parent.value[:K] = [Pdfrb::Model::Reference.new(child.oid, child.gen)]
|
|
219
|
+
elsif children.is_a?(::Array)
|
|
220
|
+
children << Pdfrb::Model::Reference.new(child.oid, child.gen)
|
|
221
|
+
else
|
|
222
|
+
parent.value[:K] = [
|
|
223
|
+
children,
|
|
224
|
+
Pdfrb::Model::Reference.new(child.oid, child.gen),
|
|
225
|
+
]
|
|
226
|
+
end
|
|
227
|
+
child.value[:P] = Pdfrb::Model::Reference.new(parent.oid, parent.gen)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
public
|
|
231
|
+
|
|
232
|
+
# Build the structure tree, ParentTree, and set up /MarkInfo.
|
|
233
|
+
# Idempotent — safe to call multiple times.
|
|
234
|
+
def build!
|
|
235
|
+
enable! if @root.nil?
|
|
236
|
+
return if @elements.empty?
|
|
237
|
+
|
|
238
|
+
build_parent_tree
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
data/lib/pdfrb/document.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
3
|
module Pdfrb
|
|
6
4
|
# Top-level PDF document facade. Owns:
|
|
7
5
|
# * The IO it was read from (or nil for in-memory).
|
|
@@ -20,8 +18,22 @@ module Pdfrb
|
|
|
20
18
|
autoload :Annotations, "pdfrb/document/annotations"
|
|
21
19
|
autoload :Outline, "pdfrb/document/outline"
|
|
22
20
|
autoload :FormXObject, "pdfrb/document/form_xobject"
|
|
21
|
+
autoload :AssociatedFiles, "pdfrb/document/associated_files"
|
|
22
|
+
autoload :Colors, "pdfrb/document/colors"
|
|
23
|
+
autoload :Display, "pdfrb/document/display"
|
|
24
|
+
autoload :Form, "pdfrb/document/form"
|
|
25
|
+
autoload :GraphicsState, "pdfrb/document/graphics_state"
|
|
26
|
+
autoload :Layers, "pdfrb/document/layers"
|
|
27
|
+
autoload :Portfolio, "pdfrb/document/portfolio"
|
|
28
|
+
autoload :Structure, "pdfrb/document/structure"
|
|
29
|
+
autoload :Shadings, "pdfrb/document/shadings"
|
|
30
|
+
autoload :OutputIntents, "pdfrb/document/output_intents"
|
|
31
|
+
autoload :Info, "pdfrb/document/info"
|
|
32
|
+
autoload :PageLabels, "pdfrb/document/page_labels"
|
|
33
|
+
autoload :Stamps, "pdfrb/document/stamps"
|
|
23
34
|
|
|
24
35
|
def initialize(io: nil, config: {})
|
|
36
|
+
Pdfrb::Model::Type.eager_load!
|
|
25
37
|
@config = Configuration.new(config)
|
|
26
38
|
@io = io
|
|
27
39
|
@objects = {} # oid -> Pdfrb::Model::Object (modified or new)
|
|
@@ -39,7 +51,7 @@ module Pdfrb
|
|
|
39
51
|
end
|
|
40
52
|
end
|
|
41
53
|
|
|
42
|
-
attr_reader :config, :io, :xref, :version
|
|
54
|
+
attr_reader :config, :io, :xref, :version, :next_oid
|
|
43
55
|
|
|
44
56
|
def self.open(path, **opts)
|
|
45
57
|
if block_given?
|
|
@@ -128,6 +140,53 @@ module Pdfrb
|
|
|
128
140
|
@outline ||= Document::Outline.new(self)
|
|
129
141
|
end
|
|
130
142
|
|
|
143
|
+
def structure; @structure ||= Document::Structure.new(self); end
|
|
144
|
+
|
|
145
|
+
def form; @form ||= Document::Form.new(self); end
|
|
146
|
+
|
|
147
|
+
def shadings; @shadings ||= Document::Shadings.new(self); end
|
|
148
|
+
|
|
149
|
+
def colors; @colors ||= Document::Colors.new(self); end
|
|
150
|
+
|
|
151
|
+
def graphics_state; @graphics_state ||= Document::GraphicsState.new(self); end
|
|
152
|
+
|
|
153
|
+
def layers; @layers ||= Document::Layers.new(self); end
|
|
154
|
+
|
|
155
|
+
def associated_files; @associated_files ||= Document::AssociatedFiles.new(self); end
|
|
156
|
+
|
|
157
|
+
def portfolio; @portfolio ||= Document::Portfolio.new(self); end
|
|
158
|
+
|
|
159
|
+
def output_intents; @output_intents ||= Document::OutputIntents.new(self); end
|
|
160
|
+
|
|
161
|
+
def page_labels; @page_labels ||= Document::PageLabels.new(self); end
|
|
162
|
+
|
|
163
|
+
def stamps; @stamps ||= Document::Stamps.new(self); end
|
|
164
|
+
|
|
165
|
+
def info; @info ||= Document::Info.new(self); end
|
|
166
|
+
|
|
167
|
+
def display; @display ||= Document::Display.new(self); end
|
|
168
|
+
|
|
169
|
+
def xmp
|
|
170
|
+
return @xmp if @xmp
|
|
171
|
+
|
|
172
|
+
packet = load_xmp_packet
|
|
173
|
+
return nil unless packet
|
|
174
|
+
|
|
175
|
+
title = metadata[:Title]
|
|
176
|
+
packet.title = title if title
|
|
177
|
+
author = metadata[:Author]
|
|
178
|
+
packet.author = author if author
|
|
179
|
+
@xmp = packet
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def load_xmp_packet
|
|
183
|
+
Pdfrb::XMP::Packet.new
|
|
184
|
+
rescue LoadError
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def version=(v); @version = v.to_s; end
|
|
189
|
+
|
|
131
190
|
def create_form_xobject(name: nil, bbox: nil, matrix: nil)
|
|
132
191
|
FormXObject.new(self, name: name, bbox: bbox, matrix: matrix)
|
|
133
192
|
end
|
|
@@ -139,13 +198,21 @@ module Pdfrb
|
|
|
139
198
|
@objects[obj.oid] = obj
|
|
140
199
|
end
|
|
141
200
|
|
|
201
|
+
# Drop a modified/new object from the in-memory table so it's no
|
|
202
|
+
# longer emitted by the writer. Used by Task::Optimize when a
|
|
203
|
+
# stream has been deduplicated against a canonical sibling.
|
|
204
|
+
# Returns the dropped object (or nil if not present).
|
|
205
|
+
def forget(oid)
|
|
206
|
+
@objects.delete(oid)
|
|
207
|
+
end
|
|
208
|
+
|
|
142
209
|
# Write this document to +path+ (or any IO via +io:).
|
|
143
210
|
def write(path = nil, io: nil)
|
|
144
211
|
target = io || (path && File.open(path, "wb"))
|
|
145
212
|
raise ArgumentError, "write needs a path or io:" unless target
|
|
146
213
|
|
|
147
214
|
Pdfrb::Writer.write(self, target)
|
|
148
|
-
target.close if path && io.nil? && target.
|
|
215
|
+
target.close if path && io.nil? && target.is_a?(IO)
|
|
149
216
|
self
|
|
150
217
|
end
|
|
151
218
|
|
|
@@ -170,6 +237,8 @@ module Pdfrb
|
|
|
170
237
|
# first (they shadow loaded ones), then every loaded entry the
|
|
171
238
|
# xref knows about.
|
|
172
239
|
def each_indirect_object
|
|
240
|
+
return enum_for(:each_indirect_object) unless block_given?
|
|
241
|
+
|
|
173
242
|
seen = Set.new
|
|
174
243
|
@objects.each_value do |obj|
|
|
175
244
|
next unless obj.indirect?
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "digest/md5"
|
|
4
4
|
require "digest/sha2"
|
|
5
|
+
require "openssl"
|
|
5
6
|
|
|
6
7
|
module Pdfrb
|
|
7
8
|
module Encryption
|
|
@@ -20,8 +21,8 @@ module Pdfrb
|
|
|
20
21
|
|
|
21
22
|
module_function
|
|
22
23
|
|
|
23
|
-
# Algorithm 2
|
|
24
|
-
#
|
|
24
|
+
# Algorithm 2 (revision 2..4, RC4 and AES-128 V4): derive the
|
|
25
|
+
# encryption key from a user password.
|
|
25
26
|
def derive_key_rc4(password:, o_entry:, p_flags:, id0:,
|
|
26
27
|
revision:, key_length_bits:, encrypt_metadata: true)
|
|
27
28
|
padded = pad_password(password)
|
|
@@ -40,12 +41,56 @@ module Pdfrb
|
|
|
40
41
|
hash.byteslice(0, key_length_bits / 8)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
# Algorithm
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
# Algorithm 2.B / 8 (revision >= 5, AES-256, PDF 2.0): verify a
|
|
45
|
+
# user password by hashing password + 8-byte validation salt from
|
|
46
|
+
# /U[32..40]. Returns the 32-byte key on success, nil on mismatch.
|
|
47
|
+
def verify_user_password_v5(password:, u_entry:, validation_salt:)
|
|
48
|
+
hash = sha256_password_rounds(password: password.b, salt: validation_salt.b,
|
|
49
|
+
u_entry: u_entry.b)
|
|
50
|
+
hash.byteslice(0, 32) == u_entry.byteslice(0, 32) ? hash : nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Algorithm 2.B / 9: same for owner password. Owner password
|
|
54
|
+
# validation uses /O[0..32] + /U[0..48] in the hash chain.
|
|
55
|
+
def verify_owner_password_v5(password:, o_entry:, u_entry:,
|
|
56
|
+
validation_salt:)
|
|
57
|
+
hash = sha256_password_rounds(password: password.b, salt: validation_salt.b,
|
|
58
|
+
u_entry: u_entry.b, o_entry: o_entry.b)
|
|
59
|
+
hash.byteslice(0, 32) == o_entry.byteslice(0, 32) ? hash : nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Algorithm 2.B: the iterative SHA-256 hash loop (sha256 + 64 bytes
|
|
63
|
+
# of last output, XORed with input, repeated until the MSB of byte
|
|
64
|
+
# 0 is 0). Used for V5 password verification and key extraction.
|
|
65
|
+
def sha256_password_rounds(password:, salt:, u_entry: "", o_entry: nil)
|
|
66
|
+
input = password + salt + (o_entry ? o_entry.byteslice(0, 48) : u_entry.byteslice(0, 48))
|
|
67
|
+
hash = Digest::SHA256.digest(input)
|
|
68
|
+
round = 0
|
|
69
|
+
while hash.getbyte(0).nobits?(0x80)
|
|
70
|
+
round += 1
|
|
71
|
+
key = hash.byteslice(0, 16)
|
|
72
|
+
iv = hash.byteslice(16, 16)
|
|
73
|
+
cipher = OpenSSL::Cipher.new("aes-128-cbc")
|
|
74
|
+
cipher.encrypt
|
|
75
|
+
cipher.key = key
|
|
76
|
+
cipher.iv = iv
|
|
77
|
+
cipher.padding = 0
|
|
78
|
+
encrypted = cipher.update(input) + cipher.final
|
|
79
|
+
input = encrypted.bytes.zip(input.bytes).map { |a, b| a ^ b }.pack("C*")
|
|
80
|
+
hash = Digest::SHA256.digest(input)
|
|
81
|
+
end
|
|
82
|
+
hash
|
|
83
|
+
end
|
|
84
|
+
private_class_method :sha256_password_rounds
|
|
85
|
+
|
|
86
|
+
# Extract the validation salt (bytes 32..40) and key salt (40..48)
|
|
87
|
+
# from a /U or /O entry for V5.
|
|
88
|
+
def extract_v5_salts(entry)
|
|
89
|
+
bytes = entry.b
|
|
90
|
+
{
|
|
91
|
+
validation_salt: bytes.byteslice(32, 8) || "".b,
|
|
92
|
+
key_salt: bytes.byteslice(40, 8) || "".b,
|
|
93
|
+
}
|
|
49
94
|
end
|
|
50
95
|
|
|
51
96
|
# Algorithm 4 (R=2): build the /U entry.
|
|
@@ -26,7 +26,7 @@ module Pdfrb
|
|
|
26
26
|
return nil unless encrypt
|
|
27
27
|
|
|
28
28
|
filter = encrypt[:Filter] || :Standard
|
|
29
|
-
klass =
|
|
29
|
+
klass = registry[filter.to_s] ||
|
|
30
30
|
raise(Pdfrb::EncryptionError, "unsupported /Filter #{filter.inspect}")
|
|
31
31
|
handler = klass.new(document)
|
|
32
32
|
handler.set_up_decryption(**decryption_opts)
|
|
@@ -50,15 +50,18 @@ module Pdfrb
|
|
|
50
50
|
!@encrypt_dict.nil?
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
HANDLERS = {}.freeze
|
|
54
|
-
private_constant :HANDLERS
|
|
55
|
-
|
|
56
53
|
class << self
|
|
57
|
-
def registry
|
|
54
|
+
def registry
|
|
55
|
+
@registry ||= {}
|
|
56
|
+
end
|
|
58
57
|
|
|
59
58
|
def register(filter_name, klass)
|
|
60
59
|
registry[filter_name.to_s] = klass
|
|
61
60
|
end
|
|
61
|
+
|
|
62
|
+
def lookup(filter_name)
|
|
63
|
+
registry[filter_name.to_s]
|
|
64
|
+
end
|
|
62
65
|
end
|
|
63
66
|
end
|
|
64
67
|
end
|