hexapdf 0.11.7 → 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
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