Class: CorpPdf::Fields::Signature

Inherits:
Object
  • Object
show all
Includes:
Base
Defined in:
lib/corp_pdf/fields/signature.rb

Overview

Handles signature field creation

Instance Attribute Summary collapse

Attributes included from Base

#document, #field_type, #field_value, #metadata, #name, #options

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Base

#height, #initialize, #page_num, #width, #x, #y

Methods included from Actions::Base

#acroform_ref, #apply_patch, #find_page_by_number, #get_object_body_with_patch, #next_fresh_object_number, #patches, #resolver

Instance Attribute Details

#field_obj_numObject (readonly)

Returns the value of attribute field_obj_num.



11
12
13
# File 'lib/corp_pdf/fields/signature.rb', line 11

def field_obj_num
  @field_obj_num
end

Class Method Details

.add_appearance(document, field_ref, image_data, width: nil, height: nil) ⇒ Object

Class method to add appearance to an existing signature field Can be called from both Signature field creation and UpdateField



15
16
17
# File 'lib/corp_pdf/fields/signature.rb', line 15

def self.add_appearance(document, field_ref, image_data, width: nil, height: nil)
  new(document, "", {}).add_appearance_to_field(field_ref, image_data, width: width, height: height)
end

Instance Method Details

#add_appearance_to_field(field_ref, image_data, width: nil, height: nil) ⇒ Object



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
# File 'lib/corp_pdf/fields/signature.rb', line 47

def add_appearance_to_field(field_ref, image_data, width: nil, height: nil)
  return false unless field_ref && image_data && !image_data.empty?

  # Decode image data if needed
  decoded_image_data = image_data.is_a?(String) && image_data.start_with?("data:") ? decode_base64_data_uri(image_data) : decode_base64_if_needed(image_data)
  return false unless decoded_image_data && !decoded_image_data.empty?

  # Detect image format and dimensions
  image_info = detect_image_format(decoded_image_data)
  return false unless image_info

  # Find widget annotation for this field
  widget_ref = find_signature_widget(field_ref)
  return false unless widget_ref

  widget_body = get_object_body_with_patch(widget_ref)
  return false unless widget_body

  # Get widget rectangle for appearance size
  rect = extract_rect(widget_body)
  return false unless rect

  # Ensure width and height are positive
  rect_width = (rect[:x2] - rect[:x1]).abs
  rect_height = (rect[:y2] - rect[:y1]).abs
  return false if rect_width <= 0 || rect_height <= 0

  # Get field dimensions (use provided width/height or field rect)
  field_width = width || rect_width
  field_height = height || rect_height

  # Get image natural dimensions
  image_width = image_info[:width].to_f
  image_height = image_info[:height].to_f
  return false if image_width <= 0 || image_height <= 0

  # Calculate scaling factor to fit image within field while maintaining aspect ratio
  scale_x = field_width / image_width
  scale_y = field_height / image_height
  scale_factor = [scale_x, scale_y].min

  # Calculate scaled dimensions (maintains aspect ratio, fits within field)
  scaled_width = image_width * scale_factor
  scaled_height = image_height * scale_factor

  # Create Image XObject(s) - use natural image dimensions (not scaled)
  image_obj_num = next_fresh_object_number
  image_result = create_image_xobject(image_obj_num, decoded_image_data, image_info, image_width, image_height)
  image_body = image_result[:body]
  mask_obj_num = image_result[:mask_obj_num]

  # Create Form XObject (appearance stream) - use field dimensions for bounding box
  form_obj_num = mask_obj_num ? mask_obj_num + 1 : image_obj_num + 1
  form_body = create_form_xobject(form_obj_num, image_obj_num, field_width, field_height, scale_factor,
                                  scaled_width, scaled_height)

  # Queue new objects
  document.instance_variable_get(:@patches) << { ref: [image_obj_num, 0], body: image_body }
  if mask_obj_num
    document.instance_variable_get(:@patches) << { ref: [mask_obj_num, 0],
                                                   body: image_result[:mask_body] }
  end
  document.instance_variable_get(:@patches) << { ref: [form_obj_num, 0], body: form_body }

  # Update widget annotation with /AP dictionary
  # Use already-loaded widget_body as original (we already have it from line 62)
  # Only reload if we don't have it (shouldn't happen, but for safety)
  original_widget_body = widget_body || resolver.object_body(widget_ref)
  updated_widget = add_appearance_to_widget(widget_body, form_obj_num)
  apply_patch(widget_ref, updated_widget, original_widget_body)

  true
end

#callObject



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
# File 'lib/corp_pdf/fields/signature.rb', line 19

def call
  @field_obj_num = next_fresh_object_number
  widget_obj_num = @field_obj_num + 1

  field_body = create_field_dictionary(@field_value, @field_type)
  page_ref = find_page_ref(page_num)

  widget_body = create_widget_annotation_with_parent(widget_obj_num, [@field_obj_num, 0], page_ref, x, y, width,
                                                     height, @field_type, @field_value)

  document.instance_variable_get(:@patches) << { ref: [@field_obj_num, 0], body: field_body }
  document.instance_variable_get(:@patches) << { ref: [widget_obj_num, 0], body: widget_body }

  add_field_to_acroform_with_defaults(@field_obj_num)
  add_widget_to_page(widget_obj_num, page_num)

  # If this is a signature field with image data, add the signature appearance
  if @field_value && !@field_value.empty?
    image_data = @field_value
    if 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}$})))
      field_ref = [@field_obj_num, 0]
      add_appearance_to_field(field_ref, image_data, width: width, height: height)
    end
  end

  true
end