class HexaPDF::DigitalSignature::Signatures

through HexaPDF::Document#signatures.
This class provides methods for interacting with digital signatures of a PDF file. It is used

def add(file_or_io, handler, signature: nil, write_options: {})

method. Note that +incremental+ will be automatically set to ensure proper behaviour.
The key-value pairs of this hash will be passed on to the HexaPDF::Document#write
+write_options+::

Signing::DefaultHandler.
signature and signature field objects to one's liking, see #signing_handler and
The signing handler that provides the necessary methods for signing and adjusting the
+handler+::

page.
If the signature field doesn't have a widget, a non-visible one is created on the first

that signature object is discarded.
If a signature field is provided and it already has a signature object as field value,

that if necessary.
field, a new signature field is created and added to the main AcroForm object, creating
If a signature object is provided and it is not associated with an AcroForm signature

* (In)directly specifying which signature field should be used.
* Setting values for optional signature object fields like /Reason and /Location.

+nil+. Providing a signature object or signature field provides for more control, e.g.:
Can either be a signature object (determined via the /Type key), a signature field or
+signature+::

signature) create a new document based on the given file or IO stream instead.
a correct digital signature. To modify the signed document (e.g. for adding another
given file or IO stream. Afterwards the document can't be modified anymore and still retain
This method will add a new signature to the document and write the updated document to the

Adds a signature to the document and returns the corresponding signature object.
def add(file_or_io, handler, signature: nil, write_options: {})
  if signature && signature.type != :Sig
    signature_field = signature
    signature = signature_field.field_value
  end
  signature ||= @document.add({Type: :Sig})
  # Prepare AcroForm
  form = @document.acro_form(create: true)
  form.signature_flag(:signatures_exist, :append_only)
  # Prepare signature field
  signature_field ||= form.each_field.find {|field| field.field_value == signature } ||
    form.create_signature_field(generate_field_name)
  signature_field.field_value = signature
  if signature_field.each_widget.to_a.empty?
    signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0])
  end
  # Work-around for Adobe Acrobat to recognize images (https://stackoverflow.com/a/73011571/8203541)
  signature_field.each_widget do |widget|
    next unless (resources = widget.appearance&.resources)
    resources[:XObject]&.each do |_name, entry|
      entry[:Resources] ||= {}
    end
  end
  # Prepare signature object
  handler.finalize_objects(signature_field, signature)
  signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000]
  signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding
  io = if file_or_io.kind_of?(String)
         File.open(file_or_io, 'wb+')
       else
         file_or_io
       end
  # Save the current state so that we can determine the correct /ByteRange value and set the
  # values
  start_xref, section = @document.write(io, incremental: true, **write_options)
  signature_offset, signature_length = Signing.locate_signature_dict(section, start_xref,
                                                                     signature.oid)
  io.pos = signature_offset
  signature_data = io.read(signature_length)
  io.seek(0, IO::SEEK_END)
  file_size = io.pos
  # Calculate the offsets for the /ByteRange
  contents_offset = signature_offset + signature_data.index('Contents(') + 8
  offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and >
  length2 = file_size - offset2
  signature[:ByteRange] = [0, contents_offset, offset2, length2]
  # Set the correct /ByteRange value
  signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match|
    length = match.size
    result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]"
    result.ljust(length)
  end
  # Now everything besides the /Contents value is correct, so we can read the contents for
  # signing
  io.pos = signature_offset
  io.write(signature_data)
  signature[:Contents] = handler.sign(io, signature[:ByteRange].value)
  # And now replace the /Contents value
  Signing.replace_signature_contents(signature_data, signature[:Contents])
  io.pos = signature_offset
  io.write(signature_data)
  signature
ensure
  io.close if io && io != file_or_io
end

def count

signatures.
Returns the number of signatures in the PDF document. May be zero if the document has no
def count
  each.to_a.size
end

def each

Iterates over all signatures in the order they are found in the PDF.

signatures.each -> Enumerator
signatures.each {|signature| block } -> signatures
:call-seq:
def each
  return to_enum(__method__) unless block_given?
  return [] unless (form = @document.acro_form)
  form.each_field do |field|
    yield(field.field_value) if field.field_type == :Sig && field.field_value
  end
end

def generate_field_name

Generates a field name for a signature field.
def generate_field_name
  index = (@document.acro_form.each_field.
           map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }.
           max || 0) + 1
  "Signature#{index}"
end

def initialize(document)

Creates a new Signatures object for the given PDF document.
def initialize(document)
  @document = document
end

def signing_handler(name: :default, **attributes)

configuration option. The default signing handler is Signing::DefaultHandler.
A signing handler name is mapped to a class via the 'signature.signing_handler'

Creates a signing handler with the given attributes and returns it.
def signing_handler(name: :default, **attributes)
  handler = @document.config.constantize('signature.signing_handler', name) do
    raise HexaPDF::Error, "No signing handler named '#{name}' is available"
  end
  handler.new(**attributes)
end