hexapdf 0.11.9 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/LICENSE +1 -1
  4. data/examples/001-hello_world.rb +1 -1
  5. data/examples/002-graphics.rb +1 -1
  6. data/examples/003-arcs.rb +1 -1
  7. data/examples/004-optimizing.rb +1 -1
  8. data/examples/005-merging.rb +1 -1
  9. data/examples/006-standard_pdf_fonts.rb +1 -1
  10. data/examples/007-truetype.rb +1 -1
  11. data/examples/008-show_char_bboxes.rb +1 -1
  12. data/examples/009-text_layouter_alignment.rb +1 -1
  13. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  14. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  15. data/examples/012-text_layouter_styling.rb +1 -1
  16. data/examples/013-text_layouter_shapes.rb +1 -1
  17. data/examples/014-text_in_polygon.rb +1 -1
  18. data/examples/015-boxes.rb +1 -1
  19. data/examples/016-frame_automatic_box_placement.rb +1 -1
  20. data/examples/017-frame_text_flow.rb +1 -1
  21. data/examples/018-composer.rb +1 -1
  22. data/examples/019-acro_form.rb +51 -0
  23. data/lib/hexapdf.rb +1 -1
  24. data/lib/hexapdf/cli.rb +3 -1
  25. data/lib/hexapdf/cli/batch.rb +1 -1
  26. data/lib/hexapdf/cli/command.rb +18 -9
  27. data/lib/hexapdf/cli/files.rb +1 -1
  28. data/lib/hexapdf/cli/form.rb +240 -0
  29. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +1 -1
  32. data/lib/hexapdf/cli/inspect.rb +1 -1
  33. data/lib/hexapdf/cli/merge.rb +1 -1
  34. data/lib/hexapdf/cli/modify.rb +1 -1
  35. data/lib/hexapdf/cli/optimize.rb +1 -1
  36. data/lib/hexapdf/cli/split.rb +1 -1
  37. data/lib/hexapdf/cli/watermark.rb +1 -1
  38. data/lib/hexapdf/composer.rb +2 -2
  39. data/lib/hexapdf/configuration.rb +66 -11
  40. data/lib/hexapdf/content.rb +3 -1
  41. data/lib/hexapdf/content/canvas.rb +5 -18
  42. data/lib/hexapdf/content/color_space.rb +111 -32
  43. data/lib/hexapdf/content/graphic_object.rb +1 -1
  44. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  45. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  46. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  47. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  48. data/lib/hexapdf/content/graphics_state.rb +1 -1
  49. data/lib/hexapdf/content/operator.rb +9 -9
  50. data/lib/hexapdf/content/parser.rb +18 -5
  51. data/lib/hexapdf/content/processor.rb +1 -1
  52. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  53. data/lib/hexapdf/data_dir.rb +1 -1
  54. data/lib/hexapdf/dictionary.rb +1 -1
  55. data/lib/hexapdf/dictionary_fields.rb +1 -1
  56. data/lib/hexapdf/document.rb +14 -5
  57. data/lib/hexapdf/document/files.rb +1 -1
  58. data/lib/hexapdf/document/fonts.rb +1 -1
  59. data/lib/hexapdf/document/images.rb +1 -1
  60. data/lib/hexapdf/document/pages.rb +3 -14
  61. data/lib/hexapdf/encryption.rb +1 -1
  62. data/lib/hexapdf/encryption/aes.rb +1 -1
  63. data/lib/hexapdf/encryption/arc4.rb +1 -1
  64. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  65. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  66. data/lib/hexapdf/encryption/identity.rb +1 -1
  67. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  68. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  69. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  71. data/lib/hexapdf/error.rb +1 -1
  72. data/lib/hexapdf/filter.rb +3 -3
  73. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  74. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  75. data/lib/hexapdf/filter/encryption.rb +1 -1
  76. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  77. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  78. data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
  79. data/lib/hexapdf/filter/predictor.rb +1 -1
  80. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  81. data/lib/hexapdf/font/cmap.rb +1 -1
  82. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  83. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  84. data/lib/hexapdf/font/encoding.rb +1 -1
  85. data/lib/hexapdf/font/encoding/base.rb +1 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  87. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  88. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  89. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  90. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  91. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  92. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  93. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  94. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  95. data/lib/hexapdf/font/true_type.rb +1 -1
  96. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  97. data/lib/hexapdf/font/true_type/font.rb +1 -1
  98. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  99. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  103. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  104. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  105. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  106. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  107. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  108. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  109. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  113. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  114. data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
  115. data/lib/hexapdf/font/type1.rb +1 -1
  116. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  117. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  118. data/lib/hexapdf/font/type1/font.rb +1 -1
  119. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  120. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  121. data/lib/hexapdf/font/type1_wrapper.rb +67 -51
  122. data/lib/hexapdf/font_loader.rb +1 -1
  123. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  124. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  125. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  126. data/lib/hexapdf/image_loader.rb +1 -1
  127. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  128. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  129. data/lib/hexapdf/image_loader/png.rb +1 -1
  130. data/lib/hexapdf/importer.rb +2 -4
  131. data/lib/hexapdf/layout.rb +1 -1
  132. data/lib/hexapdf/layout/box.rb +1 -1
  133. data/lib/hexapdf/layout/frame.rb +1 -1
  134. data/lib/hexapdf/layout/image_box.rb +1 -1
  135. data/lib/hexapdf/layout/inline_box.rb +1 -1
  136. data/lib/hexapdf/layout/line.rb +1 -1
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +1 -1
  139. data/lib/hexapdf/layout/text_box.rb +1 -1
  140. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  141. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  142. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  143. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  144. data/lib/hexapdf/name_tree_node.rb +1 -1
  145. data/lib/hexapdf/number_tree_node.rb +1 -1
  146. data/lib/hexapdf/object.rb +2 -2
  147. data/lib/hexapdf/parser.rb +4 -3
  148. data/lib/hexapdf/pdf_array.rb +1 -1
  149. data/lib/hexapdf/rectangle.rb +31 -1
  150. data/lib/hexapdf/reference.rb +1 -1
  151. data/lib/hexapdf/revision.rb +2 -1
  152. data/lib/hexapdf/revisions.rb +1 -1
  153. data/lib/hexapdf/serializer.rb +1 -1
  154. data/lib/hexapdf/stream.rb +1 -1
  155. data/lib/hexapdf/task.rb +1 -1
  156. data/lib/hexapdf/task/dereference.rb +1 -1
  157. data/lib/hexapdf/task/optimize.rb +1 -1
  158. data/lib/hexapdf/tokenizer.rb +1 -1
  159. data/lib/hexapdf/type.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +7 -1
  161. data/lib/hexapdf/type/acro_form/appearance_generator.rb +401 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +220 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +157 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +186 -0
  167. data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
  168. data/lib/hexapdf/type/action.rb +1 -1
  169. data/lib/hexapdf/type/actions.rb +1 -1
  170. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  171. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  172. data/lib/hexapdf/type/actions/launch.rb +1 -1
  173. data/lib/hexapdf/type/actions/uri.rb +1 -1
  174. data/lib/hexapdf/type/annotation.rb +73 -3
  175. data/lib/hexapdf/type/annotations.rb +1 -1
  176. data/lib/hexapdf/type/annotations/link.rb +2 -2
  177. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  178. data/lib/hexapdf/type/annotations/text.rb +1 -1
  179. data/lib/hexapdf/type/annotations/widget.rb +239 -2
  180. data/lib/hexapdf/type/catalog.rb +21 -1
  181. data/lib/hexapdf/type/cid_font.rb +1 -1
  182. data/lib/hexapdf/type/embedded_file.rb +1 -1
  183. data/lib/hexapdf/type/file_specification.rb +1 -1
  184. data/lib/hexapdf/type/font.rb +18 -1
  185. data/lib/hexapdf/type/font_descriptor.rb +2 -2
  186. data/lib/hexapdf/type/font_simple.rb +1 -1
  187. data/lib/hexapdf/type/font_true_type.rb +1 -1
  188. data/lib/hexapdf/type/font_type0.rb +1 -1
  189. data/lib/hexapdf/type/font_type1.rb +16 -1
  190. data/lib/hexapdf/type/font_type3.rb +1 -1
  191. data/lib/hexapdf/type/form.rb +1 -1
  192. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  193. data/lib/hexapdf/type/icon_fit.rb +1 -1
  194. data/lib/hexapdf/type/image.rb +3 -1
  195. data/lib/hexapdf/type/info.rb +1 -1
  196. data/lib/hexapdf/type/names.rb +1 -1
  197. data/lib/hexapdf/type/object_stream.rb +1 -1
  198. data/lib/hexapdf/type/page.rb +1 -1
  199. data/lib/hexapdf/type/page_tree_node.rb +8 -11
  200. data/lib/hexapdf/type/resources.rb +16 -3
  201. data/lib/hexapdf/type/trailer.rb +2 -3
  202. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  203. data/lib/hexapdf/type/xref_stream.rb +1 -1
  204. data/lib/hexapdf/utils/bit_field.rb +38 -24
  205. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  206. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  208. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  209. data/lib/hexapdf/utils/object_hash.rb +1 -1
  210. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  211. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  212. data/lib/hexapdf/version.rb +2 -2
  213. data/lib/hexapdf/writer.rb +1 -1
  214. data/lib/hexapdf/xref_section.rb +1 -1
  215. data/test/hexapdf/content/common.rb +2 -2
  216. data/test/hexapdf/content/test_color_space.rb +71 -8
  217. data/test/hexapdf/content/test_operator.rb +22 -22
  218. data/test/hexapdf/content/test_parser.rb +14 -0
  219. data/test/hexapdf/document/test_fonts.rb +1 -1
  220. data/test/hexapdf/document/test_pages.rb +6 -6
  221. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  222. data/test/hexapdf/font/test_type1_wrapper.rb +32 -8
  223. data/test/hexapdf/test_document.rb +12 -0
  224. data/test/hexapdf/test_parser.rb +10 -0
  225. data/test/hexapdf/test_rectangle.rb +14 -0
  226. data/test/hexapdf/test_revision.rb +3 -0
  227. data/test/hexapdf/test_writer.rb +2 -2
  228. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
  229. data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
  230. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  231. data/test/hexapdf/type/acro_form/test_field.rb +124 -6
  232. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  233. data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
  234. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  235. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  236. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  237. data/test/hexapdf/type/test_annotation.rb +45 -0
  238. data/test/hexapdf/type/test_catalog.rb +18 -0
  239. data/test/hexapdf/type/test_font.rb +5 -0
  240. data/test/hexapdf/type/test_font_type1.rb +8 -0
  241. data/test/hexapdf/type/test_image.rb +7 -0
  242. data/test/hexapdf/type/test_page_tree_node.rb +20 -12
  243. data/test/hexapdf/type/test_resources.rb +20 -0
  244. data/test/hexapdf/type/test_trailer.rb +4 -0
  245. data/test/hexapdf/utils/test_bit_field.rb +13 -1
  246. data/test/test_helper.rb +1 -1
  247. metadata +37 -18
  248. data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -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
@@ -0,0 +1,186 @@
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/error'
38
+ require 'hexapdf/type/acro_form/variable_text_field'
39
+
40
+ module HexaPDF
41
+ module Type
42
+ module AcroForm
43
+
44
+ # AcroForm text fields provide a box or space to fill-in data entered from keyboard. The text
45
+ # may be restricted to a single line or can span multiple lines.
46
+ #
47
+ # == Type Specific Field Flags
48
+ #
49
+ # :multiline:: If set, the text field may contain multiple lines.
50
+ #
51
+ # :password:: The field is a password field. This changes the behaviour of the PDF reader
52
+ # application to not echo the input text and to not store it in the PDF file.
53
+ #
54
+ # :file_select:: The text field represents a file selection control where the input text is
55
+ # the path to a file.
56
+ #
57
+ # :do_not_spell_check:: The text should not be spell-checked.
58
+ #
59
+ # :do_not_scroll:: The text field should not scroll (horizontally for single-line fields and
60
+ # vertically for multiline fields) to accomodate more text than fits into the
61
+ # annotation rectangle. This means that no more text can be entered once the
62
+ # field is full.
63
+ #
64
+ # :comb:: The field is divided into /MaxLen equally spaced positions (so /MaxLen needs to be
65
+ # set). This is useful, for example, when entering things like social security
66
+ # numbers which always have the same length.
67
+ #
68
+ # :rich_text:: The field is a rich text field.
69
+ #
70
+ # See: PDF1.7 s12.7.4.3
71
+ class TextField < VariableTextField
72
+
73
+ define_field :MaxLen, type: Integer
74
+
75
+ # All inheritable dictionary fields for text fields.
76
+ INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:MaxLen]).freeze
77
+
78
+ # Updated list of field flags.
79
+ FLAGS_BIT_MAPPING = superclass::FLAGS_BIT_MAPPING.merge(
80
+ {
81
+ multiline: 12,
82
+ password: 13,
83
+ file_select: 20,
84
+ do_not_spell_check: 22,
85
+ do_not_scroll: 23,
86
+ comb: 24,
87
+ rich_text: 25,
88
+ }
89
+ ).freeze
90
+
91
+ # Returns the field value, i.e. the text contents of the field, or +nil+ if no value is set.
92
+ #
93
+ # Note that modifying the returned value *might not* modify the text contents in case it is
94
+ # stored as stream! So always use #field_value= to set the field value.
95
+ def field_value
96
+ return unless value[:V]
97
+ self[:V].kind_of?(String) ? self[:V] : self[:V].stream
98
+ end
99
+
100
+ # Sets the field value, i.e. the text contents of the field, to the given string.
101
+ #
102
+ # Note that for single line text fields, all whitespace characters are changed to simple
103
+ # spaces.
104
+ def field_value=(str)
105
+ if flagged?(:password)
106
+ raise HexaPDF::Error, "Storing a field value for a password field is not allowed"
107
+ end
108
+ str = str.gsub(/[[:space:]]/, ' ') if concrete_field_type == :single_line_text_field
109
+ self[:V] = str
110
+ update_widgets
111
+ end
112
+
113
+ # Returns the default field value.
114
+ #
115
+ # See: #field_value
116
+ def default_field_value
117
+ self[:DV].kind_of?(String) ? self[:DV] : self[:DV].stream
118
+ end
119
+
120
+ # Sets the default field value.
121
+ #
122
+ # See: #field_value=
123
+ def default_field_value=(str)
124
+ self[:DV] = str
125
+ end
126
+
127
+ # Returns the concrete text field type, either :single_line_text_field,
128
+ # :multiline_text_field, :password_field, :file_select_field, :comb_text_field or
129
+ # :rich_text_field.
130
+ def concrete_field_type
131
+ if flagged?(:multiline)
132
+ :multiline_text_field
133
+ elsif flagged?(:password)
134
+ :password_field
135
+ elsif flagged?(:file_select)
136
+ :file_select_field
137
+ elsif flagged?(:comb)
138
+ :comb_text_field
139
+ elsif flagged?(:rich_text)
140
+ :rich_text_field
141
+ else
142
+ :single_line_text_field
143
+ end
144
+ end
145
+
146
+ # Creates appropriate appearances for all widgets.
147
+ #
148
+ # For information on how this is done see AppearanceGenerator.
149
+ #
150
+ # Note that an appearance for a text field widget is *always* created even if there is an
151
+ # existing one to make sure the current field value is properly represented.
152
+ def create_appearances
153
+ appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
154
+ each_widget do |widget|
155
+ appearance_generator_class.new(widget).create_text_appearances
156
+ end
157
+ end
158
+
159
+ # Updates the widgets so that they reflect the current field value.
160
+ def update_widgets
161
+ create_appearances
162
+ end
163
+
164
+ private
165
+
166
+ def perform_validation #:nodoc:
167
+ if field_type != :Tx
168
+ yield("Field /FT of AcroForm text field has to be :Tx", true)
169
+ self[:FT] = :Tx
170
+ end
171
+
172
+ super
173
+
174
+ if self[:V] && !(self[:V].kind_of?(String) || self[:V].kind_of?(HexaPDF::Stream))
175
+ yield("Text field doesn't contain text but #{self[:V].class} object")
176
+ end
177
+ if (max_len = self[:MaxLen]) && field_value.length > max_len
178
+ yield("Text contents of field '#{full_field_name}' is too long")
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,122 @@
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/dictionary'
38
+ require 'hexapdf/stream'
39
+ require 'hexapdf/error'
40
+ require 'hexapdf/content/parser'
41
+
42
+ module HexaPDF
43
+ module Type
44
+ module AcroForm
45
+
46
+ # An AcroForm variable text field defines how text that it is not known at generation time
47
+ # should be rendered. For example, AcroForm text fields (normally) don't have an initial
48
+ # value; the value is entered by the user and needs to be rendered correctly by the PDF
49
+ # reader.
50
+ #
51
+ # See: PDF1.7 s12.7.3.3
52
+ class VariableTextField < Field
53
+
54
+ define_field :DA, type: String
55
+ define_field :Q, type: Integer, default: 0, allowed_values: [0, 1, 2]
56
+ define_field :DS, type: String, version: '1.5'
57
+ define_field :RV, type: [String, Stream], version: '1.5'
58
+
59
+ # All inheritable dictionary fields for text fields.
60
+ INHERITABLE_FIELDS = (superclass::INHERITABLE_FIELDS + [:DA, :Q]).freeze
61
+
62
+ UNSET_ARG = ::Object.new # :nodoc:
63
+
64
+ # :call-seq:
65
+ # field.text_alignment -> alignment
66
+ # field.text_alignment(alignment) -> field
67
+ #
68
+ # Sets or returns the text alignment that should be used when displaying text.
69
+ #
70
+ # With no argument, the current text alignment is returned. When a value is provided, the
71
+ # text alignment is set accordingly.
72
+ #
73
+ # The alignment value is one of :left, :center or :right.
74
+ def text_alignment(alignment = UNSET_ARG)
75
+ if alignment == UNSET_ARG
76
+ case self[:Q]
77
+ when 0 then :left
78
+ when 1 then :center
79
+ when 2 then :right
80
+ end
81
+ else
82
+ self[:Q] = case alignment
83
+ when :left then 0
84
+ when :center then 1
85
+ when :right then 2
86
+ else
87
+ raise ArgumentError, "Invalid variable text field alignment #{alignment}"
88
+ end
89
+ end
90
+ end
91
+
92
+ # Sets the default appearance string using the provided values.
93
+ #
94
+ # The default argument values are a sane default. If +font_size+ is set to 0, the font size
95
+ # is calculated using the height/width of the field.
96
+ def set_default_appearance_string(font: 'Helvetica', font_size: 0)
97
+ name = document.acro_form(create: true).default_resources.
98
+ add_font(document.fonts.add(font).pdf_object)
99
+ self[:DA] = "0 g /#{name} #{font_size} Tf"
100
+ end
101
+
102
+ # Parses the default appearance string and returns an array containing [font_name,
103
+ # font_size].
104
+ #
105
+ # The default appearance string is taken from the field or, if not set, the default
106
+ # appearance string of the form.
107
+ def parse_default_appearance_string
108
+ da = self[:DA] || (document.acro_form && document.acro_form[:DA])
109
+ raise HexaPDF::Error, "No default appearance string set" unless da
110
+
111
+ font_params = nil
112
+ HexaPDF::Content::Parser.parse(da) do |obj, params|
113
+ font_params = params.dup if obj == :Tf
114
+ end
115
+ font_params
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+ end
122
+ end