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,88 @@
|
|
|
1
|
+
# PROPOSAL: Font embedding verification and FontFile2 output
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Ensure `Fonts#add` produces a `/FontFile2` (or `/FontFile3`) stream
|
|
6
|
+
in the output PDF when a TrueType/OpenType font file is passed. Add
|
|
7
|
+
a `Fonts#embedded?` query and log warnings when embedding fails.
|
|
8
|
+
|
|
9
|
+
## Motivation
|
|
10
|
+
|
|
11
|
+
The `idml` gem registers document fonts via `document.fonts.add(path)`.
|
|
12
|
+
The returned resource name works for `canvas.text`, but the output PDF
|
|
13
|
+
does not contain `/FontFile2`. This means:
|
|
14
|
+
- Text renders correctly only on systems with the font installed.
|
|
15
|
+
- The PDF is not self-contained.
|
|
16
|
+
- PDF/A compliance (which requires all fonts embedded) is impossible.
|
|
17
|
+
|
|
18
|
+
Testing with `/System/Library/Fonts/Supplemental/Arial.ttf`:
|
|
19
|
+
```ruby
|
|
20
|
+
doc = Pdfrb::Document.new
|
|
21
|
+
doc.fonts.add("/System/Library/Fonts/Supplemental/Arial.ttf")
|
|
22
|
+
# Output PDF has /BaseFont but no /FontFile2
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Root cause analysis
|
|
26
|
+
|
|
27
|
+
Looking at `Fonts#add`, the code path for TrueType fonts calls
|
|
28
|
+
`register_font(resource, name, name_or_io, **opts)`. This method
|
|
29
|
+
may not be creating the FontDescriptor + FontFile2 objects for
|
|
30
|
+
all font types. The standard 14 Type1 fonts don't need embedding,
|
|
31
|
+
but TrueType (.ttf) and OpenType (.otf) files should be embedded
|
|
32
|
+
as FontFile2 / FontFile3 respectively.
|
|
33
|
+
|
|
34
|
+
## Proposed changes
|
|
35
|
+
|
|
36
|
+
### 1. Ensure FontFile2 for TrueType
|
|
37
|
+
|
|
38
|
+
When `name_or_io` is a `.ttf` file path or IO:
|
|
39
|
+
1. Read the font data.
|
|
40
|
+
2. Create a stream object with the raw TTF bytes.
|
|
41
|
+
3. Create a FontDescriptor with `/FontFile2` pointing to the stream.
|
|
42
|
+
4. Set the Font dictionary's `/FontDescriptor` to the FontDescriptor.
|
|
43
|
+
|
|
44
|
+
### 2. Add `Fonts#embedded?(resource)` query
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
def embedded?(resource)
|
|
48
|
+
entry = @font_entries[resource]
|
|
49
|
+
return false unless entry
|
|
50
|
+
|
|
51
|
+
entry[:font_descriptor]&.value&.key?(:FontFile2) ||
|
|
52
|
+
entry[:font_descriptor]&.value&.key?(:FontFile3)
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Log warning on embedding failure
|
|
57
|
+
|
|
58
|
+
When embedding is requested but fails (corrupt font, unsupported
|
|
59
|
+
format), log a warning:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
Pdfrb.logger.warn("Failed to embed font #{name}: #{e.message}")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 4. Font subsetting (stretch goal)
|
|
66
|
+
|
|
67
|
+
Embed only the glyphs actually used:
|
|
68
|
+
1. Collect used codepoints from `encode_text` calls.
|
|
69
|
+
2. Subset the TTF using a glyph table parser.
|
|
70
|
+
3. Embed the subsetted font as FontFile2.
|
|
71
|
+
|
|
72
|
+
This reduces a 500KB font to ~5-20KB in the PDF.
|
|
73
|
+
|
|
74
|
+
## Usage example
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
font = doc.fonts.add("/path/to/MinionPro-Regular.otf")
|
|
78
|
+
doc.fonts.embedded?(font) # => true (after this proposal)
|
|
79
|
+
|
|
80
|
+
page = doc.pages.add
|
|
81
|
+
page.canvas.text("Hello", at: [72, 720], font: font, size: 12)
|
|
82
|
+
# PDF now contains /FontFile2 with the font data
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Priority
|
|
86
|
+
|
|
87
|
+
High — without font embedding, the PDF is not portable. This is the
|
|
88
|
+
#1 gap for the idml gem's rendering pipeline.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# PROPOSAL: FontResolver .ttc (TrueType Collection) support
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
`Pdfrb::FontResolver` doesn't search for `.ttc` (TrueType Collection)
|
|
6
|
+
files. On macOS, many common fonts (Helvetica, Times New Roman, Courier,
|
|
7
|
+
Arial Unicode) are stored exclusively as `.ttc`. Without `.ttc` support,
|
|
8
|
+
the resolver returns `nil` for these fonts.
|
|
9
|
+
|
|
10
|
+
## Bug description
|
|
11
|
+
|
|
12
|
+
In `build_cache` (font_resolver.rb):
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
Dir.glob("**/*.{ttf,otf}", base: dir).each do |rel_path|
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The glob only matches `.ttf` and `.otf` files — `.ttc` is excluded.
|
|
19
|
+
|
|
20
|
+
## Reproduction
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
resolver = Pdfrb::FontResolver.new
|
|
24
|
+
resolver.find(family: "Helvetica", style: "Regular")
|
|
25
|
+
# => nil (should find /System/Library/Fonts/Helvetica.ttc)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
On macOS:
|
|
29
|
+
- Helvetica is at `/System/Library/Fonts/Helvetica.ttc`
|
|
30
|
+
- Times New Roman is at `/System/Library/Fonts/Times New Roman.ttf` (works)
|
|
31
|
+
- Arial Unicode MS is at `/System/Library/Fonts/Supplemental/Arial Unicode.ttf` (works)
|
|
32
|
+
- Many system fonts are `.ttc` only
|
|
33
|
+
|
|
34
|
+
## Impact
|
|
35
|
+
|
|
36
|
+
The `idml` gem cannot use `Pdfrb::FontResolver` on macOS because many
|
|
37
|
+
fonts are `.ttc` only. It keeps the `Fontisan`-based resolver as a
|
|
38
|
+
fallback.
|
|
39
|
+
|
|
40
|
+
## Proposed fix
|
|
41
|
+
|
|
42
|
+
### 1. Add `.ttc` to glob
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
Dir.glob("**/*.{ttf,otf,ttc}", base: dir).each do |rel_path|
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Parse .ttc files
|
|
49
|
+
|
|
50
|
+
A `.ttc` file contains multiple fonts. The TTC header lists the offset
|
|
51
|
+
of each font's data within the file:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
TTC Header:
|
|
55
|
+
Tag: "ttcf"
|
|
56
|
+
MajorVersion, MinorVersion
|
|
57
|
+
numFonts: N
|
|
58
|
+
offsetTable[N]: offsets to each font's data
|
|
59
|
+
|
|
60
|
+
Each font at its offset has a standard TTF/OTF structure.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For each `.ttc` file, parse the TTC header and iterate each font:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
def parse_font_info(path)
|
|
67
|
+
data = File.binread(path)
|
|
68
|
+
if data[0, 4] == "ttcf"
|
|
69
|
+
parse_ttc(path, data)
|
|
70
|
+
else
|
|
71
|
+
parse_ttf(path, data) # existing single-font parsing
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_ttc(path, data)
|
|
76
|
+
results = []
|
|
77
|
+
num_fonts = data[8, 4].unpack1("N")
|
|
78
|
+
num_fonts.times do |i|
|
|
79
|
+
offset = data[12 + i * 4, 4].unpack1("N")
|
|
80
|
+
info = parse_ttf_at_offset(path, data, offset)
|
|
81
|
+
results << info if info
|
|
82
|
+
end
|
|
83
|
+
results
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Register fonts from .ttc with Fonts#add
|
|
88
|
+
|
|
89
|
+
When registering a font from a `.ttc` file, the caller should specify
|
|
90
|
+
which font within the collection:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# Option A: FontResolver returns the .ttc path + index
|
|
94
|
+
resolver.find(family: "Helvetica", style: "Regular")
|
|
95
|
+
# => { path: "/System/Library/Fonts/Helvetica.ttc", index: 0 }
|
|
96
|
+
|
|
97
|
+
# Option B: Fonts#add accepts a collection index
|
|
98
|
+
doc.fonts.add("/path/to/Helvetica.ttc", collection_index: 0)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Priority
|
|
102
|
+
|
|
103
|
+
High — macOS is a primary development platform for the idml gem.
|
|
104
|
+
Without `.ttc` support, pdfrb's FontResolver is unusable on macOS for
|
|
105
|
+
common fonts.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# PROPOSAL: Font file resolver
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Pdfrb::FontResolver` that searches system font directories for
|
|
6
|
+
font files by family name + style. Eliminates the need for external
|
|
7
|
+
font-finding libraries (like `fontisan` in the `idml` gem).
|
|
8
|
+
|
|
9
|
+
## Motivation
|
|
10
|
+
|
|
11
|
+
The `idml` gem uses `Fontisan` to:
|
|
12
|
+
1. Search `/System/Library/Fonts`, `/Library/Fonts`, `~/Library/Fonts`,
|
|
13
|
+
`/usr/share/fonts`, `~/.local/share/fonts` for `.ttf`/`.otf` files.
|
|
14
|
+
2. Parse each file's `name` table to get PostScript name, family name,
|
|
15
|
+
style name.
|
|
16
|
+
3. Match by family + style (e.g., "Minion Pro" + "Regular" →
|
|
17
|
+
"MinionPro-Regular").
|
|
18
|
+
4. Return the file path.
|
|
19
|
+
|
|
20
|
+
This is ~100 lines of `fontisan`-dependent code. If pdfrb provided font
|
|
21
|
+
resolution, the `idml` gem could drop the `fontisan` dependency entirely.
|
|
22
|
+
|
|
23
|
+
## Proposed API
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
module Pdfrb
|
|
27
|
+
class FontResolver
|
|
28
|
+
DEFAULT_SEARCH_PATHS = [
|
|
29
|
+
"/System/Library/Fonts",
|
|
30
|
+
"/Library/Fonts",
|
|
31
|
+
File.expand_path("~/Library/Fonts"),
|
|
32
|
+
"/usr/share/fonts",
|
|
33
|
+
File.expand_path("~/.local/share/fonts"),
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
# @param search_paths [Array<String>] directories to search.
|
|
37
|
+
def initialize(search_paths: DEFAULT_SEARCH_PATHS)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Find a font file by family name and optional style.
|
|
41
|
+
# @param family [String] e.g., "Arial", "Minion Pro"
|
|
42
|
+
# @param style [String] e.g., "Regular", "Bold", "Italic"
|
|
43
|
+
# @return [String, nil] file path, or nil if not found.
|
|
44
|
+
def find(family:, style: "Regular")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Find by PostScript name (exact match).
|
|
48
|
+
# @param ps_name [String] e.g., "ArialMT", "MinionPro-Regular"
|
|
49
|
+
# @return [String, nil] file path.
|
|
50
|
+
def find_by_ps_name(ps_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# List all available font files in search paths.
|
|
54
|
+
# @return [Array<Hash>] [{ path:, family:, style:, ps_name: }, ...]
|
|
55
|
+
def available_fonts
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Implementation notes
|
|
62
|
+
|
|
63
|
+
pdfrb already parses font `name` tables during `Fonts#add`. The resolver
|
|
64
|
+
would:
|
|
65
|
+
|
|
66
|
+
1. Glob `**/*.{ttf,otf,ttc}` in each search path.
|
|
67
|
+
2. For each file, parse the `name` table to get family/style/PS name.
|
|
68
|
+
3. Cache results (font directory scanning is expensive).
|
|
69
|
+
4. Match by family + style with fuzzy matching (e.g., "Bold Italic"
|
|
70
|
+
matches "BoldItalic").
|
|
71
|
+
|
|
72
|
+
For `.ttc` (TrueType Collection) files, each file contains multiple
|
|
73
|
+
fonts. The resolver should iterate all fonts within the collection.
|
|
74
|
+
|
|
75
|
+
Platform-specific paths:
|
|
76
|
+
- macOS: `/System/Library/Fonts`, `/Library/Fonts`, `~/Library/Fonts`
|
|
77
|
+
- Linux: `/usr/share/fonts`, `/usr/local/share/fonts`, `~/.local/share/fonts`, `~/.fonts`
|
|
78
|
+
- Windows: `C:\Windows\Fonts`
|
|
79
|
+
|
|
80
|
+
## Usage example
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
resolver = Pdfrb::FontResolver.new
|
|
84
|
+
path = resolver.find(family: "Minion Pro", style: "Regular")
|
|
85
|
+
# => "/Library/Fonts/MinionPro-Regular.otf"
|
|
86
|
+
|
|
87
|
+
font = doc.fonts.add(path)
|
|
88
|
+
page = doc.pages.add
|
|
89
|
+
page.canvas.text("Hello", at: [72, 720], font: font, size: 12)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Priority
|
|
93
|
+
|
|
94
|
+
Medium — eliminates the `fontisan` dependency for pdfrb consumers.
|
|
95
|
+
The `idml` gem's entire `FontResolver` + `FontMetrics` classes
|
|
96
|
+
(~300 lines) could be replaced by pdfrb's resolver + measurement.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# PROPOSAL: Font subsetting via used_codepoints
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Automatically subset embedded TrueType/OpenType fonts to include only
|
|
6
|
+
the glyphs actually used in the document. pdfrb 0.18.0 already tracks
|
|
7
|
+
`Fonts#used_codepoints` — extend this to drive the font subsetting
|
|
8
|
+
during serialization.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
The `idml` gem embeds document fonts via `Fonts#add`. Without subsetting,
|
|
13
|
+
a 500KB Minion Pro font is embedded in full, even if only 50 glyphs are
|
|
14
|
+
used. Subsetting reduces this to ~5-15KB — a 30-100× reduction.
|
|
15
|
+
|
|
16
|
+
This is critical for:
|
|
17
|
+
- Web PDFs (smaller download)
|
|
18
|
+
- Email attachments (size limits)
|
|
19
|
+
- PDF/A compliance (font embedding is mandatory, but subsetting is best practice)
|
|
20
|
+
- Batch processing (many small PDFs)
|
|
21
|
+
|
|
22
|
+
## Current state in pdfrb 0.18.0
|
|
23
|
+
|
|
24
|
+
- `Fonts#used_codepoints` — tracks which Unicode codepoints have been
|
|
25
|
+
encoded via `encode_text`. ✓
|
|
26
|
+
- `Fonts#embedded?` — checks if a font has FontFile2. ✓
|
|
27
|
+
- `Fonts#add` — registers fonts but may not subset by default.
|
|
28
|
+
|
|
29
|
+
## Proposed changes
|
|
30
|
+
|
|
31
|
+
### 1. Automatic subsetting on write
|
|
32
|
+
|
|
33
|
+
When `Document#write` serializes the PDF, for each embedded TrueType
|
|
34
|
+
font:
|
|
35
|
+
|
|
36
|
+
1. Get `used_codepoints` for the font resource.
|
|
37
|
+
2. Parse the TTF/OTF file's `glyf` (TrueType) or `CFF` (OpenType) table.
|
|
38
|
+
3. Build the glyph closure: used glyphs + their component glyphs
|
|
39
|
+
(for composite glyphs) + `.notdef`.
|
|
40
|
+
4. Rebuild the font tables with only the needed glyphs.
|
|
41
|
+
5. Write the subsetted font as the FontFile2 stream.
|
|
42
|
+
|
|
43
|
+
### 2. Opt-in flag
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
font = doc.fonts.add("/path/to/font.ttf", subset: true)
|
|
47
|
+
# ... render text ...
|
|
48
|
+
doc.write("output.pdf") # FontFile2 contains only used glyphs
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Default: `subset: true` (most callers want subsetting).
|
|
52
|
+
|
|
53
|
+
### 3. Subset prefix
|
|
54
|
+
|
|
55
|
+
Subsetting conventions use a 6-character uppercase prefix + `+` before
|
|
56
|
+
the font name (e.g., `ABCDEF+MinionPro-Regular`). This signals to PDF
|
|
57
|
+
processors that the font is a subset.
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# In the /BaseFont entry:
|
|
61
|
+
/BaseFont /ABCDEF+MinionPro-Regular
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Fallback for invalid fonts
|
|
65
|
+
|
|
66
|
+
If subsetting fails (corrupt font, unsupported table structure), fall
|
|
67
|
+
back to full embedding and log a warning.
|
|
68
|
+
|
|
69
|
+
## Implementation notes
|
|
70
|
+
|
|
71
|
+
TrueType subsetting algorithm:
|
|
72
|
+
1. Collect used codepoint → glyph ID mappings from `cmap`.
|
|
73
|
+
2. Expand glyph closure: for each glyph, check if it's a composite
|
|
74
|
+
(`.ttf` glyf table with `GF_COMPONENT` flag). Add component glyphs.
|
|
75
|
+
3. Rebuild `glyf`, `loca`, `maxp`, `hmtx`, `hhea`, `cmap`, `name`
|
|
76
|
+
tables with only the needed glyphs.
|
|
77
|
+
4. Recompute checksums.
|
|
78
|
+
|
|
79
|
+
OpenType/CFF subsetting is more complex (CharStrings, Private DICT,
|
|
80
|
+
FDArray). Consider using a library like `ttf-rb` or porting the
|
|
81
|
+
algorithm from `fontTools.subset` (Python).
|
|
82
|
+
|
|
83
|
+
## Usage example
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
doc = Pdfrb::Document.new
|
|
87
|
+
font = doc.fonts.add("/path/to/MinionPro-Regular.otf")
|
|
88
|
+
page = doc.pages.add
|
|
89
|
+
page.canvas.text("Hello, World!", at: [72, 720], font: font, size: 12)
|
|
90
|
+
doc.write("output.pdf")
|
|
91
|
+
# Only H, e, l, o, comma, space, W, r, d, ! glyphs are embedded
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Priority
|
|
95
|
+
|
|
96
|
+
High — font subsetting is the #1 remaining gap for production-quality
|
|
97
|
+
PDF output. Without it, every idml-generated PDF is 300-500KB larger
|
|
98
|
+
than necessary.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# PROPOSAL: Gradient shading patterns
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Document#shadings` facade and `Canvas#fill_shading(name)` /
|
|
6
|
+
`Canvas#stroke_shading(name)` for PDF Type 2 (axial) and Type 3 (radial)
|
|
7
|
+
gradient shading patterns. Replaces the discrete-rectangle approximation
|
|
8
|
+
used by the `idml` gem.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
The `idml` gem renders IDML gradient fills as 32 discrete colored
|
|
13
|
+
rectangles (a "staircase" approximation). This produces visible banding
|
|
14
|
+
and a larger file size than a proper PDF shading pattern.
|
|
15
|
+
|
|
16
|
+
PDF natively supports smooth gradients via shading dictionaries:
|
|
17
|
+
- **Type 2 (Axial)**: linear gradient between two points.
|
|
18
|
+
- **Type 3 (Radial)**: circular gradient between two circles.
|
|
19
|
+
- **Type 4–7 (Free-form, Coons, Tensor-product)**: complex meshes.
|
|
20
|
+
|
|
21
|
+
A proper shading pattern produces a mathematically smooth gradient with
|
|
22
|
+
zero banding.
|
|
23
|
+
|
|
24
|
+
## Proposed API
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
module Pdfrb
|
|
28
|
+
class Document
|
|
29
|
+
class Shadings
|
|
30
|
+
# Add an axial (linear) gradient.
|
|
31
|
+
# @param from [Array(Numeric, Numeric)] start point [x, y].
|
|
32
|
+
# @param to [Array(Numeric, Numeric)] end point [x, y].
|
|
33
|
+
# @param stops [Array<Array>] gradient stops:
|
|
34
|
+
# [[offset, [color_family, *values]], ...]
|
|
35
|
+
# e.g., [[0.0, [:rgb, 1, 0, 0]], [1.0, [:rgb, 0, 0, 1]]]
|
|
36
|
+
# @return [Symbol] shading resource name (e.g., :Sh1).
|
|
37
|
+
def add_axial(from:, to:, stops:)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Add a radial gradient.
|
|
41
|
+
# @param from [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
|
|
42
|
+
# @param to [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
|
|
43
|
+
# @param stops [Array<Array>] same format as axial.
|
|
44
|
+
# @return [Symbol] shading resource name.
|
|
45
|
+
def add_radial(from:, to:, stops:)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def [](name); end
|
|
49
|
+
def each(&block); end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class Pdfrb::Content::Canvas
|
|
57
|
+
# Fill the current path using a shading pattern.
|
|
58
|
+
# @param name [Symbol] shading resource name from Shadings#add_axial.
|
|
59
|
+
def fill_shading(name)
|
|
60
|
+
emit_op(Operator::ShadingFill, name)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## PDF structure
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
/Shading << /Sh1 << /Type /Shading /ShadingType 2
|
|
69
|
+
/ColorSpace /DeviceRGB
|
|
70
|
+
/Coords [x0 y0 x1 y1]
|
|
71
|
+
/Function << /FunctionType 4 /Domain [0 1]
|
|
72
|
+
/Range [0 1 0 1 0 1]
|
|
73
|
+
/Length N >> stream { ... PostScript calculator ... } endstream
|
|
74
|
+
>> >>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For simple 2-stop gradients, a Type 2 ExponentialInterpolation
|
|
78
|
+
function is simpler:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
/Function << /FunctionType 2 /Domain [0 1]
|
|
82
|
+
/C0 [1 0 0] /C1 [0 0 1] /N 1 >>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage example
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
shading = doc.shadings.add_axial(
|
|
89
|
+
from: [0, 0], to: [200, 0],
|
|
90
|
+
stops: [[0.0, [:rgb, 1, 0, 0]], [1.0, [:rgb, 0, 0, 1]]]
|
|
91
|
+
)
|
|
92
|
+
page.canvas.rectangle(0, 0, 200, 100)
|
|
93
|
+
page.canvas.fill_shading(shading)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Implementation notes
|
|
97
|
+
|
|
98
|
+
- Shadings need to be in `/Resources/Shading` on each page (or in the
|
|
99
|
+
catalog for document-wide).
|
|
100
|
+
- Multi-stop gradients require a StitchingFunction (Type 3) that
|
|
101
|
+
chains multiple Type 2 interpolation functions.
|
|
102
|
+
- For color spaces, support DeviceRGB, DeviceCMYK, and DeviceGray.
|
|
103
|
+
- The `sh` operator (shading fill) ignores the current path — it fills
|
|
104
|
+
the entire shading clip area. To clip to a shape, use the path as a
|
|
105
|
+
clip before `sh`.
|
|
106
|
+
|
|
107
|
+
## Priority
|
|
108
|
+
|
|
109
|
+
Medium — gradients are a visual quality issue, not a correctness issue.
|
|
110
|
+
The discrete approximation works but produces visible banding.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# PROPOSAL: Page MediaBox setter
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Page#media_box=` setter method so pages can have custom dimensions
|
|
6
|
+
without accessing the COS dictionary directly.
|
|
7
|
+
|
|
8
|
+
## Motivation
|
|
9
|
+
|
|
10
|
+
The `idml` gem renders IDML documents with arbitrary page sizes (A4,
|
|
11
|
+
business card, poster, etc.). pdfrb defaults to US Letter (612×792).
|
|
12
|
+
Currently, setting a custom MediaBox requires:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
page = document.pages.add
|
|
16
|
+
page[:"MediaBox"] = [0, 0, width, height] # COS dict access
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This is fragile — it bypasses pdfrb's model layer and couples the caller
|
|
20
|
+
to COS internals. A proper setter provides a clean, model-driven API.
|
|
21
|
+
|
|
22
|
+
## Proposed API
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class Pdfrb::Model::Type::Page
|
|
26
|
+
# Set the page's MediaBox.
|
|
27
|
+
# @param box [Array<Numeric>] [llx, lly, urx, ury] in PDF points.
|
|
28
|
+
def media_box=(box)
|
|
29
|
+
self[:"MediaBox"] = box
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Alternatively, support it at creation time:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
# In Pages#add
|
|
38
|
+
def add(width: 612, height: 792, **opts)
|
|
39
|
+
page = super(**opts)
|
|
40
|
+
page.media_box = [0, 0, width, height] unless width == 612 && height == 792
|
|
41
|
+
page
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage example
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# A4 portrait
|
|
49
|
+
page = document.pages.add
|
|
50
|
+
page.media_box = [0, 0, 595, 842]
|
|
51
|
+
|
|
52
|
+
# Custom size via creation
|
|
53
|
+
page = document.pages.add(width: 595, height: 842)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Implementation notes
|
|
57
|
+
|
|
58
|
+
- The reader method `media_box` already exists.
|
|
59
|
+
- The setter is a one-liner delegating to `self[:"MediaBox"]=`.
|
|
60
|
+
- The COS dict `[]=` is public (inherited from Cos::Dictionary).
|
|
61
|
+
- No validation needed at this level — callers pass valid rectangles.
|
|
62
|
+
|
|
63
|
+
## Priority
|
|
64
|
+
|
|
65
|
+
Low — workaround exists via `page[:"MediaBox"] = [...]`. But a proper
|
|
66
|
+
setter improves API cleanliness and discoverability.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# PROPOSAL: Per-glyph width measurement
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Add `Fonts#glyph_width(font, codepoint)` and `Fonts#glyph_widths(font,
|
|
6
|
+
codepoints)` for measuring individual glyph advance widths. Enables
|
|
7
|
+
text layout engines to do per-character shaping without parsing font
|
|
8
|
+
binary tables externally.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
The `idml` gem has its own text layout engine (`Idml::TextEngine`) that
|
|
13
|
+
shapes text character-by-character via a `FontMetrics#glyph_width(cp)`
|
|
14
|
+
method. This requires parsing the TTF `hmtx` table via the `fontisan`
|
|
15
|
+
gem (~200 lines of binary parsing code).
|
|
16
|
+
|
|
17
|
+
pdfrb 0.18.0 has `Fonts#measure_text(text, font:, size:)` which measures
|
|
18
|
+
a full string. But for line breaking, the layout engine needs per-glyph
|
|
19
|
+
widths to know where word boundaries fall and how to distribute space
|
|
20
|
+
across justified lines.
|
|
21
|
+
|
|
22
|
+
## Proposed API
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Measure the advance width of a single glyph at size 1000 (font units).
|
|
26
|
+
# @param font [Symbol] font resource name.
|
|
27
|
+
# @param codepoint [Integer] Unicode codepoint.
|
|
28
|
+
# @return [Integer] advance width in font units (not scaled by size).
|
|
29
|
+
def glyph_width(font, codepoint)
|
|
30
|
+
# Look up glyph ID from cmap, then width from hmtx.
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Measure advance widths for multiple codepoints in one call.
|
|
34
|
+
# @param font [Symbol] font resource name.
|
|
35
|
+
# @param codepoints [Array<Integer>] Unicode codepoints.
|
|
36
|
+
# @return [Array<Integer>] widths in font units, one per codepoint.
|
|
37
|
+
def glyph_widths(font, codepoints)
|
|
38
|
+
codepoints.map { |cp| glyph_width(font, cp) }
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Why not just measure_text per character?
|
|
43
|
+
|
|
44
|
+
`measure_text(char, font:, size:)` works but:
|
|
45
|
+
1. It returns a scaled `Float` (width in points at the given size).
|
|
46
|
+
The layout engine needs unscaled `Integer` widths (font units) to
|
|
47
|
+
avoid floating-point accumulation errors across many glyphs.
|
|
48
|
+
2. It's slower — each call goes through the full encoding + metrics
|
|
49
|
+
pipeline. A batch `glyph_widths` call is more efficient.
|
|
50
|
+
3. It doesn't distinguish between "glyph not found" (width 0) and
|
|
51
|
+
"glyph has width 0" (like space in some fonts).
|
|
52
|
+
|
|
53
|
+
## Implementation notes
|
|
54
|
+
|
|
55
|
+
pdfrb already parses the `cmap` and `hmtx` tables during font loading
|
|
56
|
+
(for `encode_text` and `measure_text`). The glyph width data is in
|
|
57
|
+
memory — `glyph_width` just needs to expose it.
|
|
58
|
+
|
|
59
|
+
For the 14 standard Type1 fonts, AFM data already has per-glyph widths.
|
|
60
|
+
For embedded TrueType/OpenType, the `hmtx` table has advance widths
|
|
61
|
+
indexed by glyph ID.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
def glyph_width(font, codepoint)
|
|
65
|
+
font_data = font_data_for(font)
|
|
66
|
+
return 0 unless font_data
|
|
67
|
+
|
|
68
|
+
glyph_id = font_data.cmap_lookup(codepoint)
|
|
69
|
+
font_data.advance_width(glyph_id)
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Usage example
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
font = doc.fonts.add("/path/to/Arial.ttf")
|
|
77
|
+
|
|
78
|
+
# Per-glyph widths for line breaking
|
|
79
|
+
widths = doc.fonts.glyph_widths(font, "Hello World".each_codepoint.to_a)
|
|
80
|
+
# => [722, 556, 556, 556, 277, 0, 667, 556, 722, 556, 500]
|
|
81
|
+
|
|
82
|
+
# Text layout engine uses these for word-wrap
|
|
83
|
+
total = widths.sum # => 5661 font units
|
|
84
|
+
width_at_12pt = total * 12.0 / 1000 # => 67.9 points
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Priority
|
|
88
|
+
|
|
89
|
+
Medium — `measure_text` is a workaround, but per-glyph widths eliminate
|
|
90
|
+
the need for `fontisan` in the `idml` gem and enable faster, more
|
|
91
|
+
accurate text layout.
|