hexapdf 0.11.9 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,220 @@
|
|
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/type/acro_form/variable_text_field'
|
38
|
+
require 'hexapdf/type/acro_form/appearance_generator'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
module Type
|
42
|
+
module AcroForm
|
43
|
+
|
44
|
+
# AcroForm choice fields contain multiple text items of which one (or, if so flagged, more)
|
45
|
+
# may be selected.
|
46
|
+
#
|
47
|
+
# They are divided into scrollable list boxes and combo boxes. To create a list or combo box,
|
48
|
+
# use the appropriate convenience methods on the main Form instance
|
49
|
+
# (HexaPDF::Document#acro_form). By using those methods, everything needed is automatically
|
50
|
+
# set up.
|
51
|
+
#
|
52
|
+
# == Type Specific Field Flags
|
53
|
+
#
|
54
|
+
# :combo:: If set, the field represents a comb box.
|
55
|
+
#
|
56
|
+
# :edit:: If set, the combo box includes an editable text box for entering arbitrary values.
|
57
|
+
# Therefore the 'combo' flag also needs to be set.
|
58
|
+
#
|
59
|
+
# :sort:: The option items have to be sorted alphabetically. This flag is intended for PDF
|
60
|
+
# writers, not readers which should display the items in the order they appear.
|
61
|
+
#
|
62
|
+
# :multi_select:: If set, more than one item may be selected.
|
63
|
+
#
|
64
|
+
# :do_not_spell_check:: The text should not be spell-checked.
|
65
|
+
#
|
66
|
+
# :commit_on_sel_change:: If set, a new value should be commited as soon as a selection is
|
67
|
+
# made.
|
68
|
+
#
|
69
|
+
# See: PDF1.7 s12.7.4.4
|
70
|
+
class ChoiceField < VariableTextField
|
71
|
+
|
72
|
+
define_field :Opt, type: PDFArray
|
73
|
+
define_field :TI, type: Integer, default: 0
|
74
|
+
define_field :I, type: PDFArray, version: '1.4'
|
75
|
+
|
76
|
+
# Updated list of field flags.
|
77
|
+
FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
|
78
|
+
{
|
79
|
+
combo: 17,
|
80
|
+
edit: 18,
|
81
|
+
sort: 19,
|
82
|
+
multi_select: 21,
|
83
|
+
do_not_spell_check: 22,
|
84
|
+
commit_on_sel_change: 26,
|
85
|
+
}
|
86
|
+
).freeze
|
87
|
+
|
88
|
+
# Initializes the choice field to be a list box.
|
89
|
+
#
|
90
|
+
# This method should only be called directly after creating a new choice field because it
|
91
|
+
# doesn't completely reset the object.
|
92
|
+
def initialize_as_list_box
|
93
|
+
self[:V] = nil
|
94
|
+
unflag(:combo)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Initializes the button field to be a combo box.
|
98
|
+
#
|
99
|
+
# This method should only be called directly after creating a new choice field because it
|
100
|
+
# doesn't completely reset the object.
|
101
|
+
def initialize_as_combo_box
|
102
|
+
self[:V] = nil
|
103
|
+
flag(:combo)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns +true+ if this choice field represents a list box.
|
107
|
+
def list_box?
|
108
|
+
!combo_box?
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns +true+ if this choice field represents a combo box.
|
112
|
+
def combo_box?
|
113
|
+
flagged?(:combo)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the field value which represents the currently selected item(s).
|
117
|
+
#
|
118
|
+
# If no item is selected, +nil+ is returned. If multiple values are selected, the return
|
119
|
+
# value is an array of strings, otherwise it is just a string.
|
120
|
+
def field_value
|
121
|
+
process_value(self[:V])
|
122
|
+
end
|
123
|
+
|
124
|
+
# Sets the field value to the given string or array of strings.
|
125
|
+
def field_value=(value)
|
126
|
+
items = option_items
|
127
|
+
all_included = [value].flatten.all? {|v| items.include?(v) }
|
128
|
+
self[:V] = if (combo_box? && value.kind_of?(String) &&
|
129
|
+
(flagged?(:edit) || all_included)) ||
|
130
|
+
(list_box? && all_included &&
|
131
|
+
(value.kind_of?(String) || flagged?(:multi_select)))
|
132
|
+
value
|
133
|
+
else
|
134
|
+
@document.config['acro_form.on_invalid_value'].call(self, value)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the default field value.
|
139
|
+
#
|
140
|
+
# See: #field_value
|
141
|
+
def default_field_value
|
142
|
+
process_value(self[:DV])
|
143
|
+
end
|
144
|
+
|
145
|
+
# Sets the default field value.
|
146
|
+
#
|
147
|
+
# See: #field_value=
|
148
|
+
def default_field_value=(value)
|
149
|
+
items = option_items
|
150
|
+
self[:DV] = if [value].flatten.all? {|v| items.include?(v) }
|
151
|
+
value
|
152
|
+
else
|
153
|
+
@document.config['acro_form.on_invalid_value'].call(self, value)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns the array with the available option items.
|
158
|
+
def option_items
|
159
|
+
key?(:Opt) ? process_value(self[:Opt]) : self[:Opt] ||= []
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets the array with the available option items to the given value.
|
163
|
+
def option_items=(value)
|
164
|
+
self[:Opt] = value
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns the concrete choice field type, either :list_box, :combo_box or
|
168
|
+
# :editable_combo_box.
|
169
|
+
def concrete_field_type
|
170
|
+
if combo_box?
|
171
|
+
flagged?(:edit) ? :editable_combo_box : :combo_box
|
172
|
+
else
|
173
|
+
:list_box
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Creates appropriate appearances for all widgets if they don't already exist.
|
178
|
+
#
|
179
|
+
# For information on how this is done see AppearanceGenerator.
|
180
|
+
#
|
181
|
+
# Note that an appearance for a choice field widget is *always* created even if there is an
|
182
|
+
# existing one to make sure the current field value is properly represented.
|
183
|
+
def create_appearances
|
184
|
+
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
185
|
+
each_widget do |widget|
|
186
|
+
if combo_box?
|
187
|
+
appearance_generator_class.new(widget).create_combo_box_appearances
|
188
|
+
else
|
189
|
+
raise HexaPDF::Error, "List boxes not yet supported"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# Uses the HexaPDF::DictionaryFields::StringConverter to process the value (a string or an
|
197
|
+
# array of strings) so that it contains only normalized strings.
|
198
|
+
def process_value(value)
|
199
|
+
value = value.value if value.kind_of?(PDFArray)
|
200
|
+
if value.kind_of?(Array)
|
201
|
+
value.map! {|item| DictionaryFields::StringConverter.convert(item, nil, nil) || item }
|
202
|
+
else
|
203
|
+
DictionaryFields::StringConverter.convert(value, nil, nil) || value
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def perform_validation #:nodoc:
|
208
|
+
if field_type != :Ch
|
209
|
+
yield("Field /FT of AcroForm choie field has to be :Ch", true)
|
210
|
+
self[:FT] = :Ch
|
211
|
+
end
|
212
|
+
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# This file is part of HexaPDF.
|
5
5
|
#
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
-
# Copyright (C) 2014-
|
7
|
+
# Copyright (C) 2014-2020 Thomas Leitner
|
8
8
|
#
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
@@ -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?
|