hexapdf 0.11.9 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/LICENSE +1 -1
- data/examples/001-hello_world.rb +1 -1
- data/examples/002-graphics.rb +1 -1
- data/examples/003-arcs.rb +1 -1
- data/examples/004-optimizing.rb +1 -1
- data/examples/005-merging.rb +1 -1
- data/examples/006-standard_pdf_fonts.rb +1 -1
- data/examples/007-truetype.rb +1 -1
- data/examples/008-show_char_bboxes.rb +1 -1
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +1 -1
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +1 -1
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +1 -1
- data/examples/016-frame_automatic_box_placement.rb +1 -1
- data/examples/017-frame_text_flow.rb +1 -1
- data/examples/018-composer.rb +1 -1
- data/examples/019-acro_form.rb +51 -0
- data/lib/hexapdf.rb +1 -1
- data/lib/hexapdf/cli.rb +3 -1
- data/lib/hexapdf/cli/batch.rb +1 -1
- data/lib/hexapdf/cli/command.rb +18 -9
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/form.rb +240 -0
- data/lib/hexapdf/cli/image2pdf.rb +1 -1
- data/lib/hexapdf/cli/images.rb +1 -1
- data/lib/hexapdf/cli/info.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/cli/watermark.rb +1 -1
- data/lib/hexapdf/composer.rb +2 -2
- data/lib/hexapdf/configuration.rb +66 -11
- data/lib/hexapdf/content.rb +3 -1
- data/lib/hexapdf/content/canvas.rb +5 -18
- data/lib/hexapdf/content/color_space.rb +111 -32
- data/lib/hexapdf/content/graphic_object.rb +1 -1
- data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/content/operator.rb +9 -9
- data/lib/hexapdf/content/parser.rb +18 -5
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/data_dir.rb +1 -1
- data/lib/hexapdf/dictionary.rb +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document.rb +14 -5
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/document/pages.rb +3 -14
- data/lib/hexapdf/encryption.rb +1 -1
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +1 -1
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/error.rb +1 -1
- data/lib/hexapdf/filter.rb +3 -3
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/encryption.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/cmap/parser.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +1 -1
- data/lib/hexapdf/font/encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/base.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +1 -1
- data/lib/hexapdf/font/true_type.rb +1 -1
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/font.rb +1 -1
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
- data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
- data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
- data/lib/hexapdf/font/true_type/table/head.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
- data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
- data/lib/hexapdf/font/true_type/table/name.rb +1 -1
- data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
- data/lib/hexapdf/font/type1.rb +1 -1
- data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
- data/lib/hexapdf/font/type1_wrapper.rb +67 -51
- data/lib/hexapdf/font_loader.rb +1 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/from_file.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +1 -1
- data/lib/hexapdf/importer.rb +2 -4
- data/lib/hexapdf/layout.rb +1 -1
- data/lib/hexapdf/layout/box.rb +1 -1
- data/lib/hexapdf/layout/frame.rb +1 -1
- data/lib/hexapdf/layout/image_box.rb +1 -1
- data/lib/hexapdf/layout/inline_box.rb +1 -1
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/style.rb +1 -1
- data/lib/hexapdf/layout/text_box.rb +1 -1
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +1 -1
- data/lib/hexapdf/layout/text_shaper.rb +1 -1
- data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +2 -2
- data/lib/hexapdf/parser.rb +4 -3
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +31 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +2 -1
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/serializer.rb +1 -1
- data/lib/hexapdf/stream.rb +1 -1
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +1 -1
- data/lib/hexapdf/type.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +7 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +401 -0
- data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
- data/lib/hexapdf/type/acro_form/field.rb +220 -17
- data/lib/hexapdf/type/acro_form/form.rb +157 -7
- data/lib/hexapdf/type/acro_form/text_field.rb +186 -0
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/annotation.rb +73 -3
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/annotations/link.rb +2 -2
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +239 -2
- data/lib/hexapdf/type/catalog.rb +21 -1
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +1 -1
- data/lib/hexapdf/type/font.rb +18 -1
- data/lib/hexapdf/type/font_descriptor.rb +2 -2
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_true_type.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +16 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +1 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +3 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/names.rb +1 -1
- data/lib/hexapdf/type/object_stream.rb +1 -1
- data/lib/hexapdf/type/page.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +8 -11
- data/lib/hexapdf/type/resources.rb +16 -3
- data/lib/hexapdf/type/trailer.rb +2 -3
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +1 -1
- data/lib/hexapdf/utils/bit_field.rb +38 -24
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
- data/lib/hexapdf/utils/lru_cache.rb +1 -1
- data/lib/hexapdf/utils/math_helpers.rb +1 -1
- data/lib/hexapdf/utils/object_hash.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
- data/lib/hexapdf/version.rb +2 -2
- data/lib/hexapdf/writer.rb +1 -1
- data/lib/hexapdf/xref_section.rb +1 -1
- data/test/hexapdf/content/common.rb +2 -2
- data/test/hexapdf/content/test_color_space.rb +71 -8
- data/test/hexapdf/content/test_operator.rb +22 -22
- data/test/hexapdf/content/test_parser.rb +14 -0
- data/test/hexapdf/document/test_fonts.rb +1 -1
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
- data/test/hexapdf/font/test_type1_wrapper.rb +32 -8
- data/test/hexapdf/test_document.rb +12 -0
- data/test/hexapdf/test_parser.rb +10 -0
- data/test/hexapdf/test_rectangle.rb +14 -0
- data/test/hexapdf/test_revision.rb +3 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
- data/test/hexapdf/type/acro_form/test_field.rb +124 -6
- data/test/hexapdf/type/acro_form/test_form.rb +189 -22
- data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
- data/test/hexapdf/type/annotations/test_text.rb +1 -1
- data/test/hexapdf/type/annotations/test_widget.rb +199 -0
- data/test/hexapdf/type/test_annotation.rb +45 -0
- data/test/hexapdf/type/test_catalog.rb +18 -0
- data/test/hexapdf/type/test_font.rb +5 -0
- data/test/hexapdf/type/test_font_type1.rb +8 -0
- data/test/hexapdf/type/test_image.rb +7 -0
- data/test/hexapdf/type/test_page_tree_node.rb +20 -12
- data/test/hexapdf/type/test_resources.rb +20 -0
- data/test/hexapdf/type/test_trailer.rb +4 -0
- data/test/hexapdf/utils/test_bit_field.rb +13 -1
- data/test/test_helper.rb +1 -1
- metadata +37 -18
- data/lib/hexapdf/filter/dct_decode.rb +0 -60
|
@@ -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?
|