Module: HexaPDF::Task::MergeAcroForm
- Defined in:
- lib/hexapdf/task/merge_acro_form.rb
Overview
Task for merging an AcroForm from one PDF into another.
It takes care of
-
adding the fields to the main Type::AcroForm::Form dictionary,
-
adjusting the field names so that they are unique,
-
and merging the properties of the main AcroForm dictionary itself and adjusting field information appropriately.
Note that the pages with the fields need to be imported already.
The steps for using this task are:
-
Import the pages into the target document and add all imported pages to an array
-
Call this task using the created array of pages.
Example:
pages = doc.pages.map {|page| target.pages.add(target.import(page)) }
target.task(:merge_acro_form, source: doc, pages: pages)
Class Method Summary collapse
-
.call(doc, source:, pages:) ⇒ Object
Performs the necessary steps to merge the AcroForm fields from the
source
into the target documentdoc
. -
.fix_calculate_actions(acro_form, source_form, import_name) ⇒ Object
Fixes the calculate actions listed in the /CO entry of the main AcroForm dictionary to use the new names of the fields.
-
.merge_form_dictionary(target_form, source_form, root_field) ⇒ Object
Merges the AcroForm
source_form
into thetarget_form
and returns a mapping of old font names to new ones.
Class Method Details
.call(doc, source:, pages:) ⇒ Object
Performs the necessary steps to merge the AcroForm fields from the source
into the target document doc
.
source
-
Specifies the source PDF document the information from which should be merged into the target document.
pages
-
An array of pages that were imported from
source
and contain the widgets of the fields that should be merged.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/hexapdf/task/merge_acro_form.rb', line 74 def self.call(doc, source:, pages:) return unless source.acro_form acro_form = doc.acro_form(create: true) # Determine a unique name for root field and create root field import_name = 'merged_' + (acro_form.root_fields.select {|field| field[:T] =~ /\Amerged_\d+\z/ }. map {|field| field[:T][/\d+/].to_i }.sort.last || 0).succ.to_s root_field = doc.add({T: import_name, Kids: []}) acro_form.root_fields << root_field # Merge the main AcroForm dictionary font_name_mapping = merge_form_dictionary(acro_form, source.acro_form, root_field) font_name_re = font_name_mapping.keys.map {|name| Regexp.escape(name) }.join('|') root_field[:DA] && root_field[:DA].sub!(font_name_re, font_name_mapping) # Process all field widgets of the given pages process_calculate_actions = false signature_field_seen = false pages.each do |page| page.each_annotation do || next unless [:Subtype] == :Widget field = .form_field # Correct the font name in the default appearance string [:DA] && [:DA].sub!(font_name_re, font_name_mapping) field[:DA] && field[:DA].sub!(font_name_re, font_name_mapping) process_calculate_actions = true if field[:AA]&.[](:C) signature_field_seen = true if field.field_type == :Sig # Add to the root field field = field[:Parent] while field[:Parent] if field != root_field field[:Parent] = root_field root_field[:Kids] << field end end end # Update calculation JavaScript actions with changed field names fix_calculate_actions(acro_form, source.acro_form, import_name) if process_calculate_actions # Update signature flags if necessary if signature_field_seen && source.acro_form.signature_flag?(:signatures_exist) acro_form.signature_flag(:signatures_exist) end end |
.fix_calculate_actions(acro_form, source_form, import_name) ⇒ Object
Fixes the calculate actions listed in the /CO entry of the main AcroForm dictionary to use the new names of the fields.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/hexapdf/task/merge_acro_form.rb', line 144 def self.fix_calculate_actions(acro_form, source_form, import_name) if source_form[:CO] acro_form[:CO] ||= [] acro_form[:CO].value.concat(acro_form.document.import(source_form[:CO]).value) acro_form[:CO].each do |field| next unless (action = field[:AA]&.[](:C)) action[:JS].gsub!(/"(.*?)"/) do |match| if source_form.field_by_name($1) "\"#{import_name}.#{$1}\"" else match end end end end end |
.merge_form_dictionary(target_form, source_form, root_field) ⇒ Object
Merges the AcroForm source_form
into the target_form
and returns a mapping of old font names to new ones.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/hexapdf/task/merge_acro_form.rb', line 126 def self.merge_form_dictionary(target_form, source_form, root_field) target_resources = target_form.default_resources font_name_mapping = {} serializer = HexaPDF::Serializer.new source_form.default_resources[:Font].each do |font_name, value| new_name = target_resources.add_font(target_form.document.import(value)) font_name_mapping[serializer.serialize(font_name)] = serializer.serialize(new_name) end root_field[:DA] = target_form.document.import(source_form[:DA]) root_field[:Q] = target_form.document.import(source_form[:Q]) font_name_mapping end |