acrofill 0.1.0 → 0.1.2
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 +60 -0
- data/README.md +7 -5
- data/lib/acrofill/appearance.rb +17 -4
- data/lib/acrofill/document.rb +38 -24
- data/lib/acrofill/form.rb +52 -24
- data/lib/acrofill/version.rb +1 -1
- data/lib/acrofill.rb +2 -2
- metadata +7 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8192b1e65d4be56876296608eecae9371866700b8a2016d6bbedad79062fca33
|
|
4
|
+
data.tar.gz: 750c2b6ccd33d9e335933c6f5e5f906f0ca8adb0f5aa07e98a4eb6019104c0f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c081d29edbd239103797267d4f47cf2ed18ef3fa46026acae8ffc03b8d27e1b13e9477bba89c7646a554774059905bd833cd7d5df41717d95eb90ab72286d0f6
|
|
7
|
+
data.tar.gz: a0b575761f075a4fc8f7690b85ec7addee099fcf5ef29ff5f1201e72ee47fd873398645c2dd51af07476073f3cb85f8cbc7570b46af158900372201c60841098
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.2] - 2026-07-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Expanded regression coverage: `/Q 2` right alignment, per-line multiline
|
|
15
|
+
centering, flatten geometry with a non-identity `/Matrix`, checkbox
|
|
16
|
+
state stamping, pushbutton and signature fields, deep hierarchical
|
|
17
|
+
names, reference-cycle guards, and `Template` snapshot isolation.
|
|
18
|
+
No library code changes.
|
|
19
|
+
|
|
20
|
+
## [0.1.1] - 2026-07-24
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Parse failures from lazily materialized objects and parser stack
|
|
25
|
+
overflows on deeply nested objects now raise `Acrofill::Error` instead
|
|
26
|
+
of leaking `PDF::Reader::MalformedPDFError` or `SystemStackError`.
|
|
27
|
+
- The page-tree and field-tree walks are iterative, so deep linear
|
|
28
|
+
`/Pages` or `/Kids` chains no longer overflow the stack.
|
|
29
|
+
- Indirect references are dereferenced where they were previously assumed
|
|
30
|
+
direct: field `/V` in metadata, `/Rect` and `/BBox` corner values,
|
|
31
|
+
`/Matrix` elements, and the `/Q` alignment value.
|
|
32
|
+
- A checkbox/radio whose on state is literally named `no` or `false` can
|
|
33
|
+
now be checked: an exact state-name match takes precedence over the
|
|
34
|
+
false-ish uncheck heuristic.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Single-line appearance baseline placement now matches pdftk exactly:
|
|
39
|
+
`ty = (h - ascent * size) / 2`.
|
|
40
|
+
- Required Ruby version raised from `>= 3.0` to `>= 3.2` (3.0/3.1 are EOL
|
|
41
|
+
and untested in CI).
|
|
42
|
+
|
|
43
|
+
## [0.1.0] - 2026-07-24
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Initial release: pure-Ruby AcroForm filling and flattening.
|
|
48
|
+
- Text fields (`/Tx`): hierarchical names, inherited `/DA`, `/Q` alignment,
|
|
49
|
+
auto font size, shrink-to-fit, multiline word wrapping.
|
|
50
|
+
- Checkboxes and radio groups (`/Btn`) via `/V` + `/AS` state selection.
|
|
51
|
+
- Choice fields (`/Ch`).
|
|
52
|
+
- Flattening of visible widget appearances into page content.
|
|
53
|
+
- `Acrofill::Template` for parse-once, fill-many workloads.
|
|
54
|
+
- `PdfForms`-compatible entry points (`Acrofill.new`, `fill_form`, `fields`,
|
|
55
|
+
`field_names`).
|
|
56
|
+
|
|
57
|
+
[Unreleased]: https://github.com/stiig/acrofill/compare/v0.1.2...HEAD
|
|
58
|
+
[0.1.2]: https://github.com/stiig/acrofill/compare/v0.1.1...v0.1.2
|
|
59
|
+
[0.1.1]: https://github.com/stiig/acrofill/compare/v0.1.0...v0.1.1
|
|
60
|
+
[0.1.0]: https://github.com/stiig/acrofill/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -137,9 +137,10 @@ Acrofill is pure Ruby with no `eval`/`system`/native calls in its hot
|
|
|
137
137
|
path, so the worst an input can do is cause an exception — never memory
|
|
138
138
|
corruption or code execution. Specifically it defends against:
|
|
139
139
|
|
|
140
|
-
- **Cyclic
|
|
141
|
-
field tree walkers carry visited-sets,
|
|
142
|
-
or a "billion laughs" DAG,
|
|
140
|
+
- **Cyclic, exponentially-shared and deeply nested object graphs** — the
|
|
141
|
+
page tree and field tree walkers are iterative and carry visited-sets,
|
|
142
|
+
so a `/Pages` or `/Kids` cycle, a "billion laughs" DAG, or a
|
|
143
|
+
thousands-deep linear chain terminates in O(objects) instead of hanging
|
|
143
144
|
or overflowing the stack.
|
|
144
145
|
- **Content-stream injection via `/DA`** — the template's default
|
|
145
146
|
appearance string is sanitized: the font name must be a regular PDF
|
|
@@ -150,8 +151,9 @@ corruption or code execution. Specifically it defends against:
|
|
|
150
151
|
mistyped dictionaries are clamped or rejected rather than crashing the
|
|
151
152
|
writer.
|
|
152
153
|
- **Encrypted / unparseable input** — rejected up front; every failure
|
|
153
|
-
at the parse boundary surfaces as `Acrofill::Error
|
|
154
|
-
|
|
154
|
+
at the parse boundary surfaces as `Acrofill::Error` (including lazy
|
|
155
|
+
per-object parse errors and parser stack overflow on pathologically
|
|
156
|
+
nested objects), so callers only ever rescue one error type.
|
|
155
157
|
|
|
156
158
|
## Feature matrix
|
|
157
159
|
|
data/lib/acrofill/appearance.rb
CHANGED
|
@@ -27,7 +27,7 @@ module Acrofill
|
|
|
27
27
|
|
|
28
28
|
font_name, size, color_ops = parse_da(field_node)
|
|
29
29
|
base_font = base_font_for(font_name)
|
|
30
|
-
align =
|
|
30
|
+
align = alignment(field_node)
|
|
31
31
|
|
|
32
32
|
body =
|
|
33
33
|
if multiline
|
|
@@ -38,7 +38,9 @@ module Acrofill
|
|
|
38
38
|
text = printable_text(value)
|
|
39
39
|
size = [height * 0.66, 12.0].min if size.zero?
|
|
40
40
|
size = shrink_to_fit(text, base_font, size, width)
|
|
41
|
-
|
|
41
|
+
# Vertically center the ascent box, matching pdftk's baseline
|
|
42
|
+
# placement exactly: ty = (h - ascent*size) / 2.
|
|
43
|
+
ty = [(height - (size * ASCENT)) / 2.0, size * DESCENT].max
|
|
42
44
|
"#{fmt(line_x(text, base_font, size, width, align))} #{fmt(ty)} Td\n" \
|
|
43
45
|
"(#{escape_literal(text)}) Tj\n"
|
|
44
46
|
end
|
|
@@ -65,6 +67,12 @@ module Acrofill
|
|
|
65
67
|
Serializer.format_number(num.to_f)
|
|
66
68
|
end
|
|
67
69
|
|
|
70
|
+
# /Q (0 left, 1 center, 2 right), inheritable and possibly indirect.
|
|
71
|
+
def alignment(field_node)
|
|
72
|
+
align = @doc.deref(@doc.inherited_value(field_node, :Q) || @acroform[:Q])
|
|
73
|
+
align.is_a?(Integer) ? align : 0
|
|
74
|
+
end
|
|
75
|
+
|
|
68
76
|
def line_x(text, base_font, size, width, align)
|
|
69
77
|
text_width = Metrics.string_width(text, base_font, size)
|
|
70
78
|
case align
|
|
@@ -117,12 +125,17 @@ module Acrofill
|
|
|
117
125
|
lines
|
|
118
126
|
end
|
|
119
127
|
|
|
128
|
+
# Array entries may legally be indirect objects, so each corner is
|
|
129
|
+
# dereferenced; a rect that is not four numbers is unusable.
|
|
120
130
|
def normalized_rect(rect)
|
|
121
131
|
rect = @doc.deref(rect)
|
|
122
132
|
return nil unless rect.is_a?(Array) && rect.size == 4
|
|
123
133
|
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
nums = rect.map { |n| @doc.deref(n) }
|
|
135
|
+
return nil unless nums.all?(Numeric)
|
|
136
|
+
|
|
137
|
+
xs = [nums[0].to_f, nums[2].to_f].sort
|
|
138
|
+
ys = [nums[1].to_f, nums[3].to_f].sort
|
|
126
139
|
[xs[0], ys[0], xs[1], ys[1]]
|
|
127
140
|
end
|
|
128
141
|
|
data/lib/acrofill/document.rb
CHANGED
|
@@ -14,14 +14,18 @@ module Acrofill
|
|
|
14
14
|
attr_reader :objects, :trailer
|
|
15
15
|
|
|
16
16
|
def initialize(path)
|
|
17
|
-
hash =
|
|
17
|
+
hash = parse_boundary { PDF::Reader::ObjectHash.new(path) }
|
|
18
18
|
raise Error, 'encrypted PDFs are not supported' if hash.trailer[:Encrypt]
|
|
19
19
|
|
|
20
20
|
@objects = {}
|
|
21
21
|
@max_oid = 0
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# ObjectHash parses object bodies lazily, so the iteration that
|
|
23
|
+
# materializes them must also stay inside the parse boundary.
|
|
24
|
+
parse_boundary do
|
|
25
|
+
hash.each do |ref, obj|
|
|
26
|
+
@objects[ref.id] = obj
|
|
27
|
+
@max_oid = ref.id if ref.id > @max_oid
|
|
28
|
+
end
|
|
25
29
|
end
|
|
26
30
|
@trailer = hash.trailer.slice(:Root, :Info, :ID)
|
|
27
31
|
raise Error, 'PDF has no document catalog' unless @trailer[:Root]
|
|
@@ -56,11 +60,13 @@ module Acrofill
|
|
|
56
60
|
# The parse boundary for untrusted input: pdf-reader (and the decryptors
|
|
57
61
|
# it calls) can raise a wide assortment of errors on crafted files, so
|
|
58
62
|
# everything is normalized to a single Acrofill::Error type.
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
# SystemStackError is not a StandardError but must be caught too: the
|
|
64
|
+
# recursive-descent parser overflows on deeply nested objects.
|
|
65
|
+
def parse_boundary
|
|
66
|
+
yield
|
|
61
67
|
rescue PDF::Reader::EncryptedPDFError => e
|
|
62
68
|
raise Error, "encrypted PDFs are not supported (#{e.message})"
|
|
63
|
-
rescue StandardError => e
|
|
69
|
+
rescue StandardError, SystemStackError => e
|
|
64
70
|
raise Error, "could not parse PDF (#{e.class}: #{e.message})"
|
|
65
71
|
end
|
|
66
72
|
|
|
@@ -94,23 +100,31 @@ module Acrofill
|
|
|
94
100
|
deref(@trailer[:Root])
|
|
95
101
|
end
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
# Depth-first, document-order walk over the /Pages tree. Iterative with
|
|
104
|
+
# an explicit stack (a deep linear chain must not overflow the Ruby
|
|
105
|
+
# stack) and a visited set (a cyclic or shared billion-laughs tree must
|
|
106
|
+
# not hang or amplify).
|
|
107
|
+
def each_page
|
|
108
|
+
stack = [root[:Pages]]
|
|
109
|
+
seen = {}
|
|
110
|
+
until stack.empty?
|
|
111
|
+
node = stack.pop
|
|
112
|
+
dict = deref(node)
|
|
113
|
+
next unless dict.is_a?(Hash)
|
|
114
|
+
|
|
115
|
+
if node.is_a?(PDF::Reader::Reference)
|
|
116
|
+
next if seen[node.id]
|
|
117
|
+
|
|
118
|
+
seen[node.id] = true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
case dict[:Type]
|
|
122
|
+
when :Pages
|
|
123
|
+
kids = deref(dict[:Kids])
|
|
124
|
+
stack.concat(kids.reverse) if kids.is_a?(Array)
|
|
125
|
+
when :Page
|
|
126
|
+
yield dict
|
|
127
|
+
end
|
|
114
128
|
end
|
|
115
129
|
end
|
|
116
130
|
|
data/lib/acrofill/form.rb
CHANGED
|
@@ -21,7 +21,8 @@ module Acrofill
|
|
|
21
21
|
# single logical field, e.g. "SSN" repeated on every page) — filling
|
|
22
22
|
# must update all of them.
|
|
23
23
|
@fields = Hash.new { |h, k| h[k] = [] }
|
|
24
|
-
|
|
24
|
+
roots = doc.deref(@acroform[:Fields])
|
|
25
|
+
collect_fields(roots.is_a?(Array) ? roots : [])
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# Metadata for every logical field, in document order.
|
|
@@ -31,7 +32,7 @@ module Acrofill
|
|
|
31
32
|
Field.new(
|
|
32
33
|
name: name,
|
|
33
34
|
type: type,
|
|
34
|
-
value: decode_text_string(groups.first[:node][:V]),
|
|
35
|
+
value: decode_text_string(@doc.deref(groups.first[:node][:V])),
|
|
35
36
|
states: type == :Btn ? on_states(all_widgets(groups)) : nil
|
|
36
37
|
)
|
|
37
38
|
end
|
|
@@ -103,7 +104,9 @@ module Acrofill
|
|
|
103
104
|
# Checkboxes and radio groups carry per-state appearance streams, so
|
|
104
105
|
# filling only selects a state: /V on the field, /AS on each widget.
|
|
105
106
|
# The value may be a state name ("Yes"), or anything non-empty when the
|
|
106
|
-
# group has a single on state. Empty, "Off" or false-ish unchecks
|
|
107
|
+
# group has a single on state. Empty, "Off" or false-ish unchecks —
|
|
108
|
+
# unless the value exactly names an on state (a checkbox whose state is
|
|
109
|
+
# literally called "no" must remain checkable).
|
|
107
110
|
def fill_button(groups, value)
|
|
108
111
|
widgets = all_widgets(groups)
|
|
109
112
|
states = on_states(widgets)
|
|
@@ -112,10 +115,10 @@ module Acrofill
|
|
|
112
115
|
# symbol creation from attacker-controlled field values).
|
|
113
116
|
named = states.find { |s| s.to_s == value }
|
|
114
117
|
state =
|
|
115
|
-
if
|
|
116
|
-
:Off
|
|
117
|
-
elsif named
|
|
118
|
+
if named
|
|
118
119
|
named
|
|
120
|
+
elsif ['', 'Off', 'false', 'no'].include?(value)
|
|
121
|
+
:Off
|
|
119
122
|
elsif states.size == 1
|
|
120
123
|
states.first
|
|
121
124
|
else
|
|
@@ -158,23 +161,27 @@ module Acrofill
|
|
|
158
161
|
false
|
|
159
162
|
end
|
|
160
163
|
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
# Depth-first, document-order walk over the field tree. Iterative with
|
|
165
|
+
# an explicit stack of [ref, prefix] pairs (a deep linear /Kids chain
|
|
166
|
+
# must not overflow the Ruby stack), with a visited set against cycles
|
|
167
|
+
# and shared nodes.
|
|
168
|
+
def collect_fields(roots)
|
|
169
|
+
seen = {}
|
|
170
|
+
stack = roots.reverse.map { |ref| [ref, nil] }
|
|
171
|
+
until stack.empty?
|
|
172
|
+
ref, prefix = stack.pop
|
|
163
173
|
next if visited?(ref, seen)
|
|
164
174
|
|
|
165
175
|
node = @doc.deref(ref)
|
|
166
176
|
next unless node.is_a?(Hash)
|
|
167
177
|
|
|
168
178
|
name = [prefix, decode_name(node[:T])].compact.join('.')
|
|
169
|
-
|
|
170
|
-
child_pairs = child_refs.map { |kid| [kid, @doc.deref(kid)] }
|
|
171
|
-
.select { |_kid, dict| dict.is_a?(Hash) }
|
|
172
|
-
subfields, widgets = child_pairs.partition { |_kid, dict| dict.key?(:T) }
|
|
179
|
+
subfields, widgets = partition_kids(node)
|
|
173
180
|
|
|
174
181
|
if subfields.any?
|
|
175
|
-
#
|
|
182
|
+
# Descend only into the named subfields; a stray bare widget mixed
|
|
176
183
|
# into the same /Kids must not register itself under this name.
|
|
177
|
-
|
|
184
|
+
stack.concat(subfields.map { |kid, _dict| [kid, name] }.reverse)
|
|
178
185
|
else
|
|
179
186
|
widget_dicts = widgets.map(&:last)
|
|
180
187
|
widget_dicts = [node] if widget_dicts.empty? && node[:Rect]
|
|
@@ -183,6 +190,16 @@ module Acrofill
|
|
|
183
190
|
end
|
|
184
191
|
end
|
|
185
192
|
|
|
193
|
+
# Splits a node's /Kids into named subfields and bare widgets, dropping
|
|
194
|
+
# anything that does not deref to a dictionary.
|
|
195
|
+
def partition_kids(node)
|
|
196
|
+
child_refs = @doc.deref(node[:Kids])
|
|
197
|
+
child_refs = [] unless child_refs.is_a?(Array)
|
|
198
|
+
child_refs.map { |kid| [kid, @doc.deref(kid)] }
|
|
199
|
+
.select { |_kid, dict| dict.is_a?(Hash) }
|
|
200
|
+
.partition { |_kid, dict| dict.key?(:T) }
|
|
201
|
+
end
|
|
202
|
+
|
|
186
203
|
def decode_name(raw)
|
|
187
204
|
decode_text_string(@doc.deref(raw))
|
|
188
205
|
end
|
|
@@ -207,7 +224,9 @@ module Acrofill
|
|
|
207
224
|
end
|
|
208
225
|
|
|
209
226
|
def flatten_page(page)
|
|
210
|
-
|
|
227
|
+
annot_refs = @doc.deref(page[:Annots])
|
|
228
|
+
annot_refs = [] unless annot_refs.is_a?(Array)
|
|
229
|
+
annots = annot_refs.map { |a| [a, @doc.deref(a)] }
|
|
211
230
|
widgets, others = annots.partition { |_ref, dict| dict.is_a?(Hash) && dict[:Subtype] == :Widget }
|
|
212
231
|
return if widgets.empty?
|
|
213
232
|
|
|
@@ -246,16 +265,16 @@ module Acrofill
|
|
|
246
265
|
dict = xobject.is_a?(StreamObject) ? xobject.dict : xobject&.hash
|
|
247
266
|
return nil unless dict.is_a?(Hash)
|
|
248
267
|
|
|
249
|
-
bbox = @doc.deref(dict[:BBox])
|
|
250
|
-
rect = @doc.deref(widget[:Rect])
|
|
251
|
-
return nil unless bbox
|
|
268
|
+
bbox = normalize_box(@doc.deref(dict[:BBox]))
|
|
269
|
+
rect = normalize_box(@doc.deref(widget[:Rect]))
|
|
270
|
+
return nil unless bbox && rect
|
|
252
271
|
|
|
253
272
|
# Appearance streams are form XObjects, but /Type and /Subtype are
|
|
254
273
|
# sometimes omitted; /Do requires them.
|
|
255
274
|
dict[:Type] ||= :XObject
|
|
256
275
|
dict[:Subtype] ||= :Form
|
|
257
276
|
|
|
258
|
-
llx, lly, urx, ury =
|
|
277
|
+
llx, lly, urx, ury = rect
|
|
259
278
|
bx0, by0, bx1, by1 = transformed_bbox(bbox, @doc.deref(dict[:Matrix]))
|
|
260
279
|
bw = bx1 - bx0
|
|
261
280
|
bh = by1 - by0
|
|
@@ -269,10 +288,11 @@ module Acrofill
|
|
|
269
288
|
"q #{ops.join(' ')} cm /#{name} Do Q"
|
|
270
289
|
end
|
|
271
290
|
|
|
272
|
-
# Bounding box of the BBox corners after the
|
|
273
|
-
# when absent or malformed).
|
|
291
|
+
# Bounding box of the (already normalized) BBox corners after the
|
|
292
|
+
# form's /Matrix (identity when absent or malformed).
|
|
274
293
|
def transformed_bbox(bbox, matrix)
|
|
275
|
-
x0, y0, x1, y1 =
|
|
294
|
+
x0, y0, x1, y1 = bbox
|
|
295
|
+
matrix = matrix.map { |m| @doc.deref(m) } if matrix.is_a?(Array)
|
|
276
296
|
return [x0, y0, x1, y1] unless matrix.is_a?(Array) && matrix.size == 6 &&
|
|
277
297
|
matrix.all?(Numeric)
|
|
278
298
|
|
|
@@ -302,9 +322,17 @@ module Acrofill
|
|
|
302
322
|
normal
|
|
303
323
|
end
|
|
304
324
|
|
|
325
|
+
# Derefs each element (array entries may legally be indirect objects)
|
|
326
|
+
# and returns [llx, lly, urx, ury], or nil when the box is not four
|
|
327
|
+
# numbers.
|
|
305
328
|
def normalize_box(box)
|
|
306
|
-
|
|
307
|
-
|
|
329
|
+
return nil unless box.is_a?(Array) && box.size == 4
|
|
330
|
+
|
|
331
|
+
nums = box.map { |n| @doc.deref(n) }
|
|
332
|
+
return nil unless nums.all?(Numeric)
|
|
333
|
+
|
|
334
|
+
xs = [nums[0].to_f, nums[2].to_f].sort
|
|
335
|
+
ys = [nums[1].to_f, nums[3].to_f].sort
|
|
308
336
|
[xs[0], ys[0], xs[1], ys[1]]
|
|
309
337
|
end
|
|
310
338
|
|
data/lib/acrofill/version.rb
CHANGED
data/lib/acrofill.rb
CHANGED
|
@@ -14,8 +14,8 @@ module Acrofill
|
|
|
14
14
|
class Error < StandardError; end
|
|
15
15
|
|
|
16
16
|
# Mirrors the PdfForms.new(pdftk_path) constructor shape.
|
|
17
|
-
def self.new(*
|
|
18
|
-
Filler.new(*
|
|
17
|
+
def self.new(*)
|
|
18
|
+
Filler.new(*)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def self.fill_form(template, destination, data = {}, options = {})
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: acrofill
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- stiig
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: pdf-reader
|
|
@@ -27,11 +26,11 @@ dependencies:
|
|
|
27
26
|
description: Fills PDF form fields (text, checkbox, radio, choice) in-process and
|
|
28
27
|
optionally flattens the result into static page content. An MIT-licensed, no-dependencies-on-binaries
|
|
29
28
|
alternative to the pdftk fill_form pipeline.
|
|
30
|
-
email:
|
|
31
29
|
executables: []
|
|
32
30
|
extensions: []
|
|
33
31
|
extra_rdoc_files: []
|
|
34
32
|
files:
|
|
33
|
+
- CHANGELOG.md
|
|
35
34
|
- LICENSE.txt
|
|
36
35
|
- README.md
|
|
37
36
|
- lib/acrofill.rb
|
|
@@ -50,8 +49,9 @@ licenses:
|
|
|
50
49
|
metadata:
|
|
51
50
|
homepage_uri: https://github.com/stiig/acrofill
|
|
52
51
|
source_code_uri: https://github.com/stiig/acrofill
|
|
52
|
+
changelog_uri: https://github.com/stiig/acrofill/blob/main/CHANGELOG.md
|
|
53
|
+
bug_tracker_uri: https://github.com/stiig/acrofill/issues
|
|
53
54
|
rubygems_mfa_required: 'true'
|
|
54
|
-
post_install_message:
|
|
55
55
|
rdoc_options: []
|
|
56
56
|
require_paths:
|
|
57
57
|
- lib
|
|
@@ -59,15 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
59
59
|
requirements:
|
|
60
60
|
- - ">="
|
|
61
61
|
- !ruby/object:Gem::Version
|
|
62
|
-
version: '3.
|
|
62
|
+
version: '3.2'
|
|
63
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
68
|
requirements: []
|
|
69
|
-
rubygems_version: 3.
|
|
70
|
-
signing_key:
|
|
69
|
+
rubygems_version: 3.6.9
|
|
71
70
|
specification_version: 4
|
|
72
71
|
summary: Pure-Ruby PDF form (AcroForm) filling and flattening
|
|
73
72
|
test_files: []
|