hexapdf 0.11.9 → 0.12.0

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