class HexaPDF::CLI::Form

Processes a PDF that contains an interactive form (AcroForm).

Private Instance Methods

apply_field_value(field, value) click to toggle source

Applies the given value to the field.

# File lib/hexapdf/cli/form.rb, line 220
def apply_field_value(field, value)
  case field.concrete_field_type
  when :single_line_text_field
    field.field_value = value
  when :combo_box, :list_box
    field.field_value = value
  when :editable_combo_box
    field.field_value = value
  when :check_box
    unless value.match?(/y(es)?|t(rue)?|f(alse)?|n(o)/)
      raise HexaPDF::Error, "Invalid input, use one of the possible values"
    end
    field.field_value = value.match?(/y(es)?|t(rue)?/)
  when :radio_button
    field.field_value = value.intern
  else
    raise "Field type not yet supported"
  end
end
each_field(doc) { |page, page_index, field| ... } click to toggle source

Iterates over all non-push button fields in page order. If a field appears on multiple pages, it is only yielded on the first page.

# File lib/hexapdf/cli/form.rb, line 242
def each_field(doc) # :yields: page, page_index, field
  seen = {}

  doc.pages.each_with_index do |page, page_index|
    page[:Annots]&.each do |annotation|
      next unless annotation[:Subtype] == :Widget
      field = annotation.form_field
      next if field.concrete_field_type == :push_button
      unless seen[field]
        yield(page, page_index, field, annotation)
        seen[field] = true
      end
    end
  end
end
fill_form(doc) click to toggle source

Fills out the form by interactively asking the user for field values.

# File lib/hexapdf/cli/form.rb, line 156
def fill_form(doc)
  current_page_index = -1
  each_field(doc) do |_page, page_index, field, _widget|
    if current_page_index != page_index
      puts "Page #{page_index + 1}"
      current_page_index = page_index
    end

    field_name = field.full_field_name +
      (field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
    concrete_field_type = field.concrete_field_type

    puts "  #{field_name}"
    puts "    └─ Current value: #{field.field_value.inspect}"

    if field.field_type == :Ch
      puts "    └─ Possible values: #{field.option_items.map(&:inspect).join(', ')}"
    elsif concrete_field_type == :radio_button
      puts "    └─ Possible values: #{field.radio_button_values.map(&:inspect).join(', ')}"
    elsif concrete_field_type == :check_box
      puts "    └─ Possible values: y(es), t(rue); n(o), f(alse)"
    end

    begin
      print "    └─ New value: "
      value = $stdin.readline.chomp
      next if value.empty?
      apply_field_value(field, value)
    rescue HexaPDF::Error => e
      puts "       ⚠  #{e.message}"
      retry
    end
  end
end
fill_form_with_template(doc) click to toggle source

Fills out the form using the data from the provided template file.

# File lib/hexapdf/cli/form.rb, line 192
def fill_form_with_template(doc)
  data = parse_template
  form = doc.acro_form
  data.each do |name, value|
    field = form.field_by_name(name)
    raise "Field '#{name}' not found in input PDF" unless field
    apply_field_value(field, value)
  end
end
list_form_fields(doc) click to toggle source

Lists all terminal form fields.

# File lib/hexapdf/cli/form.rb, line 126
def list_form_fields(doc)
  current_page_index = -1
  each_field(doc) do |_page, page_index, field, widget|
    if current_page_index != page_index
      puts "Page #{page_index + 1}"
      current_page_index = page_index
    end

    field_name = field.full_field_name +
      (field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
    concrete_field_type = field.concrete_field_type
    nice_field_type = concrete_field_type.to_s.split('_').map(&:capitalize).join(' ')
    position = "(#{widget[:Rect].left}, #{widget[:Rect].bottom})"

    puts "  #{field_name}"
    if command_parser.verbosity_info?
      printf("    └─ %-22s | %-20s\n", nice_field_type, position)
    end
    puts "    └─ #{field.field_value.inspect}"
    if command_parser.verbosity_info?
      if field.field_type == :Ch
        puts "    └─ Options: #{field.option_items.map(&:inspect).join(', ')}"
      elsif concrete_field_type == :radio_button
        puts "    └─ Options: #{field.radio_button_values.map(&:inspect).join(', ')}"
      end
    end
  end
end
parse_template() click to toggle source

Parses the data from the given template file.

# File lib/hexapdf/cli/form.rb, line 203
def parse_template
  data = {}
  scanner = StringScanner.new(File.read(@template))
  until scanner.eos?
    field_name = scanner.scan(/(\\:|[^:])*?:/)
    break unless field_name
    field_name.gsub!(/\\:/, ':')
    field_value = scanner.scan(/.*?(?=^\S|\z)/m)
    data[field_name.chop] = field_value.strip.gsub(/^\s*/, '') if field_value
  end
  if !scanner.eos? && command_parser.verbosity_warning?
    $stderr.puts "Warning: Some template could not be parsed"
  end
  data
end