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.
Files changed (248) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/LICENSE +1 -1
  4. data/examples/001-hello_world.rb +1 -1
  5. data/examples/002-graphics.rb +1 -1
  6. data/examples/003-arcs.rb +1 -1
  7. data/examples/004-optimizing.rb +1 -1
  8. data/examples/005-merging.rb +1 -1
  9. data/examples/006-standard_pdf_fonts.rb +1 -1
  10. data/examples/007-truetype.rb +1 -1
  11. data/examples/008-show_char_bboxes.rb +1 -1
  12. data/examples/009-text_layouter_alignment.rb +1 -1
  13. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  14. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  15. data/examples/012-text_layouter_styling.rb +1 -1
  16. data/examples/013-text_layouter_shapes.rb +1 -1
  17. data/examples/014-text_in_polygon.rb +1 -1
  18. data/examples/015-boxes.rb +1 -1
  19. data/examples/016-frame_automatic_box_placement.rb +1 -1
  20. data/examples/017-frame_text_flow.rb +1 -1
  21. data/examples/018-composer.rb +1 -1
  22. data/examples/019-acro_form.rb +51 -0
  23. data/lib/hexapdf.rb +1 -1
  24. data/lib/hexapdf/cli.rb +3 -1
  25. data/lib/hexapdf/cli/batch.rb +1 -1
  26. data/lib/hexapdf/cli/command.rb +18 -9
  27. data/lib/hexapdf/cli/files.rb +1 -1
  28. data/lib/hexapdf/cli/form.rb +240 -0
  29. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +1 -1
  32. data/lib/hexapdf/cli/inspect.rb +1 -1
  33. data/lib/hexapdf/cli/merge.rb +1 -1
  34. data/lib/hexapdf/cli/modify.rb +1 -1
  35. data/lib/hexapdf/cli/optimize.rb +1 -1
  36. data/lib/hexapdf/cli/split.rb +1 -1
  37. data/lib/hexapdf/cli/watermark.rb +1 -1
  38. data/lib/hexapdf/composer.rb +2 -2
  39. data/lib/hexapdf/configuration.rb +66 -11
  40. data/lib/hexapdf/content.rb +3 -1
  41. data/lib/hexapdf/content/canvas.rb +5 -18
  42. data/lib/hexapdf/content/color_space.rb +111 -32
  43. data/lib/hexapdf/content/graphic_object.rb +1 -1
  44. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  45. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  46. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  47. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  48. data/lib/hexapdf/content/graphics_state.rb +1 -1
  49. data/lib/hexapdf/content/operator.rb +9 -9
  50. data/lib/hexapdf/content/parser.rb +18 -5
  51. data/lib/hexapdf/content/processor.rb +1 -1
  52. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  53. data/lib/hexapdf/data_dir.rb +1 -1
  54. data/lib/hexapdf/dictionary.rb +1 -1
  55. data/lib/hexapdf/dictionary_fields.rb +1 -1
  56. data/lib/hexapdf/document.rb +14 -5
  57. data/lib/hexapdf/document/files.rb +1 -1
  58. data/lib/hexapdf/document/fonts.rb +1 -1
  59. data/lib/hexapdf/document/images.rb +1 -1
  60. data/lib/hexapdf/document/pages.rb +3 -14
  61. data/lib/hexapdf/encryption.rb +1 -1
  62. data/lib/hexapdf/encryption/aes.rb +1 -1
  63. data/lib/hexapdf/encryption/arc4.rb +1 -1
  64. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  65. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  66. data/lib/hexapdf/encryption/identity.rb +1 -1
  67. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  68. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  69. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  71. data/lib/hexapdf/error.rb +1 -1
  72. data/lib/hexapdf/filter.rb +3 -3
  73. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  74. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  75. data/lib/hexapdf/filter/encryption.rb +1 -1
  76. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  77. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  78. data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
  79. data/lib/hexapdf/filter/predictor.rb +1 -1
  80. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  81. data/lib/hexapdf/font/cmap.rb +1 -1
  82. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  83. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  84. data/lib/hexapdf/font/encoding.rb +1 -1
  85. data/lib/hexapdf/font/encoding/base.rb +1 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  87. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  88. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  89. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  90. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  91. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  92. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  93. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  94. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  95. data/lib/hexapdf/font/true_type.rb +1 -1
  96. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  97. data/lib/hexapdf/font/true_type/font.rb +1 -1
  98. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  99. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  103. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  104. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  105. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  106. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  107. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  108. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  109. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  113. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  114. data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
  115. data/lib/hexapdf/font/type1.rb +1 -1
  116. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  117. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  118. data/lib/hexapdf/font/type1/font.rb +1 -1
  119. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  120. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  121. data/lib/hexapdf/font/type1_wrapper.rb +67 -51
  122. data/lib/hexapdf/font_loader.rb +1 -1
  123. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  124. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  125. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  126. data/lib/hexapdf/image_loader.rb +1 -1
  127. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  128. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  129. data/lib/hexapdf/image_loader/png.rb +1 -1
  130. data/lib/hexapdf/importer.rb +2 -4
  131. data/lib/hexapdf/layout.rb +1 -1
  132. data/lib/hexapdf/layout/box.rb +1 -1
  133. data/lib/hexapdf/layout/frame.rb +1 -1
  134. data/lib/hexapdf/layout/image_box.rb +1 -1
  135. data/lib/hexapdf/layout/inline_box.rb +1 -1
  136. data/lib/hexapdf/layout/line.rb +1 -1
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +1 -1
  139. data/lib/hexapdf/layout/text_box.rb +1 -1
  140. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  141. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  142. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  143. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  144. data/lib/hexapdf/name_tree_node.rb +1 -1
  145. data/lib/hexapdf/number_tree_node.rb +1 -1
  146. data/lib/hexapdf/object.rb +2 -2
  147. data/lib/hexapdf/parser.rb +4 -3
  148. data/lib/hexapdf/pdf_array.rb +1 -1
  149. data/lib/hexapdf/rectangle.rb +31 -1
  150. data/lib/hexapdf/reference.rb +1 -1
  151. data/lib/hexapdf/revision.rb +2 -1
  152. data/lib/hexapdf/revisions.rb +1 -1
  153. data/lib/hexapdf/serializer.rb +1 -1
  154. data/lib/hexapdf/stream.rb +1 -1
  155. data/lib/hexapdf/task.rb +1 -1
  156. data/lib/hexapdf/task/dereference.rb +1 -1
  157. data/lib/hexapdf/task/optimize.rb +1 -1
  158. data/lib/hexapdf/tokenizer.rb +1 -1
  159. data/lib/hexapdf/type.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +7 -1
  161. data/lib/hexapdf/type/acro_form/appearance_generator.rb +401 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +220 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +157 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +186 -0
  167. data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
  168. data/lib/hexapdf/type/action.rb +1 -1
  169. data/lib/hexapdf/type/actions.rb +1 -1
  170. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  171. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  172. data/lib/hexapdf/type/actions/launch.rb +1 -1
  173. data/lib/hexapdf/type/actions/uri.rb +1 -1
  174. data/lib/hexapdf/type/annotation.rb +73 -3
  175. data/lib/hexapdf/type/annotations.rb +1 -1
  176. data/lib/hexapdf/type/annotations/link.rb +2 -2
  177. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  178. data/lib/hexapdf/type/annotations/text.rb +1 -1
  179. data/lib/hexapdf/type/annotations/widget.rb +239 -2
  180. data/lib/hexapdf/type/catalog.rb +21 -1
  181. data/lib/hexapdf/type/cid_font.rb +1 -1
  182. data/lib/hexapdf/type/embedded_file.rb +1 -1
  183. data/lib/hexapdf/type/file_specification.rb +1 -1
  184. data/lib/hexapdf/type/font.rb +18 -1
  185. data/lib/hexapdf/type/font_descriptor.rb +2 -2
  186. data/lib/hexapdf/type/font_simple.rb +1 -1
  187. data/lib/hexapdf/type/font_true_type.rb +1 -1
  188. data/lib/hexapdf/type/font_type0.rb +1 -1
  189. data/lib/hexapdf/type/font_type1.rb +16 -1
  190. data/lib/hexapdf/type/font_type3.rb +1 -1
  191. data/lib/hexapdf/type/form.rb +1 -1
  192. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  193. data/lib/hexapdf/type/icon_fit.rb +1 -1
  194. data/lib/hexapdf/type/image.rb +3 -1
  195. data/lib/hexapdf/type/info.rb +1 -1
  196. data/lib/hexapdf/type/names.rb +1 -1
  197. data/lib/hexapdf/type/object_stream.rb +1 -1
  198. data/lib/hexapdf/type/page.rb +1 -1
  199. data/lib/hexapdf/type/page_tree_node.rb +8 -11
  200. data/lib/hexapdf/type/resources.rb +16 -3
  201. data/lib/hexapdf/type/trailer.rb +2 -3
  202. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  203. data/lib/hexapdf/type/xref_stream.rb +1 -1
  204. data/lib/hexapdf/utils/bit_field.rb +38 -24
  205. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  206. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  208. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  209. data/lib/hexapdf/utils/object_hash.rb +1 -1
  210. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  211. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  212. data/lib/hexapdf/version.rb +2 -2
  213. data/lib/hexapdf/writer.rb +1 -1
  214. data/lib/hexapdf/xref_section.rb +1 -1
  215. data/test/hexapdf/content/common.rb +2 -2
  216. data/test/hexapdf/content/test_color_space.rb +71 -8
  217. data/test/hexapdf/content/test_operator.rb +22 -22
  218. data/test/hexapdf/content/test_parser.rb +14 -0
  219. data/test/hexapdf/document/test_fonts.rb +1 -1
  220. data/test/hexapdf/document/test_pages.rb +6 -6
  221. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  222. data/test/hexapdf/font/test_type1_wrapper.rb +32 -8
  223. data/test/hexapdf/test_document.rb +12 -0
  224. data/test/hexapdf/test_parser.rb +10 -0
  225. data/test/hexapdf/test_rectangle.rb +14 -0
  226. data/test/hexapdf/test_revision.rb +3 -0
  227. data/test/hexapdf/test_writer.rb +2 -2
  228. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
  229. data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
  230. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  231. data/test/hexapdf/type/acro_form/test_field.rb +124 -6
  232. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  233. data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
  234. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  235. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  236. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  237. data/test/hexapdf/type/test_annotation.rb +45 -0
  238. data/test/hexapdf/type/test_catalog.rb +18 -0
  239. data/test/hexapdf/type/test_font.rb +5 -0
  240. data/test/hexapdf/type/test_font_type1.rb +8 -0
  241. data/test/hexapdf/type/test_image.rb +7 -0
  242. data/test/hexapdf/type/test_page_tree_node.rb +20 -12
  243. data/test/hexapdf/type/test_resources.rb +20 -0
  244. data/test/hexapdf/type/test_trailer.rb +4 -0
  245. data/test/hexapdf/utils/test_bit_field.rb +13 -1
  246. data/test/test_helper.rb +1 -1
  247. metadata +37 -18
  248. data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -0,0 +1,220 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/acro_form/variable_text_field'
38
+ require 'hexapdf/type/acro_form/appearance_generator'
39
+
40
+ module HexaPDF
41
+ module Type
42
+ module AcroForm
43
+
44
+ # AcroForm choice fields contain multiple text items of which one (or, if so flagged, more)
45
+ # may be selected.
46
+ #
47
+ # They are divided into scrollable list boxes and combo boxes. To create a list or combo box,
48
+ # use the appropriate convenience methods on the main Form instance
49
+ # (HexaPDF::Document#acro_form). By using those methods, everything needed is automatically
50
+ # set up.
51
+ #
52
+ # == Type Specific Field Flags
53
+ #
54
+ # :combo:: If set, the field represents a comb box.
55
+ #
56
+ # :edit:: If set, the combo box includes an editable text box for entering arbitrary values.
57
+ # Therefore the 'combo' flag also needs to be set.
58
+ #
59
+ # :sort:: The option items have to be sorted alphabetically. This flag is intended for PDF
60
+ # writers, not readers which should display the items in the order they appear.
61
+ #
62
+ # :multi_select:: If set, more than one item may be selected.
63
+ #
64
+ # :do_not_spell_check:: The text should not be spell-checked.
65
+ #
66
+ # :commit_on_sel_change:: If set, a new value should be commited as soon as a selection is
67
+ # made.
68
+ #
69
+ # See: PDF1.7 s12.7.4.4
70
+ class ChoiceField < VariableTextField
71
+
72
+ define_field :Opt, type: PDFArray
73
+ define_field :TI, type: Integer, default: 0
74
+ define_field :I, type: PDFArray, version: '1.4'
75
+
76
+ # Updated list of field flags.
77
+ FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
78
+ {
79
+ combo: 17,
80
+ edit: 18,
81
+ sort: 19,
82
+ multi_select: 21,
83
+ do_not_spell_check: 22,
84
+ commit_on_sel_change: 26,
85
+ }
86
+ ).freeze
87
+
88
+ # Initializes the choice field to be a list box.
89
+ #
90
+ # This method should only be called directly after creating a new choice field because it
91
+ # doesn't completely reset the object.
92
+ def initialize_as_list_box
93
+ self[:V] = nil
94
+ unflag(:combo)
95
+ end
96
+
97
+ # Initializes the button field to be a combo box.
98
+ #
99
+ # This method should only be called directly after creating a new choice field because it
100
+ # doesn't completely reset the object.
101
+ def initialize_as_combo_box
102
+ self[:V] = nil
103
+ flag(:combo)
104
+ end
105
+
106
+ # Returns +true+ if this choice field represents a list box.
107
+ def list_box?
108
+ !combo_box?
109
+ end
110
+
111
+ # Returns +true+ if this choice field represents a combo box.
112
+ def combo_box?
113
+ flagged?(:combo)
114
+ end
115
+
116
+ # Returns the field value which represents the currently selected item(s).
117
+ #
118
+ # If no item is selected, +nil+ is returned. If multiple values are selected, the return
119
+ # value is an array of strings, otherwise it is just a string.
120
+ def field_value
121
+ process_value(self[:V])
122
+ end
123
+
124
+ # Sets the field value to the given string or array of strings.
125
+ def field_value=(value)
126
+ items = option_items
127
+ all_included = [value].flatten.all? {|v| items.include?(v) }
128
+ self[:V] = if (combo_box? && value.kind_of?(String) &&
129
+ (flagged?(:edit) || all_included)) ||
130
+ (list_box? && all_included &&
131
+ (value.kind_of?(String) || flagged?(:multi_select)))
132
+ value
133
+ else
134
+ @document.config['acro_form.on_invalid_value'].call(self, value)
135
+ end
136
+ end
137
+
138
+ # Returns the default field value.
139
+ #
140
+ # See: #field_value
141
+ def default_field_value
142
+ process_value(self[:DV])
143
+ end
144
+
145
+ # Sets the default field value.
146
+ #
147
+ # See: #field_value=
148
+ def default_field_value=(value)
149
+ items = option_items
150
+ self[:DV] = if [value].flatten.all? {|v| items.include?(v) }
151
+ value
152
+ else
153
+ @document.config['acro_form.on_invalid_value'].call(self, value)
154
+ end
155
+ end
156
+
157
+ # Returns the array with the available option items.
158
+ def option_items
159
+ key?(:Opt) ? process_value(self[:Opt]) : self[:Opt] ||= []
160
+ end
161
+
162
+ # Sets the array with the available option items to the given value.
163
+ def option_items=(value)
164
+ self[:Opt] = value
165
+ end
166
+
167
+ # Returns the concrete choice field type, either :list_box, :combo_box or
168
+ # :editable_combo_box.
169
+ def concrete_field_type
170
+ if combo_box?
171
+ flagged?(:edit) ? :editable_combo_box : :combo_box
172
+ else
173
+ :list_box
174
+ end
175
+ end
176
+
177
+ # Creates appropriate appearances for all widgets if they don't already exist.
178
+ #
179
+ # For information on how this is done see AppearanceGenerator.
180
+ #
181
+ # Note that an appearance for a choice field widget is *always* created even if there is an
182
+ # existing one to make sure the current field value is properly represented.
183
+ def create_appearances
184
+ appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
185
+ each_widget do |widget|
186
+ if combo_box?
187
+ appearance_generator_class.new(widget).create_combo_box_appearances
188
+ else
189
+ raise HexaPDF::Error, "List boxes not yet supported"
190
+ end
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ # Uses the HexaPDF::DictionaryFields::StringConverter to process the value (a string or an
197
+ # array of strings) so that it contains only normalized strings.
198
+ def process_value(value)
199
+ value = value.value if value.kind_of?(PDFArray)
200
+ if value.kind_of?(Array)
201
+ value.map! {|item| DictionaryFields::StringConverter.convert(item, nil, nil) || item }
202
+ else
203
+ DictionaryFields::StringConverter.convert(value, nil, nil) || value
204
+ end
205
+ end
206
+
207
+ def perform_validation #:nodoc:
208
+ if field_type != :Ch
209
+ yield("Field /FT of AcroForm choie field has to be :Ch", true)
210
+ self[:FT] = :Ch
211
+ end
212
+
213
+ super
214
+ end
215
+
216
+ end
217
+
218
+ end
219
+ end
220
+ end
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -35,24 +35,75 @@
35
35
  #++
36
36
 
37
37
  require 'hexapdf/dictionary'
38
+ require 'hexapdf/error'
39
+ require 'hexapdf/utils/bit_field'
40
+ require 'hexapdf/type/annotations'
38
41
 
39
42
  module HexaPDF
40
43
  module Type
41
44
  module AcroForm
42
45
 
43
- # Field dictionaries are used to define the properties of form fields of AcroForm objects.
46
+ # AcroForm field dictionaries are used to define the properties of form fields of AcroForm
47
+ # objects.
44
48
  #
45
49
  # Fields can be organized in a hierarchy using the /Kids and /Parent keys, for namespacing
46
50
  # purposes and to set default values. Those fields that have other fields as children are
47
51
  # called non-terminal fields, otherwise they are called terminal fields.
48
52
  #
53
+ # While field objects can be created manually, it is best to use the various +create_+ methods
54
+ # of the main Form object to create them so that all necessary things are set up correctly.
55
+ #
56
+ # == Field Types
57
+ #
58
+ # Subclasses are used to implement the specific AcroForm field types:
59
+ #
60
+ # * ButtonField implements the button fields (pushbuttons, check boxes and radio buttons)
61
+ # * TextField implements single or multiline text fields.
62
+ # * ChoiceField implements scrollable list boxes or (editable) combo boxes.
63
+ # * SignatureField implements signature fields.
64
+ #
65
+ # == Field Flags
66
+ #
67
+ # Various characteristics of a field can be changed by setting a certain flag. Some flags are
68
+ # defined for all types of field, some are specific to a certain type.
69
+ #
70
+ # The following flags apply to all fields:
71
+ #
72
+ # :read_only:: The field is read only which means the user can't change the value or interact
73
+ # with associated widget annotations.
74
+ #
75
+ # :required:: The field is required if the form is exported by a submit-form action.
76
+ #
77
+ # :no_export:: The field should *not* be exported by a submit-form action.
78
+ #
79
+ # == Field Type Implementation Notes
80
+ #
81
+ # If an AcroForm field type adds additional inheritable dictionary fields, it has to set the
82
+ # constant +INHERITABLE_FIELDS+ to all inheritable dictionary fields, including those from the
83
+ # superclass.
84
+ #
85
+ # Similarily, if additional flags are provided, the constant +FLAGS_BIT_MAPPING+ has to be set
86
+ # to combination of the superclass value of the constant and the mapping of flag names to bit
87
+ # indices.
88
+ #
49
89
  # See: PDF1.7 s12.7.3.1
50
90
  class Field < Dictionary
51
91
 
52
- define_type :XXAcroFormField
92
+ # Provides a #value method for hash that returns self so that a Hash can be used
93
+ # interchangably with a HexaPDF::Dictionary.
94
+ module HashRefinement
95
+ refine Hash do
96
+ def value
97
+ self
98
+ end
99
+ end
100
+ end
101
+
102
+ using HashRefinement
53
103
 
54
- # List of inheritable fields.
55
- INHERITABLE_FIELDS = [:FT, :Ff, :V, :DV]
104
+ extend Utils::BitField
105
+
106
+ define_type :XXAcroFormField
56
107
 
57
108
  define_field :FT, type: Symbol, allowed_values: [:Btn, :Tx, :Ch, :Sig]
58
109
  define_field :Parent, type: :XXAcroFormField
@@ -61,10 +112,49 @@ module HexaPDF
61
112
  define_field :TU, type: String, version: '1.3'
62
113
  define_field :TM, type: String, version: '1.3'
63
114
  define_field :Ff, type: Integer, default: 0
64
- define_field :V, type: [Symbol, String, Stream, PDFArray, Dictionary]
65
- define_field :DV, type: [Symbol, String, Stream, PDFArray, Dictionary]
115
+ define_field :V, type: [Dictionary, Symbol, String, Stream, PDFArray]
116
+ define_field :DV, type: [Dictionary, Symbol, String, Stream, PDFArray]
66
117
  define_field :AA, type: Dictionary, version: '1.2'
67
118
 
119
+ # The inheritable dictionary fields common to all AcroForm field types.
120
+ INHERITABLE_FIELDS = [:FT, :Ff, :V, :DV].freeze
121
+
122
+ ##
123
+ # :method: flags
124
+ #
125
+ # Returns an array of flag names representing the set bit flags.
126
+ #
127
+
128
+ ##
129
+ # :method: flagged?
130
+ # :call-seq:
131
+ # flagged?(flag)
132
+ #
133
+ # Returns +true+ if the given flag is set. The argument can either be the flag name or the
134
+ # bit index.
135
+ #
136
+
137
+ ##
138
+ # :method: flag
139
+ # :call-seq:
140
+ # flag(*flags, clear_existing: false)
141
+ #
142
+ # Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
143
+ # all prior flags will be cleared.
144
+ #
145
+ bit_field(:flags, {read_only: 0, required: 1, no_export: 2},
146
+ lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
147
+ value_getter: "self[:Ff]", value_setter: "self[:Ff]")
148
+
149
+ # Treats +name+ as an inheritable dictionary field and resolves its value for the AcroForm
150
+ # field +field+.
151
+ def self.inherited_value(field, name)
152
+ while field.value[name].nil? && (parent = field[:Parent])
153
+ field = parent
154
+ end
155
+ field.value[name].nil? ? nil : field[name]
156
+ end
157
+
68
158
  # Form fields must always be indirect objects.
69
159
  def must_be_indirect?
70
160
  true
@@ -72,15 +162,13 @@ module HexaPDF
72
162
 
73
163
  # Returns the value for the entry +name+.
74
164
  #
75
- # If +name+ is an inheritable value and the value has not been set on this field object, its
165
+ # If +name+ is an inheritable field and the value has not been set on this field object, its
76
166
  # value is retrieved from the parent fields.
77
167
  #
78
168
  # See: Dictionary#[]
79
169
  def [](name)
80
- if value[name].nil? && INHERITABLE_FIELDS.include?(name)
81
- field = self
82
- field = field[:Parent] while field.value[name].nil? && field[:Parent]
83
- field == self || field.value[name].nil? ? super : field[name]
170
+ if value[name].nil? && self.class::INHERITABLE_FIELDS.include?(name)
171
+ self.class.inherited_value(self, name) || super
84
172
  else
85
173
  super
86
174
  end
@@ -88,30 +176,145 @@ module HexaPDF
88
176
 
89
177
  # Returns the type of the field, either :Btn (pushbuttons, check boxes, radio buttons), :Tx
90
178
  # (text fields), :Ch (scrollable list boxes, combo boxes) or :Sig (signature fields).
179
+ #
180
+ # Also see #concrete_field_type
91
181
  def field_type
92
182
  self[:FT]
93
183
  end
94
184
 
185
+ # Returns the concrete field type (:button_field, :text_field, :choice_field or
186
+ # :signature_field) or +nil+ is no field type is set.
187
+ #
188
+ # In constrast to #field_type this method also considers the field flags and not just the
189
+ # field type. This means that subclasses can return a more concrete name for the field type.
190
+ #
191
+ # Also see #field_type
192
+ def concrete_field_type
193
+ case self[:FT]
194
+ when :Btn then :button_field
195
+ when :Tx then :text_field
196
+ when :Ch then :choice_field
197
+ when :Sig then :signature_field
198
+ else nil
199
+ end
200
+ end
201
+
202
+ # Returns the name of the field or +nil+ if no name is set.
203
+ def field_name
204
+ self[:T]
205
+ end
206
+
95
207
  # Returns the full name of the field or +nil+ if no name is set.
96
208
  #
97
209
  # The full name of a field is constructed using the full name of the parent field, a period
98
- # and the partial name of the field.
99
- def full_name
210
+ # and the field name of the field.
211
+ def full_field_name
100
212
  if key?(:Parent)
101
- [self[:Parent].full_name, self[:T]].compact.join('.')
213
+ [self[:Parent].full_field_name, field_name].compact.join('.')
102
214
  else
103
- self[:T]
215
+ field_name
104
216
  end
105
217
  end
106
218
 
219
+ # Returns the alternate field name that should be used for display purposes (e.g. Acrobat
220
+ # shows this as tool tip).
221
+ def alternate_field_name
222
+ self[:TU]
223
+ end
224
+
225
+ # Sets the alternate field name.
226
+ #
227
+ # See #alternate_field_name
228
+ def alternate_field_name=(value)
229
+ self[:TU] = value
230
+ end
231
+
107
232
  # Returns +true+ if this is a terminal field.
108
233
  def terminal_field?
109
234
  kids = self[:Kids]
110
- kids.nil? || kids.empty? || kids.any? {|kid| kid[:Subtype] == :Widget }
235
+ # PDF 2.0 s12.7.4.2 clarifies how to do check for fields since PDF 1.7 isn't clear
236
+ kids.nil? || kids.empty? || kids.none? {|kid| kid.key?(:T) }
237
+ end
238
+
239
+ # :call-seq:
240
+ # field.each_widget {|widget| block} -> field
241
+ # field.each_widget -> Enumerator
242
+ #
243
+ # Yields each widget, i.e. visual representation, of this field.
244
+ #
245
+ # See: HexaPDF::Type::Annotations::Widget
246
+ def each_widget # :yields: widget
247
+ return to_enum(__method__) unless block_given?
248
+ if self[:Subtype]
249
+ yield(document.wrap(self))
250
+ elsif terminal_field?
251
+ self[:Kids]&.each {|kid| yield(document.wrap(kid)) }
252
+ end
253
+ self
254
+ end
255
+
256
+ # Creates a new widget annotation for this form field (must be a terminal field!) on the
257
+ # given +page+, adding the +values+ to the created widget annotation oject.
258
+ #
259
+ # If +allow_embedded+ is +false+, embedding the first widget in the field itself is not
260
+ # allowed.
261
+ #
262
+ # The +values+ argument should at least include :Rect for setting the visible area of the
263
+ # widget.
264
+ #
265
+ # If the field already has an embedded widget, i.e. field and widget are the same PDF
266
+ # object, its widget data is extracted to a new PDF object and stored in the /Kids field,
267
+ # together with the new widget annotation. Note that this means that a possible reference to
268
+ # the formerly embedded widget (=this field) is not valid anymore!
269
+ #
270
+ # See: HexaPDF::Type::Annotations::Widget
271
+ def create_widget(page, allow_embedded: true, **values)
272
+ unless terminal_field?
273
+ raise HexaPDF::Error, "Widgets can only be added to terminal fields"
274
+ end
275
+
276
+ widget_data = {Type: :Annot, Subtype: :Widget, Rect: [0, 0, 0, 0], **values}
277
+
278
+ if !allow_embedded || key?(:Subtype) || (key?(:Kids) && !self[:Kids].empty?)
279
+ kids = self[:Kids] ||= []
280
+ kids << extract_widget if key?(:Subtype)
281
+ widget = document.add(widget_data)
282
+ widget[:Parent] = self
283
+ self[:Kids] << widget
284
+ else
285
+ value.update(widget_data)
286
+ widget = document.wrap(self)
287
+ end
288
+
289
+ (page[:Annots] ||= []) << widget
290
+
291
+ widget
111
292
  end
112
293
 
113
294
  private
114
295
 
296
+ # An array of all widget annotation field names.
297
+ WIDGET_FIELDS = HexaPDF::Type::Annotations::Widget.each_field.map(&:first).uniq - [:Parent]
298
+
299
+ # Returns a new dictionary object with all the widget annotation data that is stored
300
+ # directly in the field and adjust the references accordingly. If the field doesn't have any
301
+ # widget data, +nil+ is returned.
302
+ def extract_widget
303
+ return unless key?(:Subtype)
304
+ data = WIDGET_FIELDS.each_with_object({}) do |key, hash|
305
+ hash[key] = delete(key) if key?(key)
306
+ end
307
+ widget = document.add(data, type: :Annot)
308
+ widget[:Parent] = self
309
+ document.pages.each do |page|
310
+ if page.key?(:Annots) && (index = page[:Annots].index {|annot| annot.data == self.data })
311
+ page[:Annots][index] = widget
312
+ break # Each annotation dictionary may only appear on one page, see PDF1.7 12.5.2
313
+ end
314
+ end
315
+ widget
316
+ end
317
+
115
318
  def perform_validation #:nodoc:
116
319
  super
117
320
  if terminal_field? && field_type.nil?