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
|
@@ -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
|
|
@@ -44,6 +44,12 @@ module HexaPDF
|
|
|
44
44
|
|
|
45
45
|
autoload(:Form, 'hexapdf/type/acro_form/form')
|
|
46
46
|
autoload(:Field, 'hexapdf/type/acro_form/field')
|
|
47
|
+
autoload(:VariableTextField, 'hexapdf/type/acro_form/variable_text_field')
|
|
48
|
+
autoload(:TextField, 'hexapdf/type/acro_form/text_field')
|
|
49
|
+
autoload(:ButtonField, 'hexapdf/type/acro_form/button_field')
|
|
50
|
+
autoload(:ChoiceField, 'hexapdf/type/acro_form/choice_field')
|
|
51
|
+
|
|
52
|
+
autoload(:AppearanceGenerator, 'hexapdf/type/acro_form/appearance_generator')
|
|
47
53
|
|
|
48
54
|
end
|
|
49
55
|
|
|
@@ -0,0 +1,401 @@
|
|
|
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/error'
|
|
38
|
+
require 'hexapdf/layout/style'
|
|
39
|
+
require 'hexapdf/layout/text_fragment'
|
|
40
|
+
|
|
41
|
+
module HexaPDF
|
|
42
|
+
module Type
|
|
43
|
+
module AcroForm
|
|
44
|
+
|
|
45
|
+
# The AppearanceGenerator class provides methods for generating and updating the appearance
|
|
46
|
+
# streams of form fields.
|
|
47
|
+
#
|
|
48
|
+
# The only method needed is #create_appearances since this method determines to what field the
|
|
49
|
+
# widget belongs and therefore which appearance should be generated.
|
|
50
|
+
#
|
|
51
|
+
# The visual appearance of a field is constructed using information from the field itself as
|
|
52
|
+
# well as information from the widget. See the documentation for the individual methods which
|
|
53
|
+
# information is used in which way.
|
|
54
|
+
#
|
|
55
|
+
# By default, any existing appearances are overwritten and the +:print+ flag is set on the
|
|
56
|
+
# widget so that the field appearance will appear on print-outs.
|
|
57
|
+
#
|
|
58
|
+
# The visual appearances are chosen to be similar to those used by Adobe Acrobat and others.
|
|
59
|
+
# By subclassing and overriding the necessary methods it is possible to define custom
|
|
60
|
+
# appearances.
|
|
61
|
+
#
|
|
62
|
+
# See: PDF1.7 s12.5.5, s12.7
|
|
63
|
+
class AppearanceGenerator
|
|
64
|
+
|
|
65
|
+
# Creates a new instance for the given +widget+.
|
|
66
|
+
def initialize(widget)
|
|
67
|
+
@widget = widget
|
|
68
|
+
@field = widget.form_field
|
|
69
|
+
@document = widget.document
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Creates the appropriate appearances for the widget.
|
|
73
|
+
def create_appearances
|
|
74
|
+
case @field.field_type
|
|
75
|
+
when :Btn
|
|
76
|
+
if @field.check_box?
|
|
77
|
+
create_check_box_appearances
|
|
78
|
+
elsif @field.radio_button?
|
|
79
|
+
create_radio_button_appearances
|
|
80
|
+
else
|
|
81
|
+
raise HexaPDF::Error, "Unsupported button field type"
|
|
82
|
+
end
|
|
83
|
+
when :Tx
|
|
84
|
+
create_text_appearances
|
|
85
|
+
when :Ch
|
|
86
|
+
if @field.combo_box?
|
|
87
|
+
create_text_appearances
|
|
88
|
+
else
|
|
89
|
+
raise HexaPDF::Error, "List box not supported yet"
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
raise HexaPDF::Error, "Unsupported field type #{@field.field_type}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Creates the appropriate appearances for check boxes.
|
|
97
|
+
#
|
|
98
|
+
# For unchecked boxes an empty rectangle is drawn. When checked, a symbol from the
|
|
99
|
+
# ZapfDingbats font is placed inside the rectangle. How this is exactly done depends on the
|
|
100
|
+
# following values:
|
|
101
|
+
#
|
|
102
|
+
# * The widget's rectangle /Rect must be defined. If the height and/or width of the
|
|
103
|
+
# rectangle are zero, they are based on the configuration option
|
|
104
|
+
# +acro_form.default_font_size+ and widget's border width. In such a case the rectangle is
|
|
105
|
+
# appropriately updated.
|
|
106
|
+
#
|
|
107
|
+
# * The line width, style and color of the rectangle are taken from the widget's border
|
|
108
|
+
# style. See HexaPDF::Type::Annotations::Widget#border_style.
|
|
109
|
+
#
|
|
110
|
+
# * The background color is determined by the widget's background color. See
|
|
111
|
+
# HexaPDF::Type::Annotations::Widget#background_color.
|
|
112
|
+
#
|
|
113
|
+
# * The symbol (marker) as well as its size and color are determined by the marker style of
|
|
114
|
+
# the widget. See HexaPDF::Type::Annotations::Widget#marker_style for details.
|
|
115
|
+
#
|
|
116
|
+
# Examples:
|
|
117
|
+
#
|
|
118
|
+
# widget.border_style(color: 0)
|
|
119
|
+
# widget.background_color(1)
|
|
120
|
+
# widget.marker_style(style: :check, size: 0, color: 0)
|
|
121
|
+
# # => default appearance
|
|
122
|
+
#
|
|
123
|
+
# widget.border_style(color: :transparent, width: 2)
|
|
124
|
+
# widget.background_color(0.7)
|
|
125
|
+
# widget.marker_style(style: :cross)
|
|
126
|
+
# # => no visible rectangle, gray background, cross mark when checked
|
|
127
|
+
def create_check_box_appearances
|
|
128
|
+
unless @widget.appearance&.normal_appearance&.value&.size == 2
|
|
129
|
+
raise HexaPDF::Error, "Widget of check box doesn't define name for on state"
|
|
130
|
+
end
|
|
131
|
+
border_style = @widget.border_style
|
|
132
|
+
border_width = border_style.width
|
|
133
|
+
|
|
134
|
+
rect = update_widget(@field[:V], border_width)
|
|
135
|
+
|
|
136
|
+
off_form = @widget.appearance.normal_appearance[:Off] =
|
|
137
|
+
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
|
138
|
+
apply_background_and_border(border_style, off_form.canvas)
|
|
139
|
+
|
|
140
|
+
on_form = @widget.appearance.normal_appearance[@field.check_box_on_name] =
|
|
141
|
+
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
|
142
|
+
canvas = on_form.canvas
|
|
143
|
+
apply_background_and_border(border_style, canvas)
|
|
144
|
+
canvas.save_graphics_state do
|
|
145
|
+
draw_marker(canvas, rect, border_width, @widget.marker_style)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Creates the appropriate appearances for radio buttons.
|
|
150
|
+
#
|
|
151
|
+
# For unselected radio buttons an empty circle (if the marker is :circle) or rectangle is
|
|
152
|
+
# drawn inside the widget annotation's rectangle. When selected, a symbol from the
|
|
153
|
+
# ZapfDingbats font is placed inside. How this is exactly done depends on the following
|
|
154
|
+
# values:
|
|
155
|
+
#
|
|
156
|
+
# * The widget's rectangle /Rect must be defined. If the height and/or width of the
|
|
157
|
+
# rectangle are zero, they are based on the configuration option
|
|
158
|
+
# +acro_form.default_font_size+ and the widget's border width. In such a case the
|
|
159
|
+
# rectangle is appropriately updated.
|
|
160
|
+
#
|
|
161
|
+
# * The line width, style and color of the circle/rectangle are taken from the widget's
|
|
162
|
+
# border style. See HexaPDF::Type::Annotations::Widget#border_style.
|
|
163
|
+
#
|
|
164
|
+
# * The background color is determined by the widget's background color. See
|
|
165
|
+
# HexaPDF::Type::Annotations::Widget#background_color.
|
|
166
|
+
#
|
|
167
|
+
# * The symbol (marker) as well as its size and color are determined by the marker style of
|
|
168
|
+
# the widget. See HexaPDF::Type::Annotations::Widget#marker_style for details.
|
|
169
|
+
#
|
|
170
|
+
# Examples:
|
|
171
|
+
#
|
|
172
|
+
# widget.border_style(color: 0)
|
|
173
|
+
# widget.background_color(1)
|
|
174
|
+
# widget.marker_style(style: :circle, size: 0, color: 0)
|
|
175
|
+
# # => default appearance
|
|
176
|
+
def create_radio_button_appearances
|
|
177
|
+
unless @widget.appearance&.normal_appearance&.value&.size == 2
|
|
178
|
+
raise HexaPDF::Error, "Widget of radio button doesn't define unique name for on state"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
on_name = (@widget.appearance.normal_appearance.value.keys - [:Off]).first
|
|
182
|
+
border_style = @widget.border_style
|
|
183
|
+
marker_style = @widget.marker_style
|
|
184
|
+
|
|
185
|
+
rect = update_widget(@field[:V] == on_name ? on_name : :Off, border_style.width)
|
|
186
|
+
|
|
187
|
+
off_form = @widget.appearance.normal_appearance[:Off] =
|
|
188
|
+
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
|
189
|
+
apply_background_and_border(border_style, off_form.canvas,
|
|
190
|
+
circular: marker_style.style == :circle)
|
|
191
|
+
|
|
192
|
+
on_form = @widget.appearance.normal_appearance[on_name] =
|
|
193
|
+
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
|
194
|
+
canvas = on_form.canvas
|
|
195
|
+
apply_background_and_border(border_style, canvas,
|
|
196
|
+
circular: marker_style.style == :circle)
|
|
197
|
+
canvas.save_graphics_state do
|
|
198
|
+
draw_marker(canvas, rect, border_style.width, @widget.marker_style)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Creates the appropriate appearances for text fields.
|
|
203
|
+
#
|
|
204
|
+
# The following describes how the appearance is built:
|
|
205
|
+
#
|
|
206
|
+
# * The font, font size and font color are taken from the associated field's default
|
|
207
|
+
# appearance string. See VariableTextField.
|
|
208
|
+
#
|
|
209
|
+
# * The widget's rectangle /Rect must be defined. If the height is zero, it is auto-sized
|
|
210
|
+
# based on the font size. If additionally the font size is zero, a font size of
|
|
211
|
+
# +acro_form.default_font_size+ is used. If the width is zero, the
|
|
212
|
+
# +acro_form.text_field.default_width+ value is used. In such cases the rectangle is
|
|
213
|
+
# appropriately updated.
|
|
214
|
+
#
|
|
215
|
+
# * The line width, style and color of the rectangle are taken from the widget's border
|
|
216
|
+
# style. See HexaPDF::Type::Annotations::Widget#border_style.
|
|
217
|
+
#
|
|
218
|
+
# * The background color is determined by the widget's background color. See
|
|
219
|
+
# HexaPDF::Type::Annotations::Widget#background_color.
|
|
220
|
+
#
|
|
221
|
+
# Note: Multiline, comb and rich text fields are currently not supported!
|
|
222
|
+
def create_text_appearances
|
|
223
|
+
font_name, font_size = @field.parse_default_appearance_string
|
|
224
|
+
default_resources = @document.acro_form.default_resources
|
|
225
|
+
font = default_resources.font(font_name).font_wrapper
|
|
226
|
+
unless font
|
|
227
|
+
fallback_font_name, fallback_font_options = @document.config['acro_form.fallback_font']
|
|
228
|
+
if fallback_font_name
|
|
229
|
+
font = @document.fonts.add(fallback_font_name, **(fallback_font_options || {}))
|
|
230
|
+
else
|
|
231
|
+
raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
style = HexaPDF::Layout::Style.new(font: font)
|
|
235
|
+
border_style = @widget.border_style
|
|
236
|
+
padding = [1, border_style.width].max
|
|
237
|
+
|
|
238
|
+
@widget[:AS] = :N
|
|
239
|
+
@widget.flag(:print)
|
|
240
|
+
rect = @widget[:Rect]
|
|
241
|
+
rect.width = @document.config['acro_form.text_field.default_width'] if rect.width == 0
|
|
242
|
+
if rect.height == 0
|
|
243
|
+
style.font_size = \
|
|
244
|
+
(font_size == 0 ? @document.config['acro_form.default_font_size'] : font_size)
|
|
245
|
+
rect.height = style.scaled_y_max - style.scaled_y_min + 2 * padding
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
form = (@widget[:AP] ||= {})[:N] = @document.add({Type: :XObject, Subtype: :Form,
|
|
249
|
+
BBox: [0, 0, rect.width, rect.height]})
|
|
250
|
+
form[:Resources] = HexaPDF::Object.deep_copy(default_resources)
|
|
251
|
+
|
|
252
|
+
canvas = form.canvas
|
|
253
|
+
apply_background_and_border(border_style, canvas)
|
|
254
|
+
style.font_size = calculate_font_size(font, font_size, rect, border_style)
|
|
255
|
+
|
|
256
|
+
canvas.marked_content_sequence(:Tx) do
|
|
257
|
+
if (value = @field.field_value)
|
|
258
|
+
canvas.save_graphics_state do
|
|
259
|
+
canvas.rectangle(padding, padding, rect.width - 2 * padding,
|
|
260
|
+
rect.height - 2 * padding).clip_path.end_path
|
|
261
|
+
fragment = HexaPDF::Layout::TextFragment.create(value, style)
|
|
262
|
+
# Adobe seems to be left/right-aligning based on twice the border width and
|
|
263
|
+
# vertically centering based on the cap height, if enough space is available
|
|
264
|
+
x = case @field.text_alignment
|
|
265
|
+
when :left then 2 * padding
|
|
266
|
+
when :right then [rect.width - 2 * padding - fragment.width, 2 * padding].max
|
|
267
|
+
when :center then [(rect.width - fragment.width) / 2.0, 2 * padding].max
|
|
268
|
+
end
|
|
269
|
+
cap_height = font.wrapped_font.cap_height * font.scaling_factor / 1000.0 *
|
|
270
|
+
style.font_size
|
|
271
|
+
y = padding + (rect.height - 2 * padding - cap_height) / 2.0
|
|
272
|
+
y = padding - style.scaled_font_descender if y < 0
|
|
273
|
+
fragment.draw(canvas, x, y)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
alias create_combo_box_appearances create_text_appearances
|
|
280
|
+
|
|
281
|
+
private
|
|
282
|
+
|
|
283
|
+
# Updates the widget and returns its (possibly modified) rectangle.
|
|
284
|
+
#
|
|
285
|
+
# The following changes are made:
|
|
286
|
+
#
|
|
287
|
+
# * Sets the appearance state to +appearance_state+.
|
|
288
|
+
# * Sets the :print flag.
|
|
289
|
+
# * Adjusts the rectangle based on the default font size and the given border width if its
|
|
290
|
+
# width and/or height are zero.
|
|
291
|
+
def update_widget(appearance_state, border_width)
|
|
292
|
+
@widget[:AS] = appearance_state
|
|
293
|
+
@widget.flag(:print)
|
|
294
|
+
|
|
295
|
+
default_font_size = @document.config['acro_form.default_font_size']
|
|
296
|
+
rect = @widget[:Rect]
|
|
297
|
+
rect.width = default_font_size + 2 * border_width if rect.width == 0
|
|
298
|
+
rect.height = default_font_size + 2 * border_width if rect.height == 0
|
|
299
|
+
rect
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Applies the background and border style of the widget annotation to the appearances.
|
|
303
|
+
#
|
|
304
|
+
# If +circular+ is +true+, then the border is drawn as inscribed circle instead of as
|
|
305
|
+
# rectangle.
|
|
306
|
+
def apply_background_and_border(border_style, canvas, circular: false)
|
|
307
|
+
rect = @widget[:Rect]
|
|
308
|
+
background_color = @widget.background_color
|
|
309
|
+
|
|
310
|
+
if (border_style.width > 0 && border_style.color) || background_color
|
|
311
|
+
canvas.save_graphics_state
|
|
312
|
+
if background_color
|
|
313
|
+
canvas.fill_color(background_color)
|
|
314
|
+
if circular
|
|
315
|
+
canvas.circle(rect.width / 2.0, rect.height / 2.0,
|
|
316
|
+
[rect.width / 2.0, rect.height / 2.0].min)
|
|
317
|
+
else
|
|
318
|
+
canvas.rectangle(0, 0, rect.width, rect.height)
|
|
319
|
+
end
|
|
320
|
+
canvas.fill
|
|
321
|
+
end
|
|
322
|
+
if border_style.color
|
|
323
|
+
offset = [0.5, border_style.width / 2.0].max
|
|
324
|
+
width, height = rect.width - 2 * offset, rect.height - 2 * offset
|
|
325
|
+
canvas.stroke_color(border_style.color).line_width(border_style.width)
|
|
326
|
+
if border_style.style == :underlined # TODO: :beveleded, :inset
|
|
327
|
+
if circular
|
|
328
|
+
canvas.arc(rect.width / 2.0, rect.height / 2.0,
|
|
329
|
+
a: [width / 2.0, height / 2.0].min,
|
|
330
|
+
start_angle: 180, end_angle: 0)
|
|
331
|
+
else
|
|
332
|
+
canvas.line(offset, offset, offset + width, offset)
|
|
333
|
+
end
|
|
334
|
+
else
|
|
335
|
+
canvas.line_dash_pattern(border_style.style) if border_style.style.kind_of?(Array)
|
|
336
|
+
if circular
|
|
337
|
+
canvas.circle(rect.width / 2.0, rect.height / 2.0, [width / 2.0, height / 2.0].min)
|
|
338
|
+
else
|
|
339
|
+
canvas.rectangle(offset, offset, width, height)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
canvas.stroke
|
|
343
|
+
end
|
|
344
|
+
canvas.restore_graphics_state
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Draws the marker defined by the marker style inside the widget's rectangle.
|
|
349
|
+
#
|
|
350
|
+
# This method can only used for check boxes and radio buttons!
|
|
351
|
+
def draw_marker(canvas, rect, border_width, marker_style)
|
|
352
|
+
if @field.radio_button? && marker_style.style == :circle
|
|
353
|
+
# Acrobat handles this specially
|
|
354
|
+
canvas.
|
|
355
|
+
fill_color(marker_style.color).
|
|
356
|
+
circle(rect.width / 2.0, rect.height / 2.0,
|
|
357
|
+
([rect.width / 2.0, rect.height / 2.0].min - border_width) / 2).
|
|
358
|
+
fill
|
|
359
|
+
elsif marker_style.style == :cross # Acrobat just places a cross inside
|
|
360
|
+
canvas.
|
|
361
|
+
stroke_color(marker_style.color).
|
|
362
|
+
line(border_width, border_width, rect.width - border_width,
|
|
363
|
+
rect.height - border_width).
|
|
364
|
+
line(border_width, rect.height - border_width, rect.width - border_width,
|
|
365
|
+
border_width).
|
|
366
|
+
stroke
|
|
367
|
+
else
|
|
368
|
+
font = @document.fonts.add('ZapfDingbats')
|
|
369
|
+
mark = font.decode_utf8(@widget[:MK]&.[](:CA) || '4').first
|
|
370
|
+
square_width = [rect.width, rect.height].min - 2 * border_width
|
|
371
|
+
font_size = (marker_style.size == 0 ? square_width : marker_style.size)
|
|
372
|
+
mark_width = mark.width * font.scaling_factor * font_size / 1000.0
|
|
373
|
+
mark_height = (mark.y_max - mark.y_min) * font.scaling_factor * font_size / 1000.0
|
|
374
|
+
x_offset = (rect.width - square_width) / 2.0 + (square_width - mark_width) / 2.0
|
|
375
|
+
y_offset = (rect.height - square_width) / 2.0 + (square_width - mark_height) / 2.0 -
|
|
376
|
+
(mark.y_min * font.scaling_factor * font_size / 1000.0)
|
|
377
|
+
|
|
378
|
+
canvas.font(font, size: font_size)
|
|
379
|
+
canvas.fill_color(marker_style.color)
|
|
380
|
+
canvas.move_text_cursor(offset: [x_offset, y_offset]).show_glyphs_only([mark])
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Calculates the font size for text fields based on the font and font size of the default
|
|
385
|
+
# appearance string, the annotation rectangle and the border style.
|
|
386
|
+
def calculate_font_size(font, font_size, rect, border_style)
|
|
387
|
+
if font_size == 0
|
|
388
|
+
unit_font_size = (font.wrapped_font.bounding_box[3] - font.wrapped_font.bounding_box[1]) *
|
|
389
|
+
font.scaling_factor / 1000.0
|
|
390
|
+
# The constant factor was found empirically by checking what Adobe Reader etc. do
|
|
391
|
+
(rect.height - 2 * border_style.width) / unit_font_size * 0.83
|
|
392
|
+
else
|
|
393
|
+
font_size
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
@@ -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
|