hexapdf 0.11.7 → 0.12.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +121 -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 +7 -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 +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 +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 +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 +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 +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 +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 +10 -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 +20 -5
- 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/encryption/test_security_handler.rb +4 -0
- 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/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_serializer.rb +3 -3
- 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_form.rb +18 -0
- data/test/hexapdf/type/test_image.rb +7 -0
- data/test/hexapdf/type/test_page.rb +37 -6
- 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 +38 -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
|
@@ -35,24 +35,75 @@
|
|
35
35
|
#++
|
36
36
|
|
37
37
|
require 'hexapdf/dictionary'
|
38
|
+
require 'hexapdf/error'
|
39
|
+
require 'hexapdf/utils/bit_field'
|
40
|
+
require 'hexapdf/type/annotations'
|
38
41
|
|
39
42
|
module HexaPDF
|
40
43
|
module Type
|
41
44
|
module AcroForm
|
42
45
|
|
43
|
-
#
|
46
|
+
# AcroForm field dictionaries are used to define the properties of form fields of AcroForm
|
47
|
+
# objects.
|
44
48
|
#
|
45
49
|
# Fields can be organized in a hierarchy using the /Kids and /Parent keys, for namespacing
|
46
50
|
# purposes and to set default values. Those fields that have other fields as children are
|
47
51
|
# called non-terminal fields, otherwise they are called terminal fields.
|
48
52
|
#
|
53
|
+
# While field objects can be created manually, it is best to use the various +create_+ methods
|
54
|
+
# of the main Form object to create them so that all necessary things are set up correctly.
|
55
|
+
#
|
56
|
+
# == Field Types
|
57
|
+
#
|
58
|
+
# Subclasses are used to implement the specific AcroForm field types:
|
59
|
+
#
|
60
|
+
# * ButtonField implements the button fields (pushbuttons, check boxes and radio buttons)
|
61
|
+
# * TextField implements single or multiline text fields.
|
62
|
+
# * ChoiceField implements scrollable list boxes or (editable) combo boxes.
|
63
|
+
# * SignatureField implements signature fields.
|
64
|
+
#
|
65
|
+
# == Field Flags
|
66
|
+
#
|
67
|
+
# Various characteristics of a field can be changed by setting a certain flag. Some flags are
|
68
|
+
# defined for all types of field, some are specific to a certain type.
|
69
|
+
#
|
70
|
+
# The following flags apply to all fields:
|
71
|
+
#
|
72
|
+
# :read_only:: The field is read only which means the user can't change the value or interact
|
73
|
+
# with associated widget annotations.
|
74
|
+
#
|
75
|
+
# :required:: The field is required if the form is exported by a submit-form action.
|
76
|
+
#
|
77
|
+
# :no_export:: The field should *not* be exported by a submit-form action.
|
78
|
+
#
|
79
|
+
# == Field Type Implementation Notes
|
80
|
+
#
|
81
|
+
# If an AcroForm field type adds additional inheritable dictionary fields, it has to set the
|
82
|
+
# constant +INHERITABLE_FIELDS+ to all inheritable dictionary fields, including those from the
|
83
|
+
# superclass.
|
84
|
+
#
|
85
|
+
# Similarily, if additional flags are provided, the constant +FLAGS_BIT_MAPPING+ has to be set
|
86
|
+
# to combination of the superclass value of the constant and the mapping of flag names to bit
|
87
|
+
# indices.
|
88
|
+
#
|
49
89
|
# See: PDF1.7 s12.7.3.1
|
50
90
|
class Field < Dictionary
|
51
91
|
|
52
|
-
|
92
|
+
# Provides a #value method for hash that returns self so that a Hash can be used
|
93
|
+
# interchangably with a HexaPDF::Dictionary.
|
94
|
+
module HashRefinement
|
95
|
+
refine Hash do
|
96
|
+
def value
|
97
|
+
self
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
using HashRefinement
|
53
103
|
|
54
|
-
|
55
|
-
|
104
|
+
extend Utils::BitField
|
105
|
+
|
106
|
+
define_type :XXAcroFormField
|
56
107
|
|
57
108
|
define_field :FT, type: Symbol, allowed_values: [:Btn, :Tx, :Ch, :Sig]
|
58
109
|
define_field :Parent, type: :XXAcroFormField
|
@@ -61,10 +112,49 @@ module HexaPDF
|
|
61
112
|
define_field :TU, type: String, version: '1.3'
|
62
113
|
define_field :TM, type: String, version: '1.3'
|
63
114
|
define_field :Ff, type: Integer, default: 0
|
64
|
-
define_field :V, type: [Symbol, String, Stream, PDFArray
|
65
|
-
define_field :DV, type: [Symbol, String, Stream, PDFArray
|
115
|
+
define_field :V, type: [Dictionary, Symbol, String, Stream, PDFArray]
|
116
|
+
define_field :DV, type: [Dictionary, Symbol, String, Stream, PDFArray]
|
66
117
|
define_field :AA, type: Dictionary, version: '1.2'
|
67
118
|
|
119
|
+
# The inheritable dictionary fields common to all AcroForm field types.
|
120
|
+
INHERITABLE_FIELDS = [:FT, :Ff, :V, :DV].freeze
|
121
|
+
|
122
|
+
##
|
123
|
+
# :method: flags
|
124
|
+
#
|
125
|
+
# Returns an array of flag names representing the set bit flags.
|
126
|
+
#
|
127
|
+
|
128
|
+
##
|
129
|
+
# :method: flagged?
|
130
|
+
# :call-seq:
|
131
|
+
# flagged?(flag)
|
132
|
+
#
|
133
|
+
# Returns +true+ if the given flag is set. The argument can either be the flag name or the
|
134
|
+
# bit index.
|
135
|
+
#
|
136
|
+
|
137
|
+
##
|
138
|
+
# :method: flag
|
139
|
+
# :call-seq:
|
140
|
+
# flag(*flags, clear_existing: false)
|
141
|
+
#
|
142
|
+
# Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
|
143
|
+
# all prior flags will be cleared.
|
144
|
+
#
|
145
|
+
bit_field(:flags, {read_only: 0, required: 1, no_export: 2},
|
146
|
+
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
|
147
|
+
value_getter: "self[:Ff]", value_setter: "self[:Ff]")
|
148
|
+
|
149
|
+
# Treats +name+ as an inheritable dictionary field and resolves its value for the AcroForm
|
150
|
+
# field +field+.
|
151
|
+
def self.inherited_value(field, name)
|
152
|
+
while field.value[name].nil? && (parent = field[:Parent])
|
153
|
+
field = parent
|
154
|
+
end
|
155
|
+
field.value[name].nil? ? nil : field[name]
|
156
|
+
end
|
157
|
+
|
68
158
|
# Form fields must always be indirect objects.
|
69
159
|
def must_be_indirect?
|
70
160
|
true
|
@@ -72,15 +162,13 @@ module HexaPDF
|
|
72
162
|
|
73
163
|
# Returns the value for the entry +name+.
|
74
164
|
#
|
75
|
-
# If +name+ is an inheritable
|
165
|
+
# If +name+ is an inheritable field and the value has not been set on this field object, its
|
76
166
|
# value is retrieved from the parent fields.
|
77
167
|
#
|
78
168
|
# See: Dictionary#[]
|
79
169
|
def [](name)
|
80
|
-
if value[name].nil? && INHERITABLE_FIELDS.include?(name)
|
81
|
-
|
82
|
-
field = field[:Parent] while field.value[name].nil? && field[:Parent]
|
83
|
-
field == self || field.value[name].nil? ? super : field[name]
|
170
|
+
if value[name].nil? && self.class::INHERITABLE_FIELDS.include?(name)
|
171
|
+
self.class.inherited_value(self, name) || super
|
84
172
|
else
|
85
173
|
super
|
86
174
|
end
|
@@ -88,30 +176,145 @@ module HexaPDF
|
|
88
176
|
|
89
177
|
# Returns the type of the field, either :Btn (pushbuttons, check boxes, radio buttons), :Tx
|
90
178
|
# (text fields), :Ch (scrollable list boxes, combo boxes) or :Sig (signature fields).
|
179
|
+
#
|
180
|
+
# Also see #concrete_field_type
|
91
181
|
def field_type
|
92
182
|
self[:FT]
|
93
183
|
end
|
94
184
|
|
185
|
+
# Returns the concrete field type (:button_field, :text_field, :choice_field or
|
186
|
+
# :signature_field) or +nil+ is no field type is set.
|
187
|
+
#
|
188
|
+
# In constrast to #field_type this method also considers the field flags and not just the
|
189
|
+
# field type. This means that subclasses can return a more concrete name for the field type.
|
190
|
+
#
|
191
|
+
# Also see #field_type
|
192
|
+
def concrete_field_type
|
193
|
+
case self[:FT]
|
194
|
+
when :Btn then :button_field
|
195
|
+
when :Tx then :text_field
|
196
|
+
when :Ch then :choice_field
|
197
|
+
when :Sig then :signature_field
|
198
|
+
else nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns the name of the field or +nil+ if no name is set.
|
203
|
+
def field_name
|
204
|
+
self[:T]
|
205
|
+
end
|
206
|
+
|
95
207
|
# Returns the full name of the field or +nil+ if no name is set.
|
96
208
|
#
|
97
209
|
# The full name of a field is constructed using the full name of the parent field, a period
|
98
|
-
# and the
|
99
|
-
def
|
210
|
+
# and the field name of the field.
|
211
|
+
def full_field_name
|
100
212
|
if key?(:Parent)
|
101
|
-
[self[:Parent].
|
213
|
+
[self[:Parent].full_field_name, field_name].compact.join('.')
|
102
214
|
else
|
103
|
-
|
215
|
+
field_name
|
104
216
|
end
|
105
217
|
end
|
106
218
|
|
219
|
+
# Returns the alternate field name that should be used for display purposes (e.g. Acrobat
|
220
|
+
# shows this as tool tip).
|
221
|
+
def alternate_field_name
|
222
|
+
self[:TU]
|
223
|
+
end
|
224
|
+
|
225
|
+
# Sets the alternate field name.
|
226
|
+
#
|
227
|
+
# See #alternate_field_name
|
228
|
+
def alternate_field_name=(value)
|
229
|
+
self[:TU] = value
|
230
|
+
end
|
231
|
+
|
107
232
|
# Returns +true+ if this is a terminal field.
|
108
233
|
def terminal_field?
|
109
234
|
kids = self[:Kids]
|
110
|
-
|
235
|
+
# PDF 2.0 s12.7.4.2 clarifies how to do check for fields since PDF 1.7 isn't clear
|
236
|
+
kids.nil? || kids.empty? || kids.none? {|kid| kid.key?(:T) }
|
237
|
+
end
|
238
|
+
|
239
|
+
# :call-seq:
|
240
|
+
# field.each_widget {|widget| block} -> field
|
241
|
+
# field.each_widget -> Enumerator
|
242
|
+
#
|
243
|
+
# Yields each widget, i.e. visual representation, of this field.
|
244
|
+
#
|
245
|
+
# See: HexaPDF::Type::Annotations::Widget
|
246
|
+
def each_widget # :yields: widget
|
247
|
+
return to_enum(__method__) unless block_given?
|
248
|
+
if self[:Subtype]
|
249
|
+
yield(document.wrap(self))
|
250
|
+
elsif terminal_field?
|
251
|
+
self[:Kids]&.each {|kid| yield(document.wrap(kid)) }
|
252
|
+
end
|
253
|
+
self
|
254
|
+
end
|
255
|
+
|
256
|
+
# Creates a new widget annotation for this form field (must be a terminal field!) on the
|
257
|
+
# given +page+, adding the +values+ to the created widget annotation oject.
|
258
|
+
#
|
259
|
+
# If +allow_embedded+ is +false+, embedding the first widget in the field itself is not
|
260
|
+
# allowed.
|
261
|
+
#
|
262
|
+
# The +values+ argument should at least include :Rect for setting the visible area of the
|
263
|
+
# widget.
|
264
|
+
#
|
265
|
+
# If the field already has an embedded widget, i.e. field and widget are the same PDF
|
266
|
+
# object, its widget data is extracted to a new PDF object and stored in the /Kids field,
|
267
|
+
# together with the new widget annotation. Note that this means that a possible reference to
|
268
|
+
# the formerly embedded widget (=this field) is not valid anymore!
|
269
|
+
#
|
270
|
+
# See: HexaPDF::Type::Annotations::Widget
|
271
|
+
def create_widget(page, allow_embedded: true, **values)
|
272
|
+
unless terminal_field?
|
273
|
+
raise HexaPDF::Error, "Widgets can only be added to terminal fields"
|
274
|
+
end
|
275
|
+
|
276
|
+
widget_data = {Type: :Annot, Subtype: :Widget, Rect: [0, 0, 0, 0], **values}
|
277
|
+
|
278
|
+
if !allow_embedded || key?(:Subtype) || (key?(:Kids) && !self[:Kids].empty?)
|
279
|
+
kids = self[:Kids] ||= []
|
280
|
+
kids << extract_widget if key?(:Subtype)
|
281
|
+
widget = document.add(widget_data)
|
282
|
+
widget[:Parent] = self
|
283
|
+
self[:Kids] << widget
|
284
|
+
else
|
285
|
+
value.update(widget_data)
|
286
|
+
widget = document.wrap(self)
|
287
|
+
end
|
288
|
+
|
289
|
+
(page[:Annots] ||= []) << widget
|
290
|
+
|
291
|
+
widget
|
111
292
|
end
|
112
293
|
|
113
294
|
private
|
114
295
|
|
296
|
+
# An array of all widget annotation field names.
|
297
|
+
WIDGET_FIELDS = HexaPDF::Type::Annotations::Widget.each_field.map(&:first).uniq - [:Parent]
|
298
|
+
|
299
|
+
# Returns a new dictionary object with all the widget annotation data that is stored
|
300
|
+
# directly in the field and adjust the references accordingly. If the field doesn't have any
|
301
|
+
# widget data, +nil+ is returned.
|
302
|
+
def extract_widget
|
303
|
+
return unless key?(:Subtype)
|
304
|
+
data = WIDGET_FIELDS.each_with_object({}) do |key, hash|
|
305
|
+
hash[key] = delete(key) if key?(key)
|
306
|
+
end
|
307
|
+
widget = document.add(data, type: :Annot)
|
308
|
+
widget[:Parent] = self
|
309
|
+
document.pages.each do |page|
|
310
|
+
if page.key?(:Annots) && (index = page[:Annots].index {|annot| annot.data == self.data })
|
311
|
+
page[:Annots][index] = widget
|
312
|
+
break # Each annotation dictionary may only appear on one page, see PDF1.7 12.5.2
|
313
|
+
end
|
314
|
+
end
|
315
|
+
widget
|
316
|
+
end
|
317
|
+
|
115
318
|
def perform_validation #:nodoc:
|
116
319
|
super
|
117
320
|
if terminal_field? && field_type.nil?
|
@@ -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
|