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
|
@@ -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
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Output Intents facade. An output intent declares the intended
|
|
6
|
+
# output device color profile (ICC) so that downstream processors
|
|
7
|
+
# can render colors consistently. Required for PDF/X.
|
|
8
|
+
#
|
|
9
|
+
# /OutputIntents is an array on the Catalog. Each entry has:
|
|
10
|
+
# /Type /OutputIntent
|
|
11
|
+
# /S /GTS_PDFA1 (for PDF/A) or /GTS_PDFX (for PDF/X)
|
|
12
|
+
# /OutputConditionIdentifier — registry name (e.g. "FOGRA39")
|
|
13
|
+
# /OutputCondition — human-readable description
|
|
14
|
+
# /RegistryName — e.g. "http://www.color.org"
|
|
15
|
+
# /DestOutputProfile — ICC profile stream reference
|
|
16
|
+
class OutputIntents
|
|
17
|
+
attr_reader :document
|
|
18
|
+
|
|
19
|
+
def initialize(document)
|
|
20
|
+
@document = document
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Add an output intent referencing an ICC profile stream.
|
|
24
|
+
# @param icc_stream_ref [Pdfrb::Model::Reference] ICC profile stream.
|
|
25
|
+
# @param identifier [String] e.g. "FOGRA39", "CGATS TR 001".
|
|
26
|
+
# @param condition [String] human-readable description.
|
|
27
|
+
# @param registry [String] URL of the color registry.
|
|
28
|
+
# @param subtype [Symbol] :GTS_PDFA1 or :GTS_PDFX.
|
|
29
|
+
# @return [Pdfrb::Model::Cos::Dictionary] the output intent dict.
|
|
30
|
+
def add(icc_stream_ref, identifier:, condition: nil,
|
|
31
|
+
registry: nil, subtype: :GTS_PDFX)
|
|
32
|
+
intent = document.add(
|
|
33
|
+
{
|
|
34
|
+
Type: :OutputIntent,
|
|
35
|
+
S: subtype,
|
|
36
|
+
OutputConditionIdentifier: identifier,
|
|
37
|
+
OutputCondition: condition,
|
|
38
|
+
RegistryName: registry,
|
|
39
|
+
DestOutputProfile: icc_stream_ref,
|
|
40
|
+
}.compact,
|
|
41
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
42
|
+
)
|
|
43
|
+
catalog = document.catalog
|
|
44
|
+
intents = catalog.value[:OutputIntents]
|
|
45
|
+
ref = Pdfrb::Model::Reference.new(intent.oid, intent.gen)
|
|
46
|
+
if intents.nil?
|
|
47
|
+
catalog.value[:OutputIntents] = [ref]
|
|
48
|
+
elsif intents.is_a?(::Array)
|
|
49
|
+
intents << ref
|
|
50
|
+
else
|
|
51
|
+
catalog.value[:OutputIntents] = [intents, ref]
|
|
52
|
+
end
|
|
53
|
+
intent
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Convenience: embed an ICC profile and register as output intent.
|
|
57
|
+
def embed_icc(icc_bytes, identifier:, condition: nil,
|
|
58
|
+
registry: nil, subtype: :GTS_PDFX)
|
|
59
|
+
profile = Pdfrb::Color::ICCProfile.new(icc_bytes)
|
|
60
|
+
stream = document.add(
|
|
61
|
+
{ **profile.stream_dictionary_fields,
|
|
62
|
+
Length: profile.raw_data.bytesize },
|
|
63
|
+
type: Pdfrb::Model::Cos::Stream
|
|
64
|
+
)
|
|
65
|
+
stream.stream = profile.raw_data
|
|
66
|
+
ref = Pdfrb::Model::Reference.new(stream.oid, stream.gen)
|
|
67
|
+
add(ref, identifier: identifier, condition: condition, registry: registry, subtype: subtype)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Enumerate output intents.
|
|
71
|
+
def each(&block)
|
|
72
|
+
intents = document.catalog.value[:OutputIntents]
|
|
73
|
+
return enum_for(:each) unless block
|
|
74
|
+
return self unless intents
|
|
75
|
+
|
|
76
|
+
intents.each do |ref|
|
|
77
|
+
obj = ref.is_a?(Pdfrb::Model::Reference) ? document.object(ref) : ref
|
|
78
|
+
yield obj if obj
|
|
79
|
+
end
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def count
|
|
84
|
+
intents = document.catalog.value[:OutputIntents]
|
|
85
|
+
return 0 unless intents
|
|
86
|
+
|
|
87
|
+
intents.is_a?(::Array) ? intents.length : 1
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Page Labels facade. Generates the /PageLabels number tree on the
|
|
6
|
+
# Catalog for custom page numbering (roman numerals, prefixed
|
|
7
|
+
# arabic, etc.).
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# doc.page_labels.set do
|
|
11
|
+
# style :roman, count: 5 # i, ii, iii, iv, v
|
|
12
|
+
# style :decimal, prefix: "A-" # A-1, A-2, ...
|
|
13
|
+
# end
|
|
14
|
+
class PageLabels
|
|
15
|
+
STYLES = {
|
|
16
|
+
decimal: :D,
|
|
17
|
+
roman_lower: :r,
|
|
18
|
+
roman_upper: :R,
|
|
19
|
+
alpha_lower: :a,
|
|
20
|
+
alpha_upper: :A,
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
LabelRule = Struct.new(:style, :prefix, :start, :page_count, keyword_init: true)
|
|
24
|
+
|
|
25
|
+
attr_reader :document, :rules
|
|
26
|
+
|
|
27
|
+
def initialize(document)
|
|
28
|
+
@document = document
|
|
29
|
+
@rules = []
|
|
30
|
+
@current_index = 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Add a page labeling rule for the next batch of pages.
|
|
34
|
+
# @param style [Symbol] :decimal, :roman_lower, :roman_upper,
|
|
35
|
+
# :alpha_lower, :alpha_upper.
|
|
36
|
+
# @param prefix [String] prefix prepended to each label.
|
|
37
|
+
# @param start [Integer] starting number (default 1).
|
|
38
|
+
# @param count [Integer, nil] number of pages this rule covers
|
|
39
|
+
# (nil = until next rule or end).
|
|
40
|
+
def style(style, prefix: nil, start: 1, count: nil)
|
|
41
|
+
@rules << LabelRule.new(
|
|
42
|
+
style: STYLES.fetch(style),
|
|
43
|
+
prefix: prefix,
|
|
44
|
+
start: start,
|
|
45
|
+
page_count: count
|
|
46
|
+
)
|
|
47
|
+
@current_index += count if count
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Build the /PageLabels number tree and set it on the Catalog.
|
|
52
|
+
def commit!
|
|
53
|
+
return if @rules.empty?
|
|
54
|
+
|
|
55
|
+
nums = build_nums_array
|
|
56
|
+
document.catalog.value[:PageLabels] = { Nums: nums }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def build_nums_array
|
|
62
|
+
nums = []
|
|
63
|
+
index = 0
|
|
64
|
+
@rules.each do |rule|
|
|
65
|
+
nums << index
|
|
66
|
+
nums << rule_dict(rule)
|
|
67
|
+
index += rule.page_count if rule.page_count
|
|
68
|
+
end
|
|
69
|
+
nums
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def rule_dict(rule)
|
|
73
|
+
dict = { S: rule.style }
|
|
74
|
+
dict[:P] = rule.prefix if rule.prefix
|
|
75
|
+
dict[:St] = rule.start unless rule.start == 1
|
|
76
|
+
dict
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/pdfrb/document/pages.rb
CHANGED
|
@@ -20,19 +20,24 @@ module Pdfrb
|
|
|
20
20
|
# rotate: 0 # degrees
|
|
21
21
|
#
|
|
22
22
|
# Returns the new +Model::Type::Page+.
|
|
23
|
-
def add(media_box:
|
|
23
|
+
def add(media_box: nil, rotate: 0, width: nil, height: nil,
|
|
24
|
+
bleed_box: nil, trim_box: nil, art_box: nil, crop_box: nil)
|
|
25
|
+
media_box ||= (width && height ? [0, 0, width, height] : [0, 0, 612, 792])
|
|
26
|
+
media_box ||= (width && height ? [0, 0, width, height] : [0, 0, 612, 792])
|
|
24
27
|
root = pages_root
|
|
25
28
|
contents = document.add({}, type: Pdfrb::Model::Cos::Stream)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
page_hash = {
|
|
30
|
+
Type: :Page,
|
|
31
|
+
Parent: Pdfrb::Model::Reference.new(root.oid, root.gen),
|
|
32
|
+
MediaBox: media_box,
|
|
33
|
+
Resources: {},
|
|
34
|
+
Contents: Pdfrb::Model::Reference.new(contents.oid, 0)
|
|
35
|
+
}
|
|
36
|
+
page_hash[:BleedBox] = bleed_box if bleed_box
|
|
37
|
+
page_hash[:TrimBox] = trim_box if trim_box
|
|
38
|
+
page_hash[:ArtBox] = art_box if art_box
|
|
39
|
+
page_hash[:CropBox] = crop_box if crop_box
|
|
40
|
+
page = document.add(page_hash, type: Pdfrb::Model::Type::Page)
|
|
36
41
|
page.value[:Rotate] = rotate if rotate.nonzero?
|
|
37
42
|
kids = (root.value[:Kids] ||= [])
|
|
38
43
|
kids << Pdfrb::Model::Reference.new(page.oid, page.gen)
|
|
@@ -41,10 +46,10 @@ module Pdfrb
|
|
|
41
46
|
end
|
|
42
47
|
alias << add
|
|
43
48
|
|
|
44
|
-
def each(&
|
|
49
|
+
def each(&)
|
|
45
50
|
return enum_for(:each) unless block_given?
|
|
46
51
|
|
|
47
|
-
walk(pages_root, &
|
|
52
|
+
walk(pages_root, &)
|
|
48
53
|
self
|
|
49
54
|
end
|
|
50
55
|
|
|
@@ -83,6 +88,52 @@ module Pdfrb
|
|
|
83
88
|
page
|
|
84
89
|
end
|
|
85
90
|
|
|
91
|
+
# Delete the page at +index+ (0-based).
|
|
92
|
+
def delete_at(index)
|
|
93
|
+
page = self[index]
|
|
94
|
+
return nil unless page
|
|
95
|
+
|
|
96
|
+
delete(page)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Set /Rotate on every page. +angle+ must be 0, 90, 180, or 270.
|
|
100
|
+
def rotate(angle)
|
|
101
|
+
each { |page| page.value[:Rotate] = angle }
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Move a page from one position to another.
|
|
106
|
+
# @param from [Integer] source index (0-based).
|
|
107
|
+
# @param to [Integer] destination index (0-based).
|
|
108
|
+
def move(from, to)
|
|
109
|
+
return self if from == to
|
|
110
|
+
|
|
111
|
+
root = pages_root
|
|
112
|
+
kids = root.value[:Kids]
|
|
113
|
+
return self unless kids.is_a?(::Array)
|
|
114
|
+
|
|
115
|
+
page = self[from]
|
|
116
|
+
return self unless page
|
|
117
|
+
|
|
118
|
+
ref = ref_to(page)
|
|
119
|
+
kids.delete(ref)
|
|
120
|
+
# Clamp to valid range after deletion.
|
|
121
|
+
insert_at = to > from ? to - 1 : to
|
|
122
|
+
kids.insert(insert_at, ref)
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Iterate pages with index.
|
|
127
|
+
def each_with_index(&)
|
|
128
|
+
return enum_for(:each_with_index) unless block_given?
|
|
129
|
+
|
|
130
|
+
i = 0
|
|
131
|
+
each do |page|
|
|
132
|
+
yield page, i
|
|
133
|
+
i += 1
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
86
137
|
private
|
|
87
138
|
|
|
88
139
|
def pages_root
|
|
@@ -100,26 +151,24 @@ module Pdfrb
|
|
|
100
151
|
end
|
|
101
152
|
|
|
102
153
|
def walk(node, &block)
|
|
103
|
-
kids = node.
|
|
154
|
+
kids = node.is_a?(Pdfrb::Model::Cos::Dictionary) ? node.value[:Kids] : node[:Kids]
|
|
104
155
|
return unless kids
|
|
105
156
|
|
|
106
|
-
# Kids may itself be an indirect reference, especially in
|
|
107
|
-
# compressed-object PDFs (FOP output).
|
|
108
157
|
kids = document.object(kids).value if kids.is_a?(Pdfrb::Model::Reference)
|
|
109
|
-
kids = kids.value if kids.
|
|
158
|
+
kids = kids.value if kids.is_a?(Pdfrb::Model::Cos::Dictionary) && !kids.is_a?(::Hash)
|
|
110
159
|
|
|
111
160
|
Array(kids).each do |kid_ref|
|
|
112
161
|
kid = kid_ref.is_a?(Pdfrb::Model::Reference) ?
|
|
113
162
|
document.object(kid_ref) : kid_ref
|
|
114
163
|
next unless kid
|
|
115
164
|
|
|
116
|
-
type = kid.
|
|
165
|
+
type = kid.is_a?(Pdfrb::Model::Cos::Dictionary) ? kid.value[:Type] : nil
|
|
117
166
|
case type
|
|
118
167
|
when :Pages then walk(kid, &block)
|
|
119
168
|
when :Page then yield kid
|
|
120
169
|
else
|
|
121
170
|
# Unknown type — best effort, treat as a Page.
|
|
122
|
-
yield kid if kid.
|
|
171
|
+
yield kid if kid.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
123
172
|
end
|
|
124
173
|
end
|
|
125
174
|
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# PDF Portfolio facade. A portfolio is a PDF with a /Collection
|
|
6
|
+
# dictionary on the Catalog that provides a collection view of
|
|
7
|
+
# embedded files (ISO 32000-2 §12.3.5).
|
|
8
|
+
#
|
|
9
|
+
# Inspired by mn2pdf's PDFPortfolio (which uses PDFBox). Provides
|
|
10
|
+
# field schema, default view configuration, and per-item metadata.
|
|
11
|
+
class Portfolio
|
|
12
|
+
Field = Struct.new(:name, :type, :subtype, :order, keyword_init: true) do
|
|
13
|
+
def pdf_hash
|
|
14
|
+
h = { N: name, T: type }
|
|
15
|
+
h[:FT] = type unless type == :text
|
|
16
|
+
h[:Subtype] = subtype if subtype
|
|
17
|
+
h[:O] = order if order
|
|
18
|
+
h
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Item = Struct.new(:name, :file_bytes, :mime_type,
|
|
23
|
+
:description, :fields, keyword_init: true)
|
|
24
|
+
|
|
25
|
+
DEFAULT_FIELDS = [
|
|
26
|
+
{ name: "Name", type: :text }.freeze,
|
|
27
|
+
{ name: "Description", type: :text, subtype: :Description }.freeze,
|
|
28
|
+
{ name: "Size", type: :number }.freeze,
|
|
29
|
+
{ name: "Modified", type: :date, subtype: :CreationDate }.freeze,
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
attr_reader :document, :items, :schema_fields
|
|
33
|
+
|
|
34
|
+
def initialize(document)
|
|
35
|
+
@document = document
|
|
36
|
+
@items = []
|
|
37
|
+
@schema_fields = DEFAULT_FIELDS.map { |f| Field.new(**f) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Add a schema field. Order matters for display.
|
|
41
|
+
def add_field(name, type: :text, subtype: nil, order: nil)
|
|
42
|
+
@schema_fields << Field.new(
|
|
43
|
+
name: name,
|
|
44
|
+
type: type,
|
|
45
|
+
subtype: subtype,
|
|
46
|
+
order: order || @schema_fields.length
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Add an item (embedded file) to the portfolio.
|
|
51
|
+
def add_item(name, file_bytes, mime_type: nil,
|
|
52
|
+
description: nil, fields: {})
|
|
53
|
+
@items << Item.new(
|
|
54
|
+
name: name,
|
|
55
|
+
file_bytes: file_bytes,
|
|
56
|
+
mime_type: mime_type,
|
|
57
|
+
description: description,
|
|
58
|
+
fields: fields
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Commit the portfolio configuration to the document.
|
|
63
|
+
# Sets /Collection on the Catalog, creates /Filespec for each item,
|
|
64
|
+
# and embeds the file data via /EF.
|
|
65
|
+
def commit!
|
|
66
|
+
catalog = document.catalog
|
|
67
|
+
catalog.value[:Collection] = collection_dict
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def collection_dict
|
|
73
|
+
{
|
|
74
|
+
Type: :Collection,
|
|
75
|
+
Schema: schema_fields.map(&:pdf_hash),
|
|
76
|
+
D: {
|
|
77
|
+
View: :D,
|
|
78
|
+
Hide: false,
|
|
79
|
+
Order: @schema_fields.map(&:name),
|
|
80
|
+
},
|
|
81
|
+
Items: items_references,
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def items_references
|
|
86
|
+
@items.map do |item|
|
|
87
|
+
filespec = document.add(
|
|
88
|
+
{
|
|
89
|
+
Type: :Filespec,
|
|
90
|
+
F: item.name,
|
|
91
|
+
UF: item.name,
|
|
92
|
+
Desc: item.description,
|
|
93
|
+
}.compact,
|
|
94
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
95
|
+
)
|
|
96
|
+
ef = document.add(
|
|
97
|
+
{
|
|
98
|
+
F: embedded_file_ref(item),
|
|
99
|
+
UF: embedded_file_ref(item),
|
|
100
|
+
},
|
|
101
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
102
|
+
)
|
|
103
|
+
filespec.value[:EF] = Pdfrb::Model::Reference.new(ef.oid, ef.gen)
|
|
104
|
+
filespec.value[:AFRelationship] = :Unspecified
|
|
105
|
+
Pdfrb::Model::Reference.new(filespec.oid, filespec.gen)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def embedded_file_ref(item)
|
|
110
|
+
embedded = document.add(
|
|
111
|
+
{ Type: :EmbeddedFile, Length: item.file_bytes.bytesize },
|
|
112
|
+
type: Pdfrb::Model::Cos::Stream
|
|
113
|
+
)
|
|
114
|
+
embedded.stream = item.file_bytes
|
|
115
|
+
Pdfrb::Model::Reference.new(embedded.oid, embedded.gen)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Gradient shading patterns facade. Creates Type 2 (axial) and
|
|
6
|
+
# Type 3 (radial) shading dictionaries and registers them in page
|
|
7
|
+
# /Resources /Shading.
|
|
8
|
+
#
|
|
9
|
+
# For 2-stop gradients, uses a Type 2 ExponentialInterpolation
|
|
10
|
+
# function (/FunctionType 2). For multi-stop, chains Type 3
|
|
11
|
+
# StitchingFunction.
|
|
12
|
+
class Shadings
|
|
13
|
+
attr_reader :document, :registry
|
|
14
|
+
|
|
15
|
+
def initialize(document)
|
|
16
|
+
@document = document
|
|
17
|
+
@registry = {}
|
|
18
|
+
@next_id = 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Add an axial (linear) gradient.
|
|
22
|
+
# @param from [Array(Numeric, Numeric)] start point [x, y].
|
|
23
|
+
# @param to [Array(Numeric, Numeric)] end point [x, y].
|
|
24
|
+
# @param stops [Array<Array>] gradient stops:
|
|
25
|
+
# [[offset, [color_family, *values]], ...]
|
|
26
|
+
# @param extend [Array<Boolean, Boolean>] extend before/after.
|
|
27
|
+
# @return [Symbol] shading resource name (e.g., :Sh1).
|
|
28
|
+
def add_axial(from:, to:, stops:, extend: [true, true])
|
|
29
|
+
color_space = stops_to_color_values(stops.first || [0, [:gray, 0]])[0]
|
|
30
|
+
name = next_name
|
|
31
|
+
|
|
32
|
+
function = build_gradient_function(stops, color_space)
|
|
33
|
+
|
|
34
|
+
shading = document.add(
|
|
35
|
+
{
|
|
36
|
+
ShadingType: 2,
|
|
37
|
+
ColorSpace: color_space,
|
|
38
|
+
Coords: Pdfrb::Model::PdfArray.new([*from, *to]),
|
|
39
|
+
Function: function,
|
|
40
|
+
Extend: Pdfrb::Model::PdfArray.new(extend),
|
|
41
|
+
},
|
|
42
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
43
|
+
)
|
|
44
|
+
register(name, shading)
|
|
45
|
+
name
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Add a radial gradient.
|
|
49
|
+
# @param from [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
|
|
50
|
+
# @param to [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
|
|
51
|
+
# @param stops [Array<Array>] same format as axial.
|
|
52
|
+
# @param extend [Array<Boolean, Boolean>] extend before/after.
|
|
53
|
+
# @return [Symbol] shading resource name.
|
|
54
|
+
def add_radial(from:, to:, stops:, extend: [true, true])
|
|
55
|
+
color_space = stops_to_color_values(stops.first || [0, [:gray, 0]])[0]
|
|
56
|
+
name = next_name
|
|
57
|
+
|
|
58
|
+
function = build_gradient_function(stops, color_space)
|
|
59
|
+
|
|
60
|
+
shading = document.add(
|
|
61
|
+
{
|
|
62
|
+
ShadingType: 3,
|
|
63
|
+
ColorSpace: color_space,
|
|
64
|
+
Coords: Pdfrb::Model::PdfArray.new([*from, *to]),
|
|
65
|
+
Function: function,
|
|
66
|
+
Extend: Pdfrb::Model::PdfArray.new(extend),
|
|
67
|
+
},
|
|
68
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
69
|
+
)
|
|
70
|
+
register(name, shading)
|
|
71
|
+
name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def [](name)
|
|
75
|
+
@registry[name]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def each(&block)
|
|
79
|
+
return enum_for(:each) unless block
|
|
80
|
+
|
|
81
|
+
@registry.each(&block)
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def build_gradient_function(stops, color_space)
|
|
88
|
+
return build_exponential_function(nil, nil, color_space) if stops.empty?
|
|
89
|
+
|
|
90
|
+
if stops.length == 2
|
|
91
|
+
_, c0 = stops_to_color_values(stops[0])
|
|
92
|
+
_, c1 = stops_to_color_values(stops[1])
|
|
93
|
+
build_exponential_function(c0, c1, color_space)
|
|
94
|
+
else
|
|
95
|
+
build_stitching_function(stops, color_space)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_stitching_function(stops, color_space)
|
|
100
|
+
functions = stops.each_cons(2).map do |s0, s1|
|
|
101
|
+
_, c0 = stops_to_color_values(s0)
|
|
102
|
+
_, c1 = stops_to_color_values(s1)
|
|
103
|
+
build_exponential_function(c0, c1, color_space)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
bounds = stops[1..-2].map { |s| s[0] }
|
|
107
|
+
|
|
108
|
+
document.add(
|
|
109
|
+
{
|
|
110
|
+
FunctionType: 3,
|
|
111
|
+
Domain: Pdfrb::Model::PdfArray.new([0, 1]),
|
|
112
|
+
Functions: functions.map { |f| Pdfrb::Model::Reference.new(f.oid, f.gen) },
|
|
113
|
+
Bounds: Pdfrb::Model::PdfArray.new(bounds),
|
|
114
|
+
Encode: Pdfrb::Model::PdfArray.new(stops.each_cons(2).map { [0, 1] }.flatten),
|
|
115
|
+
},
|
|
116
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def stops_to_color_values(stop)
|
|
121
|
+
family, *values = stop[1]
|
|
122
|
+
cs = case family.to_sym
|
|
123
|
+
when :rgb then :DeviceRGB
|
|
124
|
+
when :cmyk then :DeviceCMYK
|
|
125
|
+
else :DeviceGray
|
|
126
|
+
end
|
|
127
|
+
[cs, values]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def build_exponential_function(c0, c1, _color_space)
|
|
131
|
+
document.add(
|
|
132
|
+
{
|
|
133
|
+
FunctionType: 2,
|
|
134
|
+
Domain: Pdfrb::Model::PdfArray.new([0, 1]),
|
|
135
|
+
C0: Pdfrb::Model::PdfArray.new(c0),
|
|
136
|
+
C1: Pdfrb::Model::PdfArray.new(c1),
|
|
137
|
+
N: 1,
|
|
138
|
+
},
|
|
139
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def register(name, shading_obj)
|
|
144
|
+
@registry[name] = shading_obj
|
|
145
|
+
catalog = document.catalog
|
|
146
|
+
catalog.value[:Resources] ||= {}
|
|
147
|
+
catalog.value[:Resources][:Shading] ||= {}
|
|
148
|
+
catalog.value[:Resources][:Shading][name] =
|
|
149
|
+
Pdfrb::Model::Reference.new(shading_obj.oid, shading_obj.gen)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def next_name
|
|
153
|
+
name = :"Sh#{@next_id}"
|
|
154
|
+
@next_id += 1
|
|
155
|
+
name
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
class Document
|
|
5
|
+
# Page stamping facade. Stamps a Form XObject (logo, watermark,
|
|
6
|
+
# page number) onto every page of the document by emitting the
|
|
7
|
+
# Do operator in each page's content stream.
|
|
8
|
+
class Stamps
|
|
9
|
+
attr_reader :document
|
|
10
|
+
|
|
11
|
+
def initialize(document)
|
|
12
|
+
@document = document
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Stamp a Form XObject on every page.
|
|
16
|
+
#
|
|
17
|
+
# @param form_xobject [Pdfrb::Model::Cos::Stream] the Form XObject.
|
|
18
|
+
# @param at [Array<Numeric>] x, y position in points.
|
|
19
|
+
# @return [Symbol] the resource name (e.g., :St1).
|
|
20
|
+
def stamp_everywhere(form_xobject, at: [0, 0])
|
|
21
|
+
name = next_stamp_name
|
|
22
|
+
ref = Pdfrb::Model::Reference.new(form_xobject.oid, form_xobject.gen)
|
|
23
|
+
|
|
24
|
+
document.pages.each do |page|
|
|
25
|
+
register_xobject_in_resources(page, name, ref)
|
|
26
|
+
emit_do_operator(page, name, at)
|
|
27
|
+
end
|
|
28
|
+
name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Stamp a Form XObject on a single page.
|
|
32
|
+
def stamp_page(page, form_xobject, at: [0, 0])
|
|
33
|
+
name = next_stamp_name
|
|
34
|
+
ref = Pdfrb::Model::Reference.new(form_xobject.oid, form_xobject.gen)
|
|
35
|
+
register_xobject_in_resources(page, name, ref)
|
|
36
|
+
emit_do_operator(page, name, at)
|
|
37
|
+
name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def next_stamp_name
|
|
43
|
+
@stamp_counter ||= 0
|
|
44
|
+
@stamp_counter += 1
|
|
45
|
+
:"St#{@stamp_counter}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def register_xobject_in_resources(page, name, ref)
|
|
49
|
+
resources = page.value[:Resources]
|
|
50
|
+
resources = page.value[:Resources] = {} unless resources.is_a?(Pdfrb::Model::Cos::Dictionary)
|
|
51
|
+
xobjects = resources.value[:XObject] || {}
|
|
52
|
+
xobjects[name] = ref
|
|
53
|
+
resources.value[:XObject] = xobjects
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def emit_do_operator(page, name, at)
|
|
57
|
+
canvas = page.canvas
|
|
58
|
+
x, y = at
|
|
59
|
+
canvas.save_graphics_state
|
|
60
|
+
canvas.translate(x, y) if x != 0 || y != 0
|
|
61
|
+
canvas.emit_op(Pdfrb::Content::Operator::InvokeXObject, name)
|
|
62
|
+
canvas.restore_graphics_state
|
|
63
|
+
rescue StandardError
|
|
64
|
+
# Canvas may not be available on pages with no content stream yet.
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|