Class: CorpPdf::Actions::UpdateField
- Inherits:
-
Object
- Object
- CorpPdf::Actions::UpdateField
- Includes:
- Base
- Defined in:
- lib/corp_pdf/actions/update_field.rb
Overview
Action to update a field’s value and optionally rename it in a PDF document
Instance Method Summary collapse
- #call ⇒ Object
-
#initialize(document, name, new_value, new_name: nil) ⇒ UpdateField
constructor
A new instance of UpdateField.
Methods included from Base
#acroform_ref, #apply_patch, #find_page_by_number, #get_object_body_with_patch, #next_fresh_object_number, #patches, #resolver
Constructor Details
#initialize(document, name, new_value, new_name: nil) ⇒ UpdateField
Returns a new instance of UpdateField.
9 10 11 12 13 14 |
# File 'lib/corp_pdf/actions/update_field.rb', line 9 def initialize(document, name, new_value, new_name: nil) @document = document @name = name @new_value = new_value @new_name = new_name end |
Instance Method Details
#call ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/corp_pdf/actions/update_field.rb', line 16 def call # First try to find in list_fields (already written fields) fld = @document.list_fields.find { |f| f.name == @name } # If not found, check if field was just added (in patches) and create a Field object for it unless fld patches = @document.instance_variable_get(:@patches) field_patch = patches.find do |p| next unless p[:body] next unless p[:body].include?("/T") t_tok = DictScan.value_token_after("/T", p[:body]) next unless t_tok field_name = DictScan.decode_pdf_string(t_tok) field_name == @name end if field_patch && field_patch[:body].include?("/FT") ft_tok = DictScan.value_token_after("/FT", field_patch[:body]) if ft_tok # Create a temporary Field object for newly added field position = {} fld = Field.new(@name, nil, ft_tok, field_patch[:ref], @document, position) end end end return false unless fld # Check if this is a signature field and if new_value looks like image data if fld.signature_field? # Check if new_value looks like base64 image data or data URI image_data = @new_value if image_data && image_data.is_a?(String) && (image_data.start_with?("data:image/") || (image_data.length > 50 && image_data.match?(%r{^[A-Za-z0-9+/]*={0,2}$}))) # Try adding signature appearance using Signature field class result = CorpPdf::Fields::Signature.add_appearance(@document, fld.ref, image_data) return result if result # If appearance fails, fall through to normal update end end original = get_object_body_with_patch(fld.ref) return false unless original # Determine if this is a widget annotation or field object = original.include?("/Subtype /Widget") field_ref = fld.ref # Default: the ref we found is the field # If this is a widget, we need to also update the parent field object (if it exists) # Otherwise, this widget IS the field (flat structure) if parent_tok = DictScan.value_token_after("/Parent", original) if parent_tok && parent_tok =~ /\A(\d+)\s+(\d+)\s+R/ field_ref = [Integer(::Regexp.last_match(1)), Integer(::Regexp.last_match(2))] field_body = get_object_body_with_patch(field_ref) if field_body && !field_body.include?("/Subtype /Widget") new_field_body = patch_field_value_body(field_body, @new_value) # Check if multiline and remove appearance stream from parent field too is_multiline = DictScan.is_multiline_field?(field_body) || DictScan.is_multiline_field?(new_field_body) if is_multiline new_field_body = DictScan.remove_appearance_stream(new_field_body) end if new_field_body && new_field_body.include?("<<") && new_field_body.include?(">>") apply_patch(field_ref, new_field_body, field_body) end end end end # Update the object we found (widget or field) - always update what we found new_body = patch_field_value_body(original, @new_value) # Check if this is a multiline field - if so, remove appearance stream # macOS Preview needs appearance streams to be regenerated for multiline fields is_multiline = check_if_multiline_field(field_ref) if is_multiline new_body = DictScan.remove_appearance_stream(new_body) end # Update field name (/T) if requested if @new_name && !@new_name.empty? new_body = patch_field_name_body(new_body, @new_name) end # Validate the patched body is valid before adding to patches unless new_body && new_body.include?("<<") && new_body.include?(">>") warn "Warning: Invalid patched body for #{fld.ref.inspect}, skipping update" return false end apply_patch(fld.ref, new_body, original) # If we renamed the field, also update the parent field object and all widgets if @new_name && !@new_name.empty? # Update parent field object if it exists (separate from widget) if field_ref != fld.ref field_body = get_object_body_with_patch(field_ref) if field_body && !field_body.include?("/Subtype /Widget") new_field_body = patch_field_name_body(field_body, @new_name) if new_field_body && new_field_body.include?("<<") && new_field_body.include?(">>") apply_patch(field_ref, new_field_body, field_body) end end end # Update all widget annotations that reference this field (field_ref, @new_name) end # Also update any widget annotations that reference this field via /Parent (field_ref, @new_value) # If this is a checkbox without appearance streams, create them if fld. # Check if it's a checkbox (not a radio button) by checking field flags field_body = get_object_body_with_patch(field_ref) is_radio = false if field_body field_flags_match = field_body.match(%r{/Ff\s+(\d+)}) if field_flags_match field_flags = field_flags_match[1].to_i # Radio button flag is bit 15 = 32768 is_radio = field_flags.anybits?(32_768) end end if is_radio # For radio buttons, update all widget appearances (overwrite existing) (field_ref) else # For checkboxes, create/update appearance = (fld.ref) if = get_object_body_with_patch() # Create appearances if /AP doesn't exist, or overwrite if it does rect = () if rect && rect[:width].positive? && rect[:height].positive? add_checkbox_appearance(, rect[:width], rect[:height]) end end end end # Best-effort: set NeedAppearances to true so viewers regenerate appearances ensure_need_appearances true end |