pdfrb 0.3.0 → 0.7.1
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 +51 -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 +61 -0
- data/data/pdfrb/afm/Courier-Bold.afm +342 -342
- data/data/pdfrb/afm/Courier-BoldOblique.afm +342 -342
- data/data/pdfrb/afm/Courier-Oblique.afm +342 -342
- data/data/pdfrb/afm/Courier.afm +342 -342
- data/data/pdfrb/afm/Helvetica-Bold.afm +2827 -2827
- data/data/pdfrb/afm/Helvetica-BoldOblique.afm +2827 -2827
- data/data/pdfrb/afm/Helvetica-Oblique.afm +3051 -3051
- data/data/pdfrb/afm/Helvetica.afm +3051 -3051
- data/data/pdfrb/afm/Symbol.afm +213 -213
- data/data/pdfrb/afm/Times-Bold.afm +2588 -2588
- data/data/pdfrb/afm/Times-BoldItalic.afm +2384 -2384
- data/data/pdfrb/afm/Times-Italic.afm +2667 -2667
- data/data/pdfrb/afm/Times-Roman.afm +2419 -2419
- data/data/pdfrb/afm/ZapfDingbats.afm +225 -225
- 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 +354 -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/inline_image.rb +53 -0
- data/lib/pdfrb/content/operators.rb +22 -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 +82 -251
- 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_piece_info.rb +58 -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 +289 -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/regenerate_appearances.rb +98 -0
- data/lib/pdfrb/task.rb +4 -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 +319 -4
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Facade for AcroForm interactive forms (ISO 32000-2 §12.7).
|
|
6
|
+
#
|
|
7
|
+
# Creates the /AcroForm dictionary on the Catalog and manages
|
|
8
|
+
# form fields. Each field is a /Type /Annot /Subtype /Widget
|
|
9
|
+
# indirect object linked to a page via /P and positioned via /Rect.
|
|
10
|
+
#
|
|
11
|
+
# Supported field types (FT):
|
|
12
|
+
# :Btn — button (checkbox, radio, pushbutton)
|
|
13
|
+
# :Tx — text input
|
|
14
|
+
# :Ch — choice (list, combo)
|
|
15
|
+
# :Sig — digital signature
|
|
16
|
+
#
|
|
17
|
+
# Appearance streams are generated by the viewer when /NeedAppearances
|
|
18
|
+
# is true on the /AcroForm dict.
|
|
19
|
+
class Form
|
|
20
|
+
attr_reader :document
|
|
21
|
+
|
|
22
|
+
def initialize(document)
|
|
23
|
+
@document = document
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Enable AcroForm by creating the /AcroForm dict on the Catalog.
|
|
27
|
+
# Idempotent. @return [Hash] the AcroForm dict value.
|
|
28
|
+
def enable!
|
|
29
|
+
catalog = document.catalog
|
|
30
|
+
return catalog.value[:AcroForm] if catalog.value[:AcroForm]
|
|
31
|
+
|
|
32
|
+
acroform = {
|
|
33
|
+
Fields: [],
|
|
34
|
+
NeedAppearances: true,
|
|
35
|
+
DA: "/Helv 0 Tf 0 g",
|
|
36
|
+
}
|
|
37
|
+
catalog.value[:AcroForm] = acroform
|
|
38
|
+
acroform
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Add a text field. @return [Pdfrb::Model::Cos::Dictionary] the field.
|
|
42
|
+
def add_text_field(name, page:, rect:, value: nil, multiline: false)
|
|
43
|
+
enable!
|
|
44
|
+
flags = 0
|
|
45
|
+
flags |= 0x1000 if multiline # bit 13 = multiline
|
|
46
|
+
|
|
47
|
+
field = build_widget(name, page: page, rect: rect)
|
|
48
|
+
field.value[:FT] = :Tx
|
|
49
|
+
field.value[:Ff] = flags if flags.positive?
|
|
50
|
+
field.value[:V] = value if value
|
|
51
|
+
register_field(field)
|
|
52
|
+
field
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add a checkbox field. @return [Pdfrb::Model::Cos::Dictionary] the field.
|
|
56
|
+
def add_checkbox(name, page:, rect:, checked: false)
|
|
57
|
+
enable!
|
|
58
|
+
field = build_widget(name, page: page, rect: rect)
|
|
59
|
+
field.value[:FT] = :Btn
|
|
60
|
+
field.value[:V] = checked ? :Yes : :Off
|
|
61
|
+
register_field(field)
|
|
62
|
+
field
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Add a combo box (dropdown) field.
|
|
66
|
+
# @return [Pdfrb::Model::Cos::Dictionary] the field.
|
|
67
|
+
def add_combo(name, page:, rect:, options:, value: nil)
|
|
68
|
+
enable!
|
|
69
|
+
field = build_widget(name, page: page, rect: rect)
|
|
70
|
+
field.value[:FT] = :Ch
|
|
71
|
+
field.value[:Ff] = 0x20000 # bit 18 = Combo
|
|
72
|
+
field.value[:Opt] = options
|
|
73
|
+
field.value[:V] = value if value
|
|
74
|
+
register_field(field)
|
|
75
|
+
field
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Total field count.
|
|
79
|
+
def count
|
|
80
|
+
acroform = document.catalog.value[:AcroForm]
|
|
81
|
+
return 0 unless acroform
|
|
82
|
+
|
|
83
|
+
fields = acroform[:Fields]
|
|
84
|
+
fields ? fields.length : 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Find a field by fully qualified name.
|
|
88
|
+
def find(name)
|
|
89
|
+
each_field.find { |f| f.value[:T] == name }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Enumerate all top-level fields.
|
|
93
|
+
def each_field(&block)
|
|
94
|
+
acroform = document.catalog.value[:AcroForm]
|
|
95
|
+
return enum_for(:each_field) unless block
|
|
96
|
+
|
|
97
|
+
return unless acroform
|
|
98
|
+
|
|
99
|
+
fields = acroform[:Fields] || []
|
|
100
|
+
fields.each do |ref|
|
|
101
|
+
obj = ref.is_a?(Pdfrb::Model::Reference) ? document.object(ref) : ref
|
|
102
|
+
yield obj if obj
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# List of fully qualified field names (T values).
|
|
107
|
+
def field_names
|
|
108
|
+
each_field.filter_map { |f| f.value[:T] }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Set a field value by name. Auto-regenerates the appearance stream
|
|
112
|
+
# for text/checkbox fields. For unsupported types, sets /V only.
|
|
113
|
+
# @param name [String] the field's /T value.
|
|
114
|
+
# @param value [Object] the new /V value.
|
|
115
|
+
# @return [Pdfrb::Model::Cos::Dictionary, nil] the field if found.
|
|
116
|
+
def set_value(name, value)
|
|
117
|
+
field = find(name)
|
|
118
|
+
return nil unless field
|
|
119
|
+
|
|
120
|
+
field.value[:V] = normalize_value(field, value)
|
|
121
|
+
regenerate_appearance(field)
|
|
122
|
+
field
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get a field value by name.
|
|
126
|
+
# @param name [String] the field's /T value.
|
|
127
|
+
# @return [Object, nil] the /V value, or nil if not found.
|
|
128
|
+
def get_value(name)
|
|
129
|
+
field = find(name)
|
|
130
|
+
return nil unless field
|
|
131
|
+
|
|
132
|
+
field.value[:V]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Flatten all form fields: stamp their appearance streams into page
|
|
136
|
+
# content, then remove the field from /AcroForm /Fields. After
|
|
137
|
+
# flattening, the form is read-only.
|
|
138
|
+
def flatten!
|
|
139
|
+
each_field.to_a.each do |field|
|
|
140
|
+
stamp_field_appearance(field)
|
|
141
|
+
remove_field(field)
|
|
142
|
+
end
|
|
143
|
+
acroform = document.catalog.value[:AcroForm]
|
|
144
|
+
delete_acroform_key(acroform, :Fields)
|
|
145
|
+
delete_acroform_key(acroform, :NeedAppearances)
|
|
146
|
+
document
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def delete_acroform_key(acroform, key)
|
|
150
|
+
return unless acroform
|
|
151
|
+
|
|
152
|
+
if acroform.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
153
|
+
acroform.value.delete(key)
|
|
154
|
+
else
|
|
155
|
+
acroform.delete(key)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Remove a single field from /AcroForm /Fields and its widget from
|
|
160
|
+
# the page's /Annots array.
|
|
161
|
+
def remove_field(field)
|
|
162
|
+
acroform = document.catalog.value[:AcroForm]
|
|
163
|
+
return nil unless acroform
|
|
164
|
+
|
|
165
|
+
fields = acroform[:Fields]
|
|
166
|
+
return nil unless fields
|
|
167
|
+
|
|
168
|
+
field_ref = Pdfrb::Model::Reference.new(field.oid, field.gen)
|
|
169
|
+
fields.delete_if { |r| r == field_ref || (r.is_a?(Pdfrb::Model::Reference) && r.oid == field.oid) }
|
|
170
|
+
|
|
171
|
+
# Remove widget annotation from page /Annots
|
|
172
|
+
page_ref = field.value[:P]
|
|
173
|
+
if page_ref
|
|
174
|
+
page = page_ref.is_a?(Pdfrb::Model::Reference) ? document.object(page_ref) : page_ref
|
|
175
|
+
annots = page&.value&.[](:Annots)
|
|
176
|
+
annots&.delete_if { |r| r == field_ref || (r.is_a?(Pdfrb::Model::Reference) && r.oid == field.oid) }
|
|
177
|
+
end
|
|
178
|
+
field
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
def normalize_value(field, value)
|
|
184
|
+
case field.value[:FT]&.to_sym
|
|
185
|
+
when :Btn
|
|
186
|
+
# Buttons accept :Yes/:Off or boolean. Normalize to symbol.
|
|
187
|
+
if [true, :Yes].include?(value)
|
|
188
|
+
:Yes
|
|
189
|
+
else
|
|
190
|
+
([false, :Off].include?(value) ? :Off : value)
|
|
191
|
+
end
|
|
192
|
+
when :Ch
|
|
193
|
+
# Choices: validate against /Opt if present.
|
|
194
|
+
value
|
|
195
|
+
else
|
|
196
|
+
# Text and signature: accept strings.
|
|
197
|
+
value.to_s
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def regenerate_appearance(field)
|
|
202
|
+
ft = field.value[:FT]&.to_sym
|
|
203
|
+
generator = Pdfrb::Appearance::Generator.new(document)
|
|
204
|
+
case ft
|
|
205
|
+
when :Tx
|
|
206
|
+
generator.text_field(field, value: field.value[:V].to_s)
|
|
207
|
+
when :Btn
|
|
208
|
+
checked = field.value[:V] == :Yes
|
|
209
|
+
generator.checkbox(field, checked: checked)
|
|
210
|
+
end
|
|
211
|
+
rescue StandardError
|
|
212
|
+
# Appearance generation is best-effort; viewer can regenerate.
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def stamp_field_appearance(field)
|
|
216
|
+
form_stream = appearance_form_stream(field)
|
|
217
|
+
return unless form_stream
|
|
218
|
+
|
|
219
|
+
page_ref = field.value[:P]
|
|
220
|
+
return unless page_ref
|
|
221
|
+
|
|
222
|
+
page = page_ref.is_a?(Pdfrb::Model::Reference) ? document.object(page_ref) : page_ref
|
|
223
|
+
return unless page
|
|
224
|
+
|
|
225
|
+
name = register_form_on_page(page, form_stream)
|
|
226
|
+
rect = field.value[:Rect]
|
|
227
|
+
origin = rect ? [rect[0], rect[1]] : [0, 0]
|
|
228
|
+
emit_do_on_page(page, name, origin)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Resolve the Form XObject stream that backs the field's current
|
|
232
|
+
# appearance. Handles single-state (/AP /N -> ref) and multi-state
|
|
233
|
+
# (/AP /N -> {state -> ref}, picked from /AS).
|
|
234
|
+
def appearance_form_stream(field)
|
|
235
|
+
ap = field.value[:AP]
|
|
236
|
+
return nil unless ap
|
|
237
|
+
|
|
238
|
+
n = dictionary_get(ap, :N)
|
|
239
|
+
return nil unless n
|
|
240
|
+
|
|
241
|
+
target = if dictionary?(n)
|
|
242
|
+
state = field.value[:AS]
|
|
243
|
+
dictionary_get(n, state) ||
|
|
244
|
+
dictionary_get(n, :Normal) ||
|
|
245
|
+
dictionary_values(n).first
|
|
246
|
+
else
|
|
247
|
+
n
|
|
248
|
+
end
|
|
249
|
+
return nil unless target
|
|
250
|
+
|
|
251
|
+
target.is_a?(Pdfrb::Model::Reference) ? document.object(target) : target
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def dictionary?(value)
|
|
255
|
+
value.is_a?(::Hash) || value.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def dictionary_get(dict, key)
|
|
259
|
+
return nil unless key
|
|
260
|
+
|
|
261
|
+
if dict.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
262
|
+
dict.value[key]
|
|
263
|
+
else
|
|
264
|
+
dict[key]
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def dictionary_values(dict)
|
|
269
|
+
if dict.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
270
|
+
dict.value.values
|
|
271
|
+
else
|
|
272
|
+
dict.values
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def register_form_on_page(page, form_stream)
|
|
277
|
+
resources = page.value[:Resources]
|
|
278
|
+
resources = page.value[:Resources] = Pdfrb::Model::Cos::Dictionary.new({}) unless resources.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
279
|
+
xobjects = resources.value[:XObject] || {}
|
|
280
|
+
name = unique_xobject_name(xobjects)
|
|
281
|
+
xobjects[name] = Pdfrb::Model::Reference.new(form_stream.oid, form_stream.gen)
|
|
282
|
+
resources.value[:XObject] = xobjects
|
|
283
|
+
name
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def unique_xobject_name(existing)
|
|
287
|
+
counter = (existing.keys.map { |k| k.to_s[/\d+\z/].to_i }.max || 0) + 1
|
|
288
|
+
:"Fm#{counter}"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def emit_do_on_page(page, name, at)
|
|
292
|
+
canvas = page.canvas
|
|
293
|
+
canvas.save_graphics_state
|
|
294
|
+
canvas.translate(at[0], at[1]) if at[0] != 0 || at[1] != 0
|
|
295
|
+
canvas.emit_op(Pdfrb::Content::Operator::InvokeXObject, name)
|
|
296
|
+
canvas.restore_graphics_state
|
|
297
|
+
rescue StandardError
|
|
298
|
+
# Pages without a content stream or with malformed resources
|
|
299
|
+
# cannot be stamped; flattening still removes the widget.
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def build_widget(name, page:, rect:)
|
|
303
|
+
widget = document.add(
|
|
304
|
+
{
|
|
305
|
+
Type: :Annot,
|
|
306
|
+
Subtype: :Widget,
|
|
307
|
+
T: name,
|
|
308
|
+
Rect: rect,
|
|
309
|
+
P: Pdfrb::Model::Reference.new(page.oid, page.gen),
|
|
310
|
+
FT: :Tx,
|
|
311
|
+
},
|
|
312
|
+
type: Pdfrb::Model::Type::Annotation
|
|
313
|
+
)
|
|
314
|
+
add_annot_to_page(page, widget)
|
|
315
|
+
widget
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def add_annot_to_page(page, widget)
|
|
319
|
+
annots = page.value[:Annots]
|
|
320
|
+
ref = Pdfrb::Model::Reference.new(widget.oid, widget.gen)
|
|
321
|
+
if annots.nil?
|
|
322
|
+
page.value[:Annots] = [ref]
|
|
323
|
+
elsif annots.is_a?(::Array)
|
|
324
|
+
annots << ref
|
|
325
|
+
else
|
|
326
|
+
page.value[:Annots] = [annots, ref]
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def register_field(field)
|
|
331
|
+
acroform = document.catalog.value[:AcroForm]
|
|
332
|
+
fields = acroform[:Fields]
|
|
333
|
+
ref = Pdfrb::Model::Reference.new(field.oid, field.gen)
|
|
334
|
+
fields << ref
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Facade for managing ExtGState (graphics state) entries on pages.
|
|
6
|
+
# Each page can reference a /ExtGState sub-dict in /Resources that
|
|
7
|
+
# holds named graphics-state parameter sets (transparency, line
|
|
8
|
+
# width, overprint, etc.).
|
|
9
|
+
class GraphicsState
|
|
10
|
+
attr_reader :document
|
|
11
|
+
|
|
12
|
+
def initialize(document)
|
|
13
|
+
@document = document
|
|
14
|
+
@next_gs_number = 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Register an ExtGState parameter dict on a page and return the
|
|
18
|
+
# resource name (e.g., :GS1).
|
|
19
|
+
#
|
|
20
|
+
# @param page [Pdfrb::Model::Type::Page] target page.
|
|
21
|
+
# @param params [Hash] graphics state parameters (e.g.,
|
|
22
|
+
# { CA: 0.5, ca: 0.5 } for 50% stroke + fill alpha).
|
|
23
|
+
# @return [Symbol] resource name.
|
|
24
|
+
def register(page, **params)
|
|
25
|
+
gs = document.add({ Type: :ExtGState, **params },
|
|
26
|
+
type: Pdfrb::Model::Type::GraphicsStateParameter)
|
|
27
|
+
ref = Pdfrb::Model::Reference.new(gs.oid, gs.gen)
|
|
28
|
+
|
|
29
|
+
resources = ensure_resources(page)
|
|
30
|
+
ext_gstate = resources.value[:ExtGState] || {}
|
|
31
|
+
name = gs_name
|
|
32
|
+
ext_gstate[name] = ref
|
|
33
|
+
resources.value[:ExtGState] = ext_gstate
|
|
34
|
+
name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Convenience: register a transparency state.
|
|
38
|
+
def register_transparency(page, opacity:, blend_mode: nil)
|
|
39
|
+
params = { ca: opacity, CA: opacity }
|
|
40
|
+
params[:BM] = blend_mode if blend_mode
|
|
41
|
+
register(page, **params)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convenience: register a line-width state.
|
|
45
|
+
def register_line_width(page, width:, line_cap: nil, line_join: nil)
|
|
46
|
+
params = { LW: width }
|
|
47
|
+
params[:LC] = line_cap if line_cap
|
|
48
|
+
params[:LJ] = line_join if line_join
|
|
49
|
+
register(page, **params)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Enumerate ExtGState entries on a page.
|
|
53
|
+
def each(page, &block)
|
|
54
|
+
return enum_for(:each, page) unless block
|
|
55
|
+
|
|
56
|
+
resources = page_resources(page)
|
|
57
|
+
return unless resources
|
|
58
|
+
|
|
59
|
+
ext = resources.value[:ExtGState]
|
|
60
|
+
return unless ext
|
|
61
|
+
|
|
62
|
+
ext.each(&block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def count(page)
|
|
66
|
+
each(page).count
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def ensure_resources(page)
|
|
72
|
+
resources = page.value[:Resources]
|
|
73
|
+
return resources if resources.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
74
|
+
|
|
75
|
+
wrapped = Pdfrb::Model::Cos::Dictionary.new(resources || {})
|
|
76
|
+
page.value[:Resources] = wrapped
|
|
77
|
+
wrapped
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def page_resources(page)
|
|
81
|
+
page.value[:Resources]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def gs_name
|
|
85
|
+
name = :"GS#{@next_gs_number}"
|
|
86
|
+
@next_gs_number += 1
|
|
87
|
+
name
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -18,8 +18,8 @@ module Pdfrb
|
|
|
18
18
|
# Build an Image XObject from +io+ (path, IO, or bytes) and
|
|
19
19
|
# register it in the document's /Resources/XObject. Returns the
|
|
20
20
|
# resource name (e.g. :Im1).
|
|
21
|
-
def add(io, **
|
|
22
|
-
image = Pdfrb::ImageLoader.load(document, io, **
|
|
21
|
+
def add(io, **)
|
|
22
|
+
image = Pdfrb::ImageLoader.load(document, io, **)
|
|
23
23
|
name = next_resource_name
|
|
24
24
|
attach_to_resources(name, image)
|
|
25
25
|
registry[name] = image
|
|
@@ -30,10 +30,10 @@ module Pdfrb
|
|
|
30
30
|
registry[name]
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def each(&
|
|
33
|
+
def each(&)
|
|
34
34
|
return enum_for(:each) unless block_given?
|
|
35
35
|
|
|
36
|
-
registry.each(&
|
|
36
|
+
registry.each(&)
|
|
37
37
|
self
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Document Info dictionary facade (PDF 1.x metadata on /Info).
|
|
6
|
+
# Also bridges to XMP packet so both metadata representations stay
|
|
7
|
+
# in sync. PDF 2.0 deprecates /Info in favor of XMP-only, but
|
|
8
|
+
# /Info is still required for backward compatibility.
|
|
9
|
+
class Info
|
|
10
|
+
attr_reader :document
|
|
11
|
+
|
|
12
|
+
def initialize(document)
|
|
13
|
+
@document = document
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
FIELD_MAP = {
|
|
17
|
+
title: :Title,
|
|
18
|
+
author: :Author,
|
|
19
|
+
subject: :Subject,
|
|
20
|
+
keywords: :Keywords,
|
|
21
|
+
creator: :Creator,
|
|
22
|
+
producer: :Producer,
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
FIELD_MAP.each do |method_name, pdf_key|
|
|
26
|
+
define_method(method_name) do
|
|
27
|
+
info_dict[pdf_key]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
define_method("#{method_name}=") do |value|
|
|
31
|
+
set_info_field(pdf_key, value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def creation_date
|
|
36
|
+
info_dict[:CreationDate]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def creation_date=(value)
|
|
40
|
+
set_info_field(:CreationDate, pdf_date(value))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mod_date
|
|
44
|
+
info_dict[:ModDate]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def mod_date=(value)
|
|
48
|
+
set_info_field(:ModDate, pdf_date(value))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def trapped
|
|
52
|
+
info_dict[:Trapped]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def trapped=(value)
|
|
56
|
+
set_info_field(:Trapped, value)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns the /Info dictionary object (creating one if needed).
|
|
60
|
+
def info_dict
|
|
61
|
+
info_ref = document.trailer && document.trailer[:Info]
|
|
62
|
+
return document.object(info_ref) if info_ref
|
|
63
|
+
|
|
64
|
+
info = document.add({}, type: Pdfrb::Model::Cos::Dictionary)
|
|
65
|
+
if document.trailer
|
|
66
|
+
document.trailer[:Info] = Pdfrb::Model::Reference.new(info.oid, info.gen)
|
|
67
|
+
end
|
|
68
|
+
info
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def set_info_field(field, value)
|
|
74
|
+
return if value.nil?
|
|
75
|
+
|
|
76
|
+
dict = info_dict
|
|
77
|
+
stored = field == :Trapped ? value : value.to_s
|
|
78
|
+
dict.value[field] = stored
|
|
79
|
+
sync_xmp(field, value)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def sync_xmp(field, value)
|
|
83
|
+
case field
|
|
84
|
+
when :Title then document.xmp.title = value
|
|
85
|
+
when :Author then document.xmp.creator = value
|
|
86
|
+
when :Subject then document.xmp.description = value
|
|
87
|
+
when :Keywords then document.xmp.keywords = value
|
|
88
|
+
when :Creator then document.xmp.creator_tool = value
|
|
89
|
+
when :Producer then document.xmp.producer = value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def pdf_date(time)
|
|
94
|
+
return time.to_s if time.is_a?(::String)
|
|
95
|
+
|
|
96
|
+
t = time.is_a?(::Time) ? time : Time.now
|
|
97
|
+
utc = t.utc
|
|
98
|
+
"D:#{utc.strftime('%Y%m%d%H%M%S')}+00'00'"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Facade for Optional Content Groups (layers). OCGs let authors
|
|
6
|
+
# mark content as belonging to a named layer that can be toggled
|
|
7
|
+
# on/off by the viewer (ISO 32000-2 §8.11).
|
|
8
|
+
#
|
|
9
|
+
# Each layer is a /Type /OCG dictionary with a /Name. The
|
|
10
|
+
# Catalog gets an /OCProperties entry:
|
|
11
|
+
# /OCGs — array of all OCG indirect refs.
|
|
12
|
+
# /D — default viewing config:
|
|
13
|
+
# /BaseState /ON, /OFF [list of off-by-default OCGs], etc.
|
|
14
|
+
class Layers
|
|
15
|
+
attr_reader :document, :groups
|
|
16
|
+
|
|
17
|
+
def initialize(document)
|
|
18
|
+
@document = document
|
|
19
|
+
@groups = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Add a named layer. @return [Pdfrb::Model::Cos::Dictionary] the OCG.
|
|
23
|
+
def add(name, default_on: true)
|
|
24
|
+
ensure_oc_properties
|
|
25
|
+
ocg = document.add(
|
|
26
|
+
{ Type: :OCG, Name: name },
|
|
27
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
28
|
+
)
|
|
29
|
+
ref = Pdfrb::Model::Reference.new(ocg.oid, ocg.gen)
|
|
30
|
+
@groups << { ref: ref, ocg: ocg, default_on: default_on }
|
|
31
|
+
ocg
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Write /OCProperties to the Catalog. Called automatically by #add
|
|
35
|
+
# but can be called again to refresh after external modifications.
|
|
36
|
+
def sync!
|
|
37
|
+
return if @groups.empty?
|
|
38
|
+
|
|
39
|
+
ocgs_array = @groups.map { |g| g[:ref] }
|
|
40
|
+
off_array = @groups.reject { |g| g[:default_on] }.map { |g| g[:ref] }
|
|
41
|
+
|
|
42
|
+
default_config = { BaseState: :ON }
|
|
43
|
+
default_config[:OFF] = off_array unless off_array.empty?
|
|
44
|
+
|
|
45
|
+
catalog = document.catalog
|
|
46
|
+
catalog.value[:OCProperties] = {
|
|
47
|
+
OCGs: ocgs_array,
|
|
48
|
+
D: default_config,
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def ensure_oc_properties
|
|
55
|
+
catalog = document.catalog
|
|
56
|
+
return if catalog.value[:OCProperties]
|
|
57
|
+
|
|
58
|
+
catalog.value[:OCProperties] = { OCGs: [], D: { BaseState: :ON } }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Pdfrb
|
|
4
4
|
class Document
|
|
5
|
-
# Document metadata facade. Reads/writes Title, Author, Subject,
|
|
6
|
-
# Keywords, Creator, Producer, CreationDate, ModDate via the
|
|
7
|
-
# trailer's /Info dict (PDF 1.x) and prefers /Catalog/Metadata
|
|
8
|
-
# XMP for PDF 2.0 per App Note 003.
|
|
9
5
|
class Metadata
|
|
10
6
|
STANDARD_FIELDS = %i[Title Author Subject Keywords Creator
|
|
11
7
|
Producer CreationDate ModDate Trapped].freeze
|
|
@@ -29,6 +25,17 @@ module Pdfrb
|
|
|
29
25
|
write_field(field, value)
|
|
30
26
|
end
|
|
31
27
|
|
|
28
|
+
def to_xmp
|
|
29
|
+
begin
|
|
30
|
+
packet = Pdfrb::XMP::Packet.new
|
|
31
|
+
rescue StandardError
|
|
32
|
+
return ""
|
|
33
|
+
end
|
|
34
|
+
title = read_field(:Title)
|
|
35
|
+
packet.title = title if title
|
|
36
|
+
packet.to_xmp
|
|
37
|
+
end
|
|
38
|
+
|
|
32
39
|
private
|
|
33
40
|
|
|
34
41
|
def read_field(field)
|
|
@@ -54,7 +61,7 @@ module Pdfrb
|
|
|
54
61
|
obj = ref ? document.object(ref) : nil
|
|
55
62
|
|
|
56
63
|
if obj.nil? && create
|
|
57
|
-
obj = document.add({
|
|
64
|
+
obj = document.add({}, type: Pdfrb::Model::Type::Info)
|
|
58
65
|
document.trailer[:Info] = Pdfrb::Model::Reference.new(obj.oid, obj.gen)
|
|
59
66
|
end
|
|
60
67
|
@cached_info = obj
|