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 +4 -4
- data/CHANGELOG.md +9 -1
- data/Gemfile.lock +1 -1
- data/README.md +26 -0
- data/lib/acro_that/actions/add_field.rb +99 -3
- data/lib/acro_that/document.rb +9 -8
- data/lib/acro_that/page.rb +90 -0
- data/lib/acro_that/version.rb +1 -1
- data/lib/acro_that.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffb2119b2d0c114ad029baeff45ebcc155c660ef16a9a8ed844a9a9759861b67
|
|
4
|
+
data.tar.gz: 4d1101c45ad53eb66cf9ef16851e182220bce3b5862fe99f2a3e7c0af8d5dd04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/acro_that/document.rb
CHANGED
|
@@ -179,13 +179,14 @@ module AcroThat
|
|
|
179
179
|
contents_refs: contents_refs
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
pages <<
|
|
183
|
-
|
|
184
|
-
width
|
|
185
|
-
height
|
|
186
|
-
ref
|
|
187
|
-
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
|
data/lib/acro_that/version.rb
CHANGED
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.
|
|
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
|