hexapdf 0.14.2 → 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -0
  3. data/lib/hexapdf/cli/form.rb +30 -8
  4. data/lib/hexapdf/configuration.rb +19 -4
  5. data/lib/hexapdf/content/canvas.rb +1 -0
  6. data/lib/hexapdf/dictionary.rb +3 -0
  7. data/lib/hexapdf/dictionary_fields.rb +1 -1
  8. data/lib/hexapdf/encryption/security_handler.rb +7 -2
  9. data/lib/hexapdf/encryption/standard_security_handler.rb +12 -0
  10. data/lib/hexapdf/error.rb +4 -3
  11. data/lib/hexapdf/filter.rb +1 -0
  12. data/lib/hexapdf/filter/crypt.rb +60 -0
  13. data/lib/hexapdf/font/true_type/subsetter.rb +5 -1
  14. data/lib/hexapdf/font/type1/afm_parser.rb +2 -1
  15. data/lib/hexapdf/parser.rb +46 -14
  16. data/lib/hexapdf/pdf_array.rb +3 -0
  17. data/lib/hexapdf/revision.rb +16 -0
  18. data/lib/hexapdf/serializer.rb +10 -3
  19. data/lib/hexapdf/tokenizer.rb +44 -3
  20. data/lib/hexapdf/type/acro_form.rb +1 -0
  21. data/lib/hexapdf/type/acro_form/appearance_generator.rb +32 -17
  22. data/lib/hexapdf/type/acro_form/button_field.rb +8 -4
  23. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  24. data/lib/hexapdf/type/acro_form/form.rb +37 -0
  25. data/lib/hexapdf/type/acro_form/signature_field.rb +223 -0
  26. data/lib/hexapdf/type/annotation.rb +13 -9
  27. data/lib/hexapdf/type/annotations/widget.rb +3 -1
  28. data/lib/hexapdf/type/font_descriptor.rb +9 -2
  29. data/lib/hexapdf/type/page.rb +81 -0
  30. data/lib/hexapdf/type/resources.rb +4 -0
  31. data/lib/hexapdf/type/xref_stream.rb +7 -0
  32. data/lib/hexapdf/utils/graphics_helpers.rb +4 -4
  33. data/lib/hexapdf/version.rb +1 -1
  34. data/test/hexapdf/content/test_canvas.rb +21 -0
  35. data/test/hexapdf/encryption/test_security_handler.rb +15 -0
  36. data/test/hexapdf/encryption/test_standard_security_handler.rb +26 -0
  37. data/test/hexapdf/filter/test_crypt.rb +21 -0
  38. data/test/hexapdf/font/true_type/test_subsetter.rb +2 -0
  39. data/test/hexapdf/font/type1/test_afm_parser.rb +5 -0
  40. data/test/hexapdf/test_dictionary_fields.rb +7 -0
  41. data/test/hexapdf/test_parser.rb +82 -2
  42. data/test/hexapdf/test_revision.rb +21 -0
  43. data/test/hexapdf/test_serializer.rb +10 -0
  44. data/test/hexapdf/test_tokenizer.rb +50 -0
  45. data/test/hexapdf/test_writer.rb +2 -2
  46. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +24 -3
  47. data/test/hexapdf/type/acro_form/test_button_field.rb +13 -7
  48. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  49. data/test/hexapdf/type/acro_form/test_form.rb +46 -2
  50. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  51. data/test/hexapdf/type/annotations/test_widget.rb +2 -0
  52. data/test/hexapdf/type/test_annotation.rb +20 -10
  53. data/test/hexapdf/type/test_font_descriptor.rb +7 -0
  54. data/test/hexapdf/type/test_page.rb +187 -49
  55. data/test/hexapdf/type/test_resources.rb +6 -0
  56. data/test/hexapdf/type/test_xref_stream.rb +7 -0
  57. data/test/hexapdf/utils/test_graphics_helpers.rb +8 -0
  58. metadata +6 -2
@@ -65,6 +65,9 @@ module HexaPDF
65
65
  # * Returns the native Ruby object for values with class HexaPDF::Object. However, all
66
66
  # subclasses of HexaPDF::Object are returned as is (it makes no sense, for example, to return
67
67
  # the hash that describes the Catalog instead of the Catalog object).
68
+ #
69
+ # Note: Hash or Array values will always be returned as-is, i.e. not wrapped with Dictionary or
70
+ # PDFArray.
68
71
  def [](arg1, arg2 = nil)
69
72
  data = arg2 ? value[arg1, arg2] : value[arg1]
70
73
  return if data.nil?
@@ -158,6 +158,22 @@ module HexaPDF
158
158
  add_without_check(obj)
159
159
  end
160
160
 
161
+ # :call-seq:
162
+ # revision.update(obj) -> obj or nil
163
+ #
164
+ # Updates the stored object to point to the given HexaPDF::Object wrapper, returning the object
165
+ # if successful or +nil+ otherwise.
166
+ #
167
+ # If +obj+ isn't stored in this revision or the stored object doesn't contain the same
168
+ # HexaPDF::PDFData object as the given object, nothing is done.
169
+ #
170
+ # This method should only be used if the wrong wrapper class is stored (e.g. because
171
+ # auto-detection didn't or couldn't work correctly) and thus needs correction.
172
+ def update(obj)
173
+ return nil if object(obj)&.data != obj.data
174
+ add_without_check(obj)
175
+ end
176
+
161
177
  # :call-seq:
162
178
  # revision.delete(ref, mark_as_free: true)
163
179
  # revision.delete(oid, mark_as_free: true)
@@ -191,7 +191,13 @@ module HexaPDF
191
191
  #
192
192
  # See: PDF1.7 s7.3.3
193
193
  def serialize_float(obj)
194
- -0.0001 < obj && obj < 0.0001 && obj != 0 ? sprintf("%.6f", obj) : obj.round(6).to_s
194
+ if -0.0001 < obj && obj < 0.0001 && obj != 0
195
+ sprintf("%.6f", obj)
196
+ elsif obj.finite?
197
+ obj.round(6).to_s
198
+ else
199
+ raise HexaPDF::Error, "Can't serialize special floating point number #{obj}"
200
+ end
195
201
  end
196
202
 
197
203
  # The regexp matches all characters that need to be escaped and the substs hash contains the
@@ -343,6 +349,7 @@ module HexaPDF
343
349
  @io << data.freeze
344
350
  end
345
351
  @io << "\nendstream"
352
+ @in_object = false
346
353
 
347
354
  nil
348
355
  else
@@ -350,12 +357,12 @@ module HexaPDF
350
357
  obj.value[:Length] = data.size
351
358
 
352
359
  str = serialize_hash(obj.value)
360
+ @in_object = false
361
+
353
362
  str << "stream\n"
354
363
  str << data
355
364
  str << "\nendstream"
356
365
  end
357
- ensure
358
- @in_object = false
359
366
  end
360
367
 
361
368
  # Invokes the correct serialization method for the object.
@@ -73,11 +73,15 @@ module HexaPDF
73
73
  # The IO object from the tokens are read.
74
74
  attr_reader :io
75
75
 
76
- # Creates a new tokenizer.
77
- def initialize(io)
76
+ # Creates a new tokenizer for the given IO stream.
77
+ #
78
+ # If +on_correctable_error+ is set to an object responding to +call(msg, pos)+, errors for
79
+ # correctable situations are only raised if the return value of calling the object is +true+.
80
+ def initialize(io, on_correctable_error: nil)
78
81
  @io = io
79
82
  @ss = StringScanner.new(''.b)
80
83
  @original_pos = -1
84
+ @on_correctable_error = on_correctable_error || proc { false }
81
85
  self.pos = 0
82
86
  end
83
87
 
@@ -180,7 +184,8 @@ module HexaPDF
180
184
  end
181
185
  else
182
186
  unless allow_keyword
183
- raise HexaPDF::MalformedPDFError.new("Invalid object, got token #{token}", pos: pos)
187
+ maybe_raise("Invalid object, got token #{token}", force: token !~ /^-?(nan|inf)$/i)
188
+ token = 0
184
189
  end
185
190
  end
186
191
  end
@@ -188,6 +193,28 @@ module HexaPDF
188
193
  token
189
194
  end
190
195
 
196
+ # Returns a single integer or keyword token read from the current position and advances the scan
197
+ # pointer. If the current position doesn't contain such a token, +nil+ is returned without
198
+ # advancing the scan pointer. The value +NO_MORE_TOKENS+ is returned if there are no more tokens
199
+ # available.
200
+ #
201
+ # Initial runs of whitespace characters are ignored.
202
+ #
203
+ # Note: This is a special method meant for use with reconstructing the cross-reference table!
204
+ def next_integer_or_keyword
205
+ skip_whitespace
206
+ byte = @ss.string.getbyte(@ss.pos) || -1
207
+ if 48 <= byte && byte <= 57
208
+ parse_number
209
+ elsif (97 <= byte && byte <= 122) || (65 <= byte && byte <= 90)
210
+ parse_keyword
211
+ elsif byte == -1 # we reached the end of the file
212
+ NO_MORE_TOKENS
213
+ else
214
+ nil
215
+ end
216
+ end
217
+
191
218
  # Reads the byte (an integer) at the current position and advances the scan pointer.
192
219
  def next_byte
193
220
  prepare_string_scanner(1)
@@ -406,6 +433,20 @@ module HexaPDF
406
433
  true
407
434
  end
408
435
 
436
+ # Calls the @on_correctable_error callable object with the given message and the current
437
+ # position. If the returned value is +true+, raises a HexaPDF::MalformedPDFError. Otherwise the
438
+ # error is corrected (by the caller) and tokenization continues.
439
+ #
440
+ # If the option +force+ is used, the callable object is not called and the error is raised
441
+ # immediately.
442
+ def maybe_raise(msg, force: false)
443
+ if force || @on_correctable_error.call(msg, pos)
444
+ error = HexaPDF::MalformedPDFError.new(msg, pos: pos)
445
+ error.set_backtrace(caller(1))
446
+ raise error
447
+ end
448
+ end
449
+
409
450
  end
410
451
 
411
452
  end
@@ -48,6 +48,7 @@ module HexaPDF
48
48
  autoload(:TextField, 'hexapdf/type/acro_form/text_field')
49
49
  autoload(:ButtonField, 'hexapdf/type/acro_form/button_field')
50
50
  autoload(:ChoiceField, 'hexapdf/type/acro_form/choice_field')
51
+ autoload(:SignatureField, 'hexapdf/type/acro_form/signature_field')
51
52
 
52
53
  autoload(:AppearanceGenerator, 'hexapdf/type/acro_form/appearance_generator')
53
54
 
@@ -120,7 +120,7 @@ module HexaPDF
120
120
  # widget.marker_style(style: :cross)
121
121
  # # => no visible rectangle, gray background, cross mark when checked
122
122
  def create_check_box_appearances
123
- unless @widget.appearance&.normal_appearance&.value&.size == 2
123
+ unless @widget.appearance_dict&.normal_appearance&.value&.size == 2
124
124
  raise HexaPDF::Error, "Widget of check box doesn't define name for on state"
125
125
  end
126
126
  border_style = @widget.border_style
@@ -128,11 +128,11 @@ module HexaPDF
128
128
 
129
129
  rect = update_widget(@field[:V], border_width)
130
130
 
131
- off_form = @widget.appearance.normal_appearance[:Off] =
131
+ off_form = @widget.appearance_dict.normal_appearance[:Off] =
132
132
  @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
133
133
  apply_background_and_border(border_style, off_form.canvas)
134
134
 
135
- on_form = @widget.appearance.normal_appearance[@field.check_box_on_name] =
135
+ on_form = @widget.appearance_dict.normal_appearance[@field.check_box_on_name] =
136
136
  @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
137
137
  canvas = on_form.canvas
138
138
  apply_background_and_border(border_style, canvas)
@@ -169,22 +169,22 @@ module HexaPDF
169
169
  # widget.marker_style(style: :circle, size: 0, color: 0)
170
170
  # # => default appearance
171
171
  def create_radio_button_appearances
172
- unless @widget.appearance&.normal_appearance&.value&.size == 2
172
+ unless @widget.appearance_dict&.normal_appearance&.value&.size == 2
173
173
  raise HexaPDF::Error, "Widget of radio button doesn't define unique name for on state"
174
174
  end
175
175
 
176
- on_name = (@widget.appearance.normal_appearance.value.keys - [:Off]).first
176
+ on_name = (@widget.appearance_dict.normal_appearance.value.keys - [:Off]).first
177
177
  border_style = @widget.border_style
178
178
  marker_style = @widget.marker_style
179
179
 
180
180
  rect = update_widget(@field[:V] == on_name ? on_name : :Off, border_style.width)
181
181
 
182
- off_form = @widget.appearance.normal_appearance[:Off] =
182
+ off_form = @widget.appearance_dict.normal_appearance[:Off] =
183
183
  @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
184
184
  apply_background_and_border(border_style, off_form.canvas,
185
185
  circular: marker_style.style == :circle)
186
186
 
187
- on_form = @widget.appearance.normal_appearance[on_name] =
187
+ on_form = @widget.appearance_dict.normal_appearance[on_name] =
188
188
  @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
189
189
  canvas = on_form.canvas
190
190
  apply_background_and_border(border_style, canvas,
@@ -219,17 +219,8 @@ module HexaPDF
219
219
  #
220
220
  # Note: Multiline, comb and rich text fields are currently not supported!
221
221
  def create_text_appearances
222
- font_name, font_size = @field.parse_default_appearance_string
223
222
  default_resources = @document.acro_form.default_resources
224
- font = default_resources.font(font_name).font_wrapper rescue nil
225
- unless font
226
- fallback_font_name, fallback_font_options = @document.config['acro_form.fallback_font']
227
- if fallback_font_name
228
- font = @document.fonts.add(fallback_font_name, **(fallback_font_options || {}))
229
- else
230
- raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
231
- end
232
- end
223
+ font, font_size = retrieve_font_information(default_resources)
233
224
  style = HexaPDF::Layout::Style.new(font: font)
234
225
  border_style = @widget.border_style
235
226
  padding = [1, border_style.width].max
@@ -245,6 +236,9 @@ module HexaPDF
245
236
  end
246
237
 
247
238
  form = (@widget[:AP] ||= {})[:N] ||= @document.add({Type: :XObject, Subtype: :Form})
239
+ # Wrap existing object in Form class in case the PDF writer didn't include the /Subtype
240
+ # key; we can do this since we know this has to be a Form object
241
+ form = @document.wrap(form, type: :XObject, subtype: :Form) unless form[:Subtype] == :Form
248
242
  form.value.replace({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
249
243
  form.contents = ''
250
244
  form[:Resources] = HexaPDF::Object.deep_copy(default_resources)
@@ -479,6 +473,27 @@ module HexaPDF
479
473
  end
480
474
  end
481
475
 
476
+ # Returns the font wrapper and font size to be used for a variable text field.
477
+ def retrieve_font_information(resources)
478
+ font_name, font_size = @field.parse_default_appearance_string
479
+ font_object = resources.font(font_name) rescue nil
480
+ font = font_object&.font_wrapper
481
+ unless font
482
+ fallback_font = @document.config['acro_form.fallback_font']
483
+ fallback_font_name, fallback_font_options = if fallback_font.respond_to?(:call)
484
+ fallback_font.call(@field, font_object)
485
+ else
486
+ fallback_font
487
+ end
488
+ if fallback_font_name
489
+ font = @document.fonts.add(fallback_font_name, **(fallback_font_options || {}))
490
+ else
491
+ raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
492
+ end
493
+ end
494
+ [font, font_size]
495
+ end
496
+
482
497
  # Calculates the font size for text fields based on the font and font size of the default
483
498
  # appearance string, the annotation rectangle and the border style.
484
499
  def calculate_font_size(font, font_size, rect, border_style)
@@ -184,7 +184,7 @@ module HexaPDF
184
184
  #
185
185
  # Defaults to :Yes if no other name could be determined.
186
186
  def check_box_on_name
187
- each_widget.to_a.first&.appearance&.normal_appearance&.value&.each_key&.
187
+ each_widget.to_a.first&.appearance_dict&.normal_appearance&.value&.each_key&.
188
188
  find {|key| key != :Off } || :Yes
189
189
  end
190
190
 
@@ -192,7 +192,7 @@ module HexaPDF
192
192
  # button.
193
193
  def radio_button_values
194
194
  each_widget.map do |widget|
195
- widget.appearance&.normal_appearance&.value&.each_key&.find {|key| key != :Off }
195
+ widget.appearance_dict&.normal_appearance&.value&.each_key&.find {|key| key != :Off }
196
196
  end.compact
197
197
  end
198
198
 
@@ -233,7 +233,11 @@ module HexaPDF
233
233
  def create_appearances(force: false)
234
234
  appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
235
235
  each_widget do |widget|
236
- next if !force && widget.appearance?
236
+ normal_appearance = widget.appearance_dict&.normal_appearance
237
+ next if !force && normal_appearance &&
238
+ ((!push_button? && normal_appearance.value.length == 2 &&
239
+ normal_appearance.value.each_value.all?(HexaPDF::Stream)) ||
240
+ (push_button? && normal_appearance.kind_of?(HexaPDF::Stream)))
237
241
  if check_box?
238
242
  appearance_generator_class.new(widget).create_check_box_appearances
239
243
  elsif radio_button?
@@ -250,7 +254,7 @@ module HexaPDF
250
254
  create_appearances
251
255
  value = self[:V]
252
256
  each_widget do |widget|
253
- widget[:AS] = (widget.appearance&.normal_appearance&.value&.key?(value) ? value : :Off)
257
+ widget[:AS] = (widget.appearance_dict&.normal_appearance&.key?(value) ? value : :Off)
254
258
  end
255
259
  end
256
260
 
@@ -315,6 +315,7 @@ module HexaPDF
315
315
 
316
316
  if embedded_widget?
317
317
  WIDGET_FIELDS.each {|key| delete(key) }
318
+ document.revisions.each {|revision| break if revision.update(self)}
318
319
  else
319
320
  self[:Kids].delete_at(widget_index)
320
321
  document.delete(widget)
@@ -331,6 +331,43 @@ module HexaPDF
331
331
  end
332
332
  end
333
333
 
334
+ # Flattens the whole interactive form or only the given fields, and returns the fields that
335
+ # couldn't be flattened.
336
+ #
337
+ # Flattening means making the appearance streams of the field widgets part of the respective
338
+ # page's content stream and removing the fields themselves.
339
+ #
340
+ # If the whole interactive form is flattened, the form object itself is also removed if all
341
+ # fields were flattened.
342
+ #
343
+ # The +create_appearances+ argument controls whether missing appearances should
344
+ # automatically be created.
345
+ #
346
+ # See: HexaPDF::Type::Page#flatten_annotations
347
+ def flatten(fields: nil, create_appearances: true)
348
+ remove_form = fields.nil?
349
+ fields ||= each_field.to_a
350
+ if create_appearances
351
+ fields.each {|field| field.create_appearances if field.respond_to?(:create_appearances) }
352
+ end
353
+
354
+ not_flattened = fields.map {|field| field.each_widget.to_a }.flatten
355
+ document.pages.each {|page| not_flattened = page.flatten_annotations(not_flattened) }
356
+ fields -= not_flattened.map(&:form_field)
357
+
358
+ fields.each do |field|
359
+ (field[:Parent]&.[](:Kids) || self[:Fields]).delete(field)
360
+ document.delete(field)
361
+ end
362
+
363
+ if remove_form && not_flattened.empty?
364
+ document.catalog.delete(:AcroForm)
365
+ document.delete(self)
366
+ end
367
+
368
+ not_flattened
369
+ end
370
+
334
371
  private
335
372
 
336
373
  # Helper method for bit field getter access.
@@ -0,0 +1,223 @@
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-2021 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/acro_form/field'
38
+ require 'hexapdf/type/acro_form/appearance_generator'
39
+
40
+ module HexaPDF
41
+ module Type
42
+ module AcroForm
43
+
44
+ # AcroForm signature fields represent a digital signature.
45
+ #
46
+ # It serves two purposes: To visually display the signature and to hold the information of the
47
+ # digital signature itself.
48
+ #
49
+ # If the signature should not be visible, the associated widget annotation should have zero
50
+ # width and height; and/or the 'hidden' or 'no_view' flags of the annotation should be set.
51
+ #
52
+ # See: PDF1.7 s12.7.4.5
53
+ class SignatureField < Field
54
+
55
+ # A signature field lock dictionary specifies a set of form fields that should be locked
56
+ # once the associated signature field is signed.
57
+ #
58
+ # See: PDF1.7 s12.7.4.5
59
+ class LockDictionary < Dictionary
60
+
61
+ define_type :SigFieldLock
62
+
63
+ define_field :Type, type: Symbol, default: type
64
+ define_field :Action, type: Symbol, required: true,
65
+ allowed_values: [:All, :Include, :Exclude]
66
+ define_field :Fields, type: PDFArray
67
+
68
+ private
69
+
70
+ def perform_validation #:nodoc:
71
+ if self[:Action] != :All && !key?(:Fields)
72
+ yield("The /Fields key of the signature lock dictionary is missing")
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ # A seed value dictionary contains information that constrains the properties of a signature
79
+ # that is applied to the associated signature field.
80
+ #
81
+ # == Flags
82
+ #
83
+ # If a flag is set it means that the associated entry is a required constraint. Otherwise it
84
+ # is optional.
85
+ #
86
+ # The available flags are: filter, sub_filter, v, reasons, legal_attestation, add_rev_info
87
+ # and digest_method.
88
+ #
89
+ # See: PDF1.7 s12.7.4.5
90
+ class SeedValueDictionary < Dictionary
91
+
92
+ extend Utils::BitField
93
+
94
+ define_type :SV
95
+
96
+ define_field :Type, type: Symbol, default: type
97
+ define_field :Ff, type: Integer, default: 0
98
+ define_field :Filter, type: Symbol
99
+ define_field :SubFilter, type: PDFArray
100
+ define_field :DigestMethod, type: PDFArray, version: '1.7'
101
+ define_field :V, type: Float
102
+ define_field :Cert, type: :SVCert
103
+ define_field :Reasons, type: PDFArray
104
+ define_field :MDP, type: Dictionary, version: '1.6'
105
+ define_field :TimeStamp, type: Dictionary, version: '1.6'
106
+ define_field :LegalAttestation, type: PDFArray, version: '1.6'
107
+ define_field :AddRevInfo, type: Boolean, version: '1.7'
108
+
109
+ ##
110
+ # :method: flags
111
+ #
112
+ # Returns an array of flag names representing the set bit flags.
113
+ #
114
+
115
+ ##
116
+ # :method: flagged?
117
+ # :call-seq:
118
+ # flagged?(flag)
119
+ #
120
+ # Returns +true+ if the given flag is set. The argument can either be the flag name or the
121
+ # bit index.
122
+ #
123
+
124
+ ##
125
+ # :method: flag
126
+ # :call-seq:
127
+ # flag(*flags, clear_existing: false)
128
+ #
129
+ # Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
130
+ # all prior flags will be cleared.
131
+ #
132
+ bit_field(:flags, {filter: 0, sub_filter: 1, v: 2, reasons: 3, legal_attestation: 4,
133
+ add_rev_info: 5, digest_method: 6},
134
+ lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
135
+ value_getter: "self[:Ff]", value_setter: "self[:Ff]")
136
+
137
+ end
138
+
139
+ # A certificate seed value dictionary contains information about the characteristics of the
140
+ # certificate that shall be used when signing.
141
+ #
142
+ # == Flags
143
+ #
144
+ # The flags describe the entries that a signer is required to use.
145
+ #
146
+ # The available flags are: subject, issuer, oid, subject_dn, reserved, key_usage and url.
147
+ #
148
+ # See: PDF1.7 s12.7.4.5
149
+ class CertificateSeedValueDictionary < Dictionary
150
+
151
+ extend Utils::BitField
152
+
153
+ define_type :SVCert
154
+
155
+ define_field :Type, type: Symbol, default: type
156
+ define_field :Ff, type: Integer, default: 0
157
+ define_field :Subject, type: PDFArray
158
+ define_field :SubjectDN, type: PDFArray, version: '1.7'
159
+ define_field :KeyUsage, type: PDFArray, version: '1.7'
160
+ define_field :Issuer, type: PDFArray
161
+ define_field :OID, type: PDFArray
162
+ define_field :URL, type: String
163
+ define_field :URLType, type: Symbol, default: :Browser
164
+
165
+ ##
166
+ # :method: flags
167
+ #
168
+ # Returns an array of flag names representing the set bit flags.
169
+ #
170
+
171
+ ##
172
+ # :method: flagged?
173
+ # :call-seq:
174
+ # flagged?(flag)
175
+ #
176
+ # Returns +true+ if the given flag is set. The argument can either be the flag name or the
177
+ # bit index.
178
+ #
179
+
180
+ ##
181
+ # :method: flag
182
+ # :call-seq:
183
+ # flag(*flags, clear_existing: false)
184
+ #
185
+ # Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
186
+ # all prior flags will be cleared.
187
+ #
188
+ bit_field(:flags, {subject: 0, issuer: 1, oid: 2, subject_dn: 3, reserved: 4,
189
+ key_usage: 5, url: 6},
190
+ lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
191
+ value_getter: "self[:Ff]", value_setter: "self[:Ff]")
192
+
193
+ end
194
+
195
+ define_field :Lock, type: :SigFieldLock, indirect: true, version: '1.5'
196
+ define_field :SV, type: :SV, indirect: true, version: '1.5'
197
+
198
+ # Returns the associated signature dictionary or +nil+ if the signature is not filled in.
199
+ def field_value
200
+ self[:V]
201
+ end
202
+
203
+ # Sets the signature dictionary as value of this signature field.
204
+ def field_value=(sig_dict)
205
+ self[:V] = sig_dict
206
+ end
207
+
208
+ private
209
+
210
+ def perform_validation #:nodoc:
211
+ if field_type != :Sig
212
+ yield("Field /FT of AcroForm signature field has to be :Sig", true)
213
+ self[:FT] = :Sig
214
+ end
215
+
216
+ super
217
+ end
218
+
219
+ end
220
+
221
+ end
222
+ end
223
+ end