acro_that 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56f6be44d023bdedaf254cc097d18791f284793d0c79c07847fd462c0643cc91
4
- data.tar.gz: b649d290433fac6a6113a74ca47bfde60e31687f6e93b85e30c32053ee416b3a
3
+ metadata.gz: ffb2119b2d0c114ad029baeff45ebcc155c660ef16a9a8ed844a9a9759861b67
4
+ data.tar.gz: 4d1101c45ad53eb66cf9ef16851e182220bce3b5862fe99f2a3e7c0af8d5dd04
5
5
  SHA512:
6
- metadata.gz: b27c5ec88a2cfdec49750d9d3316979c90c0815461eb6281b4cf602dc6533c8761c8fba7fb49407256b9ade87f2f3731e8f9d121c4ef089d08be2901107cc295
7
- data.tar.gz: f1b41cef40049564af8f081547460d72e787a721f676db4c80cd74eb21844b48364ac3b871e6ad2839505908d9e5be3b8d643c16812bd91ae6d0e858bcfb51eb
6
+ metadata.gz: 1ee4b2210fabe9d92f7296fc2d093dd281704e932e6f885b96e28a5ac470a96966180086a6689e9c8dff37bf2d35f21262a2a45f5e73a7070b9a9b1f90d8dabc
7
+ data.tar.gz: 4a23c547e4d42fad651be35ed769abf42ef308bcf6083ae11c15b7535f972d24b55afdf583a0396c4477819d2e0b7a6ef3169860b2e747206a5f9ed2fb3a5ee6
data/CHANGELOG.md CHANGED
@@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.1.3] - 2025-01-XX
8
+ ## [0.1.5] - 2025-11-01
9
+
10
+ ### Fixed
11
+ - Fixed signature field image data parsing when adding signature fields. Image data (base64 or data URI) is now properly detected and parsed when creating signature fields, matching the behavior of `update_field`.
12
+
13
+ ### Added
14
+ - Added support for `metadata` option in `add_field` to pass PDF widget properties. This allows setting properties like field flags (`Ff`) for multiline text fields, alignment (`Q`), and other PDF widget options directly when creating fields.
15
+
16
+ ## [0.1.4] - 2025-11-01
9
17
 
10
18
  ### Fixed
11
19
  - Fixed bug where fields added to multi-page PDFs were all placed on the same page. Fields now correctly appear on their specified pages when using the `page` option in `add_field`.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acro_that (0.1.2)
4
+ acro_that (0.1.4)
5
5
  chunky_png (~> 1.4)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -234,6 +234,32 @@ fields.each do |field|
234
234
  end
235
235
  ```
236
236
 
237
+ #### `#list_pages`
238
+ Returns an array of `Page` objects representing all pages in the document. Each `Page` object provides page information and methods to add fields to that specific page.
239
+
240
+ ```ruby
241
+ pages = doc.list_pages
242
+ pages.each do |page|
243
+ puts "Page #{page.page_number}: #{page.width}x#{page.height}"
244
+ end
245
+
246
+ # Add fields to specific pages - the page is automatically set!
247
+ first_page = pages[0]
248
+ first_page.add_field("Name", x: 100, y: 700, width: 200, height: 20)
249
+
250
+ second_page = pages[1]
251
+ second_page.add_field("Email", x: 100, y: 650, width: 200, height: 20)
252
+ ```
253
+
254
+ **Page Object Methods:**
255
+ - `page.page_number` - Returns the page number (1-indexed)
256
+ - `page.width` - Page width in points
257
+ - `page.height` - Page height in points
258
+ - `page.ref` - Page object reference `[obj_num, gen_num]`
259
+ - `page.metadata` - Hash containing page metadata (rotation, boxes, etc.)
260
+ - `page.add_field(name, options)` - Add a field to this page (page number is automatically set)
261
+ - `page.to_h` - Convert to hash for backward compatibility
262
+
237
263
  #### `#add_field(name, options)`
238
264
  Adds a new form field to the document. Options include:
239
265
  - `value`: Default value for the field (String)
@@ -12,6 +12,7 @@ module AcroThat
12
12
  @document = document
13
13
  @name = name
14
14
  @options = options
15
+ @metadata = options[:metadata] || {}
15
16
  end
16
17
 
17
18
  def call
@@ -60,6 +61,19 @@ module AcroThat
60
61
  # Add widget to the target page's /Annots
61
62
  add_widget_to_page(widget_obj_num, page_num)
62
63
 
64
+ # If this is a signature field with image data, add the signature appearance
65
+ if @field_type == "/Sig" && @field_value && !@field_value.empty?
66
+ image_data = @field_value
67
+ # Check if value looks like base64 image data or data URI (same logic as update_field)
68
+ 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}$})))
69
+ field_ref = [@field_obj_num, 0]
70
+ # Try adding signature appearance - use width and height from options
71
+ action = Actions::AddSignatureAppearance.new(@document, field_ref, image_data, width: width, height: height)
72
+ # NOTE: We don't fail if appearance addition fails - field was still created successfully
73
+ action.call
74
+ end
75
+ end
76
+
63
77
  true
64
78
  end
65
79
 
@@ -69,9 +83,33 @@ module AcroThat
69
83
  dict = "<<\n"
70
84
  dict += " /FT #{type}\n"
71
85
  dict += " /T #{DictScan.encode_pdf_string(@name)}\n"
72
- dict += " /Ff 0\n"
86
+
87
+ # Apply /Ff from metadata, or use default 0
88
+ field_flags = @metadata[:Ff] || @metadata["Ff"] || 0
89
+ dict += " /Ff #{field_flags}\n"
90
+
73
91
  dict += " /DA (/Helv 0 Tf 0 g)\n"
74
- dict += " /V #{DictScan.encode_pdf_string(value)}\n" if value && !value.empty?
92
+
93
+ # For signature fields with image data, don't set /V (appearance stream will be added separately)
94
+ # For other fields or non-image signature values, set /V normally
95
+ should_set_value = if type == "/Sig" && value && !value.empty?
96
+ # Check if value looks like image data
97
+ !(value.is_a?(String) && (value.start_with?("data:image/") || (value.length > 50 && value.match?(%r{^[A-Za-z0-9+/]*={0,2}$}))))
98
+ else
99
+ true
100
+ end
101
+
102
+ dict += " /V #{DictScan.encode_pdf_string(value)}\n" if should_set_value && value && !value.empty?
103
+
104
+ # Apply other metadata entries (excluding Ff which we handled above)
105
+ @metadata.each do |key, val|
106
+ next if [:Ff, "Ff"].include?(key) # Already handled above
107
+
108
+ pdf_key = format_pdf_key(key)
109
+ pdf_value = format_pdf_value(val)
110
+ dict += " #{pdf_key} #{pdf_value}\n"
111
+ end
112
+
75
113
  dict += ">>"
76
114
  dict
77
115
  end
@@ -87,7 +125,29 @@ module AcroThat
87
125
  widget += " /Rect #{rect_array}\n"
88
126
  widget += " /F 4\n"
89
127
  widget += " /DA (/Helv 0 Tf 0 g)\n"
90
- widget += " /V #{DictScan.encode_pdf_string(value)}\n" if value && !value.empty?
128
+
129
+ # For signature fields with image data, don't set /V (appearance stream will be added separately)
130
+ # For other fields or non-image signature values, set /V normally
131
+ should_set_value = if type == "/Sig" && value && !value.empty?
132
+ # Check if value looks like image data
133
+ !(value.is_a?(String) && (value.start_with?("data:image/") || (value.length > 50 && value.match?(%r{^[A-Za-z0-9+/]*={0,2}$}))))
134
+ else
135
+ true
136
+ end
137
+
138
+ widget += " /V #{DictScan.encode_pdf_string(value)}\n" if should_set_value && value && !value.empty?
139
+
140
+ # Apply metadata entries that are valid for widgets
141
+ # Common widget properties: /Q (alignment), /Ff (field flags), /BS (border style), etc.
142
+ @metadata.each do |key, val|
143
+ pdf_key = format_pdf_key(key)
144
+ pdf_value = format_pdf_value(val)
145
+ # Only add if not already present (we've added /F above, /V above if value exists)
146
+ next if ["/F", "/V"].include?(pdf_key)
147
+
148
+ widget += " #{pdf_key} #{pdf_value}\n"
149
+ end
150
+
91
151
  widget += ">>"
92
152
  widget
93
153
  end
@@ -220,6 +280,42 @@ module AcroThat
220
280
  apply_patch(target_page_ref, new_body, page_body) if new_body && new_body != page_body
221
281
  true
222
282
  end
283
+
284
+ # Format a metadata key as a PDF dictionary key (ensure it starts with /)
285
+ def format_pdf_key(key)
286
+ key_str = key.to_s
287
+ key_str.start_with?("/") ? key_str : "/#{key_str}"
288
+ end
289
+
290
+ # Format a metadata value appropriately for PDF
291
+ def format_pdf_value(value)
292
+ case value
293
+ when Integer, Float
294
+ value.to_s
295
+ when String
296
+ # If it looks like a PDF string (starts with parenthesis or angle bracket), use as-is
297
+ if value.start_with?("(") || value.start_with?("<") || value.start_with?("/")
298
+ value
299
+ else
300
+ # Otherwise encode as a PDF string
301
+ DictScan.encode_pdf_string(value)
302
+ end
303
+ when Array
304
+ # Array format: [item1 item2 item3]
305
+ items = value.map { |v| format_pdf_value(v) }.join(" ")
306
+ "[#{items}]"
307
+ when Hash
308
+ # Dictionary format: << /Key1 value1 /Key2 value2 >>
309
+ dict = value.map do |k, v|
310
+ pdf_key = format_pdf_key(k)
311
+ pdf_val = format_pdf_value(v)
312
+ " #{pdf_key} #{pdf_val}"
313
+ end.join("\n")
314
+ "<<\n#{dict}\n>>"
315
+ else
316
+ value.to_s
317
+ end
318
+ end
223
319
  end
224
320
  end
225
321
  end
@@ -179,13 +179,14 @@ module AcroThat
179
179
  contents_refs: contents_refs
180
180
  }
181
181
 
182
- pages << {
183
- page: index + 1, # Page number starting at 1
184
- width: width,
185
- height: height,
186
- ref: ref,
187
- metadata: metadata
188
- }
182
+ pages << Page.new(
183
+ index + 1, # Page number starting at 1
184
+ width,
185
+ height,
186
+ ref,
187
+ metadata,
188
+ self # Pass document reference
189
+ )
189
190
  end
190
191
 
191
192
  pages
@@ -202,7 +203,7 @@ module AcroThat
202
203
  next unless body
203
204
 
204
205
  is_widget = DictScan.is_widget?(body)
205
-
206
+
206
207
  # Collect widget information if this is a widget
207
208
  if is_widget
208
209
  # Extract position from widget
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcroThat
4
+ # Represents a page in a PDF document
5
+ class Page
6
+ attr_reader :page, :width, :height, :ref, :metadata, :document
7
+
8
+ def initialize(page, width, height, ref, metadata, document)
9
+ @page = page # Page number (1-indexed)
10
+ @width = width
11
+ @height = height
12
+ @ref = ref # [obj_num, gen_num]
13
+ @metadata = metadata # Hash with :rotate, :media_box, :crop_box, etc.
14
+ @document = document
15
+ end
16
+
17
+ # Add a field to this page
18
+ # Options are the same as Document#add_field, but :page is automatically set
19
+ def add_field(name, options = {})
20
+ # Automatically set the page number to this page
21
+ options_with_page = options.merge(page: @page)
22
+ @document.add_field(name, options_with_page)
23
+ end
24
+
25
+ # Get the page number
26
+ def page_number
27
+ @page
28
+ end
29
+
30
+ # Get the page reference [obj_num, gen_num]
31
+ def page_ref
32
+ @ref
33
+ end
34
+
35
+ # Check if page has rotation
36
+ def rotated?
37
+ !@metadata[:rotate].nil? && @metadata[:rotate] != 0
38
+ end
39
+
40
+ # Get rotation angle (0, 90, 180, 270)
41
+ def rotation
42
+ @metadata[:rotate] || 0
43
+ end
44
+
45
+ # Get MediaBox dimensions
46
+ def media_box
47
+ @metadata[:media_box]
48
+ end
49
+
50
+ # Get CropBox dimensions
51
+ def crop_box
52
+ @metadata[:crop_box]
53
+ end
54
+
55
+ # Get ArtBox dimensions
56
+ def art_box
57
+ @metadata[:art_box]
58
+ end
59
+
60
+ # Get BleedBox dimensions
61
+ def bleed_box
62
+ @metadata[:bleed_box]
63
+ end
64
+
65
+ # Get TrimBox dimensions
66
+ def trim_box
67
+ @metadata[:trim_box]
68
+ end
69
+
70
+ # String representation for debugging
71
+ def to_s
72
+ dims = width && height ? " #{width}x#{height}" : ""
73
+ rot = rotated? ? " (rotated #{rotation}°)" : ""
74
+ "#<AcroThat::Page page=#{page}#{dims}#{rot} ref=#{ref.inspect}>"
75
+ end
76
+
77
+ alias inspect to_s
78
+
79
+ # Convert to hash for backward compatibility
80
+ def to_h
81
+ {
82
+ page: @page,
83
+ width: @width,
84
+ height: @height,
85
+ ref: @ref,
86
+ metadata: @metadata
87
+ }
88
+ end
89
+ end
90
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcroThat
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
data/lib/acro_that.rb CHANGED
@@ -12,6 +12,7 @@ require_relative "acro_that/objstm"
12
12
  require_relative "acro_that/pdf_writer"
13
13
  require_relative "acro_that/incremental_writer"
14
14
  require_relative "acro_that/field"
15
+ require_relative "acro_that/page"
15
16
  require_relative "acro_that/document"
16
17
 
17
18
  # Load actions
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acro_that
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Wynkoop
@@ -116,6 +116,7 @@ files:
116
116
  - lib/acro_that/incremental_writer.rb
117
117
  - lib/acro_that/object_resolver.rb
118
118
  - lib/acro_that/objstm.rb
119
+ - lib/acro_that/page.rb
119
120
  - lib/acro_that/pdf_writer.rb
120
121
  - lib/acro_that/version.rb
121
122
  - publish