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
|
@@ -0,0 +1,300 @@
|
|
|
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/field'
|
|
38
|
+
require 'hexapdf/type/acro_form/appearance_generator'
|
|
39
|
+
|
|
40
|
+
module HexaPDF
|
|
41
|
+
module Type
|
|
42
|
+
module AcroForm
|
|
43
|
+
|
|
44
|
+
# AcroForm button fields represent interactive controls to be used with the mouse.
|
|
45
|
+
#
|
|
46
|
+
# They are divided into push buttons (things to click on), check boxes and radio buttons. All
|
|
47
|
+
# of these are represented with this class.
|
|
48
|
+
#
|
|
49
|
+
# To create a push button, check box or radio button field, use the appropriate convenience
|
|
50
|
+
# methods on the main Form instance (HexaPDF::Document#acro_form). By using those methods,
|
|
51
|
+
# everything needed is automatically set up.
|
|
52
|
+
#
|
|
53
|
+
# == Type Specific Field Flags
|
|
54
|
+
#
|
|
55
|
+
# :no_toggle_to_off:: Only used with radio buttons fields. If this flag is set, one button
|
|
56
|
+
# needs to be selected at all times. Otherwise, clicking on the selected
|
|
57
|
+
# button deselects it.
|
|
58
|
+
#
|
|
59
|
+
# :radio:: If this flag is set, the field is a set of radio buttons. Otherwise it is a check
|
|
60
|
+
# box. Additionally, the :pushbutton flag needs to be clear.
|
|
61
|
+
#
|
|
62
|
+
# :push_button:: The field represents a pushbutton without a permanent value.
|
|
63
|
+
#
|
|
64
|
+
# :radios_in_unison:: A group of radio buttons with the same value for the on state will turn
|
|
65
|
+
# on or off in unison.
|
|
66
|
+
#
|
|
67
|
+
# See: PDF1.7 s12.7.4.2
|
|
68
|
+
class ButtonField < Field
|
|
69
|
+
|
|
70
|
+
define_field :Opt, type: PDFArray, version: '1.4'
|
|
71
|
+
|
|
72
|
+
# All inheritable dictionary fields for button fields.
|
|
73
|
+
INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:Opt]).freeze
|
|
74
|
+
|
|
75
|
+
# Updated list of field flags.
|
|
76
|
+
FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
|
|
77
|
+
{
|
|
78
|
+
no_toggle_to_off: 14,
|
|
79
|
+
radio: 15,
|
|
80
|
+
push_button: 16,
|
|
81
|
+
radios_in_unison: 25,
|
|
82
|
+
}
|
|
83
|
+
).freeze
|
|
84
|
+
|
|
85
|
+
# Initializes the button field to be a push button.
|
|
86
|
+
#
|
|
87
|
+
# This method should only be called directly after creating a new button field because it
|
|
88
|
+
# doesn't completely reset the object.
|
|
89
|
+
def initialize_as_push_button
|
|
90
|
+
self[:V] = nil
|
|
91
|
+
flag(:push_button)
|
|
92
|
+
unflag(:radio)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Initializes the button field to be a check box.
|
|
96
|
+
#
|
|
97
|
+
# This method should only be called directly after creating a new button field because it
|
|
98
|
+
# doesn't completely reset the object.
|
|
99
|
+
def initialize_as_check_box
|
|
100
|
+
self[:V] = :Off
|
|
101
|
+
unflag(:push_button)
|
|
102
|
+
unflag(:radio)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Initializes the button field to be a radio button.
|
|
106
|
+
#
|
|
107
|
+
# This method should only be called directly after creating a new button field because it
|
|
108
|
+
# doesn't completely reset the object.
|
|
109
|
+
def initialize_as_radio_button
|
|
110
|
+
self[:V] = :Off
|
|
111
|
+
unflag(:push_button)
|
|
112
|
+
flag(:radio)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns +true+ if this button field represents a push button.
|
|
116
|
+
def push_button?
|
|
117
|
+
flagged?(:push_button)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns +true+ if this button field represents a check box.
|
|
121
|
+
def check_box?
|
|
122
|
+
!push_button? && !flagged?(:radio)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns +true+ if this button field represents a radio button set.
|
|
126
|
+
def radio_button?
|
|
127
|
+
!push_button? && flagged?(:radio)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns the field value which depends on the concrete type.
|
|
131
|
+
#
|
|
132
|
+
# Push buttons:: They don't have a value, so +nil+ is always returned.
|
|
133
|
+
#
|
|
134
|
+
# Check boxes:: For check boxes that are in the on state the value +true+ is returned.
|
|
135
|
+
# Otherwise +false+ is returned.
|
|
136
|
+
#
|
|
137
|
+
# Radio buttons:: If no radio button is selected, +nil+ is returned. Otherwise the name of
|
|
138
|
+
# the specific radio button that is selected is returned.
|
|
139
|
+
def field_value
|
|
140
|
+
normalized_field_value(:V)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Sets the field value which depends on the concrete type.
|
|
144
|
+
#
|
|
145
|
+
# Push buttons:: Since push buttons don't store any value, the given value is ignored and
|
|
146
|
+
# nothing is stored for them (e.g a no-op).
|
|
147
|
+
#
|
|
148
|
+
# Check boxes:: Use +true+ for checking the box, i.e. toggling it to the on state, and
|
|
149
|
+
# +false+ for unchecking it.
|
|
150
|
+
#
|
|
151
|
+
# Radio buttons:: To turn all radio buttons off, provide +nil+ as value. Otherwise provide
|
|
152
|
+
# the name of a radio button that should be turned on.
|
|
153
|
+
def field_value=(value)
|
|
154
|
+
normalized_field_value_set(:V, value)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Returns the default field value.
|
|
158
|
+
#
|
|
159
|
+
# See: #field_value
|
|
160
|
+
def default_field_value
|
|
161
|
+
normalized_field_value(:DV)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Sets the default field value.
|
|
165
|
+
#
|
|
166
|
+
# See: #field_value=
|
|
167
|
+
def default_field_value=(value)
|
|
168
|
+
normalized_field_value_set(:DV, value)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Returns the concrete button field type, either :push_button, :check_box or :radio_button.
|
|
172
|
+
def concrete_field_type
|
|
173
|
+
if push_button?
|
|
174
|
+
:push_button
|
|
175
|
+
elsif radio_button?
|
|
176
|
+
:radio_button
|
|
177
|
+
else
|
|
178
|
+
:check_box
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Returns the name used for setting the check box to the on state.
|
|
183
|
+
#
|
|
184
|
+
# Defaults to :Yes if no other name could be determined.
|
|
185
|
+
def check_box_on_name
|
|
186
|
+
each_widget.to_a.first&.appearance&.normal_appearance&.value&.each_key&.
|
|
187
|
+
find {|key| key != :Off } || :Yes
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Returns the array of values that can be used for the field value of the radio button.
|
|
191
|
+
def radio_button_values
|
|
192
|
+
each_widget.map do |widget|
|
|
193
|
+
widget.appearance&.normal_appearance&.value&.each_key&.find {|key| key != :Off }
|
|
194
|
+
end.compact
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Creates a widget for the button field.
|
|
198
|
+
#
|
|
199
|
+
# If +defaults+ is +true+, then default values will be set on the widget so that it uses a
|
|
200
|
+
# default appearance.
|
|
201
|
+
#
|
|
202
|
+
# If the widget is created for a radio button field, the +value+ argument needs to set to
|
|
203
|
+
# the value (a symbol) this widget represents. It can be used with #field_value= to set this
|
|
204
|
+
# specific widget of the radio button set to on.
|
|
205
|
+
#
|
|
206
|
+
# See: Field#create_widget, AppearanceGenerator button field methods
|
|
207
|
+
def create_widget(page, defaults: true, value: nil, **values)
|
|
208
|
+
super(page, allow_embedded: !radio_button?, **values).tap do |widget|
|
|
209
|
+
if check_box?
|
|
210
|
+
widget[:AP] = {N: {Yes: nil, Off: nil}}
|
|
211
|
+
elsif radio_button?
|
|
212
|
+
raise ArgumentError, "Argument value has to be provided for radio buttons" unless value
|
|
213
|
+
widget[:AP] = {N: {value => nil, Off: nil}}
|
|
214
|
+
end
|
|
215
|
+
next unless defaults
|
|
216
|
+
widget.border_style(color: 0, width: 1, style: (push_button? ? :beveled : :solid))
|
|
217
|
+
widget.background_color(push_button? ? 0.5 : 255)
|
|
218
|
+
widget.marker_style(style: check_box? ? :check : :circle) unless push_button?
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Creates appropriate appearances for all widgets if they don't already exist.
|
|
223
|
+
#
|
|
224
|
+
# The created appearance streams depend on the actual type of the button field. See
|
|
225
|
+
# AppearanceGenerator for the details.
|
|
226
|
+
def create_appearances
|
|
227
|
+
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
|
228
|
+
each_widget do |widget|
|
|
229
|
+
next if widget.appearance?
|
|
230
|
+
if check_box?
|
|
231
|
+
appearance_generator_class.new(widget).create_check_box_appearances
|
|
232
|
+
elsif radio_button?
|
|
233
|
+
appearance_generator_class.new(widget).create_radio_button_appearances
|
|
234
|
+
else
|
|
235
|
+
raise HexaPDF::Error, "Push buttons not yet supported"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Updates the widgets so that they reflect the current field value.
|
|
241
|
+
def update_widgets
|
|
242
|
+
return if push_button?
|
|
243
|
+
value = self[:V]
|
|
244
|
+
each_widget do |widget|
|
|
245
|
+
widget[:AS] = (widget.appearance&.normal_appearance&.value&.key?(value) ? value : :Off)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
# Returns the normalized field value for the given key which can be :V or :DV.
|
|
252
|
+
#
|
|
253
|
+
# See #field_value for details.
|
|
254
|
+
def normalized_field_value(key)
|
|
255
|
+
if push_button?
|
|
256
|
+
nil
|
|
257
|
+
elsif check_box?
|
|
258
|
+
self[key] == check_box_on_name
|
|
259
|
+
elsif radio_button?
|
|
260
|
+
self[key] == :Off ? nil : self[key]
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Sets the key, either :V or :DV, to the value. The given normalized value is first
|
|
265
|
+
# transformed into the expected value depending on the specific field type.
|
|
266
|
+
#
|
|
267
|
+
# See #field_value= for details.
|
|
268
|
+
def normalized_field_value_set(key, value)
|
|
269
|
+
return if push_button?
|
|
270
|
+
self[key] = if check_box?
|
|
271
|
+
value == true ? check_box_on_name : :Off
|
|
272
|
+
elsif value.nil?
|
|
273
|
+
:Off
|
|
274
|
+
elsif radio_button_values.include?(value)
|
|
275
|
+
value
|
|
276
|
+
else
|
|
277
|
+
@document.config['acro_form.on_invalid_value'].call(self, value)
|
|
278
|
+
end
|
|
279
|
+
update_widgets
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def perform_validation #:nodoc:
|
|
283
|
+
if field_type != :Btn
|
|
284
|
+
yield("Field /FT of AcroForm button field has to be :Btn", true)
|
|
285
|
+
self[:FT] = :Btn
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
super
|
|
289
|
+
|
|
290
|
+
unless key?(:V)
|
|
291
|
+
yield("Button field has no value set, defaulting to :Off", true)
|
|
292
|
+
self[:V] = :Off
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
@@ -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
|