hexapdf 0.11.7 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -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 +7 -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 +9 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +7 -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 +68 -52
  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 +2 -2
  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 +10 -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 +20 -5
  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/encryption/test_security_handler.rb +4 -0
  222. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  223. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  224. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  225. data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
  226. data/test/hexapdf/layout/test_style.rb +1 -1
  227. data/test/hexapdf/test_document.rb +12 -0
  228. data/test/hexapdf/test_parser.rb +10 -0
  229. data/test/hexapdf/test_rectangle.rb +14 -0
  230. data/test/hexapdf/test_revision.rb +3 -0
  231. data/test/hexapdf/test_serializer.rb +3 -3
  232. data/test/hexapdf/test_writer.rb +2 -2
  233. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
  234. data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
  235. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  236. data/test/hexapdf/type/acro_form/test_field.rb +124 -6
  237. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  238. data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
  239. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  240. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  241. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  242. data/test/hexapdf/type/test_annotation.rb +45 -0
  243. data/test/hexapdf/type/test_catalog.rb +18 -0
  244. data/test/hexapdf/type/test_font.rb +5 -0
  245. data/test/hexapdf/type/test_font_type1.rb +8 -0
  246. data/test/hexapdf/type/test_form.rb +18 -0
  247. data/test/hexapdf/type/test_image.rb +7 -0
  248. data/test/hexapdf/type/test_page.rb +37 -6
  249. data/test/hexapdf/type/test_page_tree_node.rb +20 -12
  250. data/test/hexapdf/type/test_resources.rb +20 -0
  251. data/test/hexapdf/type/test_trailer.rb +4 -0
  252. data/test/hexapdf/utils/test_bit_field.rb +13 -1
  253. data/test/test_helper.rb +1 -1
  254. metadata +38 -18
  255. data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -0,0 +1,300 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/acro_form/field'
38
+ require 'hexapdf/type/acro_form/appearance_generator'
39
+
40
+ module HexaPDF
41
+ module Type
42
+ module AcroForm
43
+
44
+ # AcroForm button fields represent interactive controls to be used with the mouse.
45
+ #
46
+ # They are divided into push buttons (things to click on), check boxes and radio buttons. All
47
+ # of these are represented with this class.
48
+ #
49
+ # To create a push button, check box or radio button field, use the appropriate convenience
50
+ # methods on the main Form instance (HexaPDF::Document#acro_form). By using those methods,
51
+ # everything needed is automatically set up.
52
+ #
53
+ # == Type Specific Field Flags
54
+ #
55
+ # :no_toggle_to_off:: Only used with radio buttons fields. If this flag is set, one button
56
+ # needs to be selected at all times. Otherwise, clicking on the selected
57
+ # button deselects it.
58
+ #
59
+ # :radio:: If this flag is set, the field is a set of radio buttons. Otherwise it is a check
60
+ # box. Additionally, the :pushbutton flag needs to be clear.
61
+ #
62
+ # :push_button:: The field represents a pushbutton without a permanent value.
63
+ #
64
+ # :radios_in_unison:: A group of radio buttons with the same value for the on state will turn
65
+ # on or off in unison.
66
+ #
67
+ # See: PDF1.7 s12.7.4.2
68
+ class ButtonField < Field
69
+
70
+ define_field :Opt, type: PDFArray, version: '1.4'
71
+
72
+ # All inheritable dictionary fields for button fields.
73
+ INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:Opt]).freeze
74
+
75
+ # Updated list of field flags.
76
+ FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
77
+ {
78
+ no_toggle_to_off: 14,
79
+ radio: 15,
80
+ push_button: 16,
81
+ radios_in_unison: 25,
82
+ }
83
+ ).freeze
84
+
85
+ # Initializes the button field to be a push button.
86
+ #
87
+ # This method should only be called directly after creating a new button field because it
88
+ # doesn't completely reset the object.
89
+ def initialize_as_push_button
90
+ self[:V] = nil
91
+ flag(:push_button)
92
+ unflag(:radio)
93
+ end
94
+
95
+ # Initializes the button field to be a check box.
96
+ #
97
+ # This method should only be called directly after creating a new button field because it
98
+ # doesn't completely reset the object.
99
+ def initialize_as_check_box
100
+ self[:V] = :Off
101
+ unflag(:push_button)
102
+ unflag(:radio)
103
+ end
104
+
105
+ # Initializes the button field to be a radio button.
106
+ #
107
+ # This method should only be called directly after creating a new button field because it
108
+ # doesn't completely reset the object.
109
+ def initialize_as_radio_button
110
+ self[:V] = :Off
111
+ unflag(:push_button)
112
+ flag(:radio)
113
+ end
114
+
115
+ # Returns +true+ if this button field represents a push button.
116
+ def push_button?
117
+ flagged?(:push_button)
118
+ end
119
+
120
+ # Returns +true+ if this button field represents a check box.
121
+ def check_box?
122
+ !push_button? && !flagged?(:radio)
123
+ end
124
+
125
+ # Returns +true+ if this button field represents a radio button set.
126
+ def radio_button?
127
+ !push_button? && flagged?(:radio)
128
+ end
129
+
130
+ # Returns the field value which depends on the concrete type.
131
+ #
132
+ # Push buttons:: They don't have a value, so +nil+ is always returned.
133
+ #
134
+ # Check boxes:: For check boxes that are in the on state the value +true+ is returned.
135
+ # Otherwise +false+ is returned.
136
+ #
137
+ # Radio buttons:: If no radio button is selected, +nil+ is returned. Otherwise the name of
138
+ # the specific radio button that is selected is returned.
139
+ def field_value
140
+ normalized_field_value(:V)
141
+ end
142
+
143
+ # Sets the field value which depends on the concrete type.
144
+ #
145
+ # Push buttons:: Since push buttons don't store any value, the given value is ignored and
146
+ # nothing is stored for them (e.g a no-op).
147
+ #
148
+ # Check boxes:: Use +true+ for checking the box, i.e. toggling it to the on state, and
149
+ # +false+ for unchecking it.
150
+ #
151
+ # Radio buttons:: To turn all radio buttons off, provide +nil+ as value. Otherwise provide
152
+ # the name of a radio button that should be turned on.
153
+ def field_value=(value)
154
+ normalized_field_value_set(:V, value)
155
+ end
156
+
157
+ # Returns the default field value.
158
+ #
159
+ # See: #field_value
160
+ def default_field_value
161
+ normalized_field_value(:DV)
162
+ end
163
+
164
+ # Sets the default field value.
165
+ #
166
+ # See: #field_value=
167
+ def default_field_value=(value)
168
+ normalized_field_value_set(:DV, value)
169
+ end
170
+
171
+ # Returns the concrete button field type, either :push_button, :check_box or :radio_button.
172
+ def concrete_field_type
173
+ if push_button?
174
+ :push_button
175
+ elsif radio_button?
176
+ :radio_button
177
+ else
178
+ :check_box
179
+ end
180
+ end
181
+
182
+ # Returns the name used for setting the check box to the on state.
183
+ #
184
+ # Defaults to :Yes if no other name could be determined.
185
+ def check_box_on_name
186
+ each_widget.to_a.first&.appearance&.normal_appearance&.value&.each_key&.
187
+ find {|key| key != :Off } || :Yes
188
+ end
189
+
190
+ # Returns the array of values that can be used for the field value of the radio button.
191
+ def radio_button_values
192
+ each_widget.map do |widget|
193
+ widget.appearance&.normal_appearance&.value&.each_key&.find {|key| key != :Off }
194
+ end.compact
195
+ end
196
+
197
+ # Creates a widget for the button field.
198
+ #
199
+ # If +defaults+ is +true+, then default values will be set on the widget so that it uses a
200
+ # default appearance.
201
+ #
202
+ # If the widget is created for a radio button field, the +value+ argument needs to set to
203
+ # the value (a symbol) this widget represents. It can be used with #field_value= to set this
204
+ # specific widget of the radio button set to on.
205
+ #
206
+ # See: Field#create_widget, AppearanceGenerator button field methods
207
+ def create_widget(page, defaults: true, value: nil, **values)
208
+ super(page, allow_embedded: !radio_button?, **values).tap do |widget|
209
+ if check_box?
210
+ widget[:AP] = {N: {Yes: nil, Off: nil}}
211
+ elsif radio_button?
212
+ raise ArgumentError, "Argument value has to be provided for radio buttons" unless value
213
+ widget[:AP] = {N: {value => nil, Off: nil}}
214
+ end
215
+ next unless defaults
216
+ widget.border_style(color: 0, width: 1, style: (push_button? ? :beveled : :solid))
217
+ widget.background_color(push_button? ? 0.5 : 255)
218
+ widget.marker_style(style: check_box? ? :check : :circle) unless push_button?
219
+ end
220
+ end
221
+
222
+ # Creates appropriate appearances for all widgets if they don't already exist.
223
+ #
224
+ # The created appearance streams depend on the actual type of the button field. See
225
+ # AppearanceGenerator for the details.
226
+ def create_appearances
227
+ appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
228
+ each_widget do |widget|
229
+ next if widget.appearance?
230
+ if check_box?
231
+ appearance_generator_class.new(widget).create_check_box_appearances
232
+ elsif radio_button?
233
+ appearance_generator_class.new(widget).create_radio_button_appearances
234
+ else
235
+ raise HexaPDF::Error, "Push buttons not yet supported"
236
+ end
237
+ end
238
+ end
239
+
240
+ # Updates the widgets so that they reflect the current field value.
241
+ def update_widgets
242
+ return if push_button?
243
+ value = self[:V]
244
+ each_widget do |widget|
245
+ widget[:AS] = (widget.appearance&.normal_appearance&.value&.key?(value) ? value : :Off)
246
+ end
247
+ end
248
+
249
+ private
250
+
251
+ # Returns the normalized field value for the given key which can be :V or :DV.
252
+ #
253
+ # See #field_value for details.
254
+ def normalized_field_value(key)
255
+ if push_button?
256
+ nil
257
+ elsif check_box?
258
+ self[key] == check_box_on_name
259
+ elsif radio_button?
260
+ self[key] == :Off ? nil : self[key]
261
+ end
262
+ end
263
+
264
+ # Sets the key, either :V or :DV, to the value. The given normalized value is first
265
+ # transformed into the expected value depending on the specific field type.
266
+ #
267
+ # See #field_value= for details.
268
+ def normalized_field_value_set(key, value)
269
+ return if push_button?
270
+ self[key] = if check_box?
271
+ value == true ? check_box_on_name : :Off
272
+ elsif value.nil?
273
+ :Off
274
+ elsif radio_button_values.include?(value)
275
+ value
276
+ else
277
+ @document.config['acro_form.on_invalid_value'].call(self, value)
278
+ end
279
+ update_widgets
280
+ end
281
+
282
+ def perform_validation #:nodoc:
283
+ if field_type != :Btn
284
+ yield("Field /FT of AcroForm button field has to be :Btn", true)
285
+ self[:FT] = :Btn
286
+ end
287
+
288
+ super
289
+
290
+ unless key?(:V)
291
+ yield("Button field has no value set, defaulting to :Off", true)
292
+ self[:V] = :Off
293
+ end
294
+ end
295
+
296
+ end
297
+
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,220 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/acro_form/variable_text_field'
38
+ require 'hexapdf/type/acro_form/appearance_generator'
39
+
40
+ module HexaPDF
41
+ module Type
42
+ module AcroForm
43
+
44
+ # AcroForm choice fields contain multiple text items of which one (or, if so flagged, more)
45
+ # may be selected.
46
+ #
47
+ # They are divided into scrollable list boxes and combo boxes. To create a list or combo box,
48
+ # use the appropriate convenience methods on the main Form instance
49
+ # (HexaPDF::Document#acro_form). By using those methods, everything needed is automatically
50
+ # set up.
51
+ #
52
+ # == Type Specific Field Flags
53
+ #
54
+ # :combo:: If set, the field represents a comb box.
55
+ #
56
+ # :edit:: If set, the combo box includes an editable text box for entering arbitrary values.
57
+ # Therefore the 'combo' flag also needs to be set.
58
+ #
59
+ # :sort:: The option items have to be sorted alphabetically. This flag is intended for PDF
60
+ # writers, not readers which should display the items in the order they appear.
61
+ #
62
+ # :multi_select:: If set, more than one item may be selected.
63
+ #
64
+ # :do_not_spell_check:: The text should not be spell-checked.
65
+ #
66
+ # :commit_on_sel_change:: If set, a new value should be commited as soon as a selection is
67
+ # made.
68
+ #
69
+ # See: PDF1.7 s12.7.4.4
70
+ class ChoiceField < VariableTextField
71
+
72
+ define_field :Opt, type: PDFArray
73
+ define_field :TI, type: Integer, default: 0
74
+ define_field :I, type: PDFArray, version: '1.4'
75
+
76
+ # Updated list of field flags.
77
+ FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
78
+ {
79
+ combo: 17,
80
+ edit: 18,
81
+ sort: 19,
82
+ multi_select: 21,
83
+ do_not_spell_check: 22,
84
+ commit_on_sel_change: 26,
85
+ }
86
+ ).freeze
87
+
88
+ # Initializes the choice field to be a list box.
89
+ #
90
+ # This method should only be called directly after creating a new choice field because it
91
+ # doesn't completely reset the object.
92
+ def initialize_as_list_box
93
+ self[:V] = nil
94
+ unflag(:combo)
95
+ end
96
+
97
+ # Initializes the button field to be a combo box.
98
+ #
99
+ # This method should only be called directly after creating a new choice field because it
100
+ # doesn't completely reset the object.
101
+ def initialize_as_combo_box
102
+ self[:V] = nil
103
+ flag(:combo)
104
+ end
105
+
106
+ # Returns +true+ if this choice field represents a list box.
107
+ def list_box?
108
+ !combo_box?
109
+ end
110
+
111
+ # Returns +true+ if this choice field represents a combo box.
112
+ def combo_box?
113
+ flagged?(:combo)
114
+ end
115
+
116
+ # Returns the field value which represents the currently selected item(s).
117
+ #
118
+ # If no item is selected, +nil+ is returned. If multiple values are selected, the return
119
+ # value is an array of strings, otherwise it is just a string.
120
+ def field_value
121
+ process_value(self[:V])
122
+ end
123
+
124
+ # Sets the field value to the given string or array of strings.
125
+ def field_value=(value)
126
+ items = option_items
127
+ all_included = [value].flatten.all? {|v| items.include?(v) }
128
+ self[:V] = if (combo_box? && value.kind_of?(String) &&
129
+ (flagged?(:edit) || all_included)) ||
130
+ (list_box? && all_included &&
131
+ (value.kind_of?(String) || flagged?(:multi_select)))
132
+ value
133
+ else
134
+ @document.config['acro_form.on_invalid_value'].call(self, value)
135
+ end
136
+ end
137
+
138
+ # Returns the default field value.
139
+ #
140
+ # See: #field_value
141
+ def default_field_value
142
+ process_value(self[:DV])
143
+ end
144
+
145
+ # Sets the default field value.
146
+ #
147
+ # See: #field_value=
148
+ def default_field_value=(value)
149
+ items = option_items
150
+ self[:DV] = if [value].flatten.all? {|v| items.include?(v) }
151
+ value
152
+ else
153
+ @document.config['acro_form.on_invalid_value'].call(self, value)
154
+ end
155
+ end
156
+
157
+ # Returns the array with the available option items.
158
+ def option_items
159
+ key?(:Opt) ? process_value(self[:Opt]) : self[:Opt] ||= []
160
+ end
161
+
162
+ # Sets the array with the available option items to the given value.
163
+ def option_items=(value)
164
+ self[:Opt] = value
165
+ end
166
+
167
+ # Returns the concrete choice field type, either :list_box, :combo_box or
168
+ # :editable_combo_box.
169
+ def concrete_field_type
170
+ if combo_box?
171
+ flagged?(:edit) ? :editable_combo_box : :combo_box
172
+ else
173
+ :list_box
174
+ end
175
+ end
176
+
177
+ # Creates appropriate appearances for all widgets if they don't already exist.
178
+ #
179
+ # For information on how this is done see AppearanceGenerator.
180
+ #
181
+ # Note that an appearance for a choice field widget is *always* created even if there is an
182
+ # existing one to make sure the current field value is properly represented.
183
+ def create_appearances
184
+ appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
185
+ each_widget do |widget|
186
+ if combo_box?
187
+ appearance_generator_class.new(widget).create_combo_box_appearances
188
+ else
189
+ raise HexaPDF::Error, "List boxes not yet supported"
190
+ end
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ # Uses the HexaPDF::DictionaryFields::StringConverter to process the value (a string or an
197
+ # array of strings) so that it contains only normalized strings.
198
+ def process_value(value)
199
+ value = value.value if value.kind_of?(PDFArray)
200
+ if value.kind_of?(Array)
201
+ value.map! {|item| DictionaryFields::StringConverter.convert(item, nil, nil) || item }
202
+ else
203
+ DictionaryFields::StringConverter.convert(value, nil, nil) || value
204
+ end
205
+ end
206
+
207
+ def perform_validation #:nodoc:
208
+ if field_type != :Ch
209
+ yield("Field /FT of AcroForm choie field has to be :Ch", true)
210
+ self[:FT] = :Ch
211
+ end
212
+
213
+ super
214
+ end
215
+
216
+ end
217
+
218
+ end
219
+ end
220
+ end