hexapdf 0.11.9 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/LICENSE +1 -1
- data/examples/001-hello_world.rb +1 -1
- data/examples/002-graphics.rb +1 -1
- data/examples/003-arcs.rb +1 -1
- data/examples/004-optimizing.rb +1 -1
- data/examples/005-merging.rb +1 -1
- data/examples/006-standard_pdf_fonts.rb +1 -1
- data/examples/007-truetype.rb +1 -1
- data/examples/008-show_char_bboxes.rb +1 -1
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +1 -1
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +1 -1
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +1 -1
- data/examples/016-frame_automatic_box_placement.rb +1 -1
- data/examples/017-frame_text_flow.rb +1 -1
- data/examples/018-composer.rb +1 -1
- data/examples/019-acro_form.rb +51 -0
- data/lib/hexapdf.rb +1 -1
- data/lib/hexapdf/cli.rb +3 -1
- data/lib/hexapdf/cli/batch.rb +1 -1
- data/lib/hexapdf/cli/command.rb +18 -9
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/form.rb +240 -0
- data/lib/hexapdf/cli/image2pdf.rb +1 -1
- data/lib/hexapdf/cli/images.rb +1 -1
- data/lib/hexapdf/cli/info.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/cli/watermark.rb +1 -1
- data/lib/hexapdf/composer.rb +2 -2
- data/lib/hexapdf/configuration.rb +66 -11
- data/lib/hexapdf/content.rb +3 -1
- data/lib/hexapdf/content/canvas.rb +5 -18
- data/lib/hexapdf/content/color_space.rb +111 -32
- data/lib/hexapdf/content/graphic_object.rb +1 -1
- data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/content/operator.rb +9 -9
- data/lib/hexapdf/content/parser.rb +18 -5
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/data_dir.rb +1 -1
- data/lib/hexapdf/dictionary.rb +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document.rb +14 -5
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/document/pages.rb +3 -14
- data/lib/hexapdf/encryption.rb +1 -1
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +1 -1
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/error.rb +1 -1
- data/lib/hexapdf/filter.rb +3 -3
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/encryption.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/cmap/parser.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +1 -1
- data/lib/hexapdf/font/encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/base.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +1 -1
- data/lib/hexapdf/font/true_type.rb +1 -1
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/font.rb +1 -1
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
- data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
- data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
- data/lib/hexapdf/font/true_type/table/head.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
- data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
- data/lib/hexapdf/font/true_type/table/name.rb +1 -1
- data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
- data/lib/hexapdf/font/type1.rb +1 -1
- data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
- data/lib/hexapdf/font/type1_wrapper.rb +67 -51
- data/lib/hexapdf/font_loader.rb +1 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/from_file.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +1 -1
- data/lib/hexapdf/importer.rb +2 -4
- data/lib/hexapdf/layout.rb +1 -1
- data/lib/hexapdf/layout/box.rb +1 -1
- data/lib/hexapdf/layout/frame.rb +1 -1
- data/lib/hexapdf/layout/image_box.rb +1 -1
- data/lib/hexapdf/layout/inline_box.rb +1 -1
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/style.rb +1 -1
- data/lib/hexapdf/layout/text_box.rb +1 -1
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +1 -1
- data/lib/hexapdf/layout/text_shaper.rb +1 -1
- data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +2 -2
- data/lib/hexapdf/parser.rb +4 -3
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +31 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +2 -1
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/serializer.rb +1 -1
- data/lib/hexapdf/stream.rb +1 -1
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +1 -1
- data/lib/hexapdf/type.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +7 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +401 -0
- data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
- data/lib/hexapdf/type/acro_form/field.rb +220 -17
- data/lib/hexapdf/type/acro_form/form.rb +157 -7
- data/lib/hexapdf/type/acro_form/text_field.rb +186 -0
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/annotation.rb +73 -3
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/annotations/link.rb +2 -2
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +239 -2
- data/lib/hexapdf/type/catalog.rb +21 -1
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +1 -1
- data/lib/hexapdf/type/font.rb +18 -1
- data/lib/hexapdf/type/font_descriptor.rb +2 -2
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_true_type.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +16 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +1 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +3 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/names.rb +1 -1
- data/lib/hexapdf/type/object_stream.rb +1 -1
- data/lib/hexapdf/type/page.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +8 -11
- data/lib/hexapdf/type/resources.rb +16 -3
- data/lib/hexapdf/type/trailer.rb +2 -3
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +1 -1
- data/lib/hexapdf/utils/bit_field.rb +38 -24
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
- data/lib/hexapdf/utils/lru_cache.rb +1 -1
- data/lib/hexapdf/utils/math_helpers.rb +1 -1
- data/lib/hexapdf/utils/object_hash.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
- data/lib/hexapdf/version.rb +2 -2
- data/lib/hexapdf/writer.rb +1 -1
- data/lib/hexapdf/xref_section.rb +1 -1
- data/test/hexapdf/content/common.rb +2 -2
- data/test/hexapdf/content/test_color_space.rb +71 -8
- data/test/hexapdf/content/test_operator.rb +22 -22
- data/test/hexapdf/content/test_parser.rb +14 -0
- data/test/hexapdf/document/test_fonts.rb +1 -1
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
- data/test/hexapdf/font/test_type1_wrapper.rb +32 -8
- data/test/hexapdf/test_document.rb +12 -0
- data/test/hexapdf/test_parser.rb +10 -0
- data/test/hexapdf/test_rectangle.rb +14 -0
- data/test/hexapdf/test_revision.rb +3 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
- data/test/hexapdf/type/acro_form/test_field.rb +124 -6
- data/test/hexapdf/type/acro_form/test_form.rb +189 -22
- data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
- data/test/hexapdf/type/annotations/test_text.rb +1 -1
- data/test/hexapdf/type/annotations/test_widget.rb +199 -0
- data/test/hexapdf/type/test_annotation.rb +45 -0
- data/test/hexapdf/type/test_catalog.rb +18 -0
- data/test/hexapdf/type/test_font.rb +5 -0
- data/test/hexapdf/type/test_font_type1.rb +8 -0
- data/test/hexapdf/type/test_image.rb +7 -0
- data/test/hexapdf/type/test_page_tree_node.rb +20 -12
- data/test/hexapdf/type/test_resources.rb +20 -0
- data/test/hexapdf/type/test_trailer.rb +4 -0
- data/test/hexapdf/utils/test_bit_field.rb +13 -1
- data/test/test_helper.rb +1 -1
- metadata +37 -18
- data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -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
|
@@ -36,6 +36,7 @@
|
|
36
36
|
|
37
37
|
require 'hexapdf/dictionary'
|
38
38
|
require 'hexapdf/stream'
|
39
|
+
require 'hexapdf/type/acro_form/field'
|
39
40
|
require 'hexapdf/utils/bit_field'
|
40
41
|
|
41
42
|
module HexaPDF
|
@@ -45,7 +46,29 @@ module HexaPDF
|
|
45
46
|
# Represents the PDF's interactive form dictionary. It is linked from the catalog dictionary
|
46
47
|
# via the /AcroForm entry.
|
47
48
|
#
|
48
|
-
#
|
49
|
+
# == Overview
|
50
|
+
#
|
51
|
+
# An interactive form consists of fields which can be structured hierarchically and shown on
|
52
|
+
# pages by using Annotations::Widget annotations. This means one field can have zero, one or
|
53
|
+
# more visual representations on one or more pages. The fields at the bottom of the hierarchy
|
54
|
+
# which have no parent are called "root fields" and are stored in /Fields.
|
55
|
+
#
|
56
|
+
# Each field in a form has a certain type which determines how it should be displayed and what
|
57
|
+
# a user can do with it. The most common type is "text field" which allows the user to enter
|
58
|
+
# one or more lines of text. There are also check boxes, radio buttons, list boxes and combo
|
59
|
+
# boxes.
|
60
|
+
#
|
61
|
+
# == Visual Appearance
|
62
|
+
#
|
63
|
+
# The visual appearance of a field is normally provided by the application creating the PDF.
|
64
|
+
# This is done by generating the so called appearances for all widgets of a field. However, it
|
65
|
+
# is also possible to instruct the PDF reader application to generate the appearances on the
|
66
|
+
# fly using the /NeedAppearances key, see #need_appearances!.
|
67
|
+
#
|
68
|
+
# HexaPDF uses the configuration option +acro_form.create_appearance_streams+ to determine
|
69
|
+
# whether appearances should automatically be generated.
|
70
|
+
#
|
71
|
+
# See: PDF1.7 s12.7.2, Field, HexaPDF::Type::Annotations::Widget
|
49
72
|
class Form < Dictionary
|
50
73
|
|
51
74
|
extend Utils::BitField
|
@@ -56,12 +79,18 @@ module HexaPDF
|
|
56
79
|
define_field :NeedAppearances, type: Boolean, default: false
|
57
80
|
define_field :SigFlags, type: Integer, version: '1.3'
|
58
81
|
define_field :CO, type: PDFArray, version: '1.3'
|
59
|
-
define_field :DR, type: :
|
82
|
+
define_field :DR, type: :XXResources
|
60
83
|
define_field :DA, type: String
|
61
84
|
define_field :XFA, type: [Stream, PDFArray], version: '1.5'
|
62
85
|
|
63
86
|
bit_field(:raw_signature_flags, {signatures_exist: 0, append_only: 1},
|
64
|
-
lister: "signature_flags", getter: "signature_flag?", setter: "signature_flag"
|
87
|
+
lister: "signature_flags", getter: "signature_flag?", setter: "signature_flag",
|
88
|
+
unsetter: "signature_unflag")
|
89
|
+
|
90
|
+
# Returns the PDFArray containing the root fields.
|
91
|
+
def root_fields
|
92
|
+
self[:Fields] ||= document.wrap([])
|
93
|
+
end
|
65
94
|
|
66
95
|
# Returns an array with all root fields that were found in the PDF document.
|
67
96
|
def find_root_fields
|
@@ -69,7 +98,7 @@ module HexaPDF
|
|
69
98
|
document.pages.each do |page|
|
70
99
|
page[:Annots]&.each do |annot|
|
71
100
|
if !annot.key?(:Parent) && annot.key?(:FT)
|
72
|
-
result << document.wrap(annot, type: :XXAcroFormField)
|
101
|
+
result << document.wrap(annot, type: :XXAcroFormField, subtype: annot[:FT])
|
73
102
|
elsif annot.key?(:Parent)
|
74
103
|
field = annot[:Parent]
|
75
104
|
field = field[:Parent] while field[:Parent]
|
@@ -96,15 +125,99 @@ module HexaPDF
|
|
96
125
|
return to_enum(__method__, terminal_only: terminal_only) unless block_given?
|
97
126
|
|
98
127
|
process_field = lambda do |field|
|
99
|
-
field = document.wrap(field, type: :XXAcroFormField
|
128
|
+
field = document.wrap(field, type: :XXAcroFormField,
|
129
|
+
subtype: Field.inherited_value(field, :FT))
|
100
130
|
yield(field) if field.terminal_field? || !terminal_only
|
101
131
|
field[:Kids].each(&process_field) unless field.terminal_field?
|
102
132
|
end
|
103
133
|
|
104
|
-
|
134
|
+
root_fields.each(&process_field)
|
105
135
|
self
|
106
136
|
end
|
107
137
|
|
138
|
+
# Returns the field with the given +name+ or +nil+ if no such field exists.
|
139
|
+
def field_by_name(name)
|
140
|
+
fields = root_fields
|
141
|
+
field = nil
|
142
|
+
name.split('.').each do |part|
|
143
|
+
field = fields&.find {|f| f[:T] == part }
|
144
|
+
break unless field
|
145
|
+
field = document.wrap(field, type: :XXAcroFormField,
|
146
|
+
subtype: Field.inherited_value(field, :FT))
|
147
|
+
fields = field[:Kids] unless field.terminal_field?
|
148
|
+
end
|
149
|
+
field
|
150
|
+
end
|
151
|
+
|
152
|
+
# Creates a new text field with the given name and adds it to the form.
|
153
|
+
#
|
154
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
155
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
156
|
+
def create_text_field(name)
|
157
|
+
create_field(name, :Tx)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Creates a new check box with the given name and adds it to the form.
|
161
|
+
#
|
162
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
163
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
164
|
+
def create_check_box(name)
|
165
|
+
create_field(name, :Btn).tap(&:initialize_as_check_box)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Creates a radio button with the given name and adds it to the form.
|
169
|
+
#
|
170
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
171
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
172
|
+
def create_radio_button(name)
|
173
|
+
create_field(name, :Btn).tap(&:initialize_as_radio_button)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates a combo box with the given name and adds it to the form.
|
177
|
+
#
|
178
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
179
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
180
|
+
def create_combo_box(name)
|
181
|
+
create_field(name, :Ch).tap(&:initialize_as_combo_box)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Creates a list box with the given name and adds it to the form.
|
185
|
+
#
|
186
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
187
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
188
|
+
def create_list_box(name)
|
189
|
+
create_field(name, :Ch).tap(&:initialize_as_list_box)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns the dictionary containing the default resources for form field appearance streams.
|
193
|
+
def default_resources
|
194
|
+
self[:DR] ||= document.wrap({}, type: :XXResources)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Sets the global default appearance string using the provided values.
|
198
|
+
#
|
199
|
+
# The default argument values are a sane default. If +font_size+ is set to 0, the font size
|
200
|
+
# is calculated using the height/width of the field.
|
201
|
+
def set_default_appearance_string(font: 'Helvetica', font_size: 0)
|
202
|
+
name = default_resources.add_font(document.fonts.add(font).pdf_object)
|
203
|
+
self[:DA] = "0 g /#{name} #{font_size} Tf"
|
204
|
+
end
|
205
|
+
|
206
|
+
# Sets the /NeedAppearances field to +true+.
|
207
|
+
#
|
208
|
+
# This will make PDF reader applications generate appropriate appearance streams based on
|
209
|
+
# the information stored in the fields and associated widgets.
|
210
|
+
def need_appearances!
|
211
|
+
self[:NeedAppearances] = true
|
212
|
+
end
|
213
|
+
|
214
|
+
# Creates the appearances for all widgets of all terminal fields if they don't exist.
|
215
|
+
def create_appearances
|
216
|
+
each_field do |field|
|
217
|
+
field.create_appearances if field.respond_to?(:create_appearances)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
108
221
|
private
|
109
222
|
|
110
223
|
# Helper method for bit field getter access.
|
@@ -117,6 +230,43 @@ module HexaPDF
|
|
117
230
|
self[:SigFlags] = value
|
118
231
|
end
|
119
232
|
|
233
|
+
# Creates a new field with the full name +name+ and the field type +type+.
|
234
|
+
def create_field(name, type)
|
235
|
+
parent_name, _, name = name.rpartition('.')
|
236
|
+
parent_field = parent_name.empty? ? nil : field_by_name(parent_name)
|
237
|
+
if !parent_name.empty? && !parent_field
|
238
|
+
raise HexaPDF::Error, "Parent field '#{parent_name}' not found"
|
239
|
+
end
|
240
|
+
|
241
|
+
field = document.add({FT: type, T: name, Parent: parent_field},
|
242
|
+
type: :XXAcroFormField, subtype: type)
|
243
|
+
if parent_field
|
244
|
+
(parent_field[:Kids] ||= []) << field
|
245
|
+
else
|
246
|
+
(self[:Fields] ||= []) << field
|
247
|
+
end
|
248
|
+
field
|
249
|
+
end
|
250
|
+
|
251
|
+
def perform_validation # :nodoc:
|
252
|
+
if (da = self[:DA])
|
253
|
+
unless self[:DR]
|
254
|
+
yield("When the field /DA is present, the field /DR must also be present")
|
255
|
+
end
|
256
|
+
font_name = nil
|
257
|
+
HexaPDF::Content::Parser.parse(da) do |obj, params|
|
258
|
+
font_name = params[0] if obj == :Tf
|
259
|
+
end
|
260
|
+
if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
|
261
|
+
yield("The font specified in /DA is not in the /DR resource dictionary")
|
262
|
+
end
|
263
|
+
else
|
264
|
+
set_default_appearance_string
|
265
|
+
end
|
266
|
+
|
267
|
+
create_appearances if document.config['acro_form.create_appearances']
|
268
|
+
end
|
269
|
+
|
120
270
|
end
|
121
271
|
|
122
272
|
end
|
@@ -0,0 +1,186 @@
|
|
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/type/acro_form/variable_text_field'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
module Type
|
42
|
+
module AcroForm
|
43
|
+
|
44
|
+
# AcroForm text fields provide a box or space to fill-in data entered from keyboard. The text
|
45
|
+
# may be restricted to a single line or can span multiple lines.
|
46
|
+
#
|
47
|
+
# == Type Specific Field Flags
|
48
|
+
#
|
49
|
+
# :multiline:: If set, the text field may contain multiple lines.
|
50
|
+
#
|
51
|
+
# :password:: The field is a password field. This changes the behaviour of the PDF reader
|
52
|
+
# application to not echo the input text and to not store it in the PDF file.
|
53
|
+
#
|
54
|
+
# :file_select:: The text field represents a file selection control where the input text is
|
55
|
+
# the path to a file.
|
56
|
+
#
|
57
|
+
# :do_not_spell_check:: The text should not be spell-checked.
|
58
|
+
#
|
59
|
+
# :do_not_scroll:: The text field should not scroll (horizontally for single-line fields and
|
60
|
+
# vertically for multiline fields) to accomodate more text than fits into the
|
61
|
+
# annotation rectangle. This means that no more text can be entered once the
|
62
|
+
# field is full.
|
63
|
+
#
|
64
|
+
# :comb:: The field is divided into /MaxLen equally spaced positions (so /MaxLen needs to be
|
65
|
+
# set). This is useful, for example, when entering things like social security
|
66
|
+
# numbers which always have the same length.
|
67
|
+
#
|
68
|
+
# :rich_text:: The field is a rich text field.
|
69
|
+
#
|
70
|
+
# See: PDF1.7 s12.7.4.3
|
71
|
+
class TextField < VariableTextField
|
72
|
+
|
73
|
+
define_field :MaxLen, type: Integer
|
74
|
+
|
75
|
+
# All inheritable dictionary fields for text fields.
|
76
|
+
INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:MaxLen]).freeze
|
77
|
+
|
78
|
+
# Updated list of field flags.
|
79
|
+
FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
|
80
|
+
{
|
81
|
+
multiline: 12,
|
82
|
+
password: 13,
|
83
|
+
file_select: 20,
|
84
|
+
do_not_spell_check: 22,
|
85
|
+
do_not_scroll: 23,
|
86
|
+
comb: 24,
|
87
|
+
rich_text: 25,
|
88
|
+
}
|
89
|
+
).freeze
|
90
|
+
|
91
|
+
# Returns the field value, i.e. the text contents of the field, or +nil+ if no value is set.
|
92
|
+
#
|
93
|
+
# Note that modifying the returned value *might not* modify the text contents in case it is
|
94
|
+
# stored as stream! So always use #field_value= to set the field value.
|
95
|
+
def field_value
|
96
|
+
return unless value[:V]
|
97
|
+
self[:V].kind_of?(String) ? self[:V] : self[:V].stream
|
98
|
+
end
|
99
|
+
|
100
|
+
# Sets the field value, i.e. the text contents of the field, to the given string.
|
101
|
+
#
|
102
|
+
# Note that for single line text fields, all whitespace characters are changed to simple
|
103
|
+
# spaces.
|
104
|
+
def field_value=(str)
|
105
|
+
if flagged?(:password)
|
106
|
+
raise HexaPDF::Error, "Storing a field value for a password field is not allowed"
|
107
|
+
end
|
108
|
+
str = str.gsub(/[[:space:]]/, ' ') if concrete_field_type == :single_line_text_field
|
109
|
+
self[:V] = str
|
110
|
+
update_widgets
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns the default field value.
|
114
|
+
#
|
115
|
+
# See: #field_value
|
116
|
+
def default_field_value
|
117
|
+
self[:DV].kind_of?(String) ? self[:DV] : self[:DV].stream
|
118
|
+
end
|
119
|
+
|
120
|
+
# Sets the default field value.
|
121
|
+
#
|
122
|
+
# See: #field_value=
|
123
|
+
def default_field_value=(str)
|
124
|
+
self[:DV] = str
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns the concrete text field type, either :single_line_text_field,
|
128
|
+
# :multiline_text_field, :password_field, :file_select_field, :comb_text_field or
|
129
|
+
# :rich_text_field.
|
130
|
+
def concrete_field_type
|
131
|
+
if flagged?(:multiline)
|
132
|
+
:multiline_text_field
|
133
|
+
elsif flagged?(:password)
|
134
|
+
:password_field
|
135
|
+
elsif flagged?(:file_select)
|
136
|
+
:file_select_field
|
137
|
+
elsif flagged?(:comb)
|
138
|
+
:comb_text_field
|
139
|
+
elsif flagged?(:rich_text)
|
140
|
+
:rich_text_field
|
141
|
+
else
|
142
|
+
:single_line_text_field
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Creates appropriate appearances for all widgets.
|
147
|
+
#
|
148
|
+
# For information on how this is done see AppearanceGenerator.
|
149
|
+
#
|
150
|
+
# Note that an appearance for a text field widget is *always* created even if there is an
|
151
|
+
# existing one to make sure the current field value is properly represented.
|
152
|
+
def create_appearances
|
153
|
+
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
154
|
+
each_widget do |widget|
|
155
|
+
appearance_generator_class.new(widget).create_text_appearances
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Updates the widgets so that they reflect the current field value.
|
160
|
+
def update_widgets
|
161
|
+
create_appearances
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def perform_validation #:nodoc:
|
167
|
+
if field_type != :Tx
|
168
|
+
yield("Field /FT of AcroForm text field has to be :Tx", true)
|
169
|
+
self[:FT] = :Tx
|
170
|
+
end
|
171
|
+
|
172
|
+
super
|
173
|
+
|
174
|
+
if self[:V] && !(self[:V].kind_of?(String) || self[:V].kind_of?(HexaPDF::Stream))
|
175
|
+
yield("Text field doesn't contain text but #{self[:V].class} object")
|
176
|
+
end
|
177
|
+
if (max_len = self[:MaxLen]) && field_value.length > max_len
|
178
|
+
yield("Text contents of field '#{full_field_name}' is too long")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,122 @@
|
|
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/dictionary'
|
38
|
+
require 'hexapdf/stream'
|
39
|
+
require 'hexapdf/error'
|
40
|
+
require 'hexapdf/content/parser'
|
41
|
+
|
42
|
+
module HexaPDF
|
43
|
+
module Type
|
44
|
+
module AcroForm
|
45
|
+
|
46
|
+
# An AcroForm variable text field defines how text that it is not known at generation time
|
47
|
+
# should be rendered. For example, AcroForm text fields (normally) don't have an initial
|
48
|
+
# value; the value is entered by the user and needs to be rendered correctly by the PDF
|
49
|
+
# reader.
|
50
|
+
#
|
51
|
+
# See: PDF1.7 s12.7.3.3
|
52
|
+
class VariableTextField < Field
|
53
|
+
|
54
|
+
define_field :DA, type: String
|
55
|
+
define_field :Q, type: Integer, default: 0, allowed_values: [0, 1, 2]
|
56
|
+
define_field :DS, type: String, version: '1.5'
|
57
|
+
define_field :RV, type: [String, Stream], version: '1.5'
|
58
|
+
|
59
|
+
# All inheritable dictionary fields for text fields.
|
60
|
+
INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:DA, :Q]).freeze
|
61
|
+
|
62
|
+
UNSET_ARG = ::Object.new # :nodoc:
|
63
|
+
|
64
|
+
# :call-seq:
|
65
|
+
# field.text_alignment -> alignment
|
66
|
+
# field.text_alignment(alignment) -> field
|
67
|
+
#
|
68
|
+
# Sets or returns the text alignment that should be used when displaying text.
|
69
|
+
#
|
70
|
+
# With no argument, the current text alignment is returned. When a value is provided, the
|
71
|
+
# text alignment is set accordingly.
|
72
|
+
#
|
73
|
+
# The alignment value is one of :left, :center or :right.
|
74
|
+
def text_alignment(alignment = UNSET_ARG)
|
75
|
+
if alignment == UNSET_ARG
|
76
|
+
case self[:Q]
|
77
|
+
when 0 then :left
|
78
|
+
when 1 then :center
|
79
|
+
when 2 then :right
|
80
|
+
end
|
81
|
+
else
|
82
|
+
self[:Q] = case alignment
|
83
|
+
when :left then 0
|
84
|
+
when :center then 1
|
85
|
+
when :right then 2
|
86
|
+
else
|
87
|
+
raise ArgumentError, "Invalid variable text field alignment #{alignment}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Sets the default appearance string using the provided values.
|
93
|
+
#
|
94
|
+
# The default argument values are a sane default. If +font_size+ is set to 0, the font size
|
95
|
+
# is calculated using the height/width of the field.
|
96
|
+
def set_default_appearance_string(font: 'Helvetica', font_size: 0)
|
97
|
+
name = document.acro_form(create: true).default_resources.
|
98
|
+
add_font(document.fonts.add(font).pdf_object)
|
99
|
+
self[:DA] = "0 g /#{name} #{font_size} Tf"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Parses the default appearance string and returns an array containing [font_name,
|
103
|
+
# font_size].
|
104
|
+
#
|
105
|
+
# The default appearance string is taken from the field or, if not set, the default
|
106
|
+
# appearance string of the form.
|
107
|
+
def parse_default_appearance_string
|
108
|
+
da = self[:DA] || (document.acro_form && document.acro_form[:DA])
|
109
|
+
raise HexaPDF::Error, "No default appearance string set" unless da
|
110
|
+
|
111
|
+
font_params = nil
|
112
|
+
HexaPDF::Content::Parser.parse(da) do |obj, params|
|
113
|
+
font_params = params.dup if obj == :Tf
|
114
|
+
end
|
115
|
+
font_params
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|