pdfrb 0.3.0 → 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/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 +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 +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/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 +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 +311 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Font
|
|
5
|
+
module TrueType
|
|
6
|
+
class Post
|
|
7
|
+
attr_reader :version, :italic_angle, :underline_position,
|
|
8
|
+
:underline_thickness, :is_fixed_pitch, :glyph_names
|
|
9
|
+
|
|
10
|
+
def initialize(data)
|
|
11
|
+
return unless data && data.bytesize >= 32
|
|
12
|
+
|
|
13
|
+
@version = data.bytes[0, 4].pack("C*").unpack1("N")
|
|
14
|
+
@italic_angle = s32(data, 4) / 65536.0
|
|
15
|
+
@underline_position = s16(data, 8)
|
|
16
|
+
@underline_thickness = s16(data, 10)
|
|
17
|
+
@is_fixed_pitch = data.bytes[12, 4].pack("C*").unpack1("N")
|
|
18
|
+
@glyph_names = parse_names(data) if @version == 0x00020000
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def glyph_name(index)
|
|
22
|
+
return nil unless @glyph_names
|
|
23
|
+
return nil if index >= @glyph_names.length
|
|
24
|
+
|
|
25
|
+
@glyph_names[index]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def s32(data, off); (data.getbyte(off) << 24) | (data.getbyte(off + 1) << 16) | (data.getbyte(off + 2) << 8) | data.getbyte(off + 3); end
|
|
29
|
+
|
|
30
|
+
STANDARD_GLYPH_NAMES = %w[
|
|
31
|
+
.notdef .null nonmarkingreturn space exclam quotedbl numbersign
|
|
32
|
+
dollar percent ampersand quotesingle parenleft parenright asterisk
|
|
33
|
+
plus comma hyphen period slash zero one two three four five six
|
|
34
|
+
seven eight nine colon semicolon less equal greater question at
|
|
35
|
+
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
|
36
|
+
bracketleft backslash bracketright asciicircum underscore grave
|
|
37
|
+
a b c d e f g h i j k l m n o p q r s t u v w x y z
|
|
38
|
+
braceleft bar braceright asciitilde Adieresis Aring Ccedilla Eacute
|
|
39
|
+
Ntilde Odieresis Udieresis aacute agrave acircumflex adieresis atilde
|
|
40
|
+
aring ccedilla eacute egrave ecircumflex edieresis iacute igrave
|
|
41
|
+
icircumflex idieresis ntilde oacute ograve ocircumflex odieresis
|
|
42
|
+
otilde uacute ugrave ucircumflex udieresis dagger degree cent
|
|
43
|
+
sterling section bullet paragraph germandbls registered copyright
|
|
44
|
+
trademark acute dieresis notequal AE Oslash infinity plusminus
|
|
45
|
+
lessequal greaterequal yen mu partialdiff summation product pi
|
|
46
|
+
integral ordfeminine ordmasculine Omega ae oslash questiondown
|
|
47
|
+
exclamdown logicalnot radical florin approxequal Delta guillemotleft
|
|
48
|
+
guillemotright ellipsis nonbreakingspace Agrave Atilde Otilde OE oe
|
|
49
|
+
endash emdash quotedblleft quotedblright quoteleft quoteright divide
|
|
50
|
+
lozenge ydieresis Ydieresis fraction currency guilsinglleft
|
|
51
|
+
guilsinglright fi fl daggerdbl periodcentered quotesinglbase
|
|
52
|
+
quotedblbase perthousand Acircumflex Ecircumflex Aacute Edieresis
|
|
53
|
+
Egrave Iacute Icircumflex Idieresis Eth eth Yacute yacute Thorne thorn
|
|
54
|
+
multiply minus onesuperior twosuperior threesuperior onehalf
|
|
55
|
+
onequarter threequarters franc Gbreve gbreve Idotaccent Scedilla
|
|
56
|
+
scedilla Cacute cacute Ccaron ccaron dcroat
|
|
57
|
+
].freeze
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def parse_names(data)
|
|
62
|
+
count = u16(data, 32)
|
|
63
|
+
indices = Array.new(count) { |i| u16(data, 34 + (i * 2)) }
|
|
64
|
+
offset = 34 + (count * 2)
|
|
65
|
+
custom_names = {}
|
|
66
|
+
|
|
67
|
+
indices.each do |idx|
|
|
68
|
+
next if idx < 258
|
|
69
|
+
next if custom_names.key?(idx)
|
|
70
|
+
|
|
71
|
+
len = data.getbyte(offset) || 0
|
|
72
|
+
offset += 1
|
|
73
|
+
custom_names[idx] = data.bytes[offset, len]&.pack("C*")&.force_encoding("UTF-8")
|
|
74
|
+
offset += len
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
STANDARD_GLYPH_NAMES.map.with_index do |_name, i|
|
|
78
|
+
indices[i] && indices[i] < 258 ? STANDARD_GLYPH_NAMES[indices[i]] : custom_names[indices[i]]
|
|
79
|
+
end
|
|
80
|
+
rescue StandardError
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def u16(data, off); (data.getbyte(off) << 8) | data.getbyte(off + 1); end
|
|
85
|
+
|
|
86
|
+
def s16(data, off)
|
|
87
|
+
v = u16(data, off)
|
|
88
|
+
v >= 32768 ? v - 65536 : v
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require "set"
|
|
2
|
-
|
|
3
1
|
# frozen_string_literal: true
|
|
4
2
|
|
|
5
3
|
module Pdfrb
|
|
@@ -43,7 +41,7 @@ module Pdfrb
|
|
|
43
41
|
private
|
|
44
42
|
|
|
45
43
|
def build_subset
|
|
46
|
-
return @ttf.
|
|
44
|
+
return @ttf.raw_data unless can_subset?
|
|
47
45
|
|
|
48
46
|
@resolved = resolve_composites(@glyph_ids)
|
|
49
47
|
|
|
@@ -65,7 +63,7 @@ module Pdfrb
|
|
|
65
63
|
copy_remaining_tables(tables)
|
|
66
64
|
assemble_ttf(tables)
|
|
67
65
|
rescue StandardError
|
|
68
|
-
@ttf.
|
|
66
|
+
@ttf.raw_data
|
|
69
67
|
end
|
|
70
68
|
|
|
71
69
|
def can_subset?
|
|
@@ -299,7 +297,7 @@ module Pdfrb
|
|
|
299
297
|
end
|
|
300
298
|
|
|
301
299
|
def copy_remaining_tables(tables)
|
|
302
|
-
raw = @ttf.
|
|
300
|
+
raw = @ttf.raw_data
|
|
303
301
|
num_tables = (raw.getbyte(4) * 256) + raw.getbyte(5)
|
|
304
302
|
num_tables.times do |i|
|
|
305
303
|
base = 12 + (i * 16)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Font
|
|
5
|
+
module TrueType
|
|
6
|
+
module Wrapper
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def embed(document, ttf_data, **)
|
|
10
|
+
ttf = File.new(ttf_data)
|
|
11
|
+
name = ttf.name_table_parsed
|
|
12
|
+
ps_name = name&.ps_name || "EmbeddedFont"
|
|
13
|
+
|
|
14
|
+
fd = create_font_descriptor(document, ttf, ps_name)
|
|
15
|
+
widths = build_widths(ttf)
|
|
16
|
+
tu_stream = build_tounicode(document, ttf)
|
|
17
|
+
font_file = embed_font_file(document, ttf_data)
|
|
18
|
+
|
|
19
|
+
fd.value[:FontFile2] = Pdfrb::Model::Reference.new(font_file.oid, font_file.gen)
|
|
20
|
+
|
|
21
|
+
document.add(
|
|
22
|
+
{
|
|
23
|
+
Type: :Font,
|
|
24
|
+
Subtype: :TrueType,
|
|
25
|
+
BaseFont: ps_name.to_sym,
|
|
26
|
+
FirstChar: 0,
|
|
27
|
+
LastChar: 255,
|
|
28
|
+
Widths: widths,
|
|
29
|
+
FontDescriptor: Pdfrb::Model::Reference.new(fd.oid, fd.gen),
|
|
30
|
+
ToUnicode: Pdfrb::Model::Reference.new(tu_stream.oid, tu_stream.gen),
|
|
31
|
+
Encoding: :WinAnsiEncoding,
|
|
32
|
+
},
|
|
33
|
+
type: Pdfrb::Model::Type::FontTrueType
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_font_descriptor(document, ttf, ps_name)
|
|
38
|
+
head = ttf.head
|
|
39
|
+
os2 = ttf.os2
|
|
40
|
+
document.add(
|
|
41
|
+
{
|
|
42
|
+
Type: :FontDescriptor,
|
|
43
|
+
FontName: ps_name.to_sym,
|
|
44
|
+
Flags: 32,
|
|
45
|
+
FontBBox: head&.bbox || [0, 0, 1000, 1000],
|
|
46
|
+
ItalicAngle: 0,
|
|
47
|
+
Ascent: os2&.s_typo_ascender || ttf.hhea&.ascender || 800,
|
|
48
|
+
Descent: os2&.s_typo_descender || ttf.hhea&.descender || -200,
|
|
49
|
+
CapHeight: os2&.s_cap_height || 700,
|
|
50
|
+
StemV: 80,
|
|
51
|
+
},
|
|
52
|
+
type: Pdfrb::Model::Cos::Dictionary
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_widths(ttf)
|
|
57
|
+
widths = Array.new(256, 500)
|
|
58
|
+
table = Pdfrb::Font::Encoding::WinAnsiEncoding::TABLE
|
|
59
|
+
Maxp.new(ttf.maxp_table)
|
|
60
|
+
|
|
61
|
+
table.each_with_index do |cp, byte|
|
|
62
|
+
next unless cp
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
gid = ttf.cmap.glyph_id_for(cp)
|
|
66
|
+
next unless gid&.positive?
|
|
67
|
+
|
|
68
|
+
w = ttf.hmtx.advance_width(gid)
|
|
69
|
+
widths[byte] = w if w&.positive?
|
|
70
|
+
rescue StandardError
|
|
71
|
+
next
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
widths
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_tounicode(document, _ttf)
|
|
78
|
+
table = Pdfrb::Font::Encoding::WinAnsiEncoding::TABLE
|
|
79
|
+
mapping = {}
|
|
80
|
+
table.each_with_index { |cp, byte| mapping[byte] = cp if cp }
|
|
81
|
+
|
|
82
|
+
cmap_data = Pdfrb::Font::CMap::Writer.write(mapping)
|
|
83
|
+
stream = document.add(
|
|
84
|
+
{ Length: cmap_data.bytesize },
|
|
85
|
+
type: Pdfrb::Model::Cos::Stream
|
|
86
|
+
)
|
|
87
|
+
stream.stream = cmap_data
|
|
88
|
+
stream
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def embed_font_file(document, ttf_data)
|
|
92
|
+
stream = document.add(
|
|
93
|
+
{ Length: ttf_data.bytesize },
|
|
94
|
+
type: Pdfrb::Model::Cos::Stream
|
|
95
|
+
)
|
|
96
|
+
stream.stream = ttf_data
|
|
97
|
+
stream
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/pdfrb/font/true_type.rb
CHANGED
|
@@ -9,6 +9,13 @@ module Pdfrb
|
|
|
9
9
|
autoload :Hmtx, "pdfrb/font/true_type/hmtx"
|
|
10
10
|
autoload :Cmap, "pdfrb/font/true_type/cmap"
|
|
11
11
|
autoload :OS2, "pdfrb/font/true_type/os2"
|
|
12
|
+
autoload :Maxp, "pdfrb/font/true_type/maxp"
|
|
13
|
+
autoload :Post, "pdfrb/font/true_type/post"
|
|
14
|
+
autoload :Name, "pdfrb/font/true_type/name"
|
|
15
|
+
autoload :Loca, "pdfrb/font/true_type/loca"
|
|
16
|
+
autoload :Glyf, "pdfrb/font/true_type/glyf"
|
|
17
|
+
autoload :Kern, "pdfrb/font/true_type/kern"
|
|
18
|
+
autoload :Wrapper, "pdfrb/font/true_type/wrapper"
|
|
12
19
|
end
|
|
13
20
|
end
|
|
14
21
|
end
|
data/lib/pdfrb/font.rb
CHANGED
|
@@ -10,6 +10,8 @@ module Pdfrb
|
|
|
10
10
|
autoload :CMap, "pdfrb/font/cmap"
|
|
11
11
|
autoload :TrueType, "pdfrb/font/true_type"
|
|
12
12
|
autoload :Type1, "pdfrb/font/type1"
|
|
13
|
+
autoload :Metrics, "pdfrb/font/metrics_helper"
|
|
14
|
+
autoload :MetricsHelper, "pdfrb/font/metrics_helper"
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
# Top-level font loader registry. Extends what +Document::Fonts+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module FontLoader
|
|
5
|
+
module FromConfiguration
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(document, name, **_opts)
|
|
9
|
+
config = document.config["font.#{name}"]
|
|
10
|
+
return nil unless config
|
|
11
|
+
|
|
12
|
+
case config["type"]
|
|
13
|
+
when "standard14" then Standard14.call(document, config["name"] || name)
|
|
14
|
+
when "file" then FromFile.call(document, config["path"])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module FontLoader
|
|
5
|
+
module FromFile
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(document, path, **_opts)
|
|
9
|
+
return nil unless path.is_a?(String) && File.file?(path)
|
|
10
|
+
|
|
11
|
+
data = File.binread(path)
|
|
12
|
+
magic = data&.byteslice(0, 4)
|
|
13
|
+
return nil unless magic
|
|
14
|
+
return nil unless ["\x00\x01\x00\x00".b, "true".b, "OTTO".b].include?(magic)
|
|
15
|
+
|
|
16
|
+
Pdfrb::Font::TrueType::Wrapper.embed(document, data, resource_name: :F1)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module FontLoader
|
|
5
|
+
module Standard14
|
|
6
|
+
STANDARDS = %w[Helvetica Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique
|
|
7
|
+
Times-Roman Times-Bold Times-Italic Times-BoldItalic
|
|
8
|
+
Courier Courier-Bold Courier-Oblique Courier-BoldOblique Symbol ZapfDingbats].freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def call(document, name, **_opts)
|
|
13
|
+
return nil unless STANDARDS.include?(name.to_s)
|
|
14
|
+
|
|
15
|
+
document.add({ Type: :Font, Subtype: :Type1, BaseFont: name.to_sym },
|
|
16
|
+
type: Pdfrb::Model::Type::FontType1)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module FontLoader
|
|
5
|
+
module VariantFromName
|
|
6
|
+
BASE_MAP = {
|
|
7
|
+
"Helvetica" => %w[Helvetica Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique],
|
|
8
|
+
"Times" => %w[Times-Roman Times-Bold Times-Italic Times-BoldItalic],
|
|
9
|
+
"Courier" => %w[Courier Courier-Bold Courier-Oblique Courier-BoldOblique],
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def call(document, name, **_opts)
|
|
15
|
+
return nil unless name.is_a?(String)
|
|
16
|
+
|
|
17
|
+
base = name.sub(/[- ](Bold|Italic|Oblique|BoldItalic|BoldOblique).*/i, "")
|
|
18
|
+
return nil unless BASE_MAP.key?(base)
|
|
19
|
+
|
|
20
|
+
idx = case name
|
|
21
|
+
when /bold.*italic|italic.*bold/i
|
|
22
|
+
3
|
|
23
|
+
when /bold/i
|
|
24
|
+
1
|
|
25
|
+
else
|
|
26
|
+
/oblique|italic/i.match?(name) ? 2 : 0
|
|
27
|
+
end
|
|
28
|
+
full_name = BASE_MAP[base][idx]
|
|
29
|
+
return nil unless full_name
|
|
30
|
+
|
|
31
|
+
document.add({ Type: :Font, Subtype: :Type1, BaseFont: full_name.to_sym },
|
|
32
|
+
type: Pdfrb::Model::Type::FontType1)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/pdfrb/font_loader.rb
CHANGED
|
@@ -6,5 +6,9 @@ module Pdfrb
|
|
|
6
6
|
module FontLoader
|
|
7
7
|
autoload :TrueType, "pdfrb/font_loader/true_type"
|
|
8
8
|
autoload :Type1, "pdfrb/font_loader/type1"
|
|
9
|
+
autoload :Standard14, "pdfrb/font_loader/standard14"
|
|
10
|
+
autoload :FromFile, "pdfrb/font_loader/from_file"
|
|
11
|
+
autoload :VariantFromName, "pdfrb/font_loader/variant_from_name"
|
|
12
|
+
autoload :FromConfiguration, "pdfrb/font_loader/from_configuration"
|
|
9
13
|
end
|
|
10
14
|
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
# Font file resolver. Searches system font directories for font files
|
|
5
|
+
# by family name + style. Eliminates the need for external font-finding
|
|
6
|
+
# libraries like fontisan.
|
|
7
|
+
class FontResolver
|
|
8
|
+
DEFAULT_SEARCH_PATHS = [
|
|
9
|
+
"/System/Library/Fonts",
|
|
10
|
+
"/Library/Fonts",
|
|
11
|
+
File.expand_path("~/Library/Fonts"),
|
|
12
|
+
"/usr/share/fonts",
|
|
13
|
+
File.expand_path("~/.local/share/fonts"),
|
|
14
|
+
"/usr/local/share/fonts",
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
FontInfo = Struct.new(:path, :family, :style, :ps_name, keyword_init: true)
|
|
18
|
+
|
|
19
|
+
attr_reader :search_paths, :cache
|
|
20
|
+
|
|
21
|
+
def initialize(search_paths: DEFAULT_SEARCH_PATHS)
|
|
22
|
+
@search_paths = search_paths.dup
|
|
23
|
+
@cache = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def find(family:, style: "Regular")
|
|
27
|
+
build_cache unless @cache
|
|
28
|
+
normalized_family = family.downcase.gsub(/\s+/, "")
|
|
29
|
+
normalized_style = style.downcase.gsub(/\s+/, "")
|
|
30
|
+
|
|
31
|
+
@cache.each do |info|
|
|
32
|
+
next unless info.family&.downcase&.gsub(/\s+/, "") == normalized_family
|
|
33
|
+
next unless match_style?(info.style, normalized_style)
|
|
34
|
+
|
|
35
|
+
return info.path
|
|
36
|
+
end
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def find_by_ps_name(ps_name)
|
|
41
|
+
build_cache unless @cache
|
|
42
|
+
normalized = ps_name.downcase
|
|
43
|
+
|
|
44
|
+
@cache.each do |info|
|
|
45
|
+
return info.path if info.ps_name&.downcase == normalized
|
|
46
|
+
end
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def available_fonts
|
|
51
|
+
build_cache unless @cache
|
|
52
|
+
@cache.dup
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def match_style?(actual, requested)
|
|
58
|
+
return true if requested == "regular" && (actual.nil? || actual == "" || actual.downcase == "regular")
|
|
59
|
+
return true if actual&.downcase&.gsub(/\s+/, "") == requested
|
|
60
|
+
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_cache
|
|
65
|
+
@cache = []
|
|
66
|
+
@search_paths.each do |dir|
|
|
67
|
+
next unless Dir.exist?(dir)
|
|
68
|
+
|
|
69
|
+
Dir.glob("**/*.{ttf,otf,ttc}", base: dir).each do |rel_path|
|
|
70
|
+
full_path = File.join(dir, rel_path)
|
|
71
|
+
info = parse_font_info(full_path)
|
|
72
|
+
@cache << info if info
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
@cache.freeze
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_font_info(path)
|
|
79
|
+
data = File.binread(path)
|
|
80
|
+
return nil unless valid_font_magic?(data)
|
|
81
|
+
|
|
82
|
+
ttf = Pdfrb::Font::TrueType::File.new(data)
|
|
83
|
+
name_table = ttf.name_table
|
|
84
|
+
return FontInfo.new(path: path) unless name_table
|
|
85
|
+
|
|
86
|
+
records = parse_name_table(name_table)
|
|
87
|
+
family = records[1] || records[16]
|
|
88
|
+
subfamily = records[2] || records[17]
|
|
89
|
+
ps_name = records[6]
|
|
90
|
+
|
|
91
|
+
FontInfo.new(path: path, family: family, style: subfamily, ps_name: ps_name)
|
|
92
|
+
rescue StandardError
|
|
93
|
+
FontInfo.new(path: path)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def valid_font_magic?(data)
|
|
97
|
+
return false if data.bytesize < 4
|
|
98
|
+
|
|
99
|
+
magic = data.byteslice(0, 4)
|
|
100
|
+
return true if magic == "ttcf".b
|
|
101
|
+
return true if magic == "\x00\x01\x00\x00".b
|
|
102
|
+
return true if magic == "OTTO".b
|
|
103
|
+
return true if magic == "true".b
|
|
104
|
+
return true if magic == "typ1".b
|
|
105
|
+
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parse_name_table(name_data)
|
|
110
|
+
results = {}
|
|
111
|
+
return results unless name_data && name_data.bytesize >= 6
|
|
112
|
+
|
|
113
|
+
name_data.unpack1("n")
|
|
114
|
+
count = name_data.unpack1("n", offset: 2)
|
|
115
|
+
string_offset = name_data.unpack1("n", offset: 4)
|
|
116
|
+
|
|
117
|
+
count.times do |i|
|
|
118
|
+
offset = 6 + (i * 12)
|
|
119
|
+
break if offset + 12 > name_data.bytesize
|
|
120
|
+
|
|
121
|
+
platform_id, _encoding_id, _lang_id, name_id, length, str_offset =
|
|
122
|
+
name_data.unpack("nnnnnn", offset: offset)
|
|
123
|
+
next unless [1, 3].include?(platform_id)
|
|
124
|
+
|
|
125
|
+
raw = name_data.byteslice(string_offset + str_offset, length)
|
|
126
|
+
next unless raw
|
|
127
|
+
|
|
128
|
+
str = if platform_id == 3
|
|
129
|
+
raw.unpack("n*").pack("U*")
|
|
130
|
+
else
|
|
131
|
+
raw.force_encoding("UTF-8")
|
|
132
|
+
end
|
|
133
|
+
results[name_id] = str
|
|
134
|
+
end
|
|
135
|
+
results
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Image
|
|
5
|
+
# Read-only metadata extractor for embedded image XObjects. Used
|
|
6
|
+
# by Task::Optimize to find downsampling candidates and by callers
|
|
7
|
+
# that need to know what's in a document without re-parsing every
|
|
8
|
+
# stream.
|
|
9
|
+
module Audit
|
|
10
|
+
ImageInfo = Struct.new(
|
|
11
|
+
:oid, :width, :height, :bits_per_component, :color_space,
|
|
12
|
+
:filter_name, :decoded_bytesize, :stream_bytesize, :smask_oid,
|
|
13
|
+
keyword_init: true
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Enumerate every image XObject in +document+. Yields an
|
|
19
|
+
# ImageInfo per image; returns an Enumerator if no block given.
|
|
20
|
+
def each_image(document)
|
|
21
|
+
return enum_for(:each_image, document) unless block_given?
|
|
22
|
+
|
|
23
|
+
document.each_indirect_object do |obj|
|
|
24
|
+
next unless image_xobject?(obj)
|
|
25
|
+
|
|
26
|
+
yield info_for(obj)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Return an Array of all image infos (mirror of #each_image).
|
|
31
|
+
def all(document)
|
|
32
|
+
each_image(document).to_a
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Find images whose pixel count exceeds +target_pixels+. Useful
|
|
36
|
+
# for spotting oversized images worth downsampling. Returns an
|
|
37
|
+
# Array of ImageInfo.
|
|
38
|
+
def oversized(document, target_pixels:)
|
|
39
|
+
all(document).select do |info|
|
|
40
|
+
(info.width || 0) * (info.height || 0) > target_pixels
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def image_xobject?(obj)
|
|
45
|
+
return false unless obj.is_a?(Pdfrb::Model::Cos::Stream)
|
|
46
|
+
|
|
47
|
+
obj.value[:Type] == :XObject && obj.value[:Subtype] == :Image
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def info_for(image)
|
|
51
|
+
ImageInfo.new(
|
|
52
|
+
oid: image.oid,
|
|
53
|
+
width: image.value[:Width],
|
|
54
|
+
height: image.value[:Height],
|
|
55
|
+
bits_per_component: image.value[:BitsPerComponent],
|
|
56
|
+
color_space: image.value[:ColorSpace],
|
|
57
|
+
filter_name: image.value[:Filter],
|
|
58
|
+
decoded_bytesize: decoded_size(image),
|
|
59
|
+
stream_bytesize: image.stream&.bytesize || 0,
|
|
60
|
+
smask_oid: smask_oid(image)
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def decoded_size(image)
|
|
65
|
+
w = image.value[:Width]
|
|
66
|
+
h = image.value[:Height]
|
|
67
|
+
bpc = image.value[:BitsPerComponent] || 8
|
|
68
|
+
return nil unless w && h
|
|
69
|
+
|
|
70
|
+
channels = channels_for(image.value[:ColorSpace])
|
|
71
|
+
(w * h * channels * bpc) / 8
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def channels_for(color_space)
|
|
75
|
+
case color_space
|
|
76
|
+
when :DeviceGray, :CalGray, :Indexed then 1
|
|
77
|
+
when :DeviceCMYK then 4
|
|
78
|
+
else
|
|
79
|
+
# :DeviceRGB, :CalRGB, :Lab, nil, and ICCBased (default to
|
|
80
|
+
# 3 channels when we can't introspect the profile) all share
|
|
81
|
+
# this branch.
|
|
82
|
+
3
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def smask_oid(image)
|
|
87
|
+
smask = image.value[:SMask]
|
|
88
|
+
return nil unless smask.is_a?(Pdfrb::Model::Reference)
|
|
89
|
+
|
|
90
|
+
smask.oid
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|