hexapdf 0.11.9 → 0.13.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 +157 -0
- data/LICENSE +1 -1
- data/examples/001-hello_world.rb +1 -1
- data/examples/002-graphics.rb +1 -1
- data/examples/003-arcs.rb +1 -1
- data/examples/004-optimizing.rb +1 -1
- data/examples/005-merging.rb +1 -1
- data/examples/006-standard_pdf_fonts.rb +1 -1
- data/examples/007-truetype.rb +1 -1
- data/examples/008-show_char_bboxes.rb +1 -1
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +1 -1
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +1 -1
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +1 -1
- data/examples/016-frame_automatic_box_placement.rb +1 -1
- data/examples/017-frame_text_flow.rb +1 -1
- data/examples/018-composer.rb +1 -1
- data/examples/019-acro_form.rb +51 -0
- data/lib/hexapdf.rb +1 -1
- data/lib/hexapdf/cli.rb +3 -1
- data/lib/hexapdf/cli/batch.rb +1 -1
- data/lib/hexapdf/cli/command.rb +22 -11
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/form.rb +240 -0
- data/lib/hexapdf/cli/image2pdf.rb +3 -2
- data/lib/hexapdf/cli/images.rb +1 -1
- data/lib/hexapdf/cli/info.rb +52 -3
- data/lib/hexapdf/cli/inspect.rb +31 -9
- data/lib/hexapdf/cli/merge.rb +2 -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/watermark.rb +1 -1
- data/lib/hexapdf/composer.rb +2 -2
- data/lib/hexapdf/configuration.rb +81 -11
- data/lib/hexapdf/content.rb +3 -1
- data/lib/hexapdf/content/canvas.rb +5 -18
- data/lib/hexapdf/content/color_space.rb +111 -32
- data/lib/hexapdf/content/graphic_object.rb +1 -1
- data/lib/hexapdf/content/graphic_object/arc.rb +4 -4
- 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/graphics_state.rb +1 -1
- data/lib/hexapdf/content/operator.rb +9 -9
- data/lib/hexapdf/content/parser.rb +18 -5
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/data_dir.rb +1 -1
- data/lib/hexapdf/dictionary.rb +5 -5
- data/lib/hexapdf/dictionary_fields.rb +2 -10
- data/lib/hexapdf/document.rb +45 -17
- data/lib/hexapdf/document/files.rb +1 -2
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/document/pages.rb +3 -14
- data/lib/hexapdf/encryption.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 +2 -2
- 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 +2 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/error.rb +1 -1
- data/lib/hexapdf/filter.rb +3 -3
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.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/{jpx_decode.rb → pass_through.rb} +5 -5
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/font/cmap.rb +2 -5
- data/lib/hexapdf/font/cmap/parser.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +1 -1
- data/lib/hexapdf/font/encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/base.rb +9 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +7 -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/invalid_glyph.rb +1 -1
- data/lib/hexapdf/font/true_type.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 +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- 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 +2 -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 +3 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
- data/lib/hexapdf/font/type1.rb +1 -1
- 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_wrapper.rb +68 -52
- data/lib/hexapdf/font_loader.rb +1 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/from_file.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/image_loader.rb +1 -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 +4 -3
- data/lib/hexapdf/importer.rb +2 -4
- data/lib/hexapdf/layout.rb +1 -1
- data/lib/hexapdf/layout/box.rb +1 -1
- 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 +2 -2
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/style.rb +24 -24
- data/lib/hexapdf/layout/text_box.rb +1 -1
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +1 -1
- data/lib/hexapdf/layout/text_shaper.rb +4 -3
- data/lib/hexapdf/layout/width_from_polygon.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 +32 -27
- data/lib/hexapdf/parser.rb +69 -6
- data/lib/hexapdf/pdf_array.rb +10 -3
- data/lib/hexapdf/rectangle.rb +31 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +2 -1
- data/lib/hexapdf/revisions.rb +30 -22
- data/lib/hexapdf/serializer.rb +2 -2
- data/lib/hexapdf/stream.rb +1 -1
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +7 -5
- data/lib/hexapdf/tokenizer.rb +5 -4
- data/lib/hexapdf/type.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +7 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +405 -0
- data/lib/hexapdf/type/acro_form/button_field.rb +305 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
- data/lib/hexapdf/type/acro_form/field.rb +250 -17
- data/lib/hexapdf/type/acro_form/form.rb +159 -7
- data/lib/hexapdf/type/acro_form/text_field.rb +187 -0
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions.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/uri.rb +4 -3
- data/lib/hexapdf/type/annotation.rb +73 -3
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/annotations/link.rb +2 -2
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +238 -2
- data/lib/hexapdf/type/catalog.rb +23 -3
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/font.rb +18 -1
- data/lib/hexapdf/type/font_descriptor.rb +2 -2
- data/lib/hexapdf/type/font_simple.rb +4 -2
- data/lib/hexapdf/type/font_true_type.rb +7 -3
- data/lib/hexapdf/type/font_type0.rb +2 -2
- data/lib/hexapdf/type/font_type1.rb +16 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +12 -2
- 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 +5 -3
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/names.rb +1 -1
- data/lib/hexapdf/type/object_stream.rb +1 -1
- data/lib/hexapdf/type/page.rb +36 -12
- data/lib/hexapdf/type/page_tree_node.rb +37 -16
- data/lib/hexapdf/type/resources.rb +17 -3
- data/lib/hexapdf/type/trailer.rb +4 -6
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +1 -1
- data/lib/hexapdf/utils/bit_field.rb +38 -24
- 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 +19 -16
- data/lib/hexapdf/version.rb +2 -2
- data/lib/hexapdf/writer.rb +1 -1
- data/lib/hexapdf/xref_section.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +6 -1
- data/test/hexapdf/content/common.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
- data/test/hexapdf/content/test_canvas.rb +3 -3
- data/test/hexapdf/content/test_color_space.rb +71 -8
- data/test/hexapdf/content/test_operator.rb +22 -22
- data/test/hexapdf/content/test_parser.rb +14 -0
- data/test/hexapdf/document/test_fonts.rb +1 -1
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/encryption/test_aes.rb +4 -4
- data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
- data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
- data/test/hexapdf/font/encoding/test_base.rb +10 -0
- data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
- data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
- data/test/hexapdf/layout/test_style.rb +1 -1
- data/test/hexapdf/layout/test_text_layouter.rb +3 -4
- data/test/hexapdf/test_configuration.rb +2 -2
- data/test/hexapdf/test_dictionary.rb +3 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -2
- data/test/hexapdf/test_document.rb +16 -4
- data/test/hexapdf/test_object.rb +44 -26
- data/test/hexapdf/test_parser.rb +125 -55
- data/test/hexapdf/test_pdf_array.rb +7 -0
- data/test/hexapdf/test_rectangle.rb +14 -0
- data/test/hexapdf/test_revision.rb +3 -0
- data/test/hexapdf/test_revisions.rb +35 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +521 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +281 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
- data/test/hexapdf/type/acro_form/test_field.rb +163 -6
- data/test/hexapdf/type/acro_form/test_form.rb +189 -22
- data/test/hexapdf/type/acro_form/test_text_field.rb +121 -0
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
- data/test/hexapdf/type/annotations/test_text.rb +1 -1
- data/test/hexapdf/type/annotations/test_widget.rb +199 -0
- data/test/hexapdf/type/test_annotation.rb +45 -0
- data/test/hexapdf/type/test_catalog.rb +18 -0
- data/test/hexapdf/type/test_font.rb +5 -0
- data/test/hexapdf/type/test_font_simple.rb +2 -1
- data/test/hexapdf/type/test_font_true_type.rb +6 -0
- data/test/hexapdf/type/test_font_type1.rb +8 -0
- data/test/hexapdf/type/test_form.rb +19 -1
- data/test/hexapdf/type/test_image.rb +7 -0
- data/test/hexapdf/type/test_page.rb +45 -7
- data/test/hexapdf/type/test_page_tree_node.rb +62 -12
- data/test/hexapdf/type/test_resources.rb +20 -0
- data/test/hexapdf/type/test_trailer.rb +4 -0
- data/test/hexapdf/utils/test_bit_field.rb +15 -1
- data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
- data/test/test_helper.rb +1 -1
- metadata +34 -21
- data/lib/hexapdf/filter/dct_decode.rb +0 -60
data/lib/hexapdf/cli/files.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-2020 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,240 @@
|
|
|
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-2020 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/cli/command'
|
|
38
|
+
require 'strscan'
|
|
39
|
+
|
|
40
|
+
module HexaPDF
|
|
41
|
+
module CLI
|
|
42
|
+
|
|
43
|
+
# Processes a PDF that contains an interactive form (AcroForm).
|
|
44
|
+
class Form < Command
|
|
45
|
+
|
|
46
|
+
def initialize #:nodoc:
|
|
47
|
+
super('form', takes_commands: false)
|
|
48
|
+
short_desc("Show form fields and fill out a form")
|
|
49
|
+
long_desc(<<~EOF)
|
|
50
|
+
Use this command to process interactive PDF forms.
|
|
51
|
+
|
|
52
|
+
If the the output file name is not given, all form fields are listed in page order. Use
|
|
53
|
+
the global --verbose option to show additional information like field type and location.
|
|
54
|
+
|
|
55
|
+
If the output file name is given, the fields can be interactively filled out. By
|
|
56
|
+
additionally using the --template option, the data for the fields is read from the given
|
|
57
|
+
template file instead of the standard input.
|
|
58
|
+
EOF
|
|
59
|
+
|
|
60
|
+
options.on("--password PASSWORD", "-p", String,
|
|
61
|
+
"The password for decryption. Use - for reading from standard input.") do |pwd|
|
|
62
|
+
@password = (pwd == '-' ? read_password : pwd)
|
|
63
|
+
end
|
|
64
|
+
options.on("--template TEMPLATE_FILE", "-t TEMPLATE_FILE",
|
|
65
|
+
"Use the template file for the field values") do |template|
|
|
66
|
+
@template = template
|
|
67
|
+
end
|
|
68
|
+
options.on("--[no-]viewer-override", "Let the PDF viewer override the visual " \
|
|
69
|
+
"appearance. Default: use setting from input PDF") do |need_appearances|
|
|
70
|
+
@need_appearances = need_appearances
|
|
71
|
+
end
|
|
72
|
+
options.on("--[no-]incremental-save", "Append the changes instead of rewriting the " \
|
|
73
|
+
"whole file. Default: true") do |incremental|
|
|
74
|
+
@incremental = incremental
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@password = nil
|
|
78
|
+
@template = nil
|
|
79
|
+
@need_appearances = nil
|
|
80
|
+
@incremental = true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def execute(in_file, out_file = nil) #:nodoc:
|
|
84
|
+
maybe_raise_on_existing_file(out_file) if out_file
|
|
85
|
+
with_document(in_file, password: @password, out_file: out_file,
|
|
86
|
+
incremental: @incremental) do |doc|
|
|
87
|
+
if !doc.acro_form
|
|
88
|
+
raise "This PDF doesn't contain an interactive form"
|
|
89
|
+
elsif out_file
|
|
90
|
+
doc.acro_form[:NeedAppearances] = @need_appearances unless @need_appearances.nil?
|
|
91
|
+
if @template
|
|
92
|
+
fill_form_with_template(doc)
|
|
93
|
+
else
|
|
94
|
+
fill_form(doc)
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
list_form_fields(doc)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Lists all terminal form fields.
|
|
105
|
+
def list_form_fields(doc)
|
|
106
|
+
current_page_index = -1
|
|
107
|
+
each_field(doc) do |_page, page_index, field, widget|
|
|
108
|
+
if current_page_index != page_index
|
|
109
|
+
puts "Page #{page_index + 1}"
|
|
110
|
+
current_page_index = page_index
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
field_name = field.full_field_name +
|
|
114
|
+
(field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
|
|
115
|
+
concrete_field_type = field.concrete_field_type
|
|
116
|
+
nice_field_type = concrete_field_type.to_s.split('_').map(&:capitalize).join(' ')
|
|
117
|
+
position = "(#{widget[:Rect].left}, #{widget[:Rect].bottom})"
|
|
118
|
+
|
|
119
|
+
puts " #{field_name}"
|
|
120
|
+
if command_parser.verbosity_info?
|
|
121
|
+
printf(" └─ %-22s | %-20s\n", nice_field_type, position)
|
|
122
|
+
end
|
|
123
|
+
puts " └─ #{field.field_value.inspect}"
|
|
124
|
+
if command_parser.verbosity_info?
|
|
125
|
+
if field.field_type == :Ch
|
|
126
|
+
puts " └─ Options: #{field.option_items.map(&:inspect).join(', ')}"
|
|
127
|
+
elsif concrete_field_type == :radio_button
|
|
128
|
+
puts " └─ Options: #{field.radio_button_values.map(&:inspect).join(', ')}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Fills out the form by interactively asking the user for field values.
|
|
135
|
+
def fill_form(doc)
|
|
136
|
+
current_page_index = -1
|
|
137
|
+
each_field(doc) do |_page, page_index, field, _widget|
|
|
138
|
+
if current_page_index != page_index
|
|
139
|
+
puts "Page #{page_index + 1}"
|
|
140
|
+
current_page_index = page_index
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
field_name = field.full_field_name +
|
|
144
|
+
(field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
|
|
145
|
+
concrete_field_type = field.concrete_field_type
|
|
146
|
+
|
|
147
|
+
puts " #{field_name}"
|
|
148
|
+
puts " └─ Current value: #{field.field_value.inspect}"
|
|
149
|
+
|
|
150
|
+
if field.field_type == :Ch
|
|
151
|
+
puts " └─ Possible values: #{field.option_items.map(&:inspect).join(', ')}"
|
|
152
|
+
elsif concrete_field_type == :radio_button
|
|
153
|
+
puts " └─ Possible values: #{field.radio_button_values.map(&:inspect).join(', ')}"
|
|
154
|
+
elsif concrete_field_type == :check_box
|
|
155
|
+
puts " └─ Possible values: y(es), t(rue); n(o), f(alse)"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
begin
|
|
159
|
+
print " └─ New value: "
|
|
160
|
+
value = $stdin.readline.chomp
|
|
161
|
+
next if value.empty?
|
|
162
|
+
apply_field_value(field, value)
|
|
163
|
+
rescue HexaPDF::Error => e
|
|
164
|
+
puts " ⚠ #{e.message}"
|
|
165
|
+
retry
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Fills out the form using the data from the provided template file.
|
|
171
|
+
def fill_form_with_template(doc)
|
|
172
|
+
data = parse_template
|
|
173
|
+
form = doc.acro_form
|
|
174
|
+
data.each do |name, value|
|
|
175
|
+
field = form.field_by_name(name)
|
|
176
|
+
raise "Field '#{name}' not found in input PDF" unless field
|
|
177
|
+
apply_field_value(field, value)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Parses the data from the given template file.
|
|
182
|
+
def parse_template
|
|
183
|
+
data = {}
|
|
184
|
+
scanner = StringScanner.new(File.read(@template))
|
|
185
|
+
until scanner.eos?
|
|
186
|
+
field_name = scanner.scan(/(\\:|[^:])*?:/)
|
|
187
|
+
break unless field_name
|
|
188
|
+
field_name.gsub!(/\\:/, ':')
|
|
189
|
+
field_value = scanner.scan(/.*?(?=^\S|\z)/m)
|
|
190
|
+
data[field_name.chop] = field_value.strip.gsub(/^\s*/, '') if field_value
|
|
191
|
+
end
|
|
192
|
+
if !scanner.eos? && command_parser.verbosity_warning?
|
|
193
|
+
$stderr.puts "Warning: Some template could not be parsed"
|
|
194
|
+
end
|
|
195
|
+
data
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Applies the given value to the field.
|
|
199
|
+
def apply_field_value(field, value)
|
|
200
|
+
case field.concrete_field_type
|
|
201
|
+
when :single_line_text_field
|
|
202
|
+
field.field_value = value
|
|
203
|
+
when :combo_box, :list_box
|
|
204
|
+
field.field_value = value
|
|
205
|
+
when :editable_combo_box
|
|
206
|
+
field.field_value = value
|
|
207
|
+
when :check_box
|
|
208
|
+
unless value.match?(/y(es)?|t(rue)?|f(alse)?|n(o)/)
|
|
209
|
+
raise HexaPDF::Error, "Invalid input, use one of the possible values"
|
|
210
|
+
end
|
|
211
|
+
field.field_value = value.match?(/y(es)?|t(rue)?/)
|
|
212
|
+
when :radio_button
|
|
213
|
+
field.field_value = value.intern
|
|
214
|
+
else
|
|
215
|
+
raise "Field type not yet supported"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Iterates over all non-push button fields in page order. If a field appears on multiple
|
|
220
|
+
# pages, it is only yielded on the first page.
|
|
221
|
+
def each_field(doc) # :yields: page, page_index, field
|
|
222
|
+
seen = {}
|
|
223
|
+
|
|
224
|
+
doc.pages.each_with_index do |page, page_index|
|
|
225
|
+
page[:Annots]&.each do |annotation|
|
|
226
|
+
next unless annotation[:Subtype] == :Widget
|
|
227
|
+
field = annotation.form_field
|
|
228
|
+
next if field.concrete_field_type == :push_button
|
|
229
|
+
unless seen[field]
|
|
230
|
+
yield(page, page_index, field, annotation)
|
|
231
|
+
seen[field] = true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
end
|
|
240
|
+
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-2020 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
|
|
@@ -64,7 +64,8 @@ module HexaPDF
|
|
|
64
64
|
orientation = :landscape
|
|
65
65
|
page_size.delete_suffix!('-landscape')
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
page_size = page_size.capitalize.to_sym
|
|
68
|
+
HexaPDF::Type::Page.media_box(page_size, orientation: orientation)
|
|
68
69
|
end
|
|
69
70
|
end
|
|
70
71
|
options.on("--[no-]auto-rotate", "Automatically rotate pages based on image dimesions. " \
|
data/lib/hexapdf/cli/images.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-2020 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/cli/info.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-2020 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
|
|
@@ -55,13 +55,21 @@ module HexaPDF
|
|
|
55
55
|
long_desc(<<~EOF)
|
|
56
56
|
This command extracts information from the Info dictionary of a PDF file as well
|
|
57
57
|
as some other useful information like the used PDF version and encryption information.
|
|
58
|
+
|
|
59
|
+
If the --check option is specified, the PDF file will also be checked for parse and
|
|
60
|
+
validation errors. And if the process doesn't abort, HexaPDF is still able to handle the
|
|
61
|
+
file by correcting the errors.
|
|
58
62
|
EOF
|
|
63
|
+
options.on("--check", "-c", "Check the PDF file for parse errors and validity") do |check|
|
|
64
|
+
@check_file = check
|
|
65
|
+
end
|
|
59
66
|
options.on("--password PASSWORD", "-p", String,
|
|
60
67
|
"The password for decryption. Use - for reading from standard input.") do |pwd|
|
|
61
68
|
@password = (pwd == '-' ? read_password : pwd)
|
|
62
69
|
end
|
|
63
70
|
@password = nil
|
|
64
71
|
@auto_decrypt = true
|
|
72
|
+
@check_file = false
|
|
65
73
|
end
|
|
66
74
|
|
|
67
75
|
def execute(file) #:nodoc:
|
|
@@ -79,8 +87,30 @@ module HexaPDF
|
|
|
79
87
|
options = pdf_options(@password)
|
|
80
88
|
options[:config]['document.auto_decrypt'] = @auto_decrypt
|
|
81
89
|
HexaPDF::Document.open(file, **options) do |doc|
|
|
90
|
+
if @check_file
|
|
91
|
+
indirect_object = nil
|
|
92
|
+
validation_block = lambda do |msg, correctable, object|
|
|
93
|
+
object = indirect_object unless object.indirect? || object.type == :XXTrailer
|
|
94
|
+
object_type = if object.type == :XXTrailer
|
|
95
|
+
'trailer'
|
|
96
|
+
elsif !object.type.to_s.start_with?("XX")
|
|
97
|
+
"object type #{object.type} (#{object.oid},#{object.gen})"
|
|
98
|
+
else
|
|
99
|
+
"object (#{object.oid},#{object.gen})"
|
|
100
|
+
end
|
|
101
|
+
object_type = "sub-object of #{object_type}" if object == indirect_object
|
|
102
|
+
puts "WARNING: Validation error for #{object_type}: #{msg} " \
|
|
103
|
+
"#{correctable ? '(correctable)' : ''}"
|
|
104
|
+
end
|
|
105
|
+
doc.trailer.validate(auto_correct: true, &validation_block)
|
|
106
|
+
doc.each(only_current: false, only_loaded: false) do |obj|
|
|
107
|
+
indirect_object = obj
|
|
108
|
+
obj.validate(auto_correct: true, &validation_block)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
82
112
|
output_line("File name", file)
|
|
83
|
-
output_line("File size", File.stat(file).size.to_s
|
|
113
|
+
output_line("File size", File.stat(file).size.to_s << " bytes")
|
|
84
114
|
@auto_decrypt && INFO_KEYS.each do |name|
|
|
85
115
|
next unless doc.trailer.info.key?(name)
|
|
86
116
|
output_line(name.to_s, doc.trailer.info[name].to_s)
|
|
@@ -110,10 +140,29 @@ module HexaPDF
|
|
|
110
140
|
else
|
|
111
141
|
raise
|
|
112
142
|
end
|
|
143
|
+
rescue HexaPDF::MalformedPDFError => e
|
|
144
|
+
$stderr.puts "Error: PDF file #{file} is damaged and cannot be recovered"
|
|
145
|
+
$stderr.puts " #{e}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Use custom options if we are checking the PDF file for errors.
|
|
149
|
+
def pdf_options(password)
|
|
150
|
+
if @check_file
|
|
151
|
+
options = {decryption_opts: {password: password}, config: {}}
|
|
152
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
|
153
|
+
options[:config]['parser.try_xref_reconstruction'] = true
|
|
154
|
+
options[:config]['parser.on_correctable_error'] = lambda do |_, msg, pos|
|
|
155
|
+
puts "WARNING: Parse error at position #{pos}: #{msg}"
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
options
|
|
159
|
+
else
|
|
160
|
+
super
|
|
161
|
+
end
|
|
113
162
|
end
|
|
114
163
|
|
|
115
164
|
def output_line(header, text) #:nodoc:
|
|
116
|
-
puts((header
|
|
165
|
+
puts(("#{header}:").ljust(COLUMN_WIDTH) << text)
|
|
117
166
|
end
|
|
118
167
|
|
|
119
168
|
end
|
data/lib/hexapdf/cli/inspect.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-2020 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
|
|
@@ -122,22 +122,22 @@ module HexaPDF
|
|
|
122
122
|
case command
|
|
123
123
|
when /^\d+(,\d+)?$/, 'o', 'object'
|
|
124
124
|
arg = (command.start_with?('o') ? data.shift : command)
|
|
125
|
-
obj = pdf_object_from_string_reference(arg) rescue puts($!.message)
|
|
126
|
-
if obj
|
|
125
|
+
obj = pdf_object_from_string_reference(arg) rescue $stderr.puts($!.message)
|
|
126
|
+
if obj&.data&.stream && command_parser.verbosity_info?
|
|
127
127
|
$stderr.puts("Note: Object also has stream data")
|
|
128
128
|
end
|
|
129
129
|
serialize(obj.value, recursive: false) if obj
|
|
130
130
|
|
|
131
131
|
when 'r', 'recursive'
|
|
132
132
|
obj = if (obj = data.shift)
|
|
133
|
-
pdf_object_from_string_reference(obj) rescue puts($!.message)
|
|
133
|
+
pdf_object_from_string_reference(obj) rescue $stderr.puts($!.message)
|
|
134
134
|
else
|
|
135
135
|
@doc.trailer
|
|
136
136
|
end
|
|
137
137
|
serialize(obj.value, recursive: true) if obj
|
|
138
138
|
|
|
139
139
|
when 's', 'stream', 'raw', 'raw-stream'
|
|
140
|
-
if (obj = pdf_object_from_string_reference(data.shift) rescue puts($!.message)) &&
|
|
140
|
+
if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message)) &&
|
|
141
141
|
obj.kind_of?(HexaPDF::Stream)
|
|
142
142
|
source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
|
|
143
143
|
while source.alive? && (stream_data = source.resume)
|
|
@@ -148,7 +148,7 @@ module HexaPDF
|
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
when 'x', 'xref'
|
|
151
|
-
if (obj = pdf_object_from_string_reference(data.shift) rescue puts($!.message))
|
|
151
|
+
if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message))
|
|
152
152
|
@doc.revisions.reverse_each do |rev|
|
|
153
153
|
if (xref = rev.xref(obj))
|
|
154
154
|
puts xref
|
|
@@ -178,6 +178,26 @@ module HexaPDF
|
|
|
178
178
|
puts str
|
|
179
179
|
end
|
|
180
180
|
|
|
181
|
+
when 'po', 'ps'
|
|
182
|
+
page_number_str = data.shift
|
|
183
|
+
unless page_number_str
|
|
184
|
+
$stderr.puts("Error: Missing PAGE argument to #{command}")
|
|
185
|
+
next
|
|
186
|
+
end
|
|
187
|
+
page_number = parse_pages_specification(page_number_str, @doc.pages.count).first&.first
|
|
188
|
+
unless page_number
|
|
189
|
+
$stderr.puts("Error: Invalid page number #{page_number_str}")
|
|
190
|
+
next
|
|
191
|
+
end
|
|
192
|
+
page = @doc.pages[page_number]
|
|
193
|
+
if command.start_with?('ps')
|
|
194
|
+
$stdout.write(page.contents)
|
|
195
|
+
else
|
|
196
|
+
puts "#{page.oid} #{page.gen} obj"
|
|
197
|
+
serialize(page.value, recursive: false)
|
|
198
|
+
puts "endobj"
|
|
199
|
+
end
|
|
200
|
+
|
|
181
201
|
when 'pc', 'page-count'
|
|
182
202
|
puts @doc.pages.count
|
|
183
203
|
|
|
@@ -217,9 +237,9 @@ module HexaPDF
|
|
|
217
237
|
if str.nil?
|
|
218
238
|
raise "Error: Missing argument object identifier OID[,GEN]"
|
|
219
239
|
elsif !str.match?(/^\d+(,\d+)?$/)
|
|
220
|
-
raise "Error: Invalid argument: Must be of form OID[,GEN]"
|
|
240
|
+
raise "Error: Invalid argument: Must be of form OID[,GEN], not '#{str}'"
|
|
221
241
|
elsif !(obj = @doc.object(pdf_reference_from_string(str)))
|
|
222
|
-
raise "Error: No object with the given object identifier found"
|
|
242
|
+
raise "Error: No object with the given object identifier '#{str}' found"
|
|
223
243
|
else
|
|
224
244
|
obj
|
|
225
245
|
end
|
|
@@ -240,7 +260,7 @@ module HexaPDF
|
|
|
240
260
|
puts "<<"
|
|
241
261
|
(recursive ? val.sort : val).each do |k, v|
|
|
242
262
|
next if v.nil? || (v.respond_to?(:null?) && v.null?)
|
|
243
|
-
print ' ' * (indent + 1)
|
|
263
|
+
print '%s%s ' % [' ' * (indent + 1), @serializer.serialize_symbol(k)]
|
|
244
264
|
serialize(v, recursive: recursive, seen: seen, indent: indent + 1)
|
|
245
265
|
puts
|
|
246
266
|
end
|
|
@@ -283,6 +303,8 @@ module HexaPDF
|
|
|
283
303
|
["c[atalog]", "Print the catalog dictionary"],
|
|
284
304
|
["t[railer]", "Print the trailer dictionary"],
|
|
285
305
|
["p[ages] [RANGE]", "Print information about pages"],
|
|
306
|
+
["po PAGE", "Print the page object"],
|
|
307
|
+
["ps PAGE", "Print the content stream of the page"],
|
|
286
308
|
["pc | page-count", "Print the number of pages"],
|
|
287
309
|
["search REGEXP", "Print objects matching the pattern"],
|
|
288
310
|
["h[elp]", "Show the help"],
|