pdfrb 0.2.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +24 -0
- data/Gemfile +1 -0
- data/PROPOSAL.canvas-clip.md +92 -0
- data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
- data/PROPOSAL.canvas-rich-text.md +103 -0
- data/PROPOSAL.canvas-text-lines.md +77 -0
- data/PROPOSAL.canvas-transparency.md +106 -0
- data/PROPOSAL.external-font-metrics.md +129 -0
- data/PROPOSAL.font-embedding-fontfile2.md +88 -0
- data/PROPOSAL.font-resolver-ttc.md +105 -0
- data/PROPOSAL.font-resolver.md +96 -0
- data/PROPOSAL.font-subsetting.md +98 -0
- data/PROPOSAL.gradient-shadings.md +110 -0
- data/PROPOSAL.page-mediabox-setter.md +66 -0
- data/PROPOSAL.per-glyph-width.md +91 -0
- data/PROPOSAL.stringio-require.md +53 -0
- data/PROPOSAL.struct-tree-root.md +102 -0
- data/PROPOSAL.text-measurement.md +93 -0
- data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
- data/RELEASE.md +48 -0
- data/lib/pdfrb/action/base.rb +21 -0
- data/lib/pdfrb/action/types.rb +178 -0
- data/lib/pdfrb/action.rb +55 -0
- data/lib/pdfrb/annotation/base.rb +79 -0
- data/lib/pdfrb/annotation/types.rb +222 -0
- data/lib/pdfrb/annotation.rb +78 -0
- data/lib/pdfrb/appearance/generator.rb +216 -0
- data/lib/pdfrb/appearance.rb +11 -0
- data/lib/pdfrb/arlington/object_definition.rb +2 -2
- data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
- data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
- data/lib/pdfrb/arlington/predicate.rb +1 -5
- data/lib/pdfrb/cli/batch.rb +18 -0
- data/lib/pdfrb/cli/debug_info.rb +18 -0
- data/lib/pdfrb/cli/files.rb +16 -0
- data/lib/pdfrb/cli/fonts.rb +16 -0
- data/lib/pdfrb/cli/form.rb +18 -0
- data/lib/pdfrb/cli/image2pdf.rb +18 -0
- data/lib/pdfrb/cli/images.rb +34 -0
- data/lib/pdfrb/cli/info.rb +20 -0
- data/lib/pdfrb/cli/inspect.rb +18 -0
- data/lib/pdfrb/cli/merge.rb +18 -0
- data/lib/pdfrb/cli/modify.rb +18 -0
- data/lib/pdfrb/cli/optimize.rb +18 -0
- data/lib/pdfrb/cli/split.rb +21 -0
- data/lib/pdfrb/cli/watermark.rb +18 -0
- data/lib/pdfrb/cli.rb +312 -7
- data/lib/pdfrb/color/color_space.rb +259 -0
- data/lib/pdfrb/color/icc_profile.rb +90 -0
- data/lib/pdfrb/color.rb +12 -0
- data/lib/pdfrb/compare/comparator.rb +244 -0
- data/lib/pdfrb/compare/report.rb +78 -0
- data/lib/pdfrb/compare.rb +42 -0
- data/lib/pdfrb/composer.rb +161 -0
- data/lib/pdfrb/conformance/pdf_a.rb +317 -56
- data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
- data/lib/pdfrb/conformance/pdf_x.rb +247 -0
- data/lib/pdfrb/conformance/rule.rb +78 -0
- data/lib/pdfrb/conformance/structure_elements.rb +174 -0
- data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
- data/lib/pdfrb/conformance.rb +7 -3
- data/lib/pdfrb/content/canvas.rb +217 -72
- data/lib/pdfrb/content/color_space.rb +157 -0
- data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
- data/lib/pdfrb/content/operator.rb +21 -1
- data/lib/pdfrb/content/operators/clipping.rb +36 -0
- data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
- data/lib/pdfrb/content/operators.rb +21 -0
- data/lib/pdfrb/content/shading/base.rb +139 -0
- data/lib/pdfrb/content/shading.rb +17 -0
- data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
- data/lib/pdfrb/content/tiling_pattern.rb +78 -0
- data/lib/pdfrb/content/transformation_matrix.rb +61 -0
- data/lib/pdfrb/content.rb +7 -26
- data/lib/pdfrb/destination/fit.rb +185 -0
- data/lib/pdfrb/destination.rb +13 -0
- data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
- data/lib/pdfrb/digital_signature/signing.rb +107 -11
- data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
- data/lib/pdfrb/digital_signature/verification.rb +88 -9
- data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
- data/lib/pdfrb/digital_signature.rb +26 -2
- data/lib/pdfrb/document/annotations.rb +120 -3
- data/lib/pdfrb/document/associated_files.rb +104 -0
- data/lib/pdfrb/document/colors.rb +95 -0
- data/lib/pdfrb/document/destinations.rb +127 -5
- data/lib/pdfrb/document/display.rb +84 -0
- data/lib/pdfrb/document/fonts.rb +417 -45
- data/lib/pdfrb/document/form.rb +338 -0
- data/lib/pdfrb/document/form_xobject.rb +68 -0
- data/lib/pdfrb/document/graphics_state.rb +91 -0
- data/lib/pdfrb/document/images.rb +4 -4
- data/lib/pdfrb/document/info.rb +102 -0
- data/lib/pdfrb/document/layers.rb +62 -0
- data/lib/pdfrb/document/metadata.rb +12 -5
- data/lib/pdfrb/document/output_intents.rb +91 -0
- data/lib/pdfrb/document/page_labels.rb +80 -0
- data/lib/pdfrb/document/pages.rb +68 -19
- data/lib/pdfrb/document/portfolio.rb +119 -0
- data/lib/pdfrb/document/shadings.rb +159 -0
- data/lib/pdfrb/document/stamps.rb +68 -0
- data/lib/pdfrb/document/structure.rb +242 -0
- data/lib/pdfrb/document.rb +73 -4
- data/lib/pdfrb/encryption/identity.rb +13 -0
- data/lib/pdfrb/encryption/password_verification.rb +53 -8
- data/lib/pdfrb/encryption/security_handler.rb +8 -5
- data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
- data/lib/pdfrb/encryption.rb +17 -0
- data/lib/pdfrb/error.rb +2 -2
- data/lib/pdfrb/filter/brotli_decode.rb +32 -0
- data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
- data/lib/pdfrb/filter/crypt.rb +2 -2
- data/lib/pdfrb/filter/dct_decode.rb +18 -0
- data/lib/pdfrb/filter/flate_decode.rb +4 -18
- data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
- data/lib/pdfrb/filter/jpx_decode.rb +18 -0
- data/lib/pdfrb/filter/lzw_decode.rb +3 -2
- data/lib/pdfrb/filter/png_predictor.rb +94 -0
- data/lib/pdfrb/filter.rb +26 -13
- data/lib/pdfrb/font/afm_parser.rb +50 -26
- data/lib/pdfrb/font/cmap/writer.rb +9 -0
- data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
- data/lib/pdfrb/font/encoding.rb +32 -3
- data/lib/pdfrb/font/metrics_helper.rb +121 -0
- data/lib/pdfrb/font/true_type/file.rb +10 -1
- data/lib/pdfrb/font/true_type/glyf.rb +82 -0
- data/lib/pdfrb/font/true_type/kern.rb +58 -0
- data/lib/pdfrb/font/true_type/loca.rb +42 -0
- data/lib/pdfrb/font/true_type/maxp.rb +44 -0
- data/lib/pdfrb/font/true_type/name.rb +53 -0
- data/lib/pdfrb/font/true_type/post.rb +93 -0
- data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
- data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
- data/lib/pdfrb/font/true_type.rb +7 -0
- data/lib/pdfrb/font.rb +2 -0
- data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
- data/lib/pdfrb/font_loader/from_file.rb +20 -0
- data/lib/pdfrb/font_loader/standard14.rb +20 -0
- data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
- data/lib/pdfrb/font_loader.rb +4 -0
- data/lib/pdfrb/font_resolver.rb +138 -0
- data/lib/pdfrb/image/audit.rb +94 -0
- data/lib/pdfrb/image/downsampler.rb +124 -0
- data/lib/pdfrb/image.rb +13 -0
- data/lib/pdfrb/image_loader/jpeg.rb +39 -58
- data/lib/pdfrb/image_loader/png.rb +31 -257
- data/lib/pdfrb/layout/bidi.rb +198 -0
- data/lib/pdfrb/layout/box.rb +49 -0
- data/lib/pdfrb/layout/box_fitter.rb +54 -0
- data/lib/pdfrb/layout/column_box.rb +71 -0
- data/lib/pdfrb/layout/container_box.rb +45 -0
- data/lib/pdfrb/layout/frame.rb +67 -0
- data/lib/pdfrb/layout/image_box.rb +44 -0
- data/lib/pdfrb/layout/inline_box.rb +25 -0
- data/lib/pdfrb/layout/line.rb +55 -0
- data/lib/pdfrb/layout/list_box.rb +87 -0
- data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
- data/lib/pdfrb/layout/page_style.rb +65 -0
- data/lib/pdfrb/layout/style.rb +62 -0
- data/lib/pdfrb/layout/table_box.rb +66 -0
- data/lib/pdfrb/layout/text_box.rb +38 -0
- data/lib/pdfrb/layout/text_fragment.rb +45 -0
- data/lib/pdfrb/layout/text_layouter.rb +123 -0
- data/lib/pdfrb/layout.rb +28 -0
- data/lib/pdfrb/linearization/hint_stream.rb +93 -0
- data/lib/pdfrb/linearization/writer.rb +259 -0
- data/lib/pdfrb/linearization.rb +21 -0
- data/lib/pdfrb/model/cos/dictionary.rb +12 -0
- data/lib/pdfrb/model/pdf_array.rb +2 -2
- data/lib/pdfrb/model/type/action.rb +42 -0
- data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
- data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
- data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
- data/lib/pdfrb/model/type/action_goto.rb +34 -0
- data/lib/pdfrb/model/type/action_hide.rb +26 -0
- data/lib/pdfrb/model/type/action_import_data.rb +18 -0
- data/lib/pdfrb/model/type/action_java_script.rb +18 -0
- data/lib/pdfrb/model/type/action_launch.rb +22 -0
- data/lib/pdfrb/model/type/action_movie.rb +22 -0
- data/lib/pdfrb/model/type/action_named.rb +23 -0
- data/lib/pdfrb/model/type/action_nop.rb +12 -0
- data/lib/pdfrb/model/type/action_rendition.rb +28 -0
- data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
- data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
- data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
- data/lib/pdfrb/model/type/action_set_state.rb +14 -0
- data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
- data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
- data/lib/pdfrb/model/type/action_thread.rb +23 -0
- data/lib/pdfrb/model/type/action_trans.rb +30 -0
- data/lib/pdfrb/model/type/action_uri.rb +30 -0
- data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
- data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
- data/lib/pdfrb/model/type/annotation.rb +49 -10
- data/lib/pdfrb/model/type/appearance.rb +41 -0
- data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
- data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
- data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
- data/lib/pdfrb/model/type/border_effect.rb +25 -0
- data/lib/pdfrb/model/type/border_styling.rb +38 -0
- data/lib/pdfrb/model/type/box_color_info.rb +21 -0
- data/lib/pdfrb/model/type/box_style.rb +19 -0
- data/lib/pdfrb/model/type/button.rb +17 -0
- data/lib/pdfrb/model/type/cal_gray.rb +17 -0
- data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
- data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
- data/lib/pdfrb/model/type/catalog.rb +99 -16
- data/lib/pdfrb/model/type/choice.rb +21 -0
- data/lib/pdfrb/model/type/cid_font.rb +52 -1
- data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
- data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
- data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
- data/lib/pdfrb/model/type/collection.rb +27 -0
- data/lib/pdfrb/model/type/collection_field.rb +24 -0
- data/lib/pdfrb/model/type/collection_item.rb +19 -0
- data/lib/pdfrb/model/type/collection_schema.rb +25 -0
- data/lib/pdfrb/model/type/collection_sort.rb +13 -0
- data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
- data/lib/pdfrb/model/type/device_n.rb +37 -0
- data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
- data/lib/pdfrb/model/type/device_n_process.rb +13 -0
- data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
- data/lib/pdfrb/model/type/document_security_store.rb +55 -0
- data/lib/pdfrb/model/type/embedded_file.rb +19 -0
- data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
- data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
- data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
- data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
- data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
- data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
- data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
- data/lib/pdfrb/model/type/file_specification.rb +43 -0
- data/lib/pdfrb/model/type/file_trailer.rb +54 -0
- data/lib/pdfrb/model/type/font.rb +73 -2
- data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
- data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
- data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
- data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
- data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
- data/lib/pdfrb/model/type/font_encoding.rb +47 -0
- data/lib/pdfrb/model/type/font_file.rb +28 -0
- data/lib/pdfrb/model/type/font_file2.rb +32 -0
- data/lib/pdfrb/model/type/font_file3.rb +23 -0
- data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
- data/lib/pdfrb/model/type/font_true_type.rb +1 -1
- data/lib/pdfrb/model/type/font_type0.rb +1 -1
- data/lib/pdfrb/model/type/font_type1.rb +1 -1
- data/lib/pdfrb/model/type/font_type3.rb +1 -1
- data/lib/pdfrb/model/type/form_field.rb +73 -0
- data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
- data/lib/pdfrb/model/type/function.rb +42 -0
- data/lib/pdfrb/model/type/function_exponential.rb +31 -0
- data/lib/pdfrb/model/type/function_postscript.rb +25 -0
- data/lib/pdfrb/model/type/function_sampled.rb +55 -0
- data/lib/pdfrb/model/type/function_stitching.rb +33 -0
- data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
- data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
- data/lib/pdfrb/model/type/halftone.rb +21 -0
- data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
- data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
- data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
- data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
- data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
- data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
- data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
- data/lib/pdfrb/model/type/icon_fit.rb +31 -0
- data/lib/pdfrb/model/type/indexed.rb +32 -0
- data/lib/pdfrb/model/type/info.rb +44 -0
- data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
- data/lib/pdfrb/model/type/interactive_form.rb +36 -0
- data/lib/pdfrb/model/type/interior_color.rb +12 -0
- data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
- data/lib/pdfrb/model/type/lab.rb +27 -0
- data/lib/pdfrb/model/type/line_annotation.rb +44 -0
- data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
- data/lib/pdfrb/model/type/link_annotation.rb +41 -0
- data/lib/pdfrb/model/type/link_appearance.rb +9 -0
- data/lib/pdfrb/model/type/mark_information.rb +23 -0
- data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
- data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
- data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
- data/lib/pdfrb/model/type/measure.rb +39 -0
- data/lib/pdfrb/model/type/media_clip.rb +28 -0
- data/lib/pdfrb/model/type/media_offset.rb +106 -0
- data/lib/pdfrb/model/type/metadata.rb +27 -3
- data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
- data/lib/pdfrb/model/type/movie.rb +28 -0
- data/lib/pdfrb/model/type/names.rb +51 -0
- data/lib/pdfrb/model/type/namespace.rb +27 -0
- data/lib/pdfrb/model/type/object_reference.rb +31 -0
- data/lib/pdfrb/model/type/object_stream.rb +38 -0
- data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
- data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
- data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
- data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
- data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
- data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
- data/lib/pdfrb/model/type/outline.rb +40 -6
- data/lib/pdfrb/model/type/outline_item.rb +45 -0
- data/lib/pdfrb/model/type/output_intent.rb +30 -0
- data/lib/pdfrb/model/type/page.rb +127 -0
- data/lib/pdfrb/model/type/page_label.rb +68 -0
- data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
- data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
- data/lib/pdfrb/model/type/pattern.rb +16 -0
- data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
- data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
- data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
- data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
- data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
- data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
- data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
- data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
- data/lib/pdfrb/model/type/rendition.rb +31 -0
- data/lib/pdfrb/model/type/resources.rb +59 -14
- data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
- data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
- data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
- data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
- data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
- data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
- data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
- data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
- data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
- data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
- data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
- data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
- data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
- data/lib/pdfrb/model/type/separation.rb +25 -0
- data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
- data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
- data/lib/pdfrb/model/type/signature.rb +65 -0
- data/lib/pdfrb/model/type/signature_build.rb +86 -0
- data/lib/pdfrb/model/type/signature_field.rb +17 -0
- data/lib/pdfrb/model/type/software_identifier.rb +19 -0
- data/lib/pdfrb/model/type/sound.rb +38 -0
- data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
- data/lib/pdfrb/model/type/square_annotation.rb +24 -0
- data/lib/pdfrb/model/type/square_circle.rb +13 -0
- data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
- data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
- data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
- data/lib/pdfrb/model/type/struct_elem.rb +75 -0
- data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
- data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
- data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
- data/lib/pdfrb/model/type/text_annotation.rb +25 -0
- data/lib/pdfrb/model/type/text_appearance.rb +9 -0
- data/lib/pdfrb/model/type/text_field.rb +21 -0
- data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
- data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
- data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
- data/lib/pdfrb/model/type/three_d_background.rb +28 -0
- data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
- data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
- data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
- data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
- data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
- data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
- data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
- data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
- data/lib/pdfrb/model/type/three_d_node.rb +37 -0
- data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
- data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
- data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
- data/lib/pdfrb/model/type/three_d_units.rb +18 -0
- data/lib/pdfrb/model/type/three_d_view.rb +40 -0
- data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
- data/lib/pdfrb/model/type/transition.rb +68 -0
- data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
- data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
- data/lib/pdfrb/model/type/uri_dict.rb +18 -0
- data/lib/pdfrb/model/type/url_alias.rb +19 -0
- data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
- data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
- data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
- data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
- data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
- data/lib/pdfrb/model/type/xobject_form.rb +41 -0
- data/lib/pdfrb/model/type/xobject_image.rb +56 -0
- data/lib/pdfrb/model/type/xref_stream.rb +37 -0
- data/lib/pdfrb/model/type.rb +286 -6
- data/lib/pdfrb/serializer.rb +2 -2
- data/lib/pdfrb/source/tokenizer.rb +0 -2
- data/lib/pdfrb/task/benchmark.rb +83 -0
- data/lib/pdfrb/task/extract_text.rb +1 -1
- data/lib/pdfrb/task/generate_corpus.rb +130 -0
- data/lib/pdfrb/task/memory_profile.rb +84 -0
- data/lib/pdfrb/task/optimize.rb +97 -7
- data/lib/pdfrb/task.rb +3 -3
- data/lib/pdfrb/test_utils.rb +112 -0
- data/lib/pdfrb/validator.rb +82 -0
- data/lib/pdfrb/version.rb +1 -1
- data/lib/pdfrb/writer.rb +117 -27
- data/lib/pdfrb/xmp/packet.rb +122 -0
- data/lib/pdfrb/xmp/schemas.rb +96 -0
- data/lib/pdfrb/xmp.rb +44 -0
- data/lib/pdfrb/xref_section.rb +2 -2
- data/lib/pdfrb.rb +25 -53
- data/pdfrb.gemspec +45 -0
- data/script/apply_fixes.sh +843 -0
- data/script/make_jpeg_fixture.rb +53 -0
- data/script/make_png_fixture.rb +25 -0
- metadata +312 -2
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# Paragraph-level Unicode bidi reordering (UAX #9, simplified).
|
|
6
|
+
#
|
|
7
|
+
# Implements:
|
|
8
|
+
# * P2-P3: paragraph embedding level from the first strong
|
|
9
|
+
# character (L => 0, R/AL => 1).
|
|
10
|
+
# * X9: skip BN/RLO/RLE/LRO/LRE/PDF/RLI/LRI/FSI (treated as
|
|
11
|
+
# neutral for this paragraph-level pass).
|
|
12
|
+
# * I1-I2: implicit level resolution for strong characters.
|
|
13
|
+
# * L1-L4: whitespace resets, neutrals on level boundaries,
|
|
14
|
+
# paired-bracket mirroring.
|
|
15
|
+
# * L2: reverse character order on level runs from highest to
|
|
16
|
+
# lowest, producing a visual order string.
|
|
17
|
+
#
|
|
18
|
+
# Scope: paragraph-level reordering only (no full isolating-run
|
|
19
|
+
# sequence handling). Sufficient for Hebrew/Arabic paragraphs and
|
|
20
|
+
# embedded LTR runs inside RTL paragraphs (and vice versa). Not
|
|
21
|
+
# sufficient for complex mixed-direction runs at character
|
|
22
|
+
# granularity inside a single visual line.
|
|
23
|
+
module Bidi
|
|
24
|
+
LEFT_TO_RIGHT = 0
|
|
25
|
+
RIGHT_TO_LEFT = 1
|
|
26
|
+
|
|
27
|
+
# Mirroring map for paired brackets (UAX #9 Bidi_Mirroring_Glyph
|
|
28
|
+
# property, subset covering common punctuation).
|
|
29
|
+
MIRROR = {
|
|
30
|
+
"(" => ")",
|
|
31
|
+
")" => "(",
|
|
32
|
+
"[" => "]",
|
|
33
|
+
"]" => "[",
|
|
34
|
+
"{" => "}",
|
|
35
|
+
"}" => "{",
|
|
36
|
+
"<" => ">",
|
|
37
|
+
">" => "<",
|
|
38
|
+
"‘" => "’",
|
|
39
|
+
"’" => "‘",
|
|
40
|
+
"“" => "”",
|
|
41
|
+
"”" => "“",
|
|
42
|
+
"«" => "»",
|
|
43
|
+
"»" => "«",
|
|
44
|
+
"‹" => "›",
|
|
45
|
+
"›" => "‹",
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
module_function
|
|
49
|
+
|
|
50
|
+
# Returns the resolved paragraph embedding level: 0 for LTR
|
|
51
|
+
# (or no strong direction), 1 for RTL.
|
|
52
|
+
def paragraph_level(text)
|
|
53
|
+
text.to_s.each_codepoint do |cp|
|
|
54
|
+
case strong_direction(cp)
|
|
55
|
+
when :ltr then return LEFT_TO_RIGHT
|
|
56
|
+
when :rtl then return RIGHT_TO_LEFT
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
LEFT_TO_RIGHT
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Reorder +text+ into visual order. Returns a new String of the
|
|
63
|
+
# same grapheme count.
|
|
64
|
+
def reorder(text)
|
|
65
|
+
clusters = text.to_s.grapheme_clusters.to_a
|
|
66
|
+
return text.to_s if clusters.size <= 1
|
|
67
|
+
|
|
68
|
+
base = paragraph_level(text)
|
|
69
|
+
levels = levels_for(clusters, base)
|
|
70
|
+
visual = reverse_by_levels(clusters, levels)
|
|
71
|
+
mirror_runs(visual, levels)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Whether +text+ contains any RTL strong character.
|
|
75
|
+
def rtl?(text)
|
|
76
|
+
text.to_s.each_codepoint.any? { |cp| strong_direction(cp) == :rtl }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# ---- Character classification (UAX #9 BD1-BD2, trimmed) ----
|
|
80
|
+
|
|
81
|
+
# Strong direction (:ltr, :rtl, or nil for non-strong). Used by
|
|
82
|
+
# paragraph_level to find the first strong character per P2-P3.
|
|
83
|
+
def strong_direction(codepoint)
|
|
84
|
+
case bidi_type(codepoint)
|
|
85
|
+
when :L then :ltr
|
|
86
|
+
when :R, :AL then :rtl
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# UAX #9 bidi type for a codepoint. Subset implementation:
|
|
91
|
+
# covers Latin, Hebrew, Arabic, digits, common punctuation.
|
|
92
|
+
def bidi_type(codepoint)
|
|
93
|
+
case codepoint
|
|
94
|
+
when 0x0660..0x0669, 0x06F0..0x06F9 then :AN
|
|
95
|
+
when 0x0300..0x036F, 0x0483..0x0489,
|
|
96
|
+
0x0591..0x05BD, 0x05BF, 0x05C1..0x05C2,
|
|
97
|
+
0x05C4..0x05C5, 0x05C7, 0x0610..0x061A,
|
|
98
|
+
0x064B..0x065F, 0x0670, 0x06D6..0x06DC,
|
|
99
|
+
0x06DF..0x06E4, 0x06E7..0x06E8,
|
|
100
|
+
0x06EA..0x06ED, 0x0711, 0x0730..0x074A then :NSM
|
|
101
|
+
when 0x0041..0x005A, 0x0061..0x007A,
|
|
102
|
+
0x00AA, 0x00B5, 0x00BA,
|
|
103
|
+
0x00C0..0x00D6, 0x00D8..0x00F6,
|
|
104
|
+
0x00F8..0x02B8, 0x02BB..0x02C1,
|
|
105
|
+
0x1D00..0x1D25, 0x1D2C..0x1D5C,
|
|
106
|
+
0x1D62..0x1D65, 0x1D6B..0x1D77,
|
|
107
|
+
0x1D79..0x1DBE, 0x1E00..0x1EFF,
|
|
108
|
+
0x2071, 0x207F, 0x2090..0x209C,
|
|
109
|
+
0x212A..0x212B, 0x2132, 0x214E,
|
|
110
|
+
0x2160..0x2188, 0x2C60..0x2C7F,
|
|
111
|
+
0xA722..0xA787, 0xA78B..0xA78E,
|
|
112
|
+
0xA790..0xA793, 0xA7A0..0xA7AA,
|
|
113
|
+
0xA7F8..0xA7FF, 0xFB00..0xFB06,
|
|
114
|
+
0xFB13..0xFB17 then :L
|
|
115
|
+
when 0x0590..0x05FF, 0x07C0..0x089F,
|
|
116
|
+
0xFB1D..0xFB4F then :R
|
|
117
|
+
when 0x0600..0x07BF, 0x08A0..0x08FF,
|
|
118
|
+
0xFB50..0xFDFF, 0xFE70..0xFEFF then :AL
|
|
119
|
+
when 0x0030..0x0039, 0x06DD then :EN
|
|
120
|
+
when 0x002B, 0x002D then :ES
|
|
121
|
+
when 0x0023, 0x0024, 0x0025, 0x00A2..0x00A5 then :ET
|
|
122
|
+
when 0x002C, 0x002E, 0x003A, 0x003B,
|
|
123
|
+
0x0028, 0x0029,
|
|
124
|
+
0x0000..0x0008, 0x000E..0x001B,
|
|
125
|
+
0x007F..0x0084, 0x0086..0x009F,
|
|
126
|
+
0x200B..0x200D, 0x2060, 0xFEFF then :ON
|
|
127
|
+
when 0x0020, 0x00A0, 0x1680, 0x2000..0x200A,
|
|
128
|
+
0x2028..0x2029, 0x202F, 0x205F, 0x3000 then :WS
|
|
129
|
+
else :BN
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# ---- Level resolution ----
|
|
134
|
+
|
|
135
|
+
# Compute embedding level per grapheme cluster using the implicit
|
|
136
|
+
# level resolution rules I1/I2 from UAX #9. NSM inherits the
|
|
137
|
+
# level of the preceding character (simplified: assumes the
|
|
138
|
+
# paragraph base level for the first character).
|
|
139
|
+
def levels_for(clusters, base_level)
|
|
140
|
+
prev = base_level
|
|
141
|
+
clusters.map do |cluster|
|
|
142
|
+
cp = cluster.codepoints.first || 0
|
|
143
|
+
level = implicit_level(bidi_type(cp), base_level, prev)
|
|
144
|
+
prev = level
|
|
145
|
+
level
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# I1/I2 from UAX #9. Even base levels add 1 for R/AL/AN, 2 for
|
|
150
|
+
# EN. Odd base levels add 1 for L/EN/AN.
|
|
151
|
+
def implicit_level(type, base_level, prev_level)
|
|
152
|
+
case type
|
|
153
|
+
when :NSM then prev_level
|
|
154
|
+
when :L then base_level.even? ? base_level : base_level + 1
|
|
155
|
+
when :R, :AL then base_level.even? ? base_level + 1 : base_level
|
|
156
|
+
when :AN then base_level + 1
|
|
157
|
+
when :EN then base_level + (base_level.even? ? 2 : 1)
|
|
158
|
+
else base_level
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# UAX #9 L2: reverse contiguous runs at each level, from highest
|
|
163
|
+
# to lowest. Returns a new array of clusters in visual order.
|
|
164
|
+
def reverse_by_levels(clusters, levels)
|
|
165
|
+
result = clusters.dup
|
|
166
|
+
max_level = levels.max || 0
|
|
167
|
+
min_odd = levels.min.to_i
|
|
168
|
+
min_odd += 1 if min_odd.even?
|
|
169
|
+
(min_odd..max_level).to_a.reverse.each do |level|
|
|
170
|
+
start = nil
|
|
171
|
+
levels.each_with_index do |lvl, i|
|
|
172
|
+
if lvl >= level
|
|
173
|
+
start ||= i
|
|
174
|
+
elsif start
|
|
175
|
+
result[start, i - start] = result[start, i - start].reverse
|
|
176
|
+
start = nil
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
next unless start
|
|
180
|
+
|
|
181
|
+
result[start, levels.size - start] = result[start, levels.size - start].reverse
|
|
182
|
+
end
|
|
183
|
+
result
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# UAX #9 L4: mirror paired brackets on RTL runs. Returns a
|
|
187
|
+
# string built from the visual-order cluster list with mirror
|
|
188
|
+
# glyphs substituted where applicable.
|
|
189
|
+
def mirror_runs(clusters, levels)
|
|
190
|
+
clusters.each_with_index.map do |cluster, i|
|
|
191
|
+
next cluster if levels[i].nil? || levels[i].even?
|
|
192
|
+
|
|
193
|
+
MIRROR[cluster] || cluster
|
|
194
|
+
end.join
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# Base class for all layout boxes. A Box has a width, height, and
|
|
6
|
+
# content; it knows how to fit itself into a Frame region and how
|
|
7
|
+
# to draw onto a Canvas at a given (x, y) location.
|
|
8
|
+
#
|
|
9
|
+
# Subclasses override +fit+ (compute height for given width) and
|
|
10
|
+
# +draw_content+ (render to canvas).
|
|
11
|
+
class Box
|
|
12
|
+
attr_reader :style, :width, :height
|
|
13
|
+
|
|
14
|
+
def initialize(width: 0, height: 0, style: Style.new(:base), **)
|
|
15
|
+
@width = width.to_f
|
|
16
|
+
@height = height.to_f
|
|
17
|
+
@style = style
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Fit this box into the given available (width, height) region.
|
|
21
|
+
# Returns true if it fits (sets @height); false otherwise.
|
|
22
|
+
# Subclasses override.
|
|
23
|
+
def fit?(_available_width, _available_height)
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Draw this box at (x, y) on +canvas+. Subclasses override
|
|
28
|
+
# +draw_content+.
|
|
29
|
+
def draw(canvas, x, y)
|
|
30
|
+
draw_background(canvas, x, y)
|
|
31
|
+
draw_content(canvas, x, y)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def supports_position_flow?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def empty?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
protected
|
|
43
|
+
|
|
44
|
+
def draw_background(canvas, x, y); end
|
|
45
|
+
|
|
46
|
+
def draw_content(canvas, x, y); end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# Fits an ordered list of boxes into a Frame, flowing to a new
|
|
6
|
+
# Frame (provided by a block) when one fills up.
|
|
7
|
+
class BoxFitter
|
|
8
|
+
attr_reader :boxes, :frame, :result, :overflow
|
|
9
|
+
|
|
10
|
+
def initialize(boxes:, frame:)
|
|
11
|
+
@boxes = boxes
|
|
12
|
+
@frame = frame
|
|
13
|
+
@result = []
|
|
14
|
+
@overflow = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fit
|
|
18
|
+
current_frame = @frame
|
|
19
|
+
cursor_y = current_frame.top
|
|
20
|
+
@boxes.each do |box|
|
|
21
|
+
loop do
|
|
22
|
+
position = current_frame.find_available_area(box.width || current_frame.width, box.height || (cursor_y - current_frame.bottom))
|
|
23
|
+
if position && box.fit?(position[2] || current_frame.width, position[3] || (cursor_y - current_frame.bottom))
|
|
24
|
+
@result << [box, position]
|
|
25
|
+
current_frame.remove_area(position[0], position[1] - box.height, box.width, box.height)
|
|
26
|
+
cursor_y = position[1] - box.height
|
|
27
|
+
break
|
|
28
|
+
else
|
|
29
|
+
next_frame = yield if block_given?
|
|
30
|
+
if next_frame
|
|
31
|
+
current_frame = next_frame
|
|
32
|
+
cursor_y = current_frame.top
|
|
33
|
+
else
|
|
34
|
+
@overflow << box
|
|
35
|
+
break
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def draw(canvas)
|
|
44
|
+
@result.each do |box, pos|
|
|
45
|
+
box.draw(canvas, pos[0], pos[1])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def fit_complete?
|
|
50
|
+
@overflow.empty?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# Lays out children into N columns of equal width separated by
|
|
6
|
+
# gutters. Children flow top-to-bottom in column 1, then wrap to
|
|
7
|
+
# column 2 when column 1 fills, etc.
|
|
8
|
+
class ColumnBox < Box
|
|
9
|
+
attr_reader :children, :columns, :gutter
|
|
10
|
+
|
|
11
|
+
def initialize(children:, columns: 2, gutter: 20, **)
|
|
12
|
+
super(**)
|
|
13
|
+
@children = children
|
|
14
|
+
@columns = columns.to_i
|
|
15
|
+
@gutter = gutter.to_f
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fit?(available_width, available_height)
|
|
19
|
+
column_w = (available_width - (@gutter * (@columns - 1))) / @columns
|
|
20
|
+
@child_heights = []
|
|
21
|
+
remaining_children = @children.dup
|
|
22
|
+
col_heights = Array.new(@columns, 0.0)
|
|
23
|
+
|
|
24
|
+
@columns.times do |col_idx|
|
|
25
|
+
col_remaining = available_height
|
|
26
|
+
while remaining_children.any?
|
|
27
|
+
child = remaining_children.first
|
|
28
|
+
if child.fit?(column_w, col_remaining)
|
|
29
|
+
col_heights[col_idx] += child.height
|
|
30
|
+
col_remaining -= child.height
|
|
31
|
+
remaining_children.shift
|
|
32
|
+
else
|
|
33
|
+
break
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
@column_width = column_w
|
|
38
|
+
@column_heights = col_heights
|
|
39
|
+
@width = available_width
|
|
40
|
+
@height = available_height
|
|
41
|
+
remaining_children.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def draw_content(canvas, x, y)
|
|
45
|
+
idx = 0
|
|
46
|
+
@column_heights.each_with_index do |_h, col_idx|
|
|
47
|
+
col_x = x + (col_idx * (@column_width + @gutter))
|
|
48
|
+
col_y = y
|
|
49
|
+
while idx < @children.size
|
|
50
|
+
child = @children[idx]
|
|
51
|
+
break if child_height_in_column(idx, col_idx).nil?
|
|
52
|
+
|
|
53
|
+
child.draw(canvas, col_x, col_y)
|
|
54
|
+
col_y -= child.height
|
|
55
|
+
idx += 1
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def empty?
|
|
61
|
+
@children.empty?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def child_height_in_column(child_idx, col_idx)
|
|
67
|
+
@column_heights[col_idx] if child_idx < @column_heights.size
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# A box that holds an ordered list of child boxes laid out
|
|
6
|
+
# vertically. Each child is fit in turn; if a child doesn't fit
|
|
7
|
+
# in the remaining height, +fit+ returns false and the fitter
|
|
8
|
+
# moves to a new Frame.
|
|
9
|
+
class ContainerBox < Box
|
|
10
|
+
attr_reader :children
|
|
11
|
+
|
|
12
|
+
def initialize(children: [], **)
|
|
13
|
+
super(**)
|
|
14
|
+
@children = children
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fit?(available_width, available_height)
|
|
18
|
+
remaining_height = available_height
|
|
19
|
+
@children.each do |child|
|
|
20
|
+
if child.fit?(available_width, remaining_height)
|
|
21
|
+
remaining_height -= child.height
|
|
22
|
+
else
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
@width = available_width
|
|
27
|
+
@height = available_height - remaining_height
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def draw(canvas, x, y)
|
|
32
|
+
super
|
|
33
|
+
offset_y = y
|
|
34
|
+
@children.each do |child|
|
|
35
|
+
child.draw(canvas, x, offset_y)
|
|
36
|
+
offset_y -= child.height
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def empty?
|
|
41
|
+
@children.empty?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# A rectangular region of a page where boxes are placed. The Frame
|
|
6
|
+
# tracks which areas are filled and exposes +find_available_area+
|
|
7
|
+
# to locate the next free region of a given width and height.
|
|
8
|
+
#
|
|
9
|
+
# Simple case: the Frame is a plain rectangle. Advanced: the
|
|
10
|
+
# +shape+ polygon allows non-rectangular regions (L-shaped, with
|
|
11
|
+
# cutouts around images).
|
|
12
|
+
class Frame
|
|
13
|
+
attr_reader :left, :bottom, :width, :height, :shape
|
|
14
|
+
|
|
15
|
+
def initialize(left:, bottom:, width:, height:, shape: nil)
|
|
16
|
+
@left = left.to_f
|
|
17
|
+
@bottom = bottom.to_f
|
|
18
|
+
@width = width.to_f
|
|
19
|
+
@height = height.to_f
|
|
20
|
+
@shape = shape || default_shape
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def right
|
|
24
|
+
@left + @width
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def top
|
|
28
|
+
@bottom + @height
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Locate the next free area of (width × height) within this Frame.
|
|
32
|
+
# Returns nil if none fits. After a successful find, callers
|
|
33
|
+
# typically call +remove_area+ with the returned rectangle to
|
|
34
|
+
# mark it as used.
|
|
35
|
+
def find_available_area(width, height)
|
|
36
|
+
w = width.to_f
|
|
37
|
+
h = height.to_f
|
|
38
|
+
return nil if w > @width || h > @height
|
|
39
|
+
|
|
40
|
+
[@left, @bottom + @height - h]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Mark the rectangle at (x, y) of (w × h) as filled. Subsequent
|
|
44
|
+
# +find_available_area+ calls avoid this region.
|
|
45
|
+
def remove_area(x, y, w, h)
|
|
46
|
+
return if x.nil? || y.nil?
|
|
47
|
+
|
|
48
|
+
@removed ||= []
|
|
49
|
+
@removed << [x.to_f, y.to_f, w.to_f, h.to_f]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def full?
|
|
53
|
+
@removed&.any?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def empty?
|
|
57
|
+
!full?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def default_shape
|
|
63
|
+
[[@left, @bottom], [right, @bottom], [right, top], [@left, top]]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# Renders an image (PNG or JPEG) into the layout flow. The image
|
|
6
|
+
# is loaded via Pdfrb::ImageLoader and registered with the
|
|
7
|
+
# document's Resources on first draw.
|
|
8
|
+
class ImageBox < Box
|
|
9
|
+
attr_reader :path, :image_data
|
|
10
|
+
|
|
11
|
+
def initialize(path:, document:, width: 0, height: 0, **)
|
|
12
|
+
super(width: width, height: height, **)
|
|
13
|
+
@path = path
|
|
14
|
+
@document = document
|
|
15
|
+
@image_data = File.binread(path)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fit?(available_width, available_height)
|
|
19
|
+
loader = Pdfrb::ImageLoader.load(@document, @image_data)
|
|
20
|
+
native_w = loader[:Width].to_f
|
|
21
|
+
native_h = loader[:Height].to_f
|
|
22
|
+
target_w = @width.positive? ? @width : native_w
|
|
23
|
+
target_h = @height.positive? ? @height : native_h
|
|
24
|
+
|
|
25
|
+
if @width.zero? && @height.zero?
|
|
26
|
+
scale = [available_width / native_w, available_height / native_h].min
|
|
27
|
+
scale = 1.0 if scale > 1
|
|
28
|
+
target_w = native_w * scale
|
|
29
|
+
target_h = native_h * scale
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@width = target_w
|
|
33
|
+
@height = target_h
|
|
34
|
+
@loaded = loader
|
|
35
|
+
@width <= available_width && @height <= available_height
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def draw_content(canvas, x, y)
|
|
39
|
+
name = canvas.document.images.add(@path)
|
|
40
|
+
canvas.draw_image(name, at: [x, y], width: @width, height: @height)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# A box that sits inline within a text line (e.g., a styled span
|
|
6
|
+
# or an inline icon). Width + height are fixed; Line arranges it
|
|
7
|
+
# alongside TextFragments.
|
|
8
|
+
class InlineBox < Box
|
|
9
|
+
attr_reader :block
|
|
10
|
+
|
|
11
|
+
def initialize(width:, height:, &block)
|
|
12
|
+
super(width: width, height: height)
|
|
13
|
+
@block = block
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def supports_position_flow?
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def draw_content(canvas, x, y)
|
|
21
|
+
@block&.call(canvas, x, y)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# A single line of text — an ordered list of TextFragment instances
|
|
6
|
+
# sharing a baseline. Knows its total width/height/ascent/descent.
|
|
7
|
+
class Line
|
|
8
|
+
attr_reader :fragments
|
|
9
|
+
|
|
10
|
+
def initialize(fragments: [])
|
|
11
|
+
@fragments = fragments
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def width
|
|
15
|
+
@fragments.sum(&:width)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def height
|
|
19
|
+
return 0 if @fragments.empty?
|
|
20
|
+
|
|
21
|
+
max_top = @fragments.map(&:y_max).max
|
|
22
|
+
min_bottom = @fragments.map(&:y_min).min
|
|
23
|
+
max_top - min_bottom
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ascender
|
|
27
|
+
@fragments.map(&:y_max).max || 0
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def descender
|
|
31
|
+
@fragments.map(&:y_min).min || 0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def empty?
|
|
35
|
+
@fragments.empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Draw the line at baseline (x, y) applying the requested alignment
|
|
39
|
+
# within the available width.
|
|
40
|
+
def draw(canvas, x, y, available_width:, alignment: :left)
|
|
41
|
+
offset = if alignment == :center
|
|
42
|
+
x + ((available_width - width) / 2)
|
|
43
|
+
elsif alignment == :right
|
|
44
|
+
x + available_width - width
|
|
45
|
+
else
|
|
46
|
+
x
|
|
47
|
+
end
|
|
48
|
+
@fragments.each do |frag|
|
|
49
|
+
frag.draw(canvas, offset, y)
|
|
50
|
+
offset += frag.width
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdfrb
|
|
4
|
+
module Layout
|
|
5
|
+
# Roman numeral converter. Extracted as a standalone helper so
|
|
6
|
+
# +ListBox+ and +PageLabel+ (model layer) can share it.
|
|
7
|
+
class RomanNumeral
|
|
8
|
+
MAPPINGS = [[1000, "M"], [900, "CM"], [500, "D"], [400, "CD"],
|
|
9
|
+
[100, "C"], [90, "XC"], [50, "L"], [40, "XL"],
|
|
10
|
+
[10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"]].freeze
|
|
11
|
+
|
|
12
|
+
def self.convert(n)
|
|
13
|
+
return "0" if n <= 0
|
|
14
|
+
|
|
15
|
+
result = +""
|
|
16
|
+
MAPPINGS.each do |value, symbol|
|
|
17
|
+
while n >= value
|
|
18
|
+
result << symbol
|
|
19
|
+
n -= value
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# A list of items, each rendered with a marker (bullet, decimal,
|
|
27
|
+
# alpha, roman, etc.). Children are boxes (typically TextBox).
|
|
28
|
+
class ListBox < Box
|
|
29
|
+
attr_reader :items, :marker_type
|
|
30
|
+
|
|
31
|
+
MARKERS = {
|
|
32
|
+
bullet: "•",
|
|
33
|
+
disc: "•",
|
|
34
|
+
circle: "○",
|
|
35
|
+
square: "▪",
|
|
36
|
+
decimal: ->(i) { "#{i + 1}." },
|
|
37
|
+
lower_alpha: ->(i) { "#{('a'.ord + i).chr}." },
|
|
38
|
+
upper_alpha: ->(i) { "#{('A'.ord + i).chr}." },
|
|
39
|
+
lower_roman: ->(i) { "#{RomanNumeral.convert(i + 1).downcase}." },
|
|
40
|
+
upper_roman: ->(i) { "#{RomanNumeral.convert(i + 1)}." },
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
def initialize(items:, marker_type: :bullet, **)
|
|
44
|
+
super(**)
|
|
45
|
+
@items = items
|
|
46
|
+
@marker_type = marker_type
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def fit?(available_width, available_height)
|
|
50
|
+
marker_w = 20.0
|
|
51
|
+
content_w = available_width - marker_w
|
|
52
|
+
remaining = available_height
|
|
53
|
+
@items.each do |item|
|
|
54
|
+
return false unless item.fit?(content_w, remaining)
|
|
55
|
+
|
|
56
|
+
remaining -= item.height
|
|
57
|
+
end
|
|
58
|
+
@width = available_width
|
|
59
|
+
@height = available_height - remaining
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def draw_content(canvas, x, y)
|
|
64
|
+
marker_w = 20.0
|
|
65
|
+
offset_y = y
|
|
66
|
+
@items.each_with_index do |item, i|
|
|
67
|
+
marker_text(canvas, x, offset_y, i)
|
|
68
|
+
item.draw(canvas, x + marker_w, offset_y)
|
|
69
|
+
offset_y -= item.height
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def empty?
|
|
74
|
+
@items.empty?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def marker_text(canvas, x, y, index)
|
|
80
|
+
marker = MARKERS[@marker_type] || MARKERS[:bullet]
|
|
81
|
+
text = marker.is_a?(Proc) ? marker.call(index) : marker
|
|
82
|
+
canvas.text(text, at: [x, y], font: :Helvetica,
|
|
83
|
+
size: @style.font_size || 10)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|