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,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Ubatch
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module UdebugUinfo
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Fonts
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
puts "Fonts:"
|
|
11
|
+
doc.fonts.each { |name, res| puts " #{res}: #{name}" }
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Uform
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Uimage2pdf
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Images
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, output_dir: nil)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
output_dir ||= File.dirname(path)
|
|
11
|
+
count = 0
|
|
12
|
+
doc.pages.each_with_index do |page, pn|
|
|
13
|
+
res = page.value[:Resources]
|
|
14
|
+
next unless res
|
|
15
|
+
|
|
16
|
+
xo = res[:XObject] || res.value[:XObject]
|
|
17
|
+
next unless xo
|
|
18
|
+
|
|
19
|
+
xo = xo.value if xo.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
20
|
+
xo&.each do |name, ref|
|
|
21
|
+
obj = ref.is_a?(Pdfrb::Model::Reference) ? doc.object(ref) : ref
|
|
22
|
+
next unless obj&.value&.[](:Subtype) == :Image
|
|
23
|
+
|
|
24
|
+
count += 1
|
|
25
|
+
data = obj.stream || ""
|
|
26
|
+
File.binwrite(File.join(output_dir, "page#{pn + 1}_#{name}.raw"), data)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
puts "Extracted #{count} images"
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Info
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
puts "File: #{path}"
|
|
11
|
+
puts "Version: #{doc.version}"
|
|
12
|
+
puts "Pages: #{doc.pages.count}"
|
|
13
|
+
puts "Encrypted: #{!doc.trailer&.[](:Encrypt).nil?}"
|
|
14
|
+
puts "Title: #{doc.metadata[:Title]}" if doc.metadata[:Title]
|
|
15
|
+
puts "Author: #{doc.metadata[:Author]}" if doc.metadata[:Author]
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Uinspect
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Umerge
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Umodify
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Uoptimize
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Split
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, output_dir: nil)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
output_dir ||= File.dirname(path)
|
|
11
|
+
basename = File.basename(path, ".pdf")
|
|
12
|
+
doc.pages.each_with_index do |page, i|
|
|
13
|
+
single = Pdfrb::Document.new
|
|
14
|
+
single.pages.add(media_box: page.media_box)
|
|
15
|
+
single.write(File.join(output_dir, "#{basename}_#{i + 1}.pdf"))
|
|
16
|
+
end
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class CLI
|
|
5
|
+
module Uwatermark
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(path, **opts)
|
|
9
|
+
doc = Pdfrb::Document.open(path)
|
|
10
|
+
yield doc if block_given?
|
|
11
|
+
output = opts[:output] || path
|
|
12
|
+
doc.write(output)
|
|
13
|
+
puts "Processed: #{output}"
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/pdfrb/cli.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Pdfrb
|
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
desc "optimize INPUT OUTPUT", "Optimize
|
|
62
|
+
desc "optimize INPUT OUTPUT", "Optimize: dedup objects, pack into ObjStm, rewrite xref stream"
|
|
63
63
|
def optimize(input, output)
|
|
64
64
|
doc = open_doc(input)
|
|
65
65
|
Pdfrb::Task::Optimize.call(doc)
|
|
@@ -67,22 +67,74 @@ module Pdfrb
|
|
|
67
67
|
puts "Wrote #{output}"
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
desc "
|
|
70
|
+
desc "diff LEFT RIGHT", "Compare two PDFs semantically"
|
|
71
|
+
def diff(left, right)
|
|
72
|
+
report = Pdfrb::Compare.compare(
|
|
73
|
+
File.binread(left),
|
|
74
|
+
File.binread(right)
|
|
75
|
+
)
|
|
76
|
+
puts report.summary
|
|
77
|
+
unless report.equivalent?
|
|
78
|
+
puts
|
|
79
|
+
report.per_page_text_diffs.first(5).each do |d|
|
|
80
|
+
puts " Page #{d[:page] + 1}: #{(d[:similarity] * 100).round(1)}% similar"
|
|
81
|
+
end
|
|
82
|
+
unless report.font_diff[:added].empty? && report.font_diff[:removed].empty?
|
|
83
|
+
puts " Fonts added: #{report.font_diff[:added].join(', ')}" unless report.font_diff[:added].empty?
|
|
84
|
+
puts " Fonts removed: #{report.font_diff[:removed].join(', ')}" unless report.font_diff[:removed].empty?
|
|
85
|
+
end
|
|
86
|
+
puts " Page count delta: #{report.page_count_delta}" unless report.page_count_delta.zero?
|
|
87
|
+
exit 1
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
desc "encrypt INPUT OUTPUT --password PASSWORD", "Encrypt a PDF with RC4 or AES"
|
|
71
92
|
method_option :password, type: :string, required: true
|
|
93
|
+
method_option :owner_password, type: :string, default: nil
|
|
94
|
+
method_option :bits, type: :numeric, default: 128, banner: "40|128|256"
|
|
95
|
+
method_option :permissions, type: :string, default: "",
|
|
96
|
+
banner: "print,copy,modify,annotate,fill,extract,assemble,print-hq"
|
|
72
97
|
def encrypt(input, output)
|
|
73
|
-
warn "encrypt: not yet fully implemented (Phase 11 integration pending)"
|
|
74
|
-
# Copy through for now.
|
|
75
98
|
doc = open_doc(input)
|
|
99
|
+
bits = options[:bits]
|
|
100
|
+
password = options[:password]
|
|
101
|
+
owner_pw = options[:owner_password] || password
|
|
102
|
+
|
|
103
|
+
perms = parse_permissions(options[:permissions])
|
|
104
|
+
|
|
105
|
+
# Build the /Encrypt dictionary.
|
|
106
|
+
case bits
|
|
107
|
+
when 256
|
|
108
|
+
build_aes256_encrypt(doc, password, owner_pw, perms)
|
|
109
|
+
when 128
|
|
110
|
+
build_encrypt(doc, password, owner_pw, perms, v: 4, r: 4, length: 128)
|
|
111
|
+
when 40
|
|
112
|
+
build_encrypt(doc, password, owner_pw, perms, v: 2, r: 3, length: 40)
|
|
113
|
+
else
|
|
114
|
+
raise "unsupported key length: #{bits} (use 40, 128, or 256)"
|
|
115
|
+
end
|
|
116
|
+
|
|
76
117
|
doc.write(output)
|
|
77
|
-
puts "
|
|
118
|
+
puts "Encrypted #{output} (#{bits}-bit)"
|
|
78
119
|
end
|
|
79
120
|
|
|
80
|
-
desc "decrypt INPUT OUTPUT --password PASSWORD", "Decrypt a PDF"
|
|
121
|
+
desc "decrypt INPUT OUTPUT --password PASSWORD", "Decrypt a PDF (remove /Encrypt)"
|
|
81
122
|
method_option :password, type: :string, default: ""
|
|
82
123
|
def decrypt(input, output)
|
|
83
124
|
doc = open_doc(input)
|
|
125
|
+
|
|
126
|
+
# Strip /Encrypt from trailer and clear handler config so the
|
|
127
|
+
# Writer doesn't re-encrypt on output.
|
|
128
|
+
trailer = doc.trailer
|
|
129
|
+
if trailer && trailer[:Encrypt]
|
|
130
|
+
trailer.value.delete(:Encrypt)
|
|
131
|
+
doc.config.delete("encryption.handler")
|
|
132
|
+
doc.config.delete("encryption.password")
|
|
133
|
+
puts "Stripped encryption"
|
|
134
|
+
end
|
|
135
|
+
|
|
84
136
|
doc.write(output)
|
|
85
|
-
puts "
|
|
137
|
+
puts "Decrypted → #{output}"
|
|
86
138
|
end
|
|
87
139
|
|
|
88
140
|
desc "images-add INPUT IMAGE OUTPUT", "Add an image to a new page"
|
|
@@ -116,8 +168,261 @@ module Pdfrb
|
|
|
116
168
|
end
|
|
117
169
|
end
|
|
118
170
|
|
|
171
|
+
desc "split INPUT OUTPUT_PATTERN [FROM [TO]]", "Split a PDF into individual pages"
|
|
172
|
+
def split(input, output_pattern, from = nil, to = nil)
|
|
173
|
+
doc = open_doc(input)
|
|
174
|
+
total = doc.pages.count
|
|
175
|
+
from_idx = from ? from.to_i : 1
|
|
176
|
+
to_idx = to ? to.to_i : total
|
|
177
|
+
(from_idx..to_idx).each do |i|
|
|
178
|
+
target = Pdfrb::Document.new
|
|
179
|
+
Pdfrb::Importer.new(target).import(doc.pages[i - 1].value, doc)
|
|
180
|
+
out_path = format(output_pattern, i)
|
|
181
|
+
target.write(out_path)
|
|
182
|
+
end
|
|
183
|
+
puts "Split #{input} into #{to_idx - from_idx + 1} files"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
desc "watermark INPUT OUTPUT --text TEXT", "Stamp text on every page"
|
|
187
|
+
method_option :text, type: :string, required: true
|
|
188
|
+
method_option :font, type: :string, default: "Helvetica"
|
|
189
|
+
method_option :size, type: :numeric, default: 50
|
|
190
|
+
method_option :color, type: :string, default: "0.5,0.5,0.5"
|
|
191
|
+
method_option :opacity, type: :numeric, default: 0.3
|
|
192
|
+
method_option :angle, type: :numeric, default: 45
|
|
193
|
+
def watermark(input, output)
|
|
194
|
+
doc = open_doc(input)
|
|
195
|
+
color = options[:color].split(",").map(&:to_f)
|
|
196
|
+
doc.pages.each do |page|
|
|
197
|
+
canvas = page.canvas
|
|
198
|
+
doc.graphics_state.register_transparency(page, opacity: options[:opacity])
|
|
199
|
+
canvas.save_graphics_state
|
|
200
|
+
canvas.opacity = options[:opacity]
|
|
201
|
+
canvas.fill_color(color)
|
|
202
|
+
canvas.text(options[:text], at: [100, 400],
|
|
203
|
+
font: options[:font].to_sym,
|
|
204
|
+
size: options[:size])
|
|
205
|
+
canvas.restore_graphics_state
|
|
206
|
+
end
|
|
207
|
+
doc.write(output)
|
|
208
|
+
puts "Watermarked #{doc.pages.count} pages → #{output}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
desc "modify INPUT OUTPUT", "Apply modifications to a PDF"
|
|
212
|
+
method_option :delete, type: :string, banner: "PAGE_RANGE e.g. 1-3,5"
|
|
213
|
+
method_option :rotate, type: :numeric, banner: "ANGLE 0/90/180/270"
|
|
214
|
+
def modify(input, output)
|
|
215
|
+
doc = open_doc(input)
|
|
216
|
+
apply_delete(doc, options[:delete]) if options[:delete]
|
|
217
|
+
apply_rotate(doc, options[:rotate]) if options[:rotate]
|
|
218
|
+
doc.write(output)
|
|
219
|
+
puts "Modified → #{output}"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
desc "inspect INPUT", "Walk the PDF structure"
|
|
223
|
+
method_option :object, type: :numeric, banner: "OID to dump"
|
|
224
|
+
def inspect(input)
|
|
225
|
+
doc = open_doc(input)
|
|
226
|
+
if options[:object]
|
|
227
|
+
ref = Pdfrb::Model::Reference.new(options[:object], 0)
|
|
228
|
+
obj = doc.object(ref)
|
|
229
|
+
puts obj&.value
|
|
230
|
+
return
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
catalog = doc.catalog
|
|
234
|
+
puts "Catalog: /Type=#{catalog[:Type]}"
|
|
235
|
+
puts " Pages: #{doc.pages.count}"
|
|
236
|
+
puts " Title: #{doc.metadata.title || '-'}"
|
|
237
|
+
af = catalog[:AcroForm]
|
|
238
|
+
puts " AcroForm: #{af ? 'present' : 'absent'}"
|
|
239
|
+
outlines = catalog[:Outlines]
|
|
240
|
+
puts " Outlines: #{outlines ? 'present' : 'absent'}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
desc "files INPUT", "List embedded files in the document"
|
|
244
|
+
def files(input)
|
|
245
|
+
doc = open_doc(input)
|
|
246
|
+
if doc.files.empty?
|
|
247
|
+
puts "No embedded files."
|
|
248
|
+
return
|
|
249
|
+
end
|
|
250
|
+
doc.files.each do |name, spec|
|
|
251
|
+
size = spec[:EF] ? "embedded" : "external"
|
|
252
|
+
puts "#{name} (#{size})"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
desc "files-add INPUT OUTPUT FILE", "Embed a file into the document"
|
|
257
|
+
method_option :relationship, type: :string, default: nil
|
|
258
|
+
def files_add(input, output, file)
|
|
259
|
+
doc = open_doc(input)
|
|
260
|
+
doc.files.add(file, name: File.basename(file),
|
|
261
|
+
relationship: options[:relationship]&.to_sym)
|
|
262
|
+
doc.write(output)
|
|
263
|
+
puts "Embedded #{file} → #{output}"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
desc "batch INPUT OUTPUT COMMAND1 ARGS... -- COMMAND2 ARGS...",
|
|
267
|
+
"Run multiple operations on a single document"
|
|
268
|
+
def batch(input, output, *args)
|
|
269
|
+
doc = open_doc(input)
|
|
270
|
+
commands = args.split("--").reject(&:empty?)
|
|
271
|
+
commands.each do |cmd_args|
|
|
272
|
+
cmd, *cmd_args = cmd_args
|
|
273
|
+
apply_batch_command(doc, cmd, cmd_args)
|
|
274
|
+
end
|
|
275
|
+
doc.write(output)
|
|
276
|
+
puts "Batch processed → #{output}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
PERMISSION_BITS = {
|
|
280
|
+
"print" => 4,
|
|
281
|
+
"modify" => 8,
|
|
282
|
+
"copy" => 16,
|
|
283
|
+
"annotate" => 32,
|
|
284
|
+
"fill" => 256,
|
|
285
|
+
"extract" => 512,
|
|
286
|
+
"assemble" => 1024,
|
|
287
|
+
"print-hq" => 2048,
|
|
288
|
+
}.freeze
|
|
289
|
+
|
|
119
290
|
private
|
|
120
291
|
|
|
292
|
+
def parse_permissions(spec)
|
|
293
|
+
return -1 if spec.empty? || spec == "all"
|
|
294
|
+
|
|
295
|
+
granted = PERMISSION_BITS.values.sum
|
|
296
|
+
spec.split(",").map(&:strip).each do |perm|
|
|
297
|
+
bit = PERMISSION_BITS[perm]
|
|
298
|
+
granted &= ~bit if bit
|
|
299
|
+
end
|
|
300
|
+
granted
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def build_encrypt(doc, user_pw, owner_pw, perms, v:, r:, length:)
|
|
304
|
+
require "digest/md5"
|
|
305
|
+
|
|
306
|
+
id0 = Digest::MD5.hexdigest(Time.now.to_s + rand.to_s)[0, 16]
|
|
307
|
+
doc.trailer[:ID] = [id0.b, id0.b]
|
|
308
|
+
|
|
309
|
+
# Compute /O entry (owner password hash).
|
|
310
|
+
o_entry = compute_o_entry(user_pw, owner_pw, r, length)
|
|
311
|
+
|
|
312
|
+
# Derive encryption key from user password (Algorithm 2).
|
|
313
|
+
key = Pdfrb::Encryption::PasswordVerification.derive_key_rc4(
|
|
314
|
+
password: user_pw, o_entry: o_entry.b, p_flags: perms,
|
|
315
|
+
id0: id0.b, revision: r, key_length_bits: length,
|
|
316
|
+
encrypt_metadata: true
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Build /U entry (Algorithm 4/5).
|
|
320
|
+
u_entry = if r == 2
|
|
321
|
+
Pdfrb::Encryption::PasswordVerification.build_u_r2(key)
|
|
322
|
+
else
|
|
323
|
+
Pdfrb::Encryption::PasswordVerification.build_u_r3plus(
|
|
324
|
+
file_key: key, id0: id0.b, revision: r
|
|
325
|
+
)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
encrypt_dict = doc.add(
|
|
329
|
+
{ Filter: :Standard, V: v, R: r, Length: length, P: perms,
|
|
330
|
+
O: o_entry, U: u_entry },
|
|
331
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
332
|
+
)
|
|
333
|
+
doc.trailer[:Encrypt] = Pdfrb::Model::Reference.new(encrypt_dict.oid, encrypt_dict.gen)
|
|
334
|
+
|
|
335
|
+
# Set up the handler so Writer can encrypt per-object.
|
|
336
|
+
handler = Pdfrb::Encryption::StandardSecurityHandler.new(
|
|
337
|
+
Encrypt: { V: v, R: r, Length: length, P: perms, O: o_entry, U: u_entry },
|
|
338
|
+
ID: [id0.b, id0.b]
|
|
339
|
+
)
|
|
340
|
+
handler.verify_user_password(user_pw)
|
|
341
|
+
doc.config["encryption.handler"] = handler
|
|
342
|
+
doc.config["encryption.password"] = user_pw
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def build_aes256_encrypt(doc, user_pw, owner_pw, perms)
|
|
346
|
+
require "digest/sha2"
|
|
347
|
+
require "securerandom"
|
|
348
|
+
|
|
349
|
+
val_salt = SecureRandom.random_bytes(8)
|
|
350
|
+
key_salt = SecureRandom.random_bytes(8)
|
|
351
|
+
u_hash = Digest::SHA256.digest(user_pw + val_salt)
|
|
352
|
+
u_entry = u_hash + val_salt + key_salt
|
|
353
|
+
|
|
354
|
+
o_val_salt = SecureRandom.random_bytes(8)
|
|
355
|
+
o_key_salt = SecureRandom.random_bytes(8)
|
|
356
|
+
o_hash = Digest::SHA256.digest(owner_pw + o_val_salt + u_hash)
|
|
357
|
+
o_entry = o_hash + o_val_salt + o_key_salt
|
|
358
|
+
|
|
359
|
+
id0 = Digest::MD5.hexdigest(Time.now.to_s + rand.to_s)[0, 16]
|
|
360
|
+
doc.trailer[:ID] = [id0.b, id0.b]
|
|
361
|
+
|
|
362
|
+
encrypt_dict = doc.add(
|
|
363
|
+
{ Filter: :Standard, V: 5, R: 6, Length: 256, P: perms,
|
|
364
|
+
O: o_entry, U: u_entry, OE: "".b, UE: "".b },
|
|
365
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
366
|
+
)
|
|
367
|
+
doc.trailer[:Encrypt] = Pdfrb::Model::Reference.new(encrypt_dict.oid, encrypt_dict.gen)
|
|
368
|
+
doc.config["encryption.password"] = user_pw
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def compute_o_entry(user_pw, owner_pw, revision, length_bits)
|
|
372
|
+
Pdfrb::Encryption::PasswordVerification.pad_password(owner_pw)
|
|
373
|
+
require "digest/md5"
|
|
374
|
+
padded = Pdfrb::Encryption::PasswordVerification.pad_password(owner_pw)
|
|
375
|
+
hash = Digest::MD5.digest(padded)
|
|
376
|
+
|
|
377
|
+
if revision >= 3
|
|
378
|
+
50.times { hash = Digest::MD5.digest(hash[0, length_bits / 8]) }
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
rc4 = Pdfrb::Encryption::RC4.new(hash[0, length_bits / 8])
|
|
382
|
+
user_padded = Pdfrb::Encryption::PasswordVerification.pad_password(user_pw)
|
|
383
|
+
result = rc4.process(user_padded)
|
|
384
|
+
|
|
385
|
+
if revision >= 3
|
|
386
|
+
19.times do |i|
|
|
387
|
+
key = hash[0, length_bits / 8].bytes.map { |b| (b ^ i).chr }.join
|
|
388
|
+
result = Pdfrb::Encryption::RC4.new(key).process(result)
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
result + ("\x00".b * [0, 32 - result.bytesize].max)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def apply_delete(doc, range_spec)
|
|
396
|
+
ranges = range_spec.split(",").map { |r| parse_range(r, doc.pages.count) }
|
|
397
|
+
oids_to_delete = []
|
|
398
|
+
ranges.each do |(from, to)|
|
|
399
|
+
(from..to).each { |i| oids_to_delete << doc.pages[i - 1].oid }
|
|
400
|
+
end
|
|
401
|
+
oids_to_delete.uniq.each { |oid| doc.pages.delete_at(oid) }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def apply_rotate(doc, angle)
|
|
405
|
+
doc.pages.each { |page| page.value[:Rotate] = angle }
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def parse_range(spec, _total)
|
|
409
|
+
if spec.include?("-")
|
|
410
|
+
from, to = spec.split("-").map(&:to_i)
|
|
411
|
+
[from, to]
|
|
412
|
+
else
|
|
413
|
+
n = spec.to_i
|
|
414
|
+
[n, n]
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def apply_batch_command(doc, cmd, args)
|
|
419
|
+
case cmd
|
|
420
|
+
when "rotate" then apply_rotate(doc, args.first.to_i)
|
|
421
|
+
when "delete" then apply_delete(doc, args.first)
|
|
422
|
+
else warn "unknown batch command: #{cmd}"
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
121
426
|
def open_doc(path)
|
|
122
427
|
raise "no such file: #{path}" unless File.exist?(path)
|
|
123
428
|
|