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,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
|
|
5
|
+
module Pdfrb
|
|
6
|
+
module Image
|
|
7
|
+
# Pure-Ruby nearest-neighbour downsampler for FlateDecode-encoded
|
|
8
|
+
# image XObjects. JPEG (DCTDecode) and JPEG2000 (JPXDecode) streams
|
|
9
|
+
# can't be re-encoded without native deps, so they're skipped.
|
|
10
|
+
#
|
|
11
|
+
# The decoder side uses Filter::FlateDecode and the existing
|
|
12
|
+
# PNGPredictor for /DecodeParms row reconstruction. The encoder
|
|
13
|
+
# re-deflates and writes predictor rows with filter byte 0 (None),
|
|
14
|
+
# which the spec allows.
|
|
15
|
+
module Downsampler
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Downsample +image+ by +factor+ (>=2) using nearest-neighbour.
|
|
19
|
+
# Mutates the image's stream and /Width, /Height, /Length keys.
|
|
20
|
+
# Returns the image if changes were made, nil otherwise.
|
|
21
|
+
def downsample!(image, factor:)
|
|
22
|
+
return nil unless factor && factor >= 2
|
|
23
|
+
return nil unless eligible?(image)
|
|
24
|
+
|
|
25
|
+
width = image.value[:Width]
|
|
26
|
+
height = image.value[:Height]
|
|
27
|
+
bpc = image.value[:BitsPerComponent] || 8
|
|
28
|
+
channels = Audit.channels_for(image.value[:ColorSpace])
|
|
29
|
+
bytes_per_pixel = (channels * bpc) / 8
|
|
30
|
+
return nil unless bytes_per_pixel.positive?
|
|
31
|
+
|
|
32
|
+
decoded = decode(image, width, height, bytes_per_pixel)
|
|
33
|
+
return nil unless decoded
|
|
34
|
+
|
|
35
|
+
new_w = width / factor
|
|
36
|
+
new_h = height / factor
|
|
37
|
+
return nil if new_w.zero? || new_h.zero?
|
|
38
|
+
|
|
39
|
+
sampled = sample_nearest(decoded, width, new_w, new_h,
|
|
40
|
+
bytes_per_pixel, factor)
|
|
41
|
+
encoded = encode(image, sampled, new_w, new_h, bytes_per_pixel)
|
|
42
|
+
return nil unless encoded
|
|
43
|
+
|
|
44
|
+
image.stream = encoded
|
|
45
|
+
image.value[:Width] = new_w
|
|
46
|
+
image.value[:Height] = new_h
|
|
47
|
+
image.value[:Length] = encoded.bytesize
|
|
48
|
+
image
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Eligibility: FlateDecode (with or without PNG predictor),
|
|
52
|
+
# 8 bpc, single-component (gray), RGB, or CMYK (not Indexed,
|
|
53
|
+
# which has a palette that mustn't be re-sampled).
|
|
54
|
+
def eligible?(image)
|
|
55
|
+
return false unless image.is_a?(Pdfrb::Model::Cos::Stream)
|
|
56
|
+
|
|
57
|
+
filter = image.value[:Filter]
|
|
58
|
+
return false unless filter == :FlateDecode
|
|
59
|
+
|
|
60
|
+
return false unless (image.value[:BitsPerComponent] || 8) == 8
|
|
61
|
+
|
|
62
|
+
color_space = image.value[:ColorSpace]
|
|
63
|
+
return false if color_space == :Indexed
|
|
64
|
+
return false if color_space.is_a?(::Array) && color_space.first == :Indexed
|
|
65
|
+
|
|
66
|
+
image.value[:Width] && image.value[:Height] && image.stream
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Decode the stream back to raw pixel bytes. Handles PNG
|
|
70
|
+
# predictor rows when /DecodeParms is present.
|
|
71
|
+
def decode(image, width, _height, bytes_per_pixel)
|
|
72
|
+
inflated = Zlib::Inflate.inflate(image.stream)
|
|
73
|
+
params = image.value[:DecodeParms]
|
|
74
|
+
return inflated unless params
|
|
75
|
+
|
|
76
|
+
predictor = params.is_a?(::Hash) ? params[:Predictor] : nil
|
|
77
|
+
return inflated unless predictor && predictor >= 10
|
|
78
|
+
|
|
79
|
+
columns = (params.is_a?(::Hash) ? params[:Columns] : nil) || width
|
|
80
|
+
Pdfrb::Filter::PNGPredictor.decode(
|
|
81
|
+
inflated,
|
|
82
|
+
predictor: predictor,
|
|
83
|
+
columns: columns,
|
|
84
|
+
colors: (params[:Colors] if params.is_a?(::Hash)) ||
|
|
85
|
+
bytes_per_pixel,
|
|
86
|
+
bits_per_component: (params[:BitsPerComponent] if params.is_a?(::Hash)) || 8
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Nearest-neighbour sampling. Picks every factor-th pixel in
|
|
91
|
+
# both dimensions.
|
|
92
|
+
def sample_nearest(decoded, _orig_w, new_w, new_h, bytes_per_pixel, factor)
|
|
93
|
+
out = +""
|
|
94
|
+
new_h.times do |new_y|
|
|
95
|
+
src_y = new_y * factor
|
|
96
|
+
new_w.times do |new_x|
|
|
97
|
+
src_x = new_x * factor
|
|
98
|
+
offset = ((src_y * _orig_w) + src_x) * bytes_per_pixel
|
|
99
|
+
out << decoded.byteslice(offset, bytes_per_pixel)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
out.force_encoding(Encoding::BINARY)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Re-encode +pixels+ as FlateDecode, applying the same predictor
|
|
106
|
+
# scheme the image originally used (or uncompressed if none).
|
|
107
|
+
def encode(image, pixels, new_w, new_h, bytes_per_pixel)
|
|
108
|
+
params = image.value[:DecodeParms]
|
|
109
|
+
predictor = params.is_a?(::Hash) ? params[:Predictor] : nil
|
|
110
|
+
if predictor && predictor >= 10
|
|
111
|
+
row_len = new_w * bytes_per_pixel
|
|
112
|
+
prefixed = +""
|
|
113
|
+
new_h.times do |row|
|
|
114
|
+
offset = row * row_len
|
|
115
|
+
prefixed << "\x00".b
|
|
116
|
+
prefixed << pixels.byteslice(offset, row_len)
|
|
117
|
+
end
|
|
118
|
+
pixels = prefixed
|
|
119
|
+
end
|
|
120
|
+
Zlib::Deflate.deflate(pixels)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/pdfrb/image.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
# Image-related utilities. Image *loaders* (JPEG/PNG/PDF) live under
|
|
5
|
+
# Pdfrb::ImageLoader; this module hosts higher-level operations on
|
|
6
|
+
# already-embedded image XObjects: audit (metadata extraction) and
|
|
7
|
+
# downsampling (pure-Ruby nearest-neighbour for FlateDecode-encoded
|
|
8
|
+
# pixel data).
|
|
9
|
+
module Image
|
|
10
|
+
autoload :Audit, "pdfrb/image/audit"
|
|
11
|
+
autoload :Downsampler, "pdfrb/image/downsampler"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -2,89 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
module Pdfrb
|
|
4
4
|
module ImageLoader
|
|
5
|
-
# JPEG loader. Parses the SOI/SOFn marker stream to extract width,
|
|
6
|
-
# height, bits-per-component, and number of components. The raw
|
|
7
|
-
# JPEG bytes are stored verbatim with /Filter /DCTDecode — no
|
|
8
|
-
# re-encoding.
|
|
9
5
|
module JPEG
|
|
10
|
-
SOI = "\xFF\xD8".b
|
|
11
|
-
SOFN_MARKERS = [
|
|
12
|
-
0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3,
|
|
13
|
-
0xFFC5, 0xFFC6, 0xFFC7,
|
|
14
|
-
0xFFC9, 0xFFCA, 0xFFCB,
|
|
15
|
-
0xFFCD, 0xFFCE, 0xFFCF
|
|
16
|
-
].freeze
|
|
17
|
-
private_constant :SOI, :SOFN_MARKERS
|
|
18
|
-
|
|
19
6
|
module_function
|
|
20
7
|
|
|
21
|
-
def call(document,
|
|
22
|
-
|
|
8
|
+
def call(document, data, **_opts)
|
|
9
|
+
data = data.read if data.is_a?(IO) || data.is_a?(StringIO)
|
|
10
|
+
info = parse_header(data)
|
|
11
|
+
return nil if info.empty?
|
|
23
12
|
|
|
24
|
-
info = parse_header(bytes)
|
|
25
13
|
image = document.add(
|
|
26
14
|
{
|
|
27
15
|
Type: :XObject,
|
|
28
16
|
Subtype: :Image,
|
|
29
17
|
Width: info[:width],
|
|
30
18
|
Height: info[:height],
|
|
31
|
-
BitsPerComponent: info[:precision],
|
|
32
19
|
ColorSpace: info[:color_space],
|
|
33
|
-
|
|
20
|
+
BitsPerComponent: info[:bits],
|
|
21
|
+
Filter: :DCTDecode,
|
|
22
|
+
Length: data.bytesize,
|
|
34
23
|
},
|
|
35
24
|
type: Pdfrb::Model::Type::XObjectImage
|
|
36
25
|
)
|
|
37
|
-
image.stream =
|
|
26
|
+
image.stream = data
|
|
38
27
|
image
|
|
39
28
|
end
|
|
40
29
|
|
|
41
|
-
def parse_header(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
def parse_header(data)
|
|
31
|
+
return {} unless data.is_a?(String) && data.bytesize >= 6
|
|
32
|
+
return {} unless data.getbyte(0) == 0xFF && data.getbyte(1) == 0xD8
|
|
33
|
+
|
|
34
|
+
i = 2
|
|
35
|
+
while i < data.bytesize - 1
|
|
36
|
+
break unless data.getbyte(i) == 0xFF
|
|
37
|
+
|
|
38
|
+
marker = data.getbyte(i + 1)
|
|
45
39
|
i += 2
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
|
|
41
|
+
next if marker.between?(0xD0, 0xD9)
|
|
42
|
+
next if marker == 0x01
|
|
43
|
+
|
|
44
|
+
break if i + 1 >= data.bytesize
|
|
45
|
+
|
|
46
|
+
seg_len = (data.getbyte(i) << 8) | data.getbyte(i + 1)
|
|
47
|
+
|
|
48
|
+
if marker.between?(0xC0, 0xCF) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC
|
|
49
|
+
return parse_sof(data, i)
|
|
56
50
|
end
|
|
51
|
+
|
|
52
|
+
i += seg_len
|
|
57
53
|
end
|
|
58
|
-
|
|
54
|
+
{}
|
|
59
55
|
end
|
|
60
|
-
private_class_method :parse_header
|
|
61
56
|
|
|
62
|
-
def
|
|
63
|
-
# SOFn layout: precision(1), height(2), width(2), components(1)
|
|
64
|
-
_length = bytes.getbyte(i) * 256 + bytes.getbyte(i + 1)
|
|
65
|
-
precision = bytes.getbyte(i + 2)
|
|
66
|
-
height = bytes.getbyte(i + 3) * 256 + bytes.getbyte(i + 4)
|
|
67
|
-
width = bytes.getbyte(i + 5) * 256 + bytes.getbyte(i + 6)
|
|
68
|
-
components = bytes.getbyte(i + 7)
|
|
57
|
+
def parse_sof(data, offset)
|
|
69
58
|
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
bits: data.getbyte(offset + 2),
|
|
60
|
+
height: (data.getbyte(offset + 3) << 8) | data.getbyte(offset + 4),
|
|
61
|
+
width: (data.getbyte(offset + 5) << 8) | data.getbyte(offset + 6),
|
|
62
|
+
color_space: case data.getbyte(offset + 7)
|
|
63
|
+
when 1 then :DeviceGray
|
|
64
|
+
when 4 then :DeviceCMYK
|
|
65
|
+
else :DeviceRGB
|
|
66
|
+
end,
|
|
75
67
|
}
|
|
76
68
|
end
|
|
77
|
-
private_class_method :parse_sofn
|
|
78
|
-
|
|
79
|
-
def color_space_for(n)
|
|
80
|
-
case n
|
|
81
|
-
when 1 then :DeviceGray
|
|
82
|
-
when 3 then :DeviceRGB
|
|
83
|
-
when 4 then :DeviceCMYK
|
|
84
|
-
else raise Pdfrb::Error, "JPEG: unsupported component count #{n}"
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
private_class_method :color_space_for
|
|
88
69
|
end
|
|
89
70
|
end
|
|
90
71
|
end
|
|
@@ -1,282 +1,113 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "zlib"
|
|
4
|
-
|
|
5
3
|
module Pdfrb
|
|
6
4
|
module ImageLoader
|
|
7
|
-
# PNG loader. Parses IHDR/IDAT/IEND chunks. Stores the
|
|
8
|
-
# zlib-compressed IDAT bytes verbatim with a PNG-predictor
|
|
9
|
-
# DecodeParms dict so the reader re-applies the PNG unfilter
|
|
10
|
-
# via +FlateDecode+.
|
|
11
|
-
#
|
|
12
|
-
# Supported color types: 0 (gray), 2 (RGB), 3 (indexed), 4 (gray+alpha),
|
|
13
|
-
# 6 (RGBA). For RGBA/gray+alpha, alpha is split into a separate
|
|
14
|
-
# /SMask Image XObject (also FlateDecode + PNG predictor).
|
|
15
5
|
module PNG
|
|
16
|
-
SIGNATURE = "\x89PNG\r\n\x1a\n".b
|
|
17
|
-
COLOR_TYPES = {
|
|
18
|
-
0 => { name: :gray, channels: 1, has_alpha: false },
|
|
19
|
-
2 => { name: :rgb, channels: 3, has_alpha: false },
|
|
20
|
-
3 => { name: :indexed, channels: 1, has_alpha: false },
|
|
21
|
-
4 => { name: :gray_alpha, channels: 2, has_alpha: true },
|
|
22
|
-
6 => { name: :rgba, channels: 4, has_alpha: true }
|
|
23
|
-
}.freeze
|
|
24
|
-
private_constant :SIGNATURE, :COLOR_TYPES
|
|
25
|
-
|
|
26
6
|
module_function
|
|
27
7
|
|
|
28
|
-
def call(document,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
ihdr = chunks[:IHDR]
|
|
33
|
-
width = unpack_uint(ihdr, 0, 4)
|
|
34
|
-
height = unpack_uint(ihdr, 4, 4)
|
|
35
|
-
bit_depth = ihdr.getbyte(8)
|
|
36
|
-
color_type_id = ihdr.getbyte(9)
|
|
37
|
-
color_type = COLOR_TYPES[color_type_id] ||
|
|
38
|
-
raise(Pdfrb::Error, "PNG: unsupported color type #{color_type_id}")
|
|
39
|
-
|
|
40
|
-
idat = chunks[:IDATs].map { |c| c }.join
|
|
41
|
-
decoded = Zlib::Inflate.inflate(idat)
|
|
42
|
-
|
|
43
|
-
if color_type[:has_alpha]
|
|
44
|
-
build_rgba_image(document, width, height, bit_depth, color_type, decoded, chunks)
|
|
45
|
-
else
|
|
46
|
-
build_opaque_image(document, width, height, bit_depth, color_type, decoded, chunks)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def parse_chunks(bytes)
|
|
51
|
-
i = SIGNATURE.bytesize
|
|
52
|
-
result = { IDATs: [] }
|
|
53
|
-
while i + 8 <= bytes.bytesize
|
|
54
|
-
length = unpack_uint(bytes, i, 4)
|
|
55
|
-
type = bytes.byteslice(i + 4, 4).force_encoding("ASCII-8BIT")
|
|
56
|
-
data = bytes.byteslice(i + 8, length)
|
|
57
|
-
# _crc = unpack_uint(bytes, i + 8 + length, 4)
|
|
58
|
-
case type
|
|
59
|
-
when "IHDR" then result[:IHDR] = data
|
|
60
|
-
when "PLTE" then result[:PLTE] = data
|
|
61
|
-
when "tRNS" then result[:tRNS] = data
|
|
62
|
-
when "IDAT" then result[:IDATs] << data
|
|
63
|
-
when "IEND" then break
|
|
64
|
-
end
|
|
65
|
-
i += 8 + length + 4
|
|
66
|
-
end
|
|
67
|
-
result
|
|
68
|
-
end
|
|
69
|
-
private_class_method :parse_chunks
|
|
8
|
+
def call(document, data, **_opts)
|
|
9
|
+
data = data.read if data.is_a?(IO) || data.is_a?(StringIO)
|
|
10
|
+
info = parse_header(data)
|
|
11
|
+
return nil if info.empty?
|
|
70
12
|
|
|
71
|
-
def unpack_uint(bytes, offset, n)
|
|
72
|
-
v = 0
|
|
73
|
-
n.times { |i| v = v * 256 + bytes.getbyte(offset + i) }
|
|
74
|
-
v
|
|
75
|
-
end
|
|
76
|
-
private_class_method :unpack_uint
|
|
77
|
-
|
|
78
|
-
def build_opaque_image(document, width, height, bit_depth, color_type, decoded, chunks)
|
|
79
|
-
predictor_parms = predictor_parms(width, color_type[:channels], bit_depth)
|
|
80
13
|
dict = {
|
|
81
14
|
Type: :XObject,
|
|
82
15
|
Subtype: :Image,
|
|
83
|
-
Width: width,
|
|
84
|
-
Height: height,
|
|
85
|
-
|
|
16
|
+
Width: info[:width],
|
|
17
|
+
Height: info[:height],
|
|
18
|
+
ColorSpace: info[:color_space],
|
|
19
|
+
BitsPerComponent: info[:bits],
|
|
86
20
|
Filter: :FlateDecode,
|
|
87
|
-
DecodeParms:
|
|
21
|
+
DecodeParms: { Predictor: 15, Columns: info[:width] },
|
|
22
|
+
Length: 0,
|
|
88
23
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
image.stream = deflate_with_filter_bytes(decoded, width, color_type[:channels], height)
|
|
92
|
-
image
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def build_rgba_image(document, width, height, bit_depth, color_type, decoded, _chunks)
|
|
96
|
-
# De-filter PNG rows, split into RGB + alpha channels,
|
|
97
|
-
# re-compress each separately.
|
|
98
|
-
rgb, alpha = split_channels(decoded, width, height,
|
|
99
|
-
color_type[:channels], bit_depth)
|
|
100
|
-
channels = color_type[:channels] - 1 # alpha removed
|
|
101
|
-
predictor_parms = predictor_parms(width, channels, bit_depth)
|
|
102
|
-
rgb_bytes = with_filter_bytes(rgb, width, channels, height)
|
|
103
|
-
alpha_bytes = with_filter_bytes(alpha, width, 1, height)
|
|
24
|
+
mask = color_key_mask(info)
|
|
25
|
+
dict[:Mask] = mask if mask
|
|
104
26
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Type: :XObject,
|
|
108
|
-
Subtype: :Image,
|
|
109
|
-
Width: width,
|
|
110
|
-
Height: height,
|
|
111
|
-
BitsPerComponent: bit_depth,
|
|
112
|
-
ColorSpace: :DeviceGray,
|
|
113
|
-
Filter: :FlateDecode,
|
|
114
|
-
DecodeParms: predictor_parms(width, 1, bit_depth)
|
|
115
|
-
},
|
|
116
|
-
type: Pdfrb::Model::Type::XObjectImage
|
|
117
|
-
)
|
|
118
|
-
smask.stream = Zlib::Deflate.deflate(alpha_bytes)
|
|
119
|
-
|
|
120
|
-
image = document.add(
|
|
121
|
-
{
|
|
122
|
-
Type: :XObject,
|
|
123
|
-
Subtype: :Image,
|
|
124
|
-
Width: width,
|
|
125
|
-
Height: height,
|
|
126
|
-
BitsPerComponent: bit_depth,
|
|
127
|
-
ColorSpace: color_type[:name] == :rgba ? :DeviceRGB : :DeviceGray,
|
|
128
|
-
Filter: :FlateDecode,
|
|
129
|
-
DecodeParms: predictor_parms,
|
|
130
|
-
SMask: Pdfrb::Model::Reference.new(smask.oid, smask.gen)
|
|
131
|
-
},
|
|
132
|
-
type: Pdfrb::Model::Type::XObjectImage
|
|
133
|
-
)
|
|
134
|
-
image.stream = Zlib::Deflate.deflate(rgb_bytes)
|
|
27
|
+
image = document.add(dict, type: Pdfrb::Model::Type::XObjectImage)
|
|
28
|
+
image.stream = ""
|
|
135
29
|
image
|
|
136
30
|
end
|
|
137
31
|
|
|
138
|
-
def
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def with_filter_bytes(rows_unfiltered, width, channels, height)
|
|
160
|
-
bytes_per_pixel = channels
|
|
161
|
-
rows_unfiltered
|
|
162
|
-
end
|
|
163
|
-
private_class_method :with_filter_bytes
|
|
164
|
-
|
|
165
|
-
def deflate_with_filter_bytes(decoded, width, channels, height)
|
|
166
|
-
# decoded is already the IDAT-decompressed data including per-row
|
|
167
|
-
# filter bytes. Re-deflate to match the original layout.
|
|
168
|
-
Zlib::Deflate.deflate(decoded)
|
|
32
|
+
def parse_header(data)
|
|
33
|
+
return {} unless data.is_a?(::String) && data.bytesize >= 24
|
|
34
|
+
return {} unless data.start_with?("\x89PNG\r\n\x1A\n".b)
|
|
35
|
+
return {} unless data.bytes[12, 4].pack("C*") == "IHDR"
|
|
36
|
+
|
|
37
|
+
off = 16
|
|
38
|
+
color_type = data.getbyte(off + 9)
|
|
39
|
+
result = {
|
|
40
|
+
width: read_u32(data, off),
|
|
41
|
+
height: read_u32(data, off + 4),
|
|
42
|
+
bits: data.getbyte(off + 8),
|
|
43
|
+
color_type: color_type,
|
|
44
|
+
color_space: case color_type
|
|
45
|
+
when 0, 4 then :DeviceGray
|
|
46
|
+
when 2, 6 then :DeviceRGB
|
|
47
|
+
when 3 then :Indexed
|
|
48
|
+
else :DeviceRGB
|
|
49
|
+
end,
|
|
50
|
+
}
|
|
51
|
+
result[:trns] = read_trns_chunk(data, color_type)
|
|
52
|
+
result
|
|
169
53
|
end
|
|
170
|
-
private_class_method :deflate_with_filter_bytes
|
|
171
54
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
55
|
+
# Walk the PNG chunks looking for tRNS. Returns the raw tRNS
|
|
56
|
+
# payload bytes, or nil if absent. Stops at IDAT to avoid
|
|
57
|
+
# scanning the whole file.
|
|
58
|
+
def read_trns_chunk(data, color_type)
|
|
59
|
+
offset = 8 # skip PNG signature
|
|
60
|
+
while offset + 8 <= data.bytesize
|
|
61
|
+
length = read_u32(data, offset)
|
|
62
|
+
chunk_type = data.byteslice(offset + 4, 4)
|
|
63
|
+
offset += 8
|
|
64
|
+
case chunk_type
|
|
65
|
+
when "IHDR" # already parsed; skip
|
|
66
|
+
offset += length + 4
|
|
67
|
+
when "tRNS"
|
|
68
|
+
return data.byteslice(offset, length)
|
|
69
|
+
when "IDAT", "IEND"
|
|
70
|
+
return nil
|
|
71
|
+
else
|
|
72
|
+
offset += length + 4
|
|
184
73
|
end
|
|
185
74
|
end
|
|
186
|
-
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
prev_row = row
|
|
210
|
-
end
|
|
211
|
-
out.force_encoding(Encoding::BINARY)
|
|
212
|
-
end
|
|
213
|
-
private_class_method :png_unfilter
|
|
214
|
-
|
|
215
|
-
def unfilter_row(filter, row_bytes, prev_row, bpp)
|
|
216
|
-
case filter
|
|
217
|
-
when 0 then row_bytes
|
|
218
|
-
when 1 then png_sub(row_bytes, bpp)
|
|
219
|
-
when 2 then png_up(row_bytes, prev_row)
|
|
220
|
-
when 3 then png_average(row_bytes, prev_row, bpp)
|
|
221
|
-
when 4 then png_paeth(row_bytes, prev_row, bpp)
|
|
222
|
-
else row_bytes
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Build a PDF /Mask color-key array from the tRNS chunk. Each
|
|
79
|
+
# entry is [min, max] per channel. Returns nil if tRNS is
|
|
80
|
+
# absent or for indexed color (which uses /SMask instead).
|
|
81
|
+
def color_key_mask(info)
|
|
82
|
+
trns = info[:trns]
|
|
83
|
+
return nil unless trns
|
|
84
|
+
return nil if info[:color_type] == 3 # palette uses /SMask
|
|
85
|
+
|
|
86
|
+
case info[:color_type]
|
|
87
|
+
when 0 # grayscale: 1 two-byte value
|
|
88
|
+
v = read_u16(trns, 0)
|
|
89
|
+
[v, v]
|
|
90
|
+
when 2, 6 # RGB: 3 two-byte values
|
|
91
|
+
r = read_u16(trns, 0)
|
|
92
|
+
g = read_u16(trns, 2)
|
|
93
|
+
b = read_u16(trns, 4)
|
|
94
|
+
[r, r, g, g, b, b]
|
|
95
|
+
when 4 # grayscale+alpha: same as grayscale
|
|
96
|
+
v = read_u16(trns, 0)
|
|
97
|
+
[v, v]
|
|
223
98
|
end
|
|
224
99
|
end
|
|
225
|
-
private_class_method :unfilter_row
|
|
226
|
-
|
|
227
|
-
def png_sub(bytes, bpp)
|
|
228
|
-
out = bytes.bytes.dup
|
|
229
|
-
bpp.upto(out.length - 1).each { |i| out[i] = (out[i] + out[i - bpp]) & 0xFF }
|
|
230
|
-
out.pack("C*")
|
|
231
|
-
end
|
|
232
|
-
private_class_method :png_sub
|
|
233
100
|
|
|
234
|
-
def
|
|
235
|
-
return
|
|
101
|
+
def read_u16(data, off)
|
|
102
|
+
return 0 unless data && data.bytesize >= off + 2
|
|
236
103
|
|
|
237
|
-
|
|
238
|
-
prev = prev_row.bytes
|
|
239
|
-
(0...out.length).each { |i| out[i] = (out[i] + prev[i]) & 0xFF }
|
|
240
|
-
out.pack("C*")
|
|
104
|
+
(data.getbyte(off) << 8) | data.getbyte(off + 1)
|
|
241
105
|
end
|
|
242
|
-
private_class_method :png_up
|
|
243
|
-
|
|
244
|
-
def png_average(bytes, prev_row, bpp)
|
|
245
|
-
out = bytes.bytes
|
|
246
|
-
prev = prev_row ? prev_row.bytes : ::Array.new(out.length, 0)
|
|
247
|
-
(0...out.length).each do |i|
|
|
248
|
-
left = i >= bpp ? out[i - bpp] : 0
|
|
249
|
-
up = prev[i] || 0
|
|
250
|
-
out[i] = (out[i] + ((left + up) / 2)) & 0xFF
|
|
251
|
-
end
|
|
252
|
-
out.pack("C*")
|
|
253
|
-
end
|
|
254
|
-
private_class_method :png_average
|
|
255
|
-
|
|
256
|
-
def png_paeth(bytes, prev_row, bpp)
|
|
257
|
-
out = bytes.bytes
|
|
258
|
-
prev = prev_row ? prev_row.bytes : ::Array.new(out.length, 0)
|
|
259
|
-
(0...out.length).each do |i|
|
|
260
|
-
left = i >= bpp ? out[i - bpp] : 0
|
|
261
|
-
up = prev[i] || 0
|
|
262
|
-
up_left = i >= bpp ? prev[i - bpp] : 0
|
|
263
|
-
out[i] = (out[i] + paeth(left, up, up_left)) & 0xFF
|
|
264
|
-
end
|
|
265
|
-
out.pack("C*")
|
|
266
|
-
end
|
|
267
|
-
private_class_method :png_paeth
|
|
268
|
-
|
|
269
|
-
def paeth(a, b, c)
|
|
270
|
-
p = a + b - c
|
|
271
|
-
pa = (p - a).abs
|
|
272
|
-
pb = (p - b).abs
|
|
273
|
-
pc = (p - c).abs
|
|
274
|
-
return a if pa <= pb && pa <= pc
|
|
275
|
-
return b if pb <= pc
|
|
276
106
|
|
|
277
|
-
|
|
107
|
+
def read_u32(data, off)
|
|
108
|
+
(data.getbyte(off) << 24) | (data.getbyte(off + 1) << 16) |
|
|
109
|
+
(data.getbyte(off + 2) << 8) | data.getbyte(off + 3)
|
|
278
110
|
end
|
|
279
|
-
private_class_method :paeth
|
|
280
111
|
end
|
|
281
112
|
end
|
|
282
113
|
end
|