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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 14c300c425848b557b210be218c07de40adbb5188e0c36c15d7d72336395d7c9
|
|
4
|
+
data.tar.gz: 672e663a9efe0991f97aceda226a1fa909ef804d8dd2a94e1cb7f2df4d1cd1c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18c8b3c2c2a15a4fd6dff6f8d58c4403e2e23067f9ad8c655e4a8fe5d43678d748f8d0378eddfc92c9d98f118b710887c0bf95374756088f4885461373d1e30c
|
|
7
|
+
data.tar.gz: cfcbe27e32085ad4a9e753645a8e56571630c230f69aae6f289c7d618bc23bba98b49830be6e01a5317de922f78faba5ec3b1d106436dd75cf600872ab5e75e1
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the pdfrb gem will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.7.0] — 2026-08-08
|
|
6
|
+
|
|
7
|
+
### Added — Release infrastructure
|
|
8
|
+
|
|
9
|
+
* `.github/workflows/rake.yml` — Metanorma CI generic-rake workflow
|
|
10
|
+
for tests on every push, PR, tag, and dispatch.
|
|
11
|
+
* `.github/workflows/release.yml` — Metanorma CI rubygems-release
|
|
12
|
+
workflow driven by `workflow_dispatch` with `next_version` input
|
|
13
|
+
or `repository_dispatch` of type `do-release`.
|
|
14
|
+
* `RELEASE.md` — documents the publish flow and required secrets.
|
|
15
|
+
|
|
16
|
+
### Added — Image subsystem
|
|
17
|
+
|
|
18
|
+
* `Pdfrb::Image::Audit` — read-only metadata extractor (ImageInfo
|
|
19
|
+
struct per image XObject) with `each_image`, `all`, `oversized`
|
|
20
|
+
helpers.
|
|
21
|
+
* `Pdfrb::Image::Downsampler` — pure-Ruby nearest-neighbour for
|
|
22
|
+
FlateDecode-encoded 8-bpc non-palette images. PNG predictor
|
|
23
|
+
round-trip on both decode and encode. JPEG/JP2K skipped (no
|
|
24
|
+
native deps).
|
|
25
|
+
* `Pdfrb::Task::Optimize#downsample_images!(factor:)` — applies the
|
|
26
|
+
downsampler across every image XObject in the document.
|
|
27
|
+
|
|
28
|
+
### Added — Layout bidi
|
|
29
|
+
|
|
30
|
+
* `Pdfrb::Layout::Bidi` — paragraph-level UAX #9 bidi reordering
|
|
31
|
+
(P2-P3 paragraph level, BD1 type classification, I1/I2 implicit
|
|
32
|
+
levels, L2 reverse by level, L4 paired-bracket mirroring).
|
|
33
|
+
* `Pdfrb::Layout::TextLayouter#layout` calls `Bidi.reorder` before
|
|
34
|
+
line breaking, so Hebrew and Arabic render in visual order.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
* Issue #63: `Document::Fonts#add` now extracts the PostScript name
|
|
39
|
+
(name table nameID 6) and uses it in `/BaseFont` (e.g.
|
|
40
|
+
`ABCDEF+MinionPro-Regular` instead of `ABCDEF+EmbeddedFont3132`).
|
|
41
|
+
* Issue #64: `Model::Type::Resources#empty?` handles Hash, Array,
|
|
42
|
+
Cos::Dictionary, and PdfArray values via a single
|
|
43
|
+
`empty_collection?` helper.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
* `Pdfrb::Task::Optimize.dedup_streams!` now actually rewrites
|
|
48
|
+
references to duplicate streams (was a stub).
|
|
49
|
+
* `Pdfrb::Writer#write_xref_stream` emits a free entry for every
|
|
50
|
+
gap in the oid space, so dedup'd oids don't misalign the reader's
|
|
51
|
+
per-oid index.
|
|
52
|
+
* `Pdfrb::Document::Form#flatten!` stamps each field's `/AP /N`
|
|
53
|
+
appearance stream into the page content via the `/Do` operator
|
|
54
|
+
(was a stubbed no-op).
|
|
55
|
+
|
|
5
56
|
## [0.3.0] — 2026-08-02
|
|
6
57
|
|
|
7
58
|
### Added — Semantic PDF diff
|
data/Gemfile
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# PROPOSAL: Canvas clip operators
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Canvas#clip` and `Canvas#clip_path` methods for PDF clipping path
|
|
6
|
+
operators (`W`, `W*`, `n`), enabling content to be clipped to arbitrary
|
|
7
|
+
paths.
|
|
8
|
+
|
|
9
|
+
## Motivation
|
|
10
|
+
|
|
11
|
+
The IDML→PDF rendering pipeline (the `idml` gem) needs to clip placed
|
|
12
|
+
images to their parent Rectangle's bounds. Currently, pdfrb 0.3.0 has
|
|
13
|
+
no clip operator on Canvas. The `idml` gem implements a workaround via
|
|
14
|
+
`PdfrbExt::Clip` (custom `Operator::Base` subclass), but this belongs
|
|
15
|
+
in pdfrb itself.
|
|
16
|
+
|
|
17
|
+
Other use cases:
|
|
18
|
+
- Clipping text frames to their bounds
|
|
19
|
+
- Masking layers
|
|
20
|
+
- Cropping form XObjects
|
|
21
|
+
- Print marks (crop/bleed/trim)
|
|
22
|
+
|
|
23
|
+
## Proposed API
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# Set clipping path using nonzero winding rule.
|
|
27
|
+
# Must be called after defining a path (rectangle, move_to/line_to, etc.)
|
|
28
|
+
# and before drawing content that should be clipped.
|
|
29
|
+
# The clip persists until the graphics state is restored (Q).
|
|
30
|
+
def clip
|
|
31
|
+
emit_op(Operator::ClipNonZero)
|
|
32
|
+
emit_op(Operator::EndPath)
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Set clipping path using even-odd rule.
|
|
37
|
+
def clip_even_odd
|
|
38
|
+
emit_op(Operator::ClipEvenOdd)
|
|
39
|
+
emit_op(Operator::EndPath)
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Operator classes to add
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
module Pdfrb::Content::Operator
|
|
48
|
+
# PDF `W` — modify clipping path (nonzero winding)
|
|
49
|
+
class ClipNonZero < NoArg
|
|
50
|
+
def self.name; "W"; end
|
|
51
|
+
register
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# PDF `W*` — modify clipping path (even-odd)
|
|
55
|
+
class ClipEvenOdd < NoArg
|
|
56
|
+
def self.name; "W*"; end
|
|
57
|
+
register
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# PDF `n` — end path without filling or stroking
|
|
61
|
+
class EndPath < NoArg
|
|
62
|
+
def self.name; "n"; end
|
|
63
|
+
register
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage example
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
canvas.save_graphics_state do
|
|
72
|
+
canvas.rectangle(100, 100, 200, 200)
|
|
73
|
+
canvas.clip # clip to the rectangle
|
|
74
|
+
canvas.draw_image(:Im1, at: [50, 50], width: 300, height: 300)
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Implementation notes
|
|
79
|
+
|
|
80
|
+
- `W` and `W*` are technically not no-arg operators in the strict PDF
|
|
81
|
+
sense — they are modifiers applied to the current path. But since they
|
|
82
|
+
take no operands, `NoArg` is the correct base class.
|
|
83
|
+
- `n` is already listed as `EndPath` in the operator module but may not
|
|
84
|
+
be registered. If it is, `Canvas#end_path` already exists and can be
|
|
85
|
+
reused.
|
|
86
|
+
- The `clip` method should call `end_path` after `W`/`W*` to consume
|
|
87
|
+
the path, matching the common `W n` idiom.
|
|
88
|
+
|
|
89
|
+
## Priority
|
|
90
|
+
|
|
91
|
+
Medium — commonly needed for any PDF rendering pipeline that places
|
|
92
|
+
images or text within geometric frames.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# PROPOSAL: Canvas draw_image with transform matrix
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Canvas#draw_image_matrix(name, a:, b:, c:, d:, e:, f:)` for drawing
|
|
6
|
+
image XObjects with an arbitrary affine transform matrix, complementing
|
|
7
|
+
the existing `Canvas#draw_image(name, at:, width:, height:)` which only
|
|
8
|
+
supports position + uniform scale.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
The `idml` gem places images using IDML's `ItemTransform` — a full 2D
|
|
13
|
+
affine matrix `a b c d e f` that includes non-uniform scaling, rotation,
|
|
14
|
+
and translation. The current `draw_image` only accepts `at: [x, y]` and
|
|
15
|
+
`width:, height:`, which can't express rotation or skew.
|
|
16
|
+
|
|
17
|
+
Currently the `idml` gem works around this by emitting raw operators:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
canvas.concat(scale_x, 0, 0, scale_y, x, y)
|
|
21
|
+
canvas.emit_op(PdfrbExt::InvokeXObject, name)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This works but bypasses pdfrb's Canvas abstraction. A proper API method
|
|
25
|
+
would be cleaner and more discoverable.
|
|
26
|
+
|
|
27
|
+
## Proposed API
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# Draw an image XObject with a full affine transform matrix.
|
|
31
|
+
# The matrix maps the image's unit square (0,0)-(1,1) to the
|
|
32
|
+
# destination rectangle on the page.
|
|
33
|
+
#
|
|
34
|
+
# @param name [Symbol] image resource name from Document#images#add.
|
|
35
|
+
# @param a, b, c, d, e, f [Numeric] affine matrix components.
|
|
36
|
+
#
|
|
37
|
+
# Example: scale 200×150 and place at (72, 300):
|
|
38
|
+
# canvas.draw_image_matrix(:Im1, a: 200, b: 0, c: 0, d: 150, e: 72, f: 300)
|
|
39
|
+
#
|
|
40
|
+
# Example: rotate 45° and scale:
|
|
41
|
+
# rad = 45 * Math::PI / 180
|
|
42
|
+
# cos = Math.cos(rad)
|
|
43
|
+
# sin = Math.sin(rad)
|
|
44
|
+
# canvas.draw_image_matrix(:Im1, a: cos*200, b: sin*200, c: -sin*150, d: cos*150, e: 100, f: 400)
|
|
45
|
+
def draw_image_matrix(name, a:, b:, c:, d:, e:, f:)
|
|
46
|
+
save_graphics_state do
|
|
47
|
+
concat(a, b, c, d, e, f)
|
|
48
|
+
emit_op(Operator::InvokeXObject, name)
|
|
49
|
+
end
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Alternative: extend draw_image
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
def draw_image(name, at: nil, width: nil, height: nil, matrix: nil)
|
|
58
|
+
if matrix
|
|
59
|
+
a, b, c, d, e, f = matrix
|
|
60
|
+
save_graphics_state { concat(a, b, c, d, e, f); emit_op(Operator::InvokeXObject, name) }
|
|
61
|
+
else
|
|
62
|
+
# existing at:/width:/height: behavior
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Register InvokeXObject operator
|
|
68
|
+
|
|
69
|
+
This proposal also requires registering the `Do` (Invoke XObject)
|
|
70
|
+
operator in pdfrb's operator catalogue:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
module Pdfrb::Content::Operator
|
|
74
|
+
class InvokeXObject < Base
|
|
75
|
+
class << self
|
|
76
|
+
def name; "Do"; end
|
|
77
|
+
def serialize(serializer, name); "/#{name} Do\n"; end
|
|
78
|
+
end
|
|
79
|
+
register
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Priority
|
|
85
|
+
|
|
86
|
+
Medium — the `idml` gem has a workaround via PdfrbExt, but pdfrb should
|
|
87
|
+
have the `Do` operator and a matrix-aware image drawing method built-in.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# PROPOSAL: Canvas rich text — multi-run text in one BT/ET block
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Canvas#text_rich(runs, at:)` for drawing text with per-run font,
|
|
6
|
+
size, color, and positioning. Eliminates the need for N separate
|
|
7
|
+
`text` calls when a paragraph contains mixed styling (e.g., bold word
|
|
8
|
+
within regular text, different font sizes, color changes).
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
The `idml` gem's `TextFrameRenderer` renders styled text from IDML
|
|
13
|
+
documents where a single paragraph can contain multiple
|
|
14
|
+
`CharacterStyleRange` runs — each with its own font, size, and color.
|
|
15
|
+
Currently, each run requires a separate `canvas.text` call (separate
|
|
16
|
+
BT/ET block), which:
|
|
17
|
+
|
|
18
|
+
1. Produces a larger content stream (more BT/ET pairs).
|
|
19
|
+
2. Loses cursor continuity — the caller must compute the x-offset
|
|
20
|
+
after each run manually.
|
|
21
|
+
3. Can't use `TJ` (array show) for kerning between runs.
|
|
22
|
+
|
|
23
|
+
## Proposed API
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# Draw multiple text runs in a single BT/ET block with cursor
|
|
27
|
+
# tracking. Each run advances the text cursor by its width.
|
|
28
|
+
#
|
|
29
|
+
# @param runs [Array<Hash>] each hash has:
|
|
30
|
+
# :text [String] the text to show.
|
|
31
|
+
# :font [Symbol] font resource name.
|
|
32
|
+
# :size [Float] font size in points.
|
|
33
|
+
# :color [Array, nil] optional fill color (e.g., [:rgb, 1, 0, 0]).
|
|
34
|
+
# :char_spacing [Float, nil] optional character spacing.
|
|
35
|
+
# @param at [Array(Numeric, Numeric)] [x, y] of the first run's baseline.
|
|
36
|
+
#
|
|
37
|
+
# Example:
|
|
38
|
+
# canvas.text_rich([
|
|
39
|
+
# { text: "Hello ", font: :F1, size: 12 },
|
|
40
|
+
# { text: "World", font: :F1, size: 12, color: [:rgb, 1, 0, 0] },
|
|
41
|
+
# { text: "!", font: :F1, size: 14 },
|
|
42
|
+
# ], at: [72, 720])
|
|
43
|
+
def text_rich(runs, at:)
|
|
44
|
+
emit_op Operator::BeginText
|
|
45
|
+
cursor_x, cursor_y = at
|
|
46
|
+
|
|
47
|
+
runs.each do |run|
|
|
48
|
+
emit_op Operator::Font, run[:font], run[:size]
|
|
49
|
+
emit_op Operator::SetTextMatrix, 1, 0, 0, 1, cursor_x, cursor_y
|
|
50
|
+
if run[:color]
|
|
51
|
+
fill_color(run[:color])
|
|
52
|
+
end
|
|
53
|
+
encoded = document.fonts.encode_text(run[:text], run[:font])
|
|
54
|
+
width = document.fonts.measure_text(run[:text],
|
|
55
|
+
font: run[:font],
|
|
56
|
+
size: run[:size])
|
|
57
|
+
emit_op Operator::ShowText, escape_pdf_string(encoded)
|
|
58
|
+
cursor_x += width
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
emit_op Operator::EndText
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Alternative: TJ (array show) optimization
|
|
67
|
+
|
|
68
|
+
For runs with the same font and size, use `TJ` to show text with
|
|
69
|
+
positioning adjustments in a single operator:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
[(Hel) -50 (lo W) 20 (orld!)] TJ
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This enables kerning between runs and reduces operator count. But it
|
|
76
|
+
requires all runs to use the same font/size, which limits its use.
|
|
77
|
+
|
|
78
|
+
## Implementation notes
|
|
79
|
+
|
|
80
|
+
- `text_rich` uses `SetFont` (Tf) per run to switch fonts within the
|
|
81
|
+
same BT/ET block. This is valid PDF — Tf can be called multiple times.
|
|
82
|
+
- Color changes use `SetFillColor` (rg/k) between runs.
|
|
83
|
+
- `SetTextMatrix` (Tm) positions each run at the absolute cursor.
|
|
84
|
+
- The cursor advances by `measure_text(run[:text], ...)` after each run.
|
|
85
|
+
|
|
86
|
+
## Usage example
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# Render a paragraph with mixed styling
|
|
90
|
+
canvas.text_rich([
|
|
91
|
+
{ text: "This is ", font: :F1, size: 12 },
|
|
92
|
+
{ text: "bold", font: :F2, size: 12 }, # F2 = bold variant
|
|
93
|
+
{ text: " text in ", font: :F1, size: 12 },
|
|
94
|
+
{ text: "red", font: :F1, size: 12, color: [:rgb, 1, 0, 0] },
|
|
95
|
+
{ text: ".", font: :F1, size: 12 },
|
|
96
|
+
], at: [72, 720])
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Priority
|
|
100
|
+
|
|
101
|
+
Medium — the `idml` gem works around this with N `text` calls, but
|
|
102
|
+
`text_rich` would produce cleaner, smaller content streams and enable
|
|
103
|
+
proper kerning between styled runs.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# PROPOSAL: Canvas multi-line text helper
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Canvas#text_lines(lines, font:, size:, leading:, at:)` for drawing
|
|
6
|
+
multiple lines of text with consistent leading. Simplifies multi-line
|
|
7
|
+
text rendering that currently requires N separate `text` calls.
|
|
8
|
+
|
|
9
|
+
## Motivation
|
|
10
|
+
|
|
11
|
+
The `idml` gem's `TextFrameRenderer` breaks text into lines via a line
|
|
12
|
+
breaker, then calls `canvas.text` once per line at a manually computed
|
|
13
|
+
y position:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
lines.each do |line|
|
|
17
|
+
canvas.text(line.text, at: [x, baseline_y], font: font, size: size)
|
|
18
|
+
baseline_y -= size * 1.2
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This is verbose and error-prone (leading calculation, y overflow). A
|
|
23
|
+
multi-line helper centralizes the logic.
|
|
24
|
+
|
|
25
|
+
## Proposed API
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# Draw multiple lines of text with consistent leading.
|
|
29
|
+
# @param lines [Array<String>] text strings, one per line.
|
|
30
|
+
# @param font [Symbol] font resource name.
|
|
31
|
+
# @param size [Float] font size in points.
|
|
32
|
+
# @param leading [Float] line spacing in points (default: size * 1.2).
|
|
33
|
+
# @param at [Array(Numeric, Numeric)] [x, y] of the first line's baseline.
|
|
34
|
+
# @param char_spacing [Float, nil] optional character spacing.
|
|
35
|
+
# @param word_spacing [Float, nil] optional word spacing.
|
|
36
|
+
def text_lines(lines, font:, size:, at:, leading: nil,
|
|
37
|
+
char_spacing: nil, word_spacing: nil)
|
|
38
|
+
lead = leading || size * 1.2
|
|
39
|
+
x, y = at
|
|
40
|
+
lines.each do |line|
|
|
41
|
+
text(line, at: [x, y], font: font, size: size,
|
|
42
|
+
char_spacing: char_spacing, word_spacing: word_spacing)
|
|
43
|
+
y -= lead
|
|
44
|
+
end
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage example
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
# Simple multi-line text
|
|
53
|
+
canvas.text_lines(
|
|
54
|
+
["Line 1", "Line 2", "Line 3"],
|
|
55
|
+
font: :F1, size: 12, at: [72, 720]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Custom leading
|
|
59
|
+
canvas.text_lines(
|
|
60
|
+
wrapped_text_array,
|
|
61
|
+
font: :F1, size: 10, at: [72, 700], leading: 14
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Implementation notes
|
|
66
|
+
|
|
67
|
+
- Each line is a separate `text` call (no TJ array optimization).
|
|
68
|
+
This is intentional: different lines may have different widths
|
|
69
|
+
(justified vs left-aligned).
|
|
70
|
+
- The caller controls wrapping (via their own line breaker). This
|
|
71
|
+
method only handles vertical positioning.
|
|
72
|
+
- `leading` follows PDF convention: distance between baselines.
|
|
73
|
+
|
|
74
|
+
## Priority
|
|
75
|
+
|
|
76
|
+
Low — the current approach (N `text` calls) works. But the helper
|
|
77
|
+
reduces boilerplate in every text-heavy rendering pipeline.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# PROPOSAL: Canvas transparency and blend modes
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Canvas#opacity=`, `Canvas#blend_mode=`, and
|
|
6
|
+
`Canvas#with_transparency(opacity:, blend_mode:, &block)` for
|
|
7
|
+
rendering semi-transparent page items with blending modes
|
|
8
|
+
(Multiply, Screen, Overlay, etc.).
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
The `idml` gem's element models include transparency attributes
|
|
13
|
+
(`BlendingSetting`, `OpacityGradientStop`, `FillTransparencySetting`)
|
|
14
|
+
that carry Opacity, BlendMode, and IsolatedKnockout settings. Without
|
|
15
|
+
transparency support in pdfrb's Canvas, these attributes are silently
|
|
16
|
+
ignored — semi-transparent shapes render at full opacity.
|
|
17
|
+
|
|
18
|
+
IDML documents commonly use:
|
|
19
|
+
- Semi-transparent overlays (Opacity = 50%)
|
|
20
|
+
- Multiply blend mode for shadow effects
|
|
21
|
+
- Screen blend mode for highlights
|
|
22
|
+
- Isolated blending groups
|
|
23
|
+
|
|
24
|
+
## Proposed API
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class Pdfrb::Content::Canvas
|
|
28
|
+
# Set fill/stroke opacity (0.0–1.0).
|
|
29
|
+
# Uses ExtGState with /ca (fill alpha) and /CA (stroke alpha).
|
|
30
|
+
# @param alpha [Float] opacity value (0.0 = transparent, 1.0 = opaque).
|
|
31
|
+
def opacity=(alpha)
|
|
32
|
+
gs = document.ext_g_states.add(ca: alpha, CA: alpha)
|
|
33
|
+
emit_op Operator::ApplyExtGState, gs
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Set blend mode for subsequent drawing operations.
|
|
37
|
+
# @param mode [Symbol] one of: :Normal, :Multiply, :Screen,
|
|
38
|
+
# :Overlay, :Darken, :Lighten, :ColorDodge, :ColorBurn,
|
|
39
|
+
# :HardLight, :SoftLight, :Difference, :Exclusion,
|
|
40
|
+
# :Hue, :Saturation, :Color, :Luminosity.
|
|
41
|
+
def blend_mode=(mode)
|
|
42
|
+
gs = document.ext_g_states.add(BM: mode.to_s)
|
|
43
|
+
emit_op Operator::ApplyExtGState, gs
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Execute a block with transparency settings, then restore.
|
|
47
|
+
def with_transparency(opacity: 1.0, blend_mode: nil)
|
|
48
|
+
save_graphics_state
|
|
49
|
+
self.opacity = opacity if opacity < 1.0
|
|
50
|
+
self.blend_mode = blend_mode if blend_mode
|
|
51
|
+
yield self
|
|
52
|
+
restore_graphics_state
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## PDF structure
|
|
58
|
+
|
|
59
|
+
Transparency requires an `/ExtGState` (Extended Graphics State)
|
|
60
|
+
dictionary with alpha and blend mode entries:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
/ExtGState <<
|
|
64
|
+
/Type /ExtGState
|
|
65
|
+
/ca 0.5 % fill alpha
|
|
66
|
+
/CA 0.5 % stroke alpha
|
|
67
|
+
/BM /Multiply
|
|
68
|
+
/SMask << % soft mask (optional)
|
|
69
|
+
/Type /Mask /S /Alpha /G <form xobject>
|
|
70
|
+
>>
|
|
71
|
+
>>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The Canvas applies it via `gs` operator:
|
|
75
|
+
```
|
|
76
|
+
/GS1 gs
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Document-level support
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class Pdfrb::Document
|
|
83
|
+
class ExtGStates
|
|
84
|
+
def add(ca: nil, CA: nil, BM: nil, SMask: nil)
|
|
85
|
+
# Create ExtGState dict, register in /Resources/ExtGState
|
|
86
|
+
# Return resource name (e.g., :GS1)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Usage example
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# Semi-transparent red rectangle with multiply blend
|
|
96
|
+
canvas.with_transparency(opacity: 0.5, blend_mode: :Multiply) do
|
|
97
|
+
canvas.fill_color([:rgb, 1, 0, 0])
|
|
98
|
+
canvas.rectangle(100, 100, 200, 200)
|
|
99
|
+
canvas.fill
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Priority
|
|
104
|
+
|
|
105
|
+
Low — transparency is an advanced feature. Most IDML documents don't
|
|
106
|
+
use it. But for documents that do, it's a visible rendering gap.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# PROPOSAL: Accept externally-provided font metrics in Fonts#add
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Allow `Fonts#add` to accept pre-parsed font metrics (glyph widths,
|
|
6
|
+
ascent/descent, encoding) from the caller, instead of requiring pdfrb
|
|
7
|
+
to parse TTF/OTF binary tables internally. This lets consumers use
|
|
8
|
+
well-tested font parsers (like Fontisan) and pass the results to pdfrb.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
pdfrb's internal TTF table parser (`TrueType::File#table`) is broken
|
|
13
|
+
— it returns raw `String` bytes instead of parsed table objects, causing
|
|
14
|
+
`glyph_id_for` to fail for all TrueType/OpenType fonts. This breaks
|
|
15
|
+
`glyph_width`, `measure_text`, `metrics_for`, and font width arrays.
|
|
16
|
+
|
|
17
|
+
Fontisan (used by the `idml` gem) correctly parses all TTF tables
|
|
18
|
+
(cmap, hmtx, head, hhea, name) and provides per-glyph widths, metrics,
|
|
19
|
+
and encoding. It is battle-tested across hundreds of font files.
|
|
20
|
+
|
|
21
|
+
Instead of pdfrb reimplementing TTF parsing (and getting it wrong),
|
|
22
|
+
pdfrb should accept externally-provided metrics. This follows the
|
|
23
|
+
Unix philosophy: do one thing well. pdfrb assembles PDFs; Fontisan
|
|
24
|
+
parses fonts.
|
|
25
|
+
|
|
26
|
+
## Proposed API
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# Register a font with externally-provided metrics.
|
|
30
|
+
# @param name_or_io [String, IO] font file path or IO (for FontFile2
|
|
31
|
+
# embedding — pdfrb reads the raw bytes, does NOT parse tables).
|
|
32
|
+
# @param widths [Hash{Integer => Integer}, nil] codepoint → advance
|
|
33
|
+
# width in font units. Required for correct text width measurement.
|
|
34
|
+
# @param metrics [Hash, nil] font-level metrics:
|
|
35
|
+
# { units_per_em:, ascent:, descent:, cap_height:, x_height: }
|
|
36
|
+
# @param encoding [Hash{Integer => Integer}, nil] codepoint → glyph ID
|
|
37
|
+
# mapping (from cmap table). Used for encode_text.
|
|
38
|
+
# @param subset [Boolean] if true, subset during write using
|
|
39
|
+
# used_codepoints.
|
|
40
|
+
# @return [Symbol] font resource name.
|
|
41
|
+
def add(name_or_io, widths: nil, metrics: nil, encoding: nil,
|
|
42
|
+
subset: nil, **opts)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
When `widths:` is provided, pdfrb uses it instead of calling the broken
|
|
46
|
+
`extract_ttf_widths`. When `metrics:` is provided, `metrics_for` returns
|
|
47
|
+
it instead of an empty hash. When `encoding:` is provided, `encode_text`
|
|
48
|
+
uses it instead of the broken cmap parser.
|
|
49
|
+
|
|
50
|
+
## Usage with Fontisan
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
require "fontisan"
|
|
54
|
+
require "pdfrb"
|
|
55
|
+
|
|
56
|
+
# Parse font with Fontisan (correct, tested)
|
|
57
|
+
font_path = "/path/to/Arial.ttf"
|
|
58
|
+
fontisan = Fontisan::FontLoader.load(font_path)
|
|
59
|
+
|
|
60
|
+
# Extract metrics via Fontisan
|
|
61
|
+
metrics = {
|
|
62
|
+
units_per_em: fontisan.head.units_per_em,
|
|
63
|
+
ascent: fontisan.hhea.ascent,
|
|
64
|
+
descent: fontisan.hhea.descent,
|
|
65
|
+
}
|
|
66
|
+
widths = fontisan.hmtx.advance_widths # {glyph_id => width}
|
|
67
|
+
encoding = fontisan.cmap.code_map # {codepoint => glyph_id}
|
|
68
|
+
|
|
69
|
+
# Register with pdfrb using externally-provided data
|
|
70
|
+
doc = Pdfrb::Document.new
|
|
71
|
+
font = doc.fonts.add(font_path,
|
|
72
|
+
widths: widths,
|
|
73
|
+
metrics: metrics,
|
|
74
|
+
encoding: encoding)
|
|
75
|
+
|
|
76
|
+
# Now glyph_width, measure_text, metrics_for all work correctly
|
|
77
|
+
doc.fonts.glyph_width(font, 65) # => 667 (for 'A')
|
|
78
|
+
doc.fonts.measure_text("Hello", font: font, size: 12) # => ~28.0
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage from the idml gem
|
|
82
|
+
|
|
83
|
+
The `idml` gem already has `FontMetrics` (Fontisan-based) that provides
|
|
84
|
+
`glyph_width(codepoint)`, `units_per_em`, `ascent`, `descent`. It would
|
|
85
|
+
build the `widths:` and `metrics:` hashes from FontMetrics and pass them
|
|
86
|
+
to pdfrb:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# In Idml::Render::Pipeline
|
|
90
|
+
metrics = Idml::TextEngine::FontMetrics.open(font_path)
|
|
91
|
+
widths = {}
|
|
92
|
+
(32..255).each { |cp| widths[cp] = metrics.glyph_width(cp) }
|
|
93
|
+
|
|
94
|
+
font_name = writer.register_font_with_metrics(
|
|
95
|
+
font_path,
|
|
96
|
+
widths: widths,
|
|
97
|
+
metrics: { units_per_em: metrics.units_per_em,
|
|
98
|
+
ascent: metrics.ascent,
|
|
99
|
+
descent: metrics.descent },
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Implementation notes
|
|
104
|
+
|
|
105
|
+
1. Store `widths`, `metrics`, `encoding` alongside the font registration.
|
|
106
|
+
2. In `glyph_width(font, codepoint)`: if external widths provided,
|
|
107
|
+
return `widths[codepoint] || 0`. Otherwise fall back to AFM (standard
|
|
108
|
+
14) or the internal TTF parser.
|
|
109
|
+
3. In `metrics_for(font)`: if external metrics provided, return them.
|
|
110
|
+
Otherwise fall back to AFM or internal parser.
|
|
111
|
+
4. In `encode_text(text, font)`: if external encoding provided, use it
|
|
112
|
+
for codepoint → glyph ID mapping. Otherwise fall back to existing
|
|
113
|
+
encoding.
|
|
114
|
+
5. FontFile2 embedding reads the raw file bytes (pdfrb already does
|
|
115
|
+
this correctly — the bug is only in table *parsing*, not file reading).
|
|
116
|
+
|
|
117
|
+
## What pdfrb should NOT do
|
|
118
|
+
|
|
119
|
+
- Parse TTF/OTF tables internally — this is Fontisan's job.
|
|
120
|
+
- Depend on Fontisan — pdfrb should remain dependency-free for font
|
|
121
|
+
parsing. The caller decides which parser to use.
|
|
122
|
+
- Reimplement cmap/hmtx/head/hhea/name parsing — these are complex
|
|
123
|
+
binary formats with many edge cases that Fontisan handles correctly.
|
|
124
|
+
|
|
125
|
+
## Priority
|
|
126
|
+
|
|
127
|
+
**Critical** — this unblocks TODOs 52 and 63 in the idml gem. Without
|
|
128
|
+
external metrics support, the idml gem cannot use pdfrb's measurement
|
|
129
|
+
API and must keep its entire text engine separate from pdfrb.
|