hexapdf 0.11.7 → 0.12.2
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 +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
|