hexapdf 1.7.0 → 1.9.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/CHANGELOG.md +50 -1
- data/LICENSE +1 -1
- data/README.md +3 -0
- data/Rakefile +1 -1
- data/data/hexapdf/fonts/Inter-Bold.ttf +0 -0
- data/data/hexapdf/fonts/Inter-BoldItalic.ttf +0 -0
- data/data/hexapdf/fonts/Inter-Italic.ttf +0 -0
- data/data/hexapdf/fonts/Inter-Regular.ttf +0 -0
- data/data/hexapdf/fonts/OFL.txt +92 -0
- data/examples/005-merging.rb +2 -1
- data/examples/019-acro_form.rb +3 -1
- data/examples/030-pdfa.rb +9 -16
- data/examples/034-text_shaping.rb +37 -0
- data/lib/hexapdf/cli/batch.rb +1 -1
- data/lib/hexapdf/cli/command.rb +1 -1
- data/lib/hexapdf/cli/debug_info.rb +1 -1
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/fonts.rb +6 -4
- data/lib/hexapdf/cli/form.rb +1 -1
- data/lib/hexapdf/cli/image2pdf.rb +1 -1
- data/lib/hexapdf/cli/images.rb +17 -17
- data/lib/hexapdf/cli/info.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/merge.rb +14 -2
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/cli/usage.rb +1 -1
- data/lib/hexapdf/cli/watermark.rb +1 -1
- data/lib/hexapdf/cli.rb +1 -1
- data/lib/hexapdf/composer.rb +1 -1
- data/lib/hexapdf/configuration.rb +15 -2
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/canvas_composer.rb +1 -1
- data/lib/hexapdf/content/color_space.rb +1 -1
- data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object.rb +1 -1
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/content/operator.rb +1 -1
- data/lib/hexapdf/content/parser.rb +1 -1
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/content/smart_text_extractor.rb +10 -4
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/content.rb +1 -1
- data/lib/hexapdf/data_dir.rb +1 -1
- data/lib/hexapdf/dictionary.rb +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/digital_signature/cms_handler.rb +1 -1
- data/lib/hexapdf/digital_signature/handler.rb +1 -1
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +1 -1
- data/lib/hexapdf/digital_signature/signature.rb +1 -1
- data/lib/hexapdf/digital_signature/signatures.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +1 -1
- data/lib/hexapdf/digital_signature/signing.rb +1 -1
- data/lib/hexapdf/digital_signature/verification_result.rb +1 -1
- data/lib/hexapdf/digital_signature.rb +1 -1
- data/lib/hexapdf/document/annotations.rb +26 -1
- data/lib/hexapdf/document/destinations.rb +1 -1
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/document/layout.rb +1 -1
- data/lib/hexapdf/document/metadata.rb +1 -1
- data/lib/hexapdf/document/pages.rb +1 -1
- data/lib/hexapdf/document.rb +1 -1
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +1 -1
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/encryption.rb +1 -1
- data/lib/hexapdf/error.rb +1 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/brotli_decode.rb +1 -1
- data/lib/hexapdf/filter/crypt.rb +1 -1
- data/lib/hexapdf/filter/encryption.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/pass_through.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/filter.rb +1 -1
- data/lib/hexapdf/font/cmap/parser.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +16 -10
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/encoding/base.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +1 -1
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/font.rb +1 -1
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +4 -4
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
- data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
- data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
- data/lib/hexapdf/font/true_type/table/head.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
- data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
- data/lib/hexapdf/font/true_type/table/name.rb +1 -1
- data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type.rb +1 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +9 -6
- data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
- data/lib/hexapdf/font/type1.rb +1 -1
- data/lib/hexapdf/font/type1_wrapper.rb +1 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/from_file.rb +5 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/font_loader/variant_from_name.rb +1 -1
- data/lib/hexapdf/font_loader.rb +48 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +1 -1
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/importer.rb +1 -1
- data/lib/hexapdf/layout/box.rb +1 -1
- data/lib/hexapdf/layout/box_fitter.rb +1 -1
- data/lib/hexapdf/layout/column_box.rb +1 -1
- data/lib/hexapdf/layout/container_box.rb +3 -5
- data/lib/hexapdf/layout/frame.rb +1 -1
- data/lib/hexapdf/layout/image_box.rb +1 -1
- data/lib/hexapdf/layout/inline_box.rb +1 -1
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/list_box.rb +1 -1
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/page_style.rb +1 -1
- data/lib/hexapdf/layout/style.rb +67 -5
- data/lib/hexapdf/layout/table_box.rb +97 -14
- data/lib/hexapdf/layout/text_box.rb +1 -1
- data/lib/hexapdf/layout/text_fragment.rb +14 -8
- data/lib/hexapdf/layout/text_layouter.rb +1 -1
- data/lib/hexapdf/layout/text_shaper.rb +163 -11
- data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
- data/lib/hexapdf/layout.rb +1 -1
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +1 -1
- data/lib/hexapdf/parser.rb +1 -1
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +1 -1
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/serializer.rb +3 -3
- data/lib/hexapdf/stream.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/import_pages.rb +185 -0
- data/lib/hexapdf/task/merge_acro_form.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +1 -1
- data/lib/hexapdf/task/pdfa.rb +1 -1
- data/lib/hexapdf/task.rb +2 -1
- data/lib/hexapdf/test_utils.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +1 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
- data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/field.rb +1 -1
- data/lib/hexapdf/type/acro_form/form.rb +1 -1
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +1 -1
- data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +1 -1
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/set_ocg_state.rb +1 -1
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/actions.rb +1 -1
- data/lib/hexapdf/type/annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/appearance_generator.rb +43 -1
- data/lib/hexapdf/type/annotations/border_effect.rb +1 -1
- data/lib/hexapdf/type/annotations/border_styling.rb +1 -1
- data/lib/hexapdf/type/annotations/circle.rb +1 -1
- data/lib/hexapdf/type/annotations/ink.rb +107 -0
- data/lib/hexapdf/type/annotations/interior_color.rb +1 -1
- data/lib/hexapdf/type/annotations/line.rb +1 -1
- data/lib/hexapdf/type/annotations/line_ending_styling.rb +1 -1
- data/lib/hexapdf/type/annotations/link.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/polygon.rb +1 -1
- data/lib/hexapdf/type/annotations/polygon_polyline.rb +1 -1
- data/lib/hexapdf/type/annotations/polyline.rb +1 -1
- data/lib/hexapdf/type/annotations/square.rb +1 -1
- data/lib/hexapdf/type/annotations/square_circle.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +1 -1
- data/lib/hexapdf/type/annotations.rb +2 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/cmap.rb +1 -1
- data/lib/hexapdf/type/document_security_store.rb +1 -1
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +1 -1
- data/lib/hexapdf/type/font.rb +4 -4
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_true_type.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +1 -1
- data/lib/hexapdf/type/font_type3.rb +6 -1
- data/lib/hexapdf/type/form.rb +1 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +1 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/mark_information.rb +1 -1
- data/lib/hexapdf/type/marked_content_reference.rb +1 -1
- data/lib/hexapdf/type/measure.rb +1 -1
- data/lib/hexapdf/type/metadata.rb +1 -1
- data/lib/hexapdf/type/names.rb +1 -1
- data/lib/hexapdf/type/namespace.rb +1 -1
- data/lib/hexapdf/type/object_reference.rb +1 -1
- data/lib/hexapdf/type/object_stream.rb +1 -1
- data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
- data/lib/hexapdf/type/optional_content_group.rb +1 -1
- data/lib/hexapdf/type/optional_content_membership.rb +1 -1
- data/lib/hexapdf/type/optional_content_properties.rb +1 -1
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/outline_item.rb +1 -1
- data/lib/hexapdf/type/output_intent.rb +1 -1
- data/lib/hexapdf/type/page.rb +1 -1
- data/lib/hexapdf/type/page_label.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +1 -1
- data/lib/hexapdf/type/resources.rb +1 -1
- data/lib/hexapdf/type/struct_elem.rb +1 -1
- data/lib/hexapdf/type/struct_tree_root.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +1 -1
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +1 -1
- data/lib/hexapdf/type.rb +1 -1
- data/lib/hexapdf/utils/bit_field.rb +1 -1
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
- data/lib/hexapdf/utils/lru_cache.rb +1 -1
- data/lib/hexapdf/utils/math_helpers.rb +1 -1
- data/lib/hexapdf/utils/object_hash.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
- data/lib/hexapdf/utils.rb +1 -1
- data/lib/hexapdf/version.rb +2 -2
- data/lib/hexapdf/writer.rb +1 -1
- data/lib/hexapdf/xref_section.rb +1 -1
- data/lib/hexapdf.rb +1 -1
- data/test/hexapdf/digital_signature/common.rb +5 -5
- data/test/hexapdf/digital_signature/test_cms_handler.rb +1 -1
- data/test/hexapdf/document/test_annotations.rb +10 -0
- data/test/hexapdf/document/test_layout.rb +6 -3
- data/test/hexapdf/filter/test_brotli_decode.rb +1 -1
- data/test/hexapdf/font/cmap/test_writer.rb +8 -6
- data/test/hexapdf/font/test_true_type_wrapper.rb +6 -2
- data/test/hexapdf/font/true_type/test_subsetter.rb +7 -6
- data/test/hexapdf/font_loader/test_from_file.rb +7 -0
- data/test/hexapdf/layout/test_container_box.rb +3 -1
- data/test/hexapdf/layout/test_style.rb +4 -0
- data/test/hexapdf/layout/test_table_box.rb +117 -1
- data/test/hexapdf/layout/test_text_fragment.rb +18 -8
- data/test/hexapdf/layout/test_text_shaper.rb +55 -5
- data/test/hexapdf/task/test_import_pages.rb +126 -0
- data/test/hexapdf/test_serializer.rb +1 -1
- data/test/hexapdf/type/annotations/test_appearance_generator.rb +63 -0
- data/test/hexapdf/type/annotations/test_ink.rb +31 -0
- data/test/hexapdf/type/test_font_type3.rb +4 -0
- metadata +26 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -111,20 +111,24 @@ module HexaPDF
|
|
|
111
111
|
font = style.font
|
|
112
112
|
text.each_codepoint do |codepoint|
|
|
113
113
|
glyph = font.decode_codepoint(codepoint)
|
|
114
|
-
if glyph.valid?
|
|
114
|
+
if glyph.valid?
|
|
115
115
|
items << glyph
|
|
116
116
|
else
|
|
117
117
|
unless items.empty?
|
|
118
|
-
result
|
|
118
|
+
result.append(*shaper.shape_text(new(items, style)))
|
|
119
119
|
items = []
|
|
120
120
|
end
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
if glyph.control_char?
|
|
122
|
+
result.append(new([glyph], style))
|
|
123
|
+
else
|
|
124
|
+
fallback = yield(codepoint, glyph)
|
|
125
|
+
unless fallback.empty?
|
|
126
|
+
result.append(*shaper.shape_text(new(fallback, styles[fallback.first.font_wrapper])))
|
|
127
|
+
end
|
|
124
128
|
end
|
|
125
129
|
end
|
|
126
130
|
end
|
|
127
|
-
result
|
|
131
|
+
result.append(*shaper.shape_text(new(items, style))) unless items.empty?
|
|
128
132
|
result
|
|
129
133
|
end
|
|
130
134
|
|
|
@@ -251,7 +255,9 @@ module HexaPDF
|
|
|
251
255
|
tx = x - tlm.e
|
|
252
256
|
ty = y - tlm.f
|
|
253
257
|
if tx.abs < PRECISION
|
|
254
|
-
if
|
|
258
|
+
if ty.abs < PRECISION
|
|
259
|
+
# do nothing
|
|
260
|
+
elsif (ty + canvas.graphics_state.leading).abs < PRECISION
|
|
255
261
|
canvas.move_text_cursor
|
|
256
262
|
else
|
|
257
263
|
canvas.move_text_cursor(offset: [0, ty], absolute: false)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -34,8 +34,71 @@
|
|
|
34
34
|
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
35
|
#++
|
|
36
36
|
|
|
37
|
+
require 'hexapdf/error'
|
|
37
38
|
require 'hexapdf/layout/numeric_refinements'
|
|
38
39
|
|
|
40
|
+
HARFBUZZ_AVAILABLE = begin
|
|
41
|
+
require 'harfbuzz'
|
|
42
|
+
true
|
|
43
|
+
rescue LoadError
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if HARFBUZZ_AVAILABLE
|
|
47
|
+
class HarfBuzz::Buffer #:nodoc:
|
|
48
|
+
|
|
49
|
+
GLYPH_INFO_SIZE = HarfBuzz::C::HbGlyphInfoT.size
|
|
50
|
+
GLYPH_INFO_CODEPOINT_OFFSET = HarfBuzz::C::HbGlyphInfoT.offset_of(:codepoint)
|
|
51
|
+
GLYPH_INFO_CLUSTER_OFFSET = HarfBuzz::C::HbGlyphInfoT.offset_of(:cluster)
|
|
52
|
+
GLYPH_POS_SIZE = HarfBuzz::C::HbGlyphPositionT.size
|
|
53
|
+
GLYPH_POS_XADVANCE_OFFSET = HarfBuzz::C::HbGlyphPositionT.offset_of(:x_advance)
|
|
54
|
+
GLYPH_POS_YADVANCE_OFFSET = HarfBuzz::C::HbGlyphPositionT.offset_of(:y_advance)
|
|
55
|
+
GLYPH_POS_XOFFSET_OFFSET = HarfBuzz::C::HbGlyphPositionT.offset_of(:x_offset)
|
|
56
|
+
GLYPH_POS_YOFFSET_OFFSET = HarfBuzz::C::HbGlyphPositionT.offset_of(:y_offset)
|
|
57
|
+
|
|
58
|
+
# Iterates efficiently over the shaping result without creating intermediary objects.
|
|
59
|
+
def each_result
|
|
60
|
+
return enum_for(__method__) unless block_given?
|
|
61
|
+
|
|
62
|
+
length_ptr = FFI::MemoryPointer.new(:uint)
|
|
63
|
+
infos_ptr = HarfBuzz::C.hb_buffer_get_glyph_infos(@ptr, length_ptr)
|
|
64
|
+
length_ptr = FFI::MemoryPointer.new(:uint)
|
|
65
|
+
positions_ptr = HarfBuzz::C.hb_buffer_get_glyph_positions(@ptr, length_ptr)
|
|
66
|
+
length = length_ptr.read_uint
|
|
67
|
+
|
|
68
|
+
return if infos_ptr.null? || positions_ptr.null? || length.zero?
|
|
69
|
+
|
|
70
|
+
last_info_cluster_offset = (length - 1) * GLYPH_INFO_SIZE + GLYPH_INFO_CLUSTER_OFFSET
|
|
71
|
+
i = 0
|
|
72
|
+
while i < length
|
|
73
|
+
info_offset = i * GLYPH_INFO_SIZE
|
|
74
|
+
pos_offset = i * GLYPH_POS_SIZE
|
|
75
|
+
|
|
76
|
+
glyph_id = infos_ptr.get_uint32(info_offset + GLYPH_INFO_CODEPOINT_OFFSET)
|
|
77
|
+
cluster = infos_ptr.get_uint32(info_offset + GLYPH_INFO_CLUSTER_OFFSET)
|
|
78
|
+
|
|
79
|
+
next_cluster = nil
|
|
80
|
+
tmp_offset = info_offset + GLYPH_INFO_CLUSTER_OFFSET + GLYPH_INFO_SIZE
|
|
81
|
+
while tmp_offset <= last_info_cluster_offset &&
|
|
82
|
+
(next_cluster = infos_ptr.get_uint32(tmp_offset)) == cluster
|
|
83
|
+
tmp_offset += GLYPH_INFO_SIZE
|
|
84
|
+
next_cluster = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
x_advance = positions_ptr.get_int32(pos_offset + GLYPH_POS_XADVANCE_OFFSET)
|
|
88
|
+
y_advance = positions_ptr.get_int32(pos_offset + GLYPH_POS_YADVANCE_OFFSET)
|
|
89
|
+
x_offset = positions_ptr.get_int32(pos_offset + GLYPH_POS_XOFFSET_OFFSET)
|
|
90
|
+
y_offset = positions_ptr.get_int32(pos_offset + GLYPH_POS_YOFFSET_OFFSET)
|
|
91
|
+
|
|
92
|
+
yield(glyph_id, cluster, next_cluster, x_advance, y_advance, x_offset, y_offset)
|
|
93
|
+
|
|
94
|
+
i += 1
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
39
102
|
module HexaPDF
|
|
40
103
|
module Layout
|
|
41
104
|
|
|
@@ -44,23 +107,33 @@ module HexaPDF
|
|
|
44
107
|
# This class is used to perform text shaping, i.e. changing the position of glyphs (e.g. for
|
|
45
108
|
# kerning) or substituting one or more glyphs for other glyphs (e.g. for ligatures).
|
|
46
109
|
#
|
|
47
|
-
#
|
|
110
|
+
# The class contains two shaping engines: A very limited custom one and one based on
|
|
111
|
+
# HarfBuzz. Which one is used for shaping can be selected via the Style#shaping_engine property.
|
|
48
112
|
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
113
|
+
# The custom implementation is always used for Type1 fonts and supports kerning and ligature
|
|
114
|
+
# substitution. It also supports the 'kern' table for TrueType fonts if HarfBuzz is not used.
|
|
51
115
|
#
|
|
52
|
-
#
|
|
116
|
+
# For complex scripts or the need of special font features it is recommended to use the shaping
|
|
117
|
+
# engine based on HarfBuzz, even though it is slightly slower. For it to work the
|
|
118
|
+
# +harfbuzz-ruby+ gem needs to be installed. OpenType features can be activated and deactivated
|
|
119
|
+
# using Style#font_features.
|
|
53
120
|
class TextShaper
|
|
54
121
|
|
|
55
|
-
# Shapes the given text fragment in-place
|
|
122
|
+
# Shapes the given text fragment. Returns either the in-place modified fragment or, for
|
|
123
|
+
# complex shaping, an array of fragments.
|
|
56
124
|
#
|
|
57
|
-
# The
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# :kern:: Pair-wise kerning.
|
|
61
|
-
# :liga:: Ligature substitution.
|
|
125
|
+
# The style properties Style#shaping_engine, Style#font_features, Style#font_script,
|
|
126
|
+
# Style#language and Style#direction are used for shaping.
|
|
62
127
|
def shape_text(text_fragment)
|
|
63
128
|
font = text_fragment.style.font
|
|
129
|
+
if text_fragment.style.shaping_engine == :harfbuzz && font.font_type == :TrueType
|
|
130
|
+
unless HARFBUZZ_AVAILABLE
|
|
131
|
+
raise HexaPDF::Error, "Shaping engine harfbuzz required but the needed Rubygem " \
|
|
132
|
+
"harfbuzz-ruby is not available"
|
|
133
|
+
end
|
|
134
|
+
return harfbuzz_shape_text(text_fragment)
|
|
135
|
+
end
|
|
136
|
+
|
|
64
137
|
if text_fragment.style.font_features[:liga] && font.wrapped_font.features.include?(:liga)
|
|
65
138
|
if font.font_type == :Type1
|
|
66
139
|
process_type1_ligatures(text_fragment)
|
|
@@ -76,11 +149,90 @@ module HexaPDF
|
|
|
76
149
|
end
|
|
77
150
|
text_fragment.clear_cache
|
|
78
151
|
end
|
|
152
|
+
|
|
79
153
|
text_fragment
|
|
80
154
|
end
|
|
81
155
|
|
|
82
156
|
private
|
|
83
157
|
|
|
158
|
+
# Shapes the text fragment with HarfBuzz.
|
|
159
|
+
def harfbuzz_shape_text(text_fragment, text = nil)
|
|
160
|
+
text ||= text_fragment.items.map(&:str).join
|
|
161
|
+
style = text_fragment.style
|
|
162
|
+
|
|
163
|
+
# Cache the used main Harfbuzz font objects
|
|
164
|
+
hb_font = style.font.pdf_object.document.cache('harfbuzz', style.font.filename) do
|
|
165
|
+
blob = HarfBuzz::Blob.from_file!(style.font.filename)
|
|
166
|
+
face = HarfBuzz::Face.new(blob, 0)
|
|
167
|
+
HarfBuzz::Font.new(face)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Prepare the buffer and then shape the text. We are using cluster level 1 as this is the
|
|
171
|
+
# recommended level.
|
|
172
|
+
buffer = HarfBuzz::Buffer.new
|
|
173
|
+
buffer.add_utf8(text)
|
|
174
|
+
buffer.cluster_level = 1
|
|
175
|
+
buffer.direction = style.direction
|
|
176
|
+
buffer.script = style.font_script if style.font_script?
|
|
177
|
+
buffer.language = style.language if style.language?
|
|
178
|
+
buffer.guess_segment_properties
|
|
179
|
+
HarfBuzz.shape(hb_font, buffer, HarfBuzz::Feature.from_hash(style.font_features))
|
|
180
|
+
|
|
181
|
+
# Prepare the iteration over the shaping result. The final output will either be
|
|
182
|
+
# +text_fragment+ (no non-zero y_offsets) or +result+ containing at least two TextFragment
|
|
183
|
+
# instances.
|
|
184
|
+
result = nil
|
|
185
|
+
font = style.font
|
|
186
|
+
fragment = text_fragment
|
|
187
|
+
fragment.clear_cache
|
|
188
|
+
items = text_fragment.items.clear
|
|
189
|
+
last_cluster = nil
|
|
190
|
+
last_y_offset = 0
|
|
191
|
+
buffer.each_result do |glyph_id, cluster, next_cluster, x_advance, y_advance, x_offset, y_offset|
|
|
192
|
+
advance = (x_advance - x_offset) * font.scaling_factor
|
|
193
|
+
|
|
194
|
+
# 1. Determine the source characters for each glyph via their cluster numbers. If two or
|
|
195
|
+
# more glyphs have the same cluster number, the first gets the resulting string while the
|
|
196
|
+
# rest map to an empty string. Otherwise copying from the PDF would result in multiple
|
|
197
|
+
# copies of the resulting string.
|
|
198
|
+
str = (cluster == last_cluster ? '' : text.byteslice(cluster...(next_cluster || text.bytesize)))
|
|
199
|
+
|
|
200
|
+
# 2. Handle invalid glyphs with id=0 by mapping them to an InvalidGlyph instance
|
|
201
|
+
if glyph_id.zero?
|
|
202
|
+
glyph = font.decode_codepoint(str.ord)
|
|
203
|
+
advance = glyph.width
|
|
204
|
+
else
|
|
205
|
+
glyph = font.glyph(glyph_id, str)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# 3. Handle differing y_offsets by creating TextFragment instances with appropriate text
|
|
209
|
+
# rise properties.
|
|
210
|
+
if y_offset != last_y_offset
|
|
211
|
+
(result ||= []) << fragment
|
|
212
|
+
items = []
|
|
213
|
+
if y_offset.zero?
|
|
214
|
+
fragment = text_fragment.dup_attributes(items)
|
|
215
|
+
else
|
|
216
|
+
fragment = TextFragment.new(items, style.dup, properties: text_fragment.properties)
|
|
217
|
+
fragment.style.text_rise += y_offset * font.scaling_factor * fragment.style.font_size *
|
|
218
|
+
fragment.style.font.pdf_object.glyph_scaling_factor
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# 4. Handle the correct x-positioning using x_offset. Also addjust the horizontal advance
|
|
223
|
+
# based on the glyph's fixed advance width as well as x_advance and x_offset (via
|
|
224
|
+
# +advance+).
|
|
225
|
+
items << -x_offset * font.scaling_factor unless x_offset.zero?
|
|
226
|
+
items << glyph
|
|
227
|
+
items << glyph.width - advance if glyph.width - advance != 0
|
|
228
|
+
|
|
229
|
+
last_cluster = cluster
|
|
230
|
+
last_y_offset = y_offset
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
result ? result.append(fragment) : text_fragment
|
|
234
|
+
end
|
|
235
|
+
|
|
84
236
|
# Processes the text fragment and substitutes ligatures.
|
|
85
237
|
def process_type1_ligatures(text_fragment)
|
|
86
238
|
items = text_fragment.items
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/layout.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/object.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/parser.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/pdf_array.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/rectangle.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/reference.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/revision.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/revisions.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/serializer.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -270,7 +270,7 @@ module HexaPDF
|
|
|
270
270
|
str << ">>"
|
|
271
271
|
end
|
|
272
272
|
|
|
273
|
-
STRING_ESCAPE_MAP = {"(" => "\\(", ")" => "\\)", "\\" => "\\\\", "\r" => "\\r"}.freeze # :nodoc:
|
|
273
|
+
STRING_ESCAPE_MAP = {"(" => "\\(", ")" => "\\)", "\\" => "\\\\", "\r" => "\\r", "\n" => "\\n"}.freeze # :nodoc:
|
|
274
274
|
|
|
275
275
|
# Serializes a String object.
|
|
276
276
|
#
|
|
@@ -287,7 +287,7 @@ module HexaPDF
|
|
|
287
287
|
else
|
|
288
288
|
obj.b
|
|
289
289
|
end
|
|
290
|
-
obj.gsub!(/[()\\\r]/n, STRING_ESCAPE_MAP)
|
|
290
|
+
obj.gsub!(/[()\\\r\n]/n, STRING_ESCAPE_MAP)
|
|
291
291
|
"(#{obj})"
|
|
292
292
|
end
|
|
293
293
|
|
data/lib/hexapdf/stream.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# This file is part of HexaPDF.
|
|
5
|
+
#
|
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
|
+
#
|
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
16
|
+
#
|
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
20
|
+
# License for more details.
|
|
21
|
+
#
|
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
|
24
|
+
#
|
|
25
|
+
# The interactive user interfaces in modified source and object code
|
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
|
28
|
+
#
|
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
|
31
|
+
# is created or manipulated using HexaPDF.
|
|
32
|
+
#
|
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
|
+
#++
|
|
36
|
+
|
|
37
|
+
require 'hexapdf/serializer'
|
|
38
|
+
require 'set'
|
|
39
|
+
|
|
40
|
+
module HexaPDF
|
|
41
|
+
module Task
|
|
42
|
+
|
|
43
|
+
# Task for importing pages from another document that preserves the visual appearance.
|
|
44
|
+
#
|
|
45
|
+
# It takes care of
|
|
46
|
+
#
|
|
47
|
+
# * importing the specified pages with all associated objects,
|
|
48
|
+
# * handling optional content groups and their default state,
|
|
49
|
+
# * and merging form fields.
|
|
50
|
+
#
|
|
51
|
+
# Note that the /Order, /AS and /Locked fields of the default optional content configuration
|
|
52
|
+
# dictionary are not preserved.
|
|
53
|
+
#
|
|
54
|
+
# Example:
|
|
55
|
+
#
|
|
56
|
+
# doc.task(:import_pages, source: source_doc, pages: [1..-2])
|
|
57
|
+
module ImportPages
|
|
58
|
+
|
|
59
|
+
# Performs the necessary steps to import the pages from the +source+ docment into the target
|
|
60
|
+
# document +doc+. Returns the imported pages.
|
|
61
|
+
#
|
|
62
|
+
# +source+::
|
|
63
|
+
# Specifies the source PDF document from which the pages should be imported.
|
|
64
|
+
#
|
|
65
|
+
# +pages+::
|
|
66
|
+
# Specifies the pages that should be imported. The argument has to be one of the
|
|
67
|
+
# following:
|
|
68
|
+
#
|
|
69
|
+
# +:all+:: Imports all pages from the +source+ document.
|
|
70
|
+
# Integer value:: Imports the page with the given zero-based index.
|
|
71
|
+
# Range value:: Imports the pages from the zero-based range.
|
|
72
|
+
# Array of Integer or Range values:: Imports all specified pages or page ranges.
|
|
73
|
+
# Array of source page objects:: Imports the given pages.
|
|
74
|
+
#
|
|
75
|
+
# +:append+::
|
|
76
|
+
# Specifies whether the imported pages should be appended to the target document's page
|
|
77
|
+
# tree.
|
|
78
|
+
#
|
|
79
|
+
# +ocgs+::
|
|
80
|
+
# Specifies the handling of optional content groups:
|
|
81
|
+
#
|
|
82
|
+
# +:preserve+:: Preserve the on/off state for all used OCGs.
|
|
83
|
+
# +:ignore+:: Ignore the on/off state.
|
|
84
|
+
#
|
|
85
|
+
# +:acro_form+::
|
|
86
|
+
# Specifies whether AcroForm fields should be merged into the target document.
|
|
87
|
+
#
|
|
88
|
+
# +:merge+:: Merge AcroForm fields using the MergeAcroForm task.
|
|
89
|
+
# +:ignore+:: Ignore AcroForm fields.
|
|
90
|
+
def self.call(doc, source:, pages: :all, append: true, ocgs: :preserve, acro_form: :merge)
|
|
91
|
+
# Retrieve all specified source pages
|
|
92
|
+
pages = if pages == :all
|
|
93
|
+
source.pages.each.to_a
|
|
94
|
+
elsif pages.kind_of?(Integer)
|
|
95
|
+
[source.pages[pages]]
|
|
96
|
+
elsif pages.kind_of?(Array) && pages[0].kind_of?(HexaPDF::Type::Page)
|
|
97
|
+
pages
|
|
98
|
+
else
|
|
99
|
+
result = Set.new
|
|
100
|
+
all_pages = source.pages.each.to_a
|
|
101
|
+
pages = [pages] unless pages.kind_of?(Array)
|
|
102
|
+
pages.each {|selector| result.merge(Array(all_pages[selector])) }
|
|
103
|
+
result
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Import the source pages and optionally append them to the target page tree
|
|
107
|
+
pages = pages.map do |page|
|
|
108
|
+
imported_page = doc.import(page)
|
|
109
|
+
doc.pages << imported_page if append
|
|
110
|
+
imported_page
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
doc.task(:merge_acro_form, source: source, pages: pages) if acro_form == :merge
|
|
114
|
+
preserve_ocgs(doc, source, pages) if ocgs == :preserve
|
|
115
|
+
|
|
116
|
+
pages
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Preserves the state of the OCGs found on +pages+ so that the visual appearance in the target
|
|
120
|
+
# document +doc+ is the same as in the +source+ document.
|
|
121
|
+
def self.preserve_ocgs(doc, source, pages)
|
|
122
|
+
# Find all OCGs used on all pages
|
|
123
|
+
ocgs = Set.new
|
|
124
|
+
process_ocg_or_ocmd = lambda do |obj|
|
|
125
|
+
if obj.type == :OCG
|
|
126
|
+
ocgs << obj
|
|
127
|
+
elsif obj.type == :OCMD
|
|
128
|
+
ocgs.merge(obj[:OCGs].to_ary)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
seen_resources = {}
|
|
132
|
+
pages.each do |page|
|
|
133
|
+
unless seen_resources[page.resources] # handle case when pages share the resources dict
|
|
134
|
+
page.resources[:Properties]&.each do |name, obj|
|
|
135
|
+
next unless obj
|
|
136
|
+
process_ocg_or_ocmd.call(obj)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
page.resources[:XObject]&.each do |name, obj|
|
|
140
|
+
process_ocg_or_ocmd.call(obj[:OC]) if obj.key?(:OC)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
page.each_annotation do |annot|
|
|
145
|
+
process_ocg_or_ocmd.call(annot[:OC]) if annot.key?(:OC)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
seen_resources[page.resources] = true
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
return if ocgs.empty?
|
|
152
|
+
|
|
153
|
+
# Add all found OCGs to the optional content properties dictionary
|
|
154
|
+
ocp = doc.optional_content
|
|
155
|
+
ocgs.each {|ocg| ocp.add_ocg(ocg) }
|
|
156
|
+
|
|
157
|
+
# Create a mapping from source OCGs to target OCGs and vice-versa
|
|
158
|
+
source_ocg = {}
|
|
159
|
+
target_ocg = {}
|
|
160
|
+
source.optional_content.ocgs.each do |ocg|
|
|
161
|
+
imported_ocg = doc.import(ocg)
|
|
162
|
+
next unless ocgs.include?(imported_ocg)
|
|
163
|
+
source_ocg[imported_ocg] = ocg
|
|
164
|
+
target_ocg[ocg] = imported_ocg
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Ensure the initial state of the OCGs is correct
|
|
168
|
+
source_config = source.optional_content.default_configuration
|
|
169
|
+
target_config = ocp.default_configuration
|
|
170
|
+
ocgs.each do |ocg|
|
|
171
|
+
target_config.ocg_state(ocg, source_config.ocg_state(source_ocg[ocg]))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Copy radio button groups from the source document, removing unknown OCGs from them
|
|
175
|
+
source_config[:RBGroups]&.each do |array|
|
|
176
|
+
result = array.map {|ocg| target_ocg[ocg] }.compact
|
|
177
|
+
next if result.empty?
|
|
178
|
+
(target_config[:RBGroups] ||= []) << result
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/task/pdfa.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/task.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2026 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -66,6 +66,7 @@ module HexaPDF
|
|
|
66
66
|
autoload(:Dereference, 'hexapdf/task/dereference')
|
|
67
67
|
autoload(:PDFA, 'hexapdf/task/pdfa')
|
|
68
68
|
autoload(:MergeAcroForm, 'hexapdf/task/merge_acro_form')
|
|
69
|
+
autoload(:ImportPages, 'hexapdf/task/import_pages')
|
|
69
70
|
|
|
70
71
|
end
|
|
71
72
|
|