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
@@ -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?
@@ -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
@@ -36,6 +36,7 @@
36
36
 
37
37
  require 'hexapdf/dictionary'
38
38
  require 'hexapdf/stream'
39
+ require 'hexapdf/type/acro_form/field'
39
40
  require 'hexapdf/utils/bit_field'
40
41
 
41
42
  module HexaPDF
@@ -45,7 +46,29 @@ module HexaPDF
45
46
  # Represents the PDF's interactive form dictionary. It is linked from the catalog dictionary
46
47
  # via the /AcroForm entry.
47
48
  #
48
- # See: PDF1.7 s12.7.2
49
+ # == Overview
50
+ #
51
+ # An interactive form consists of fields which can be structured hierarchically and shown on
52
+ # pages by using Annotations::Widget annotations. This means one field can have zero, one or
53
+ # more visual representations on one or more pages. The fields at the bottom of the hierarchy
54
+ # which have no parent are called "root fields" and are stored in /Fields.
55
+ #
56
+ # Each field in a form has a certain type which determines how it should be displayed and what
57
+ # a user can do with it. The most common type is "text field" which allows the user to enter
58
+ # one or more lines of text. There are also check boxes, radio buttons, list boxes and combo
59
+ # boxes.
60
+ #
61
+ # == Visual Appearance
62
+ #
63
+ # The visual appearance of a field is normally provided by the application creating the PDF.
64
+ # This is done by generating the so called appearances for all widgets of a field. However, it
65
+ # is also possible to instruct the PDF reader application to generate the appearances on the
66
+ # fly using the /NeedAppearances key, see #need_appearances!.
67
+ #
68
+ # HexaPDF uses the configuration option +acro_form.create_appearance_streams+ to determine
69
+ # whether appearances should automatically be generated.
70
+ #
71
+ # See: PDF1.7 s12.7.2, Field, HexaPDF::Type::Annotations::Widget
49
72
  class Form < Dictionary
50
73
 
51
74
  extend Utils::BitField
@@ -56,12 +79,18 @@ module HexaPDF
56
79
  define_field :NeedAppearances, type: Boolean, default: false
57
80
  define_field :SigFlags, type: Integer, version: '1.3'
58
81
  define_field :CO, type: PDFArray, version: '1.3'
59
- define_field :DR, type: :Ressources
82
+ define_field :DR, type: :XXResources
60
83
  define_field :DA, type: String
61
84
  define_field :XFA, type: [Stream, PDFArray], version: '1.5'
62
85
 
63
86
  bit_field(:raw_signature_flags, {signatures_exist: 0, append_only: 1},
64
- lister: "signature_flags", getter: "signature_flag?", setter: "signature_flag")
87
+ lister: "signature_flags", getter: "signature_flag?", setter: "signature_flag",
88
+ unsetter: "signature_unflag")
89
+
90
+ # Returns the PDFArray containing the root fields.
91
+ def root_fields
92
+ self[:Fields] ||= document.wrap([])
93
+ end
65
94
 
66
95
  # Returns an array with all root fields that were found in the PDF document.
67
96
  def find_root_fields
@@ -69,7 +98,7 @@ module HexaPDF
69
98
  document.pages.each do |page|
70
99
  page[:Annots]&.each do |annot|
71
100
  if !annot.key?(:Parent) && annot.key?(:FT)
72
- result << document.wrap(annot, type: :XXAcroFormField)
101
+ result << document.wrap(annot, type: :XXAcroFormField, subtype: annot[:FT])
73
102
  elsif annot.key?(:Parent)
74
103
  field = annot[:Parent]
75
104
  field = field[:Parent] while field[:Parent]
@@ -96,15 +125,99 @@ module HexaPDF
96
125
  return to_enum(__method__, terminal_only: terminal_only) unless block_given?
97
126
 
98
127
  process_field = lambda do |field|
99
- field = document.wrap(field, type: :XXAcroFormField)
128
+ field = document.wrap(field, type: :XXAcroFormField,
129
+ subtype: Field.inherited_value(field, :FT))
100
130
  yield(field) if field.terminal_field? || !terminal_only
101
131
  field[:Kids].each(&process_field) unless field.terminal_field?
102
132
  end
103
133
 
104
- self[:Fields]&.each(&process_field)
134
+ root_fields.each(&process_field)
105
135
  self
106
136
  end
107
137
 
138
+ # Returns the field with the given +name+ or +nil+ if no such field exists.
139
+ def field_by_name(name)
140
+ fields = root_fields
141
+ field = nil
142
+ name.split('.').each do |part|
143
+ field = fields&.find {|f| f[:T] == part }
144
+ break unless field
145
+ field = document.wrap(field, type: :XXAcroFormField,
146
+ subtype: Field.inherited_value(field, :FT))
147
+ fields = field[:Kids] unless field.terminal_field?
148
+ end
149
+ field
150
+ end
151
+
152
+ # Creates a new text field with the given name and adds it to the form.
153
+ #
154
+ # The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
155
+ # fields must already exist. If it doesn't contain dots, a top-level field is created.
156
+ def create_text_field(name)
157
+ create_field(name, :Tx)
158
+ end
159
+
160
+ # Creates a new check box with the given name and adds it to the form.
161
+ #
162
+ # The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
163
+ # fields must already exist. If it doesn't contain dots, a top-level field is created.
164
+ def create_check_box(name)
165
+ create_field(name, :Btn).tap(&:initialize_as_check_box)
166
+ end
167
+
168
+ # Creates a radio button with the given name and adds it to the form.
169
+ #
170
+ # The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
171
+ # fields must already exist. If it doesn't contain dots, a top-level field is created.
172
+ def create_radio_button(name)
173
+ create_field(name, :Btn).tap(&:initialize_as_radio_button)
174
+ end
175
+
176
+ # Creates a combo box with the given name and adds it to the form.
177
+ #
178
+ # The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
179
+ # fields must already exist. If it doesn't contain dots, a top-level field is created.
180
+ def create_combo_box(name)
181
+ create_field(name, :Ch).tap(&:initialize_as_combo_box)
182
+ end
183
+
184
+ # Creates a list box with the given name and adds it to the form.
185
+ #
186
+ # The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
187
+ # fields must already exist. If it doesn't contain dots, a top-level field is created.
188
+ def create_list_box(name)
189
+ create_field(name, :Ch).tap(&:initialize_as_list_box)
190
+ end
191
+
192
+ # Returns the dictionary containing the default resources for form field appearance streams.
193
+ def default_resources
194
+ self[:DR] ||= document.wrap({}, type: :XXResources)
195
+ end
196
+
197
+ # Sets the global default appearance string using the provided values.
198
+ #
199
+ # The default argument values are a sane default. If +font_size+ is set to 0, the font size
200
+ # is calculated using the height/width of the field.
201
+ def set_default_appearance_string(font: 'Helvetica', font_size: 0)
202
+ name = default_resources.add_font(document.fonts.add(font).pdf_object)
203
+ self[:DA] = "0 g /#{name} #{font_size} Tf"
204
+ end
205
+
206
+ # Sets the /NeedAppearances field to +true+.
207
+ #
208
+ # This will make PDF reader applications generate appropriate appearance streams based on
209
+ # the information stored in the fields and associated widgets.
210
+ def need_appearances!
211
+ self[:NeedAppearances] = true
212
+ end
213
+
214
+ # Creates the appearances for all widgets of all terminal fields if they don't exist.
215
+ def create_appearances
216
+ each_field do |field|
217
+ field.create_appearances if field.respond_to?(:create_appearances)
218
+ end
219
+ end
220
+
108
221
  private
109
222
 
110
223
  # Helper method for bit field getter access.
@@ -117,6 +230,43 @@ module HexaPDF
117
230
  self[:SigFlags] = value
118
231
  end
119
232
 
233
+ # Creates a new field with the full name +name+ and the field type +type+.
234
+ def create_field(name, type)
235
+ parent_name, _, name = name.rpartition('.')
236
+ parent_field = parent_name.empty? ? nil : field_by_name(parent_name)
237
+ if !parent_name.empty? && !parent_field
238
+ raise HexaPDF::Error, "Parent field '#{parent_name}' not found"
239
+ end
240
+
241
+ field = document.add({FT: type, T: name, Parent: parent_field},
242
+ type: :XXAcroFormField, subtype: type)
243
+ if parent_field
244
+ (parent_field[:Kids] ||= []) << field
245
+ else
246
+ (self[:Fields] ||= []) << field
247
+ end
248
+ field
249
+ end
250
+
251
+ def perform_validation # :nodoc:
252
+ if (da = self[:DA])
253
+ unless self[:DR]
254
+ yield("When the field /DA is present, the field /DR must also be present")
255
+ end
256
+ font_name = nil
257
+ HexaPDF::Content::Parser.parse(da) do |obj, params|
258
+ font_name = params[0] if obj == :Tf
259
+ end
260
+ if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
261
+ yield("The font specified in /DA is not in the /DR resource dictionary")
262
+ end
263
+ else
264
+ set_default_appearance_string
265
+ end
266
+
267
+ create_appearances if document.config['acro_form.create_appearances']
268
+ end
269
+
120
270
  end
121
271
 
122
272
  end