hexapdf 0.11.9 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -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 +22 -11
  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 +3 -2
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +52 -3
  32. data/lib/hexapdf/cli/inspect.rb +31 -9
  33. data/lib/hexapdf/cli/merge.rb +2 -2
  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 +81 -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 +4 -4
  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 +5 -5
  55. data/lib/hexapdf/dictionary_fields.rb +2 -10
  56. data/lib/hexapdf/document.rb +45 -17
  57. data/lib/hexapdf/document/files.rb +1 -2
  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 +2 -2
  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 +2 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -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 +2 -5
  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 +2 -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 +3 -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 +4 -3
  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 +2 -2
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +24 -24
  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 +4 -3
  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 +32 -27
  147. data/lib/hexapdf/parser.rb +69 -6
  148. data/lib/hexapdf/pdf_array.rb +10 -3
  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 +30 -22
  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 +7 -5
  158. data/lib/hexapdf/tokenizer.rb +5 -4
  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 +405 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +305 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +250 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +159 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +187 -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 +4 -3
  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 +238 -2
  180. data/lib/hexapdf/type/catalog.rb +23 -3
  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 +2 -2
  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 +4 -2
  187. data/lib/hexapdf/type/font_true_type.rb +7 -3
  188. data/lib/hexapdf/type/font_type0.rb +2 -2
  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 +12 -2
  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 +5 -3
  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 +36 -12
  199. data/lib/hexapdf/type/page_tree_node.rb +37 -16
  200. data/lib/hexapdf/type/resources.rb +17 -3
  201. data/lib/hexapdf/type/trailer.rb +4 -6
  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 +19 -16
  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/common_tokenizer_tests.rb +6 -1
  216. data/test/hexapdf/content/common.rb +2 -2
  217. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  218. data/test/hexapdf/content/test_canvas.rb +3 -3
  219. data/test/hexapdf/content/test_color_space.rb +71 -8
  220. data/test/hexapdf/content/test_operator.rb +22 -22
  221. data/test/hexapdf/content/test_parser.rb +14 -0
  222. data/test/hexapdf/document/test_fonts.rb +1 -1
  223. data/test/hexapdf/document/test_pages.rb +6 -6
  224. data/test/hexapdf/encryption/test_aes.rb +4 -4
  225. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  226. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  227. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  228. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  229. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  230. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  231. data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
  232. data/test/hexapdf/layout/test_style.rb +1 -1
  233. data/test/hexapdf/layout/test_text_layouter.rb +3 -4
  234. data/test/hexapdf/test_configuration.rb +2 -2
  235. data/test/hexapdf/test_dictionary.rb +3 -1
  236. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  237. data/test/hexapdf/test_document.rb +16 -4
  238. data/test/hexapdf/test_object.rb +44 -26
  239. data/test/hexapdf/test_parser.rb +125 -55
  240. data/test/hexapdf/test_pdf_array.rb +7 -0
  241. data/test/hexapdf/test_rectangle.rb +14 -0
  242. data/test/hexapdf/test_revision.rb +3 -0
  243. data/test/hexapdf/test_revisions.rb +35 -0
  244. data/test/hexapdf/test_writer.rb +2 -2
  245. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +521 -0
  246. data/test/hexapdf/type/acro_form/test_button_field.rb +281 -0
  247. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  248. data/test/hexapdf/type/acro_form/test_field.rb +163 -6
  249. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  250. data/test/hexapdf/type/acro_form/test_text_field.rb +121 -0
  251. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  252. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  253. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  254. data/test/hexapdf/type/test_annotation.rb +45 -0
  255. data/test/hexapdf/type/test_catalog.rb +18 -0
  256. data/test/hexapdf/type/test_font.rb +5 -0
  257. data/test/hexapdf/type/test_font_simple.rb +2 -1
  258. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  259. data/test/hexapdf/type/test_font_type1.rb +8 -0
  260. data/test/hexapdf/type/test_form.rb +19 -1
  261. data/test/hexapdf/type/test_image.rb +7 -0
  262. data/test/hexapdf/type/test_page.rb +45 -7
  263. data/test/hexapdf/type/test_page_tree_node.rb +62 -12
  264. data/test/hexapdf/type/test_resources.rb +20 -0
  265. data/test/hexapdf/type/test_trailer.rb +4 -0
  266. data/test/hexapdf/utils/test_bit_field.rb +15 -1
  267. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  268. data/test/test_helper.rb +1 -1
  269. metadata +34 -21
  270. 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,175 @@ 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
+ # Returns +true+ if the field contains an embedded widget.
240
+ def embedded_widget?
241
+ key?(:Subtype)
242
+ end
243
+
244
+ # :call-seq:
245
+ # field.each_widget {|widget| block} -> field
246
+ # field.each_widget -> Enumerator
247
+ #
248
+ # Yields each widget, i.e. visual representation, of this field.
249
+ #
250
+ # See: HexaPDF::Type::Annotations::Widget
251
+ def each_widget # :yields: widget
252
+ return to_enum(__method__) unless block_given?
253
+ if embedded_widget?
254
+ yield(document.wrap(self))
255
+ elsif terminal_field?
256
+ self[:Kids]&.each {|kid| yield(document.wrap(kid)) }
257
+ end
258
+ self
259
+ end
260
+
261
+ # Creates a new widget annotation for this form field (must be a terminal field!) on the
262
+ # given +page+, adding the +values+ to the created widget annotation oject.
263
+ #
264
+ # If +allow_embedded+ is +false+, embedding the first widget in the field itself is not
265
+ # allowed.
266
+ #
267
+ # The +values+ argument should at least include :Rect for setting the visible area of the
268
+ # widget.
269
+ #
270
+ # If the field already has an embedded widget, i.e. field and widget are the same PDF
271
+ # object, its widget data is extracted to a new PDF object and stored in the /Kids field,
272
+ # together with the new widget annotation. Note that this means that a possible reference to
273
+ # the formerly embedded widget (=this field) is not valid anymore!
274
+ #
275
+ # See: HexaPDF::Type::Annotations::Widget
276
+ def create_widget(page, allow_embedded: true, **values)
277
+ unless terminal_field?
278
+ raise HexaPDF::Error, "Widgets can only be added to terminal fields"
279
+ end
280
+
281
+ widget_data = {Type: :Annot, Subtype: :Widget, Rect: [0, 0, 0, 0], **values}
282
+
283
+ if !allow_embedded || embedded_widget? || (key?(:Kids) && !self[:Kids].empty?)
284
+ kids = self[:Kids] ||= []
285
+ kids << extract_widget if embedded_widget?
286
+ widget = document.add(widget_data)
287
+ widget[:Parent] = self
288
+ self[:Kids] << widget
289
+ else
290
+ value.update(widget_data)
291
+ widget = document.wrap(self)
292
+ end
293
+
294
+ (page[:Annots] ||= []) << widget
295
+
296
+ widget
297
+ end
298
+
299
+ # Deletes the given widget annotation object from this field, the page it appears on and the
300
+ # document.
301
+ #
302
+ # If the given widget is not a widget of this field, nothing is done.
303
+ def delete_widget(widget)
304
+ widget = if embedded_widget? && self == widget
305
+ widget
306
+ elsif terminal_field?
307
+ (widget_index = self[:Kids]&.index(widget)) && widget
308
+ end
309
+
310
+ return unless widget
311
+
312
+ document.pages.each do |page|
313
+ break if page[:Annots]&.delete(widget) # See comment in #extract_widget
314
+ end
315
+
316
+ if embedded_widget?
317
+ WIDGET_FIELDS.each {|key| delete(key) }
318
+ else
319
+ self[:Kids].delete_at(widget_index)
320
+ document.delete(widget)
321
+ end
111
322
  end
112
323
 
113
324
  private
114
325
 
326
+ # An array of all widget annotation field names.
327
+ WIDGET_FIELDS = HexaPDF::Type::Annotations::Widget.each_field.map(&:first).uniq - [:Parent]
328
+
329
+ # Returns a new dictionary object with all the widget annotation data that is stored
330
+ # directly in the field and adjust the references accordingly. If the field doesn't have any
331
+ # widget data, +nil+ is returned.
332
+ def extract_widget
333
+ return unless embedded_widget?
334
+ data = WIDGET_FIELDS.each_with_object({}) do |key, hash|
335
+ hash[key] = delete(key) if key?(key)
336
+ end
337
+ widget = document.add(data, type: :Annot)
338
+ widget[:Parent] = self
339
+ document.pages.each do |page|
340
+ if page.key?(:Annots) && (index = page[:Annots].index(self))
341
+ page[:Annots][index] = widget
342
+ break # Each annotation dictionary may only appear on one page, see PDF1.7 12.5.2
343
+ end
344
+ end
345
+ widget
346
+ end
347
+
115
348
  def perform_validation #:nodoc:
116
349
  super
117
350
  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,100 @@ 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({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]},
195
+ type: :XXResources)
196
+ end
197
+
198
+ # Sets the global default appearance string using the provided values.
199
+ #
200
+ # The default argument values are a sane default. If +font_size+ is set to 0, the font size
201
+ # is calculated using the height/width of the field.
202
+ def set_default_appearance_string(font: 'Helvetica', font_size: 0)
203
+ name = default_resources.add_font(document.fonts.add(font).pdf_object)
204
+ self[:DA] = "0 g /#{name} #{font_size} Tf"
205
+ end
206
+
207
+ # Sets the /NeedAppearances field to +true+.
208
+ #
209
+ # This will make PDF reader applications generate appropriate appearance streams based on
210
+ # the information stored in the fields and associated widgets.
211
+ def need_appearances!
212
+ self[:NeedAppearances] = true
213
+ end
214
+
215
+ # Creates the appearances for all widgets of all terminal fields if they don't exist.
216
+ def create_appearances
217
+ each_field do |field|
218
+ field.create_appearances if field.respond_to?(:create_appearances)
219
+ end
220
+ end
221
+
108
222
  private
109
223
 
110
224
  # Helper method for bit field getter access.
@@ -117,6 +231,44 @@ module HexaPDF
117
231
  self[:SigFlags] = value
118
232
  end
119
233
 
234
+ # Creates a new field with the full name +name+ and the field type +type+.
235
+ def create_field(name, type)
236
+ parent_name, _, name = name.rpartition('.')
237
+ parent_field = parent_name.empty? ? nil : field_by_name(parent_name)
238
+ if !parent_name.empty? && !parent_field
239
+ raise HexaPDF::Error, "Parent field '#{parent_name}' not found"
240
+ end
241
+
242
+ field = document.add({FT: type, T: name, Parent: parent_field},
243
+ type: :XXAcroFormField, subtype: type)
244
+ if parent_field
245
+ (parent_field[:Kids] ||= []) << field
246
+ else
247
+ (self[:Fields] ||= []) << field
248
+ end
249
+ field
250
+ end
251
+
252
+ def perform_validation # :nodoc:
253
+ super
254
+
255
+ if (da = self[:DA])
256
+ unless self[:DR]
257
+ yield("When the field /DA is present, the field /DR must also be present")
258
+ return
259
+ end
260
+ font_name = nil
261
+ HexaPDF::Content::Parser.parse(da) {|obj, params| font_name = params[0] if obj == :Tf }
262
+ if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
263
+ yield("The font specified in /DA is not in the /DR resource dictionary")
264
+ end
265
+ else
266
+ set_default_appearance_string
267
+ end
268
+
269
+ create_appearances if document.config['acro_form.create_appearances']
270
+ end
271
+
120
272
  end
121
273
 
122
274
  end