hexapdf 0.11.9 → 0.13.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 +157 -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 +22 -11
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/form.rb +240 -0
- data/lib/hexapdf/cli/image2pdf.rb +3 -2
- data/lib/hexapdf/cli/images.rb +1 -1
- data/lib/hexapdf/cli/info.rb +52 -3
- data/lib/hexapdf/cli/inspect.rb +31 -9
- data/lib/hexapdf/cli/merge.rb +2 -2
- 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 +81 -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 +4 -4
- 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 +5 -5
- data/lib/hexapdf/dictionary_fields.rb +2 -10
- data/lib/hexapdf/document.rb +45 -17
- data/lib/hexapdf/document/files.rb +1 -2
- 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 +2 -2
- 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 +2 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -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 +2 -5
- 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 +2 -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 +3 -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 +4 -3
- 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 +2 -2
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/style.rb +24 -24
- 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 +4 -3
- 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 +32 -27
- data/lib/hexapdf/parser.rb +69 -6
- data/lib/hexapdf/pdf_array.rb +10 -3
- 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 +30 -22
- 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 +7 -5
- data/lib/hexapdf/tokenizer.rb +5 -4
- 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 +405 -0
- data/lib/hexapdf/type/acro_form/button_field.rb +305 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
- data/lib/hexapdf/type/acro_form/field.rb +250 -17
- data/lib/hexapdf/type/acro_form/form.rb +159 -7
- data/lib/hexapdf/type/acro_form/text_field.rb +187 -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 +4 -3
- 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 +238 -2
- data/lib/hexapdf/type/catalog.rb +23 -3
- 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 +2 -2
- data/lib/hexapdf/type/font.rb +18 -1
- data/lib/hexapdf/type/font_descriptor.rb +2 -2
- data/lib/hexapdf/type/font_simple.rb +4 -2
- data/lib/hexapdf/type/font_true_type.rb +7 -3
- data/lib/hexapdf/type/font_type0.rb +2 -2
- data/lib/hexapdf/type/font_type1.rb +16 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +12 -2
- 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 +5 -3
- 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 +36 -12
- data/lib/hexapdf/type/page_tree_node.rb +37 -16
- data/lib/hexapdf/type/resources.rb +17 -3
- data/lib/hexapdf/type/trailer.rb +4 -6
- 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 +19 -16
- 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/common_tokenizer_tests.rb +6 -1
- data/test/hexapdf/content/common.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
- data/test/hexapdf/content/test_canvas.rb +3 -3
- 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_aes.rb +4 -4
- data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
- data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
- 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/layout/test_text_layouter.rb +3 -4
- data/test/hexapdf/test_configuration.rb +2 -2
- data/test/hexapdf/test_dictionary.rb +3 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -2
- data/test/hexapdf/test_document.rb +16 -4
- data/test/hexapdf/test_object.rb +44 -26
- data/test/hexapdf/test_parser.rb +125 -55
- data/test/hexapdf/test_pdf_array.rb +7 -0
- data/test/hexapdf/test_rectangle.rb +14 -0
- data/test/hexapdf/test_revision.rb +3 -0
- data/test/hexapdf/test_revisions.rb +35 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +521 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +281 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
- data/test/hexapdf/type/acro_form/test_field.rb +163 -6
- data/test/hexapdf/type/acro_form/test_form.rb +189 -22
- data/test/hexapdf/type/acro_form/test_text_field.rb +121 -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_simple.rb +2 -1
- data/test/hexapdf/type/test_font_true_type.rb +6 -0
- data/test/hexapdf/type/test_font_type1.rb +8 -0
- data/test/hexapdf/type/test_form.rb +19 -1
- data/test/hexapdf/type/test_image.rb +7 -0
- data/test/hexapdf/type/test_page.rb +45 -7
- data/test/hexapdf/type/test_page_tree_node.rb +62 -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 +15 -1
- data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
- data/test/test_helper.rb +1 -1
- metadata +34 -21
- data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -0,0 +1,305 @@
|
|
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 value (a
|
138
|
+
# Symbol) of 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 value (a Symbol or an object responding to +#to_sym+) of a radio
|
153
|
+
# button that should be turned on.
|
154
|
+
def field_value=(value)
|
155
|
+
normalized_field_value_set(:V, value)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the default field value.
|
159
|
+
#
|
160
|
+
# See: #field_value
|
161
|
+
def default_field_value
|
162
|
+
normalized_field_value(:DV)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Sets the default field value.
|
166
|
+
#
|
167
|
+
# See: #field_value=
|
168
|
+
def default_field_value=(value)
|
169
|
+
normalized_field_value_set(:DV, value)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns the concrete button field type, either :push_button, :check_box or :radio_button.
|
173
|
+
def concrete_field_type
|
174
|
+
if push_button?
|
175
|
+
:push_button
|
176
|
+
elsif radio_button?
|
177
|
+
:radio_button
|
178
|
+
else
|
179
|
+
:check_box
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the name (a Symbol) used for setting the check box to the on state.
|
184
|
+
#
|
185
|
+
# Defaults to :Yes if no other name could be determined.
|
186
|
+
def check_box_on_name
|
187
|
+
each_widget.to_a.first&.appearance&.normal_appearance&.value&.each_key&.
|
188
|
+
find {|key| key != :Off } || :Yes
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns the array of Symbol values that can be used for the field value of the radio
|
192
|
+
# button.
|
193
|
+
def radio_button_values
|
194
|
+
each_widget.map do |widget|
|
195
|
+
widget.appearance&.normal_appearance&.value&.each_key&.find {|key| key != :Off }
|
196
|
+
end.compact
|
197
|
+
end
|
198
|
+
|
199
|
+
# Creates a widget for the button field.
|
200
|
+
#
|
201
|
+
# If +defaults+ is +true+, then default values will be set on the widget so that it uses a
|
202
|
+
# default appearance.
|
203
|
+
#
|
204
|
+
# If the widget is created for a radio button field, the +value+ argument needs to set to
|
205
|
+
# the value (a Symbol or an object responding to +#to_sym+) this widget represents. It can
|
206
|
+
# be used with #field_value= to set this specific widget of the radio button set to on.
|
207
|
+
#
|
208
|
+
# See: Field#create_widget, AppearanceGenerator button field methods
|
209
|
+
def create_widget(page, defaults: true, value: nil, **values)
|
210
|
+
super(page, allow_embedded: !radio_button?, **values).tap do |widget|
|
211
|
+
if check_box?
|
212
|
+
widget[:AP] = {N: {Yes: nil, Off: nil}}
|
213
|
+
elsif radio_button?
|
214
|
+
unless value.respond_to?(:to_sym)
|
215
|
+
raise ArgumentError, "Argument 'value' has to be provided for radio buttons " \
|
216
|
+
"and needs to respond to #to_sym"
|
217
|
+
end
|
218
|
+
widget[:AP] = {N: {value.to_sym => nil, Off: nil}}
|
219
|
+
end
|
220
|
+
next unless defaults
|
221
|
+
widget.border_style(color: 0, width: 1, style: (push_button? ? :beveled : :solid))
|
222
|
+
widget.background_color(push_button? ? 0.5 : 255)
|
223
|
+
widget.marker_style(style: check_box? ? :check : :circle) unless push_button?
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Creates appropriate appearances for all widgets if they don't already exist.
|
228
|
+
#
|
229
|
+
# The created appearance streams depend on the actual type of the button field. See
|
230
|
+
# AppearanceGenerator for the details.
|
231
|
+
def create_appearances
|
232
|
+
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
233
|
+
each_widget do |widget|
|
234
|
+
next if widget.appearance?
|
235
|
+
if check_box?
|
236
|
+
appearance_generator_class.new(widget).create_check_box_appearances
|
237
|
+
elsif radio_button?
|
238
|
+
appearance_generator_class.new(widget).create_radio_button_appearances
|
239
|
+
else
|
240
|
+
raise HexaPDF::Error, "Push buttons not yet supported"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Updates the widgets so that they reflect the current field value.
|
246
|
+
def update_widgets
|
247
|
+
return if push_button?
|
248
|
+
value = self[:V]
|
249
|
+
each_widget do |widget|
|
250
|
+
widget[:AS] = (widget.appearance&.normal_appearance&.value&.key?(value) ? value : :Off)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
# Returns the normalized field value for the given key which can be :V or :DV.
|
257
|
+
#
|
258
|
+
# See #field_value for details.
|
259
|
+
def normalized_field_value(key)
|
260
|
+
if push_button?
|
261
|
+
nil
|
262
|
+
elsif check_box?
|
263
|
+
self[key] == check_box_on_name
|
264
|
+
elsif radio_button?
|
265
|
+
self[key] == :Off ? nil : self[key]
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Sets the key, either :V or :DV, to the value. The given normalized value is first
|
270
|
+
# transformed into the expected value depending on the specific field type.
|
271
|
+
#
|
272
|
+
# See #field_value= for details.
|
273
|
+
def normalized_field_value_set(key, value)
|
274
|
+
return if push_button?
|
275
|
+
self[key] = if check_box?
|
276
|
+
value == true ? check_box_on_name : :Off
|
277
|
+
elsif value.nil?
|
278
|
+
:Off
|
279
|
+
elsif radio_button_values.include?(value.to_sym)
|
280
|
+
value.to_sym
|
281
|
+
else
|
282
|
+
@document.config['acro_form.on_invalid_value'].call(self, value)
|
283
|
+
end
|
284
|
+
update_widgets
|
285
|
+
end
|
286
|
+
|
287
|
+
def perform_validation #:nodoc:
|
288
|
+
if field_type != :Btn
|
289
|
+
yield("Field /FT of AcroForm button field has to be :Btn", true)
|
290
|
+
self[:FT] = :Btn
|
291
|
+
end
|
292
|
+
|
293
|
+
super
|
294
|
+
|
295
|
+
unless key?(:V)
|
296
|
+
yield("Button field has no value set, defaulting to :Off", true)
|
297
|
+
self[:V] = :Off
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
305
|
+
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
|