lib/hexapdf/cli/form.rb in hexapdf-0.40.0 vs lib/hexapdf/cli/form.rb in hexapdf-0.41.0

- old
+ new

@@ -74,10 +74,14 @@ @generate_template = true end options.on('--flatten', 'Flatten the form fields') do @flatten = true end + options.on("--[no-]fill-read-only-fields", "Allow filling in fields that are " \ + "marked as read only. Default: false") do |read_only| + @fill_read_only_fields = read_only + end options.on("--[no-]viewer-override", "Let the PDF viewer override the visual " \ "appearance. Default: use setting from input PDF") do |need_appearances| @need_appearances = need_appearances end options.on("--[no-]incremental-save", "Append the changes instead of rewriting the " \ @@ -88,10 +92,11 @@ @password = nil @fill = false @flatten = false @generate_template = false @template = nil + @fill_read_only_fields = false @need_appearances = nil @incremental = true end def execute(in_file, out_file = nil) #:nodoc: @@ -113,10 +118,11 @@ if @template fill_form_with_template(doc) else fill_form(doc) end + doc.acro_form.recalculate_fields end if @flatten && !doc.acro_form.flatten.empty? $stderr.puts "Warning: Not all form fields could be flattened" doc.catalog.delete(:AcroForm) doc.delete(doc.acro_form) @@ -124,12 +130,16 @@ elsif @generate_template unsupported_fields = [:signature_field, :password_field] each_field(doc) do |_, _, field, _| next if unsupported_fields.include?(field.concrete_field_type) name = field.full_field_name.gsub(':', "\\:") - Array(field.field_value).each do |val| - puts "#{name}: #{val.to_s.gsub(/(\r|\r\n|\n)/, '\1 ')}" + if field.field_value + Array(field.field_value).each do |val| + puts "#{name}: #{val.to_s.gsub(/(\r|\r\n|\n)/, '\1 ')}" + end + else + puts "#{name}: " end end else list_form_fields(doc) end @@ -139,59 +149,67 @@ private # Lists all terminal form fields. def list_form_fields(doc) current_page_index = -1 - each_field(doc) do |_page, page_index, field, widget| + each_field(doc, with_seen: true) 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(' ') + size = "(#{widget[:Rect].width.round(3)}x#{widget[:Rect].height.round(3)})" position = "(#{widget[:Rect].left}, #{widget[:Rect].bottom})" field_value = if !field.field_value || concrete_field_type != :signature_field field.field_value.inspect else sig = field.field_value temp = "#{sig.signer_name} (#{sig.signing_time})" temp << " (#{sig.signing_reason})" if sig.signing_reason temp end - puts " #{field_name}" + flags = field_flags(field) + puts " #{field_name}" << (flags.empty? ? '' : " (#{flags.join(', ')})") if command_parser.verbosity_info? - printf(" └─ %-22s | %-20s\n", nice_field_type, position) + printf(" └─ %-22s | %-20s\n", nice_field_type, "#{size} #{position}") end puts " └─ #{field_value}" if command_parser.verbosity_info? if field.field_type == :Ch puts " └─ Options: #{field.option_items.map(&:inspect).join(', ')}" elsif concrete_field_type == :radio_button || concrete_field_type == :check_box puts " └─ Options: #{([:Off] + field.allowed_values).map(&:to_s).join(', ')}" end + puts " └─ Widget OID: #{widget.oid},#{widget.gen}" + if field != widget + puts " └─ Field OID: #{field.oid},#{field.gen}" + end end end end # Fills out the form by interactively asking the user for field values. def fill_form(doc) current_page_index = -1 each_field(doc) do |_page, page_index, field, _widget| + next if field.flagged?(:read_only) && !@fill_read_only_fields 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}" + flags = field_flags(field) + puts " #{field_name}" << (flags.empty? ? '' : " (#{flags.join(', ')})") puts " └─ Current value: #{field.field_value.inspect}" if field.field_type == :Ch puts " └─ Possible values: #{field.option_items.map(&:to_s).join(', ')}" elsif concrete_field_type == :radio_button @@ -219,10 +237,15 @@ data = parse_template form = doc.acro_form data.each do |name, value| field = form.field_by_name(name) raise Error, "Field '#{name}' not found in input PDF" unless field + if field.flagged?(:read_only) && !@fill_read_only_fields + puts "Ignoring field '#{name}' because it is read only and --fill-read-only-fields " \ + "is no set" + next + end apply_field_value(field, value) end end # Parses the data from the given template file. @@ -273,24 +296,30 @@ rescue StandardError raise Error, "Error while setting '#{field.full_field_name}': #{$!.message}" end # 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. - def each_field(doc) # :yields: page, page_index, field + # pages, it is only yielded on the first page if +with_seen+ is +false. + def each_field(doc, with_seen: false) # :yields: page, page_index, field seen = {} doc.pages.each_with_index do |page, page_index| page.each_annotation do |annotation| next unless annotation[:Subtype] == :Widget field = annotation.form_field next if field.concrete_field_type == :push_button - unless seen[field.full_field_name] + if with_seen || !seen[field.full_field_name] yield(page, page_index, field, annotation) seen[field.full_field_name] = true end end end + end + + # Returns an array with the flags "read only" and "required" if they are set. + def field_flags(field) + [field.flagged?(:read_only) ? "read only" : nil, + field.flagged?(:required) ? "required" : nil].compact end end end