hexapdf 0.11.9 → 0.12.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 +82 -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 +18 -9
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/form.rb +240 -0
- data/lib/hexapdf/cli/image2pdf.rb +1 -1
- data/lib/hexapdf/cli/images.rb +1 -1
- data/lib/hexapdf/cli/info.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- 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 +66 -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 +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/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 +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document.rb +14 -5
- 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/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 +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/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 +1 -1
- 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 +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/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 +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_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 +67 -51
- 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 +1 -1
- 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 +1 -1
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/style.rb +1 -1
- 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 +1 -1
- 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 +2 -2
- data/lib/hexapdf/parser.rb +4 -3
- data/lib/hexapdf/pdf_array.rb +1 -1
- 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 +1 -1
- data/lib/hexapdf/serializer.rb +1 -1
- 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 +1 -1
- data/lib/hexapdf/tokenizer.rb +1 -1
- 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 +401 -0
- data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
- data/lib/hexapdf/type/acro_form/field.rb +220 -17
- data/lib/hexapdf/type/acro_form/form.rb +157 -7
- data/lib/hexapdf/type/acro_form/text_field.rb +186 -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 +1 -1
- 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 +239 -2
- data/lib/hexapdf/type/catalog.rb +21 -1
- 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 +1 -1
- data/lib/hexapdf/type/font.rb +18 -1
- data/lib/hexapdf/type/font_descriptor.rb +2 -2
- 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 +16 -1
- data/lib/hexapdf/type/font_type3.rb +1 -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 +3 -1
- 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 +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +8 -11
- data/lib/hexapdf/type/resources.rb +16 -3
- data/lib/hexapdf/type/trailer.rb +2 -3
- 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 +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/test/hexapdf/content/common.rb +2 -2
- 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/font/test_true_type_wrapper.rb +10 -7
- data/test/hexapdf/font/test_type1_wrapper.rb +32 -8
- data/test/hexapdf/test_document.rb +12 -0
- data/test/hexapdf/test_parser.rb +10 -0
- data/test/hexapdf/test_rectangle.rb +14 -0
- data/test/hexapdf/test_revision.rb +3 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
- data/test/hexapdf/type/acro_form/test_field.rb +124 -6
- data/test/hexapdf/type/acro_form/test_form.rb +189 -22
- data/test/hexapdf/type/acro_form/test_text_field.rb +119 -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_type1.rb +8 -0
- data/test/hexapdf/type/test_image.rb +7 -0
- data/test/hexapdf/type/test_page_tree_node.rb +20 -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 +13 -1
- data/test/test_helper.rb +1 -1
- metadata +37 -18
- data/lib/hexapdf/filter/dct_decode.rb +0 -60
|
@@ -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
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
require 'hexapdf/dictionary'
|
|
38
38
|
require 'hexapdf/stream'
|
|
39
|
+
require 'hexapdf/type/acro_form/field'
|
|
39
40
|
require 'hexapdf/utils/bit_field'
|
|
40
41
|
|
|
41
42
|
module HexaPDF
|
|
@@ -45,7 +46,29 @@ module HexaPDF
|
|
|
45
46
|
# Represents the PDF's interactive form dictionary. It is linked from the catalog dictionary
|
|
46
47
|
# via the /AcroForm entry.
|
|
47
48
|
#
|
|
48
|
-
#
|
|
49
|
+
# == Overview
|
|
50
|
+
#
|
|
51
|
+
# An interactive form consists of fields which can be structured hierarchically and shown on
|
|
52
|
+
# pages by using Annotations::Widget annotations. This means one field can have zero, one or
|
|
53
|
+
# more visual representations on one or more pages. The fields at the bottom of the hierarchy
|
|
54
|
+
# which have no parent are called "root fields" and are stored in /Fields.
|
|
55
|
+
#
|
|
56
|
+
# Each field in a form has a certain type which determines how it should be displayed and what
|
|
57
|
+
# a user can do with it. The most common type is "text field" which allows the user to enter
|
|
58
|
+
# one or more lines of text. There are also check boxes, radio buttons, list boxes and combo
|
|
59
|
+
# boxes.
|
|
60
|
+
#
|
|
61
|
+
# == Visual Appearance
|
|
62
|
+
#
|
|
63
|
+
# The visual appearance of a field is normally provided by the application creating the PDF.
|
|
64
|
+
# This is done by generating the so called appearances for all widgets of a field. However, it
|
|
65
|
+
# is also possible to instruct the PDF reader application to generate the appearances on the
|
|
66
|
+
# fly using the /NeedAppearances key, see #need_appearances!.
|
|
67
|
+
#
|
|
68
|
+
# HexaPDF uses the configuration option +acro_form.create_appearance_streams+ to determine
|
|
69
|
+
# whether appearances should automatically be generated.
|
|
70
|
+
#
|
|
71
|
+
# See: PDF1.7 s12.7.2, Field, HexaPDF::Type::Annotations::Widget
|
|
49
72
|
class Form < Dictionary
|
|
50
73
|
|
|
51
74
|
extend Utils::BitField
|
|
@@ -56,12 +79,18 @@ module HexaPDF
|
|
|
56
79
|
define_field :NeedAppearances, type: Boolean, default: false
|
|
57
80
|
define_field :SigFlags, type: Integer, version: '1.3'
|
|
58
81
|
define_field :CO, type: PDFArray, version: '1.3'
|
|
59
|
-
define_field :DR, type: :
|
|
82
|
+
define_field :DR, type: :XXResources
|
|
60
83
|
define_field :DA, type: String
|
|
61
84
|
define_field :XFA, type: [Stream, PDFArray], version: '1.5'
|
|
62
85
|
|
|
63
86
|
bit_field(:raw_signature_flags, {signatures_exist: 0, append_only: 1},
|
|
64
|
-
lister: "signature_flags", getter: "signature_flag?", setter: "signature_flag"
|
|
87
|
+
lister: "signature_flags", getter: "signature_flag?", setter: "signature_flag",
|
|
88
|
+
unsetter: "signature_unflag")
|
|
89
|
+
|
|
90
|
+
# Returns the PDFArray containing the root fields.
|
|
91
|
+
def root_fields
|
|
92
|
+
self[:Fields] ||= document.wrap([])
|
|
93
|
+
end
|
|
65
94
|
|
|
66
95
|
# Returns an array with all root fields that were found in the PDF document.
|
|
67
96
|
def find_root_fields
|
|
@@ -69,7 +98,7 @@ module HexaPDF
|
|
|
69
98
|
document.pages.each do |page|
|
|
70
99
|
page[:Annots]&.each do |annot|
|
|
71
100
|
if !annot.key?(:Parent) && annot.key?(:FT)
|
|
72
|
-
result << document.wrap(annot, type: :XXAcroFormField)
|
|
101
|
+
result << document.wrap(annot, type: :XXAcroFormField, subtype: annot[:FT])
|
|
73
102
|
elsif annot.key?(:Parent)
|
|
74
103
|
field = annot[:Parent]
|
|
75
104
|
field = field[:Parent] while field[:Parent]
|
|
@@ -96,15 +125,99 @@ module HexaPDF
|
|
|
96
125
|
return to_enum(__method__, terminal_only: terminal_only) unless block_given?
|
|
97
126
|
|
|
98
127
|
process_field = lambda do |field|
|
|
99
|
-
field = document.wrap(field, type: :XXAcroFormField
|
|
128
|
+
field = document.wrap(field, type: :XXAcroFormField,
|
|
129
|
+
subtype: Field.inherited_value(field, :FT))
|
|
100
130
|
yield(field) if field.terminal_field? || !terminal_only
|
|
101
131
|
field[:Kids].each(&process_field) unless field.terminal_field?
|
|
102
132
|
end
|
|
103
133
|
|
|
104
|
-
|
|
134
|
+
root_fields.each(&process_field)
|
|
105
135
|
self
|
|
106
136
|
end
|
|
107
137
|
|
|
138
|
+
# Returns the field with the given +name+ or +nil+ if no such field exists.
|
|
139
|
+
def field_by_name(name)
|
|
140
|
+
fields = root_fields
|
|
141
|
+
field = nil
|
|
142
|
+
name.split('.').each do |part|
|
|
143
|
+
field = fields&.find {|f| f[:T] == part }
|
|
144
|
+
break unless field
|
|
145
|
+
field = document.wrap(field, type: :XXAcroFormField,
|
|
146
|
+
subtype: Field.inherited_value(field, :FT))
|
|
147
|
+
fields = field[:Kids] unless field.terminal_field?
|
|
148
|
+
end
|
|
149
|
+
field
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Creates a new text field with the given name and adds it to the form.
|
|
153
|
+
#
|
|
154
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
155
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
156
|
+
def create_text_field(name)
|
|
157
|
+
create_field(name, :Tx)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Creates a new check box with the given name and adds it to the form.
|
|
161
|
+
#
|
|
162
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
163
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
164
|
+
def create_check_box(name)
|
|
165
|
+
create_field(name, :Btn).tap(&:initialize_as_check_box)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Creates a radio button with the given name and adds it to the form.
|
|
169
|
+
#
|
|
170
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
171
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
172
|
+
def create_radio_button(name)
|
|
173
|
+
create_field(name, :Btn).tap(&:initialize_as_radio_button)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Creates a combo box with the given name and adds it to the form.
|
|
177
|
+
#
|
|
178
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
179
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
180
|
+
def create_combo_box(name)
|
|
181
|
+
create_field(name, :Ch).tap(&:initialize_as_combo_box)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Creates a list box with the given name and adds it to the form.
|
|
185
|
+
#
|
|
186
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
187
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
188
|
+
def create_list_box(name)
|
|
189
|
+
create_field(name, :Ch).tap(&:initialize_as_list_box)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns the dictionary containing the default resources for form field appearance streams.
|
|
193
|
+
def default_resources
|
|
194
|
+
self[:DR] ||= document.wrap({}, type: :XXResources)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Sets the global default appearance string using the provided values.
|
|
198
|
+
#
|
|
199
|
+
# The default argument values are a sane default. If +font_size+ is set to 0, the font size
|
|
200
|
+
# is calculated using the height/width of the field.
|
|
201
|
+
def set_default_appearance_string(font: 'Helvetica', font_size: 0)
|
|
202
|
+
name = default_resources.add_font(document.fonts.add(font).pdf_object)
|
|
203
|
+
self[:DA] = "0 g /#{name} #{font_size} Tf"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Sets the /NeedAppearances field to +true+.
|
|
207
|
+
#
|
|
208
|
+
# This will make PDF reader applications generate appropriate appearance streams based on
|
|
209
|
+
# the information stored in the fields and associated widgets.
|
|
210
|
+
def need_appearances!
|
|
211
|
+
self[:NeedAppearances] = true
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Creates the appearances for all widgets of all terminal fields if they don't exist.
|
|
215
|
+
def create_appearances
|
|
216
|
+
each_field do |field|
|
|
217
|
+
field.create_appearances if field.respond_to?(:create_appearances)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
108
221
|
private
|
|
109
222
|
|
|
110
223
|
# Helper method for bit field getter access.
|
|
@@ -117,6 +230,43 @@ module HexaPDF
|
|
|
117
230
|
self[:SigFlags] = value
|
|
118
231
|
end
|
|
119
232
|
|
|
233
|
+
# Creates a new field with the full name +name+ and the field type +type+.
|
|
234
|
+
def create_field(name, type)
|
|
235
|
+
parent_name, _, name = name.rpartition('.')
|
|
236
|
+
parent_field = parent_name.empty? ? nil : field_by_name(parent_name)
|
|
237
|
+
if !parent_name.empty? && !parent_field
|
|
238
|
+
raise HexaPDF::Error, "Parent field '#{parent_name}' not found"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
field = document.add({FT: type, T: name, Parent: parent_field},
|
|
242
|
+
type: :XXAcroFormField, subtype: type)
|
|
243
|
+
if parent_field
|
|
244
|
+
(parent_field[:Kids] ||= []) << field
|
|
245
|
+
else
|
|
246
|
+
(self[:Fields] ||= []) << field
|
|
247
|
+
end
|
|
248
|
+
field
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def perform_validation # :nodoc:
|
|
252
|
+
if (da = self[:DA])
|
|
253
|
+
unless self[:DR]
|
|
254
|
+
yield("When the field /DA is present, the field /DR must also be present")
|
|
255
|
+
end
|
|
256
|
+
font_name = nil
|
|
257
|
+
HexaPDF::Content::Parser.parse(da) do |obj, params|
|
|
258
|
+
font_name = params[0] if obj == :Tf
|
|
259
|
+
end
|
|
260
|
+
if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
|
|
261
|
+
yield("The font specified in /DA is not in the /DR resource dictionary")
|
|
262
|
+
end
|
|
263
|
+
else
|
|
264
|
+
set_default_appearance_string
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
create_appearances if document.config['acro_form.create_appearances']
|
|
268
|
+
end
|
|
269
|
+
|
|
120
270
|
end
|
|
121
271
|
|
|
122
272
|
end
|
|
@@ -0,0 +1,186 @@
|
|
|
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/error'
|
|
38
|
+
require 'hexapdf/type/acro_form/variable_text_field'
|
|
39
|
+
|
|
40
|
+
module HexaPDF
|
|
41
|
+
module Type
|
|
42
|
+
module AcroForm
|
|
43
|
+
|
|
44
|
+
# AcroForm text fields provide a box or space to fill-in data entered from keyboard. The text
|
|
45
|
+
# may be restricted to a single line or can span multiple lines.
|
|
46
|
+
#
|
|
47
|
+
# == Type Specific Field Flags
|
|
48
|
+
#
|
|
49
|
+
# :multiline:: If set, the text field may contain multiple lines.
|
|
50
|
+
#
|
|
51
|
+
# :password:: The field is a password field. This changes the behaviour of the PDF reader
|
|
52
|
+
# application to not echo the input text and to not store it in the PDF file.
|
|
53
|
+
#
|
|
54
|
+
# :file_select:: The text field represents a file selection control where the input text is
|
|
55
|
+
# the path to a file.
|
|
56
|
+
#
|
|
57
|
+
# :do_not_spell_check:: The text should not be spell-checked.
|
|
58
|
+
#
|
|
59
|
+
# :do_not_scroll:: The text field should not scroll (horizontally for single-line fields and
|
|
60
|
+
# vertically for multiline fields) to accomodate more text than fits into the
|
|
61
|
+
# annotation rectangle. This means that no more text can be entered once the
|
|
62
|
+
# field is full.
|
|
63
|
+
#
|
|
64
|
+
# :comb:: The field is divided into /MaxLen equally spaced positions (so /MaxLen needs to be
|
|
65
|
+
# set). This is useful, for example, when entering things like social security
|
|
66
|
+
# numbers which always have the same length.
|
|
67
|
+
#
|
|
68
|
+
# :rich_text:: The field is a rich text field.
|
|
69
|
+
#
|
|
70
|
+
# See: PDF1.7 s12.7.4.3
|
|
71
|
+
class TextField < VariableTextField
|
|
72
|
+
|
|
73
|
+
define_field :MaxLen, type: Integer
|
|
74
|
+
|
|
75
|
+
# All inheritable dictionary fields for text fields.
|
|
76
|
+
INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:MaxLen]).freeze
|
|
77
|
+
|
|
78
|
+
# Updated list of field flags.
|
|
79
|
+
FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
|
|
80
|
+
{
|
|
81
|
+
multiline: 12,
|
|
82
|
+
password: 13,
|
|
83
|
+
file_select: 20,
|
|
84
|
+
do_not_spell_check: 22,
|
|
85
|
+
do_not_scroll: 23,
|
|
86
|
+
comb: 24,
|
|
87
|
+
rich_text: 25,
|
|
88
|
+
}
|
|
89
|
+
).freeze
|
|
90
|
+
|
|
91
|
+
# Returns the field value, i.e. the text contents of the field, or +nil+ if no value is set.
|
|
92
|
+
#
|
|
93
|
+
# Note that modifying the returned value *might not* modify the text contents in case it is
|
|
94
|
+
# stored as stream! So always use #field_value= to set the field value.
|
|
95
|
+
def field_value
|
|
96
|
+
return unless value[:V]
|
|
97
|
+
self[:V].kind_of?(String) ? self[:V] : self[:V].stream
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Sets the field value, i.e. the text contents of the field, to the given string.
|
|
101
|
+
#
|
|
102
|
+
# Note that for single line text fields, all whitespace characters are changed to simple
|
|
103
|
+
# spaces.
|
|
104
|
+
def field_value=(str)
|
|
105
|
+
if flagged?(:password)
|
|
106
|
+
raise HexaPDF::Error, "Storing a field value for a password field is not allowed"
|
|
107
|
+
end
|
|
108
|
+
str = str.gsub(/[[:space:]]/, ' ') if concrete_field_type == :single_line_text_field
|
|
109
|
+
self[:V] = str
|
|
110
|
+
update_widgets
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns the default field value.
|
|
114
|
+
#
|
|
115
|
+
# See: #field_value
|
|
116
|
+
def default_field_value
|
|
117
|
+
self[:DV].kind_of?(String) ? self[:DV] : self[:DV].stream
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Sets the default field value.
|
|
121
|
+
#
|
|
122
|
+
# See: #field_value=
|
|
123
|
+
def default_field_value=(str)
|
|
124
|
+
self[:DV] = str
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns the concrete text field type, either :single_line_text_field,
|
|
128
|
+
# :multiline_text_field, :password_field, :file_select_field, :comb_text_field or
|
|
129
|
+
# :rich_text_field.
|
|
130
|
+
def concrete_field_type
|
|
131
|
+
if flagged?(:multiline)
|
|
132
|
+
:multiline_text_field
|
|
133
|
+
elsif flagged?(:password)
|
|
134
|
+
:password_field
|
|
135
|
+
elsif flagged?(:file_select)
|
|
136
|
+
:file_select_field
|
|
137
|
+
elsif flagged?(:comb)
|
|
138
|
+
:comb_text_field
|
|
139
|
+
elsif flagged?(:rich_text)
|
|
140
|
+
:rich_text_field
|
|
141
|
+
else
|
|
142
|
+
:single_line_text_field
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Creates appropriate appearances for all widgets.
|
|
147
|
+
#
|
|
148
|
+
# For information on how this is done see AppearanceGenerator.
|
|
149
|
+
#
|
|
150
|
+
# Note that an appearance for a text field widget is *always* created even if there is an
|
|
151
|
+
# existing one to make sure the current field value is properly represented.
|
|
152
|
+
def create_appearances
|
|
153
|
+
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
|
154
|
+
each_widget do |widget|
|
|
155
|
+
appearance_generator_class.new(widget).create_text_appearances
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Updates the widgets so that they reflect the current field value.
|
|
160
|
+
def update_widgets
|
|
161
|
+
create_appearances
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def perform_validation #:nodoc:
|
|
167
|
+
if field_type != :Tx
|
|
168
|
+
yield("Field /FT of AcroForm text field has to be :Tx", true)
|
|
169
|
+
self[:FT] = :Tx
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
super
|
|
173
|
+
|
|
174
|
+
if self[:V] && !(self[:V].kind_of?(String) || self[:V].kind_of?(HexaPDF::Stream))
|
|
175
|
+
yield("Text field doesn't contain text but #{self[:V].class} object")
|
|
176
|
+
end
|
|
177
|
+
if (max_len = self[:MaxLen]) && field_value.length > max_len
|
|
178
|
+
yield("Text contents of field '#{full_field_name}' is too long")
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
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/dictionary'
|
|
38
|
+
require 'hexapdf/stream'
|
|
39
|
+
require 'hexapdf/error'
|
|
40
|
+
require 'hexapdf/content/parser'
|
|
41
|
+
|
|
42
|
+
module HexaPDF
|
|
43
|
+
module Type
|
|
44
|
+
module AcroForm
|
|
45
|
+
|
|
46
|
+
# An AcroForm variable text field defines how text that it is not known at generation time
|
|
47
|
+
# should be rendered. For example, AcroForm text fields (normally) don't have an initial
|
|
48
|
+
# value; the value is entered by the user and needs to be rendered correctly by the PDF
|
|
49
|
+
# reader.
|
|
50
|
+
#
|
|
51
|
+
# See: PDF1.7 s12.7.3.3
|
|
52
|
+
class VariableTextField < Field
|
|
53
|
+
|
|
54
|
+
define_field :DA, type: String
|
|
55
|
+
define_field :Q, type: Integer, default: 0, allowed_values: [0, 1, 2]
|
|
56
|
+
define_field :DS, type: String, version: '1.5'
|
|
57
|
+
define_field :RV, type: [String, Stream], version: '1.5'
|
|
58
|
+
|
|
59
|
+
# All inheritable dictionary fields for text fields.
|
|
60
|
+
INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:DA, :Q]).freeze
|
|
61
|
+
|
|
62
|
+
UNSET_ARG = ::Object.new # :nodoc:
|
|
63
|
+
|
|
64
|
+
# :call-seq:
|
|
65
|
+
# field.text_alignment -> alignment
|
|
66
|
+
# field.text_alignment(alignment) -> field
|
|
67
|
+
#
|
|
68
|
+
# Sets or returns the text alignment that should be used when displaying text.
|
|
69
|
+
#
|
|
70
|
+
# With no argument, the current text alignment is returned. When a value is provided, the
|
|
71
|
+
# text alignment is set accordingly.
|
|
72
|
+
#
|
|
73
|
+
# The alignment value is one of :left, :center or :right.
|
|
74
|
+
def text_alignment(alignment = UNSET_ARG)
|
|
75
|
+
if alignment == UNSET_ARG
|
|
76
|
+
case self[:Q]
|
|
77
|
+
when 0 then :left
|
|
78
|
+
when 1 then :center
|
|
79
|
+
when 2 then :right
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
self[:Q] = case alignment
|
|
83
|
+
when :left then 0
|
|
84
|
+
when :center then 1
|
|
85
|
+
when :right then 2
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, "Invalid variable text field alignment #{alignment}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Sets the default appearance string using the provided values.
|
|
93
|
+
#
|
|
94
|
+
# The default argument values are a sane default. If +font_size+ is set to 0, the font size
|
|
95
|
+
# is calculated using the height/width of the field.
|
|
96
|
+
def set_default_appearance_string(font: 'Helvetica', font_size: 0)
|
|
97
|
+
name = document.acro_form(create: true).default_resources.
|
|
98
|
+
add_font(document.fonts.add(font).pdf_object)
|
|
99
|
+
self[:DA] = "0 g /#{name} #{font_size} Tf"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Parses the default appearance string and returns an array containing [font_name,
|
|
103
|
+
# font_size].
|
|
104
|
+
#
|
|
105
|
+
# The default appearance string is taken from the field or, if not set, the default
|
|
106
|
+
# appearance string of the form.
|
|
107
|
+
def parse_default_appearance_string
|
|
108
|
+
da = self[:DA] || (document.acro_form && document.acro_form[:DA])
|
|
109
|
+
raise HexaPDF::Error, "No default appearance string set" unless da
|
|
110
|
+
|
|
111
|
+
font_params = nil
|
|
112
|
+
HexaPDF::Content::Parser.parse(da) do |obj, params|
|
|
113
|
+
font_params = params.dup if obj == :Tf
|
|
114
|
+
end
|
|
115
|
+
font_params
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|