hexapdf 0.22.0 → 0.23.0
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 +50 -0
- data/lib/hexapdf/cli/form.rb +26 -3
- data/lib/hexapdf/cli/inspect.rb +12 -3
- data/lib/hexapdf/cli/modify.rb +23 -3
- data/lib/hexapdf/composer.rb +24 -2
- data/lib/hexapdf/document/destinations.rb +396 -0
- data/lib/hexapdf/document.rb +38 -89
- data/lib/hexapdf/layout/frame.rb +8 -9
- data/lib/hexapdf/layout/style.rb +280 -7
- data/lib/hexapdf/layout/text_box.rb +10 -2
- data/lib/hexapdf/layout/text_layouter.rb +6 -1
- data/lib/hexapdf/revision.rb +8 -1
- data/lib/hexapdf/revisions.rb +151 -50
- data/lib/hexapdf/task/optimize.rb +21 -11
- data/lib/hexapdf/type/acro_form/text_field.rb +8 -0
- data/lib/hexapdf/type/catalog.rb +9 -1
- data/lib/hexapdf/type/names.rb +13 -0
- data/lib/hexapdf/type/xref_stream.rb +2 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +3 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +15 -2
- data/test/hexapdf/document/test_destinations.rb +338 -0
- data/test/hexapdf/encryption/test_security_handler.rb +2 -2
- data/test/hexapdf/layout/test_frame.rb +15 -1
- data/test/hexapdf/layout/test_text_box.rb +16 -0
- data/test/hexapdf/layout/test_text_layouter.rb +7 -0
- data/test/hexapdf/task/test_optimize.rb +17 -4
- data/test/hexapdf/test_composer.rb +24 -1
- data/test/hexapdf/test_document.rb +30 -133
- data/test/hexapdf/test_parser.rb +1 -1
- data/test/hexapdf/test_revision.rb +14 -0
- data/test/hexapdf/test_revisions.rb +137 -29
- data/test/hexapdf/test_writer.rb +43 -14
- data/test/hexapdf/type/acro_form/test_text_field.rb +17 -0
- data/test/hexapdf/type/test_catalog.rb +8 -0
- data/test/hexapdf/type/test_names.rb +20 -0
- data/test/hexapdf/type/test_xref_stream.rb +2 -1
- data/test/hexapdf/utils/test_sorted_tree_node.rb +11 -1
- metadata +5 -2
@@ -91,43 +91,6 @@ describe HexaPDF::Document do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
describe "object" do
|
95
|
-
it "accepts a Reference object as argument" do
|
96
|
-
assert_equal(10, @io_doc.object(HexaPDF::Reference.new(1, 0)).value)
|
97
|
-
end
|
98
|
-
|
99
|
-
it "accepts an object number as arguments" do
|
100
|
-
assert_equal(10, @io_doc.object(1).value)
|
101
|
-
end
|
102
|
-
|
103
|
-
it "returns added objects" do
|
104
|
-
obj = @io_doc.add(@io_doc.wrap({Type: :Test}, oid: 100))
|
105
|
-
assert_equal(obj, @io_doc.object(100))
|
106
|
-
end
|
107
|
-
|
108
|
-
it "returns nil for unknown object references" do
|
109
|
-
assert_nil(@io_doc.object(100))
|
110
|
-
end
|
111
|
-
|
112
|
-
it "returns only the newest version of an object" do
|
113
|
-
assert_equal(200, @io_doc.object(2).value)
|
114
|
-
assert_equal(200, @io_doc.object(HexaPDF::Reference.new(2, 0)).value)
|
115
|
-
assert_nil(@io_doc.object(3).value)
|
116
|
-
assert_nil(@io_doc.object(HexaPDF::Reference.new(3, 1)).value)
|
117
|
-
assert_equal(30, @io_doc.object(HexaPDF::Reference.new(3, 0)).value)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
describe "object?" do
|
122
|
-
it "works with a Reference object as argument" do
|
123
|
-
assert(@io_doc.object?(HexaPDF::Reference.new(1, 0)))
|
124
|
-
end
|
125
|
-
|
126
|
-
it "works with an object number as arguments" do
|
127
|
-
assert(@io_doc.object?(1))
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
94
|
describe "deref" do
|
132
95
|
it "returns a dereferenced object when given a Reference object" do
|
133
96
|
assert_equal(@io_doc.object(1), @io_doc.deref(HexaPDF::Reference.new(1, 0)))
|
@@ -139,13 +102,6 @@ describe HexaPDF::Document do
|
|
139
102
|
end
|
140
103
|
|
141
104
|
describe "add" do
|
142
|
-
it "automatically assigns free object numbers" do
|
143
|
-
assert_equal(1, @doc.add(5).oid)
|
144
|
-
assert_equal(2, @doc.add(5).oid)
|
145
|
-
@doc.revisions.add
|
146
|
-
assert_equal(3, @doc.add(5).oid)
|
147
|
-
end
|
148
|
-
|
149
105
|
it "assigns the object's document" do
|
150
106
|
obj = @doc.add(5)
|
151
107
|
assert_equal(@doc, obj.document)
|
@@ -166,82 +122,38 @@ describe HexaPDF::Document do
|
|
166
122
|
assert_equal(5, obj.value)
|
167
123
|
end
|
168
124
|
|
169
|
-
it "returns the given object if it is already stored in the document" do
|
170
|
-
obj = @doc.add(5)
|
171
|
-
assert_same(obj, @doc.add(obj))
|
172
|
-
end
|
173
|
-
|
174
|
-
it "allows specifying a revision to which the object should be added" do
|
175
|
-
@doc.revisions.add
|
176
|
-
@doc.revisions.add
|
177
|
-
|
178
|
-
@doc.add(@doc.wrap(5, oid: 1), revision: 0)
|
179
|
-
assert_equal(5, @doc.object(1).value)
|
180
|
-
|
181
|
-
@doc.add(@doc.wrap(10, oid: 1), revision: 2)
|
182
|
-
assert_equal(10, @doc.object(1).value)
|
183
|
-
|
184
|
-
@doc.add(@doc.wrap(7.5, oid: 1), revision: 1)
|
185
|
-
assert_equal(10, @doc.object(1).value)
|
186
|
-
end
|
187
|
-
|
188
|
-
it "fails if the specified revision index is invalid" do
|
189
|
-
assert_raises(ArgumentError) { @doc.add(5, revision: 5) }
|
190
|
-
end
|
191
|
-
|
192
125
|
it "fails if the object to be added is associated with another document" do
|
193
126
|
doc = HexaPDF::Document.new
|
194
127
|
obj = doc.add(5)
|
195
128
|
assert_raises(HexaPDF::Error) { @doc.add(obj) }
|
196
129
|
end
|
197
|
-
|
198
|
-
it "fails if the object number is already associated with another object" do
|
199
|
-
obj = @doc.add(5)
|
200
|
-
assert_raises(HexaPDF::Error) { @doc.add(@doc.wrap(5, oid: obj.oid, gen: 1)) }
|
201
|
-
end
|
202
130
|
end
|
203
131
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
@doc.add(5)
|
213
|
-
@doc.delete(1, mark_as_free: false)
|
214
|
-
refute(@doc.object?(1))
|
215
|
-
end
|
216
|
-
|
217
|
-
describe "with an object in multiple revisions" do
|
218
|
-
before do
|
219
|
-
@ref = HexaPDF::Reference.new(2, 3)
|
220
|
-
obj = @doc.wrap(5, oid: @ref.oid, gen: @ref.gen)
|
221
|
-
@doc.revisions.add
|
222
|
-
@doc.add(obj, revision: 0)
|
223
|
-
@doc.add(obj, revision: 1)
|
224
|
-
end
|
225
|
-
|
226
|
-
it "deletes an object for all revisions when revision = :all" do
|
227
|
-
@doc.delete(@ref, revision: :all, mark_as_free: false)
|
228
|
-
refute(@doc.object?(@ref))
|
229
|
-
end
|
230
|
-
|
231
|
-
it "deletes an object only in the current revision when revision = :current" do
|
232
|
-
@doc.delete(@ref, revision: :current, mark_as_free: false)
|
233
|
-
assert(@doc.object?(@ref))
|
234
|
-
end
|
132
|
+
it "defers to @revisions for retrieving an object" do
|
133
|
+
revs = Minitest::Mock.new
|
134
|
+
revs.expect(:object, :retval, [:ref])
|
135
|
+
doc = HexaPDF::Document.new
|
136
|
+
doc.instance_variable_set(:@revisions, revs)
|
137
|
+
doc.object(:ref)
|
138
|
+
revs.verify
|
139
|
+
end
|
235
140
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
141
|
+
it "defers to @revisions for checking for the existence of an object" do
|
142
|
+
revs = Minitest::Mock.new
|
143
|
+
revs.expect(:object?, :retval, [:ref])
|
144
|
+
doc = HexaPDF::Document.new
|
145
|
+
doc.instance_variable_set(:@revisions, revs)
|
146
|
+
doc.object?(:ref)
|
147
|
+
revs.verify
|
148
|
+
end
|
241
149
|
|
242
|
-
|
243
|
-
|
244
|
-
|
150
|
+
it "defers to @revisions for deleting an object" do
|
151
|
+
revs = Minitest::Mock.new
|
152
|
+
revs.expect(:delete_object, :retval, [:ref])
|
153
|
+
doc = HexaPDF::Document.new
|
154
|
+
doc.instance_variable_set(:@revisions, revs)
|
155
|
+
doc.delete(:ref)
|
156
|
+
revs.verify
|
245
157
|
end
|
246
158
|
|
247
159
|
describe "import" do
|
@@ -388,28 +300,13 @@ describe HexaPDF::Document do
|
|
388
300
|
end
|
389
301
|
end
|
390
302
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
end
|
399
|
-
|
400
|
-
it "iterates over all loaded objects" do
|
401
|
-
assert_equal(200, @io_doc.object(2).value)
|
402
|
-
assert_equal([200], @io_doc.each(only_loaded: true).sort.map(&:value))
|
403
|
-
end
|
404
|
-
|
405
|
-
it "yields the revision as second argument if the block accepts exactly two arguments" do
|
406
|
-
objs = [[10, 20, 30], [200, nil]]
|
407
|
-
data = @io_doc.revisions.map.with_index {|rev, i| objs[i].map {|o| [o, rev] } }.reverse.flatten
|
408
|
-
@io_doc.each(only_current: false) do |obj, rev|
|
409
|
-
assert(data.shift == obj.value)
|
410
|
-
assert_equal(data.shift, rev)
|
411
|
-
end
|
412
|
-
end
|
303
|
+
it "defers to @revisions for iterating over all objects" do
|
304
|
+
revs = Minitest::Mock.new
|
305
|
+
revs.expect(:each_object, :retval, [{only_current: true, only_loaded: true}])
|
306
|
+
doc = HexaPDF::Document.new
|
307
|
+
doc.instance_variable_set(:@revisions, revs)
|
308
|
+
doc.each(only_current: true, only_loaded: true)
|
309
|
+
revs.verify
|
413
310
|
end
|
414
311
|
|
415
312
|
describe "encryption" do
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -548,7 +548,7 @@ describe HexaPDF::Parser do
|
|
548
548
|
|
549
549
|
it "works for a cross-reference stream" do
|
550
550
|
xref_section, trailer = @parser.load_revision(212)
|
551
|
-
assert_equal({Size: 2}, trailer)
|
551
|
+
assert_equal({Size: 2, Type: :XRef}, trailer)
|
552
552
|
assert(xref_section[1].in_use?)
|
553
553
|
assert(@parser.contains_xref_streams?)
|
554
554
|
end
|
@@ -215,4 +215,18 @@ describe HexaPDF::Revision do
|
|
215
215
|
assert_equal([], @rev.each_modified_object.to_a)
|
216
216
|
end
|
217
217
|
end
|
218
|
+
|
219
|
+
describe "reset_objects" do
|
220
|
+
it "deletes loaded objects" do
|
221
|
+
@rev.object(2)
|
222
|
+
@rev.reset_objects
|
223
|
+
assert(@rev.instance_variable_get(:@objects).oids.empty?)
|
224
|
+
end
|
225
|
+
|
226
|
+
it "deletes added objects" do
|
227
|
+
@rev.add(@obj)
|
228
|
+
@rev.reset_objects
|
229
|
+
assert(@rev.instance_variable_get(:@objects).oids.empty?)
|
230
|
+
end
|
231
|
+
end
|
218
232
|
end
|
@@ -70,61 +70,169 @@ describe HexaPDF::Revisions do
|
|
70
70
|
@revisions = @doc.revisions
|
71
71
|
end
|
72
72
|
|
73
|
-
describe "
|
74
|
-
it "
|
75
|
-
|
76
|
-
assert_equal(
|
77
|
-
assert_equal(
|
73
|
+
describe "initialize" do
|
74
|
+
it "automatically loads all revisions from the underlying IO object" do
|
75
|
+
assert_kind_of(HexaPDF::Parser, @revisions.parser)
|
76
|
+
assert_equal(20, @revisions.all[0].object(2).value)
|
77
|
+
assert_equal(200, @revisions.all[1].object(2).value)
|
78
|
+
assert_equal(400, @revisions.all[2].object(2).value)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "creates an empty revision when not using initial revisions" do
|
82
|
+
revisions = HexaPDF::Revisions.new(@doc)
|
83
|
+
assert_equal(1, revisions.all.count)
|
78
84
|
end
|
79
85
|
end
|
80
86
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
87
|
+
it "returns the next free oid" do
|
88
|
+
assert_equal(4, @revisions.next_oid)
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "object" do
|
92
|
+
it "accepts a Reference object as argument" do
|
93
|
+
assert_equal(400, @revisions.object(HexaPDF::Reference.new(2, 0)).value)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "accepts an object number as arguments" do
|
97
|
+
assert_equal(400, @revisions.object(2).value)
|
86
98
|
end
|
87
99
|
|
88
|
-
it "
|
89
|
-
|
90
|
-
@revisions.delete(rev)
|
91
|
-
refute(@revisions.any? {|r| r == rev })
|
100
|
+
it "returns nil for unknown object references" do
|
101
|
+
assert_nil(@revisions.object(100))
|
92
102
|
end
|
93
103
|
|
94
|
-
it "
|
95
|
-
|
104
|
+
it "returns a null object for freed objects" do
|
105
|
+
@revisions.delete_object(2)
|
106
|
+
assert(@revisions.object(2).null?)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "object?" do
|
111
|
+
it "works with a Reference object as argument" do
|
112
|
+
assert(@revisions.object?(HexaPDF::Reference.new(2, 0)))
|
113
|
+
end
|
114
|
+
|
115
|
+
it "works with an object number as arguments" do
|
116
|
+
assert(@revisions.object?(2))
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns false when no object is found" do
|
120
|
+
refute(@revisions.object?(20))
|
121
|
+
end
|
122
|
+
|
123
|
+
it "returns true for freed objects" do
|
124
|
+
@revisions.delete_object(2)
|
125
|
+
assert(@revisions.object?(2))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "add_object" do
|
130
|
+
before do
|
131
|
+
@obj = HexaPDF::Object.new(5)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "adds the object to the current revision" do
|
135
|
+
@revisions.add_object(@obj)
|
136
|
+
assert_same(@obj, @revisions.current.object(@obj))
|
137
|
+
end
|
138
|
+
|
139
|
+
it "returns the added object" do
|
140
|
+
obj = @revisions.add_object(@obj)
|
141
|
+
assert_same(@obj, obj)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "returns the given object if it is already stored in the document" do
|
145
|
+
obj = @revisions.add_object(@obj)
|
146
|
+
assert_same(obj, @revisions.add_object(obj))
|
147
|
+
end
|
148
|
+
|
149
|
+
it "fails if the object number is already associated with another object" do
|
150
|
+
@revisions.add_object(@obj)
|
151
|
+
assert_raises(HexaPDF::Error) { @revisions.add_object(@doc.wrap(5, oid: @obj.oid)) }
|
152
|
+
end
|
153
|
+
|
154
|
+
it "automatically assign an object number for direct objects" do
|
155
|
+
assert_equal(4, @revisions.add_object(@obj).oid)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "delete_object" do
|
160
|
+
it "works with a Reference object as argument" do
|
161
|
+
@revisions.delete_object(@doc.object(2))
|
162
|
+
assert(@revisions.object(2).null?)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "works with an object number as arguments" do
|
166
|
+
@revisions.delete_object(2)
|
167
|
+
assert(@revisions.object(2).null?)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "deletes an object only in the most recent revision" do
|
171
|
+
@revisions.delete_object(2)
|
172
|
+
assert_equal(20, @revisions.all[0].object(2).value)
|
173
|
+
assert_equal(200, @revisions.all[1].object(2).value)
|
174
|
+
assert(@revisions.all[2].object(2).null?)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "each_object" do
|
179
|
+
before do
|
180
|
+
@obj3 = @revisions.object(3).value
|
181
|
+
end
|
182
|
+
|
183
|
+
it "iterates over the current objects" do
|
184
|
+
assert_equal([10, 400, @obj3], @revisions.each_object(only_current: true).sort.map(&:value))
|
185
|
+
end
|
186
|
+
|
187
|
+
it "iterates over all objects" do
|
188
|
+
assert_equal([10, 400, 200, 20, @obj3, @obj3],
|
189
|
+
@revisions.each_object(only_current: false).sort.map(&:value))
|
190
|
+
end
|
191
|
+
|
192
|
+
it "iterates over all loaded objects" do
|
193
|
+
assert_equal([@obj3], @revisions.each_object(only_loaded: true).map(&:value))
|
194
|
+
assert_equal(400, @revisions.object(2).value)
|
195
|
+
assert_equal([400, @obj3], @revisions.each_object(only_loaded: true).sort.map(&:value))
|
196
|
+
end
|
197
|
+
|
198
|
+
it "yields the revision as second argument if the block accepts exactly two arguments" do
|
199
|
+
data = [@obj3, @revisions.all[-1], 400, @revisions.all[-1], 10, @revisions.all[0]]
|
200
|
+
@revisions.each_object do |obj, rev|
|
201
|
+
assert_equal(data.shift, obj.value)
|
202
|
+
assert_equal(data.shift, rev)
|
203
|
+
end
|
204
|
+
assert(data.empty?)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "add" do
|
209
|
+
it "adds an empty revision as the current revision" do
|
210
|
+
rev = @revisions.add
|
211
|
+
assert_equal({Size: 4}, rev.trailer.value)
|
212
|
+
assert_equal(rev, @revisions.current)
|
96
213
|
end
|
97
214
|
end
|
98
215
|
|
99
216
|
describe "merge" do
|
100
217
|
it "does nothing when only one revision is specified" do
|
101
218
|
@revisions.merge(1..1)
|
102
|
-
assert_equal(3, @revisions.
|
219
|
+
assert_equal(3, @revisions.all.size)
|
103
220
|
end
|
104
221
|
|
105
222
|
it "merges the higher into the the lower revision" do
|
106
223
|
@revisions.merge
|
107
|
-
assert_equal(1, @revisions.
|
224
|
+
assert_equal(1, @revisions.all.size)
|
108
225
|
assert_equal([10, 400, @doc.object(3).value], @revisions.current.each.to_a.sort.map(&:value))
|
109
226
|
end
|
110
227
|
|
111
228
|
it "handles objects correctly that are in multiple revisions" do
|
112
|
-
@revisions.current.add(@revisions[0].object(1))
|
229
|
+
@revisions.current.add(@revisions.all[0].object(1))
|
113
230
|
@revisions.merge
|
114
231
|
assert_equal(1, @revisions.each.to_a.size)
|
115
232
|
assert_equal([10, 400, @doc.object(3).value], @revisions.current.each.to_a.sort.map(&:value))
|
116
233
|
end
|
117
234
|
end
|
118
235
|
|
119
|
-
describe "initialize" do
|
120
|
-
it "automatically loads all revisions from the underlying IO object" do
|
121
|
-
assert_kind_of(HexaPDF::Parser, @revisions.parser)
|
122
|
-
assert_equal(20, @revisions.revision(0).object(2).value)
|
123
|
-
assert_equal(300, @revisions[1].object(2).value)
|
124
|
-
assert_equal(400, @revisions[2].object(2).value)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
236
|
it "handles invalid PDFs that have a loop via the xref /Prev or /XRefStm entries" do
|
129
237
|
io = StringIO.new(<<~EOF)
|
130
238
|
%PDF-1.7
|
@@ -191,6 +299,6 @@ describe HexaPDF::Revisions do
|
|
191
299
|
EOF
|
192
300
|
doc = HexaPDF::Document.new(io: io)
|
193
301
|
assert_equal(2, doc.revisions.count)
|
194
|
-
assert_same(doc.revisions[0].trailer.value, doc.revisions[1].trailer.value)
|
302
|
+
assert_same(doc.revisions.all[0].trailer.value, doc.revisions.all[1].trailer.value)
|
195
303
|
end
|
196
304
|
end
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,13 +40,13 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.
|
43
|
+
<</Producer(HexaPDF version 0.23.0)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
47
47
|
0000000296 00000 n
|
48
48
|
trailer
|
49
|
-
<</
|
49
|
+
<</Size 4/Root<</Type/Catalog>>/Info 3 0 R/Prev 219>>
|
50
50
|
startxref
|
51
51
|
349
|
52
52
|
%%EOF
|
@@ -64,7 +64,7 @@ describe HexaPDF::Writer do
|
|
64
64
|
20
|
65
65
|
endobj
|
66
66
|
3 0 obj
|
67
|
-
<</Size 6/
|
67
|
+
<</Type/XRef/Size 6/W[1 1 2]/Index[0 4 5 1]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 31>>stream
|
68
68
|
x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x88he`\x00\x00B4\x04\x1E
|
69
69
|
endstream
|
70
70
|
endobj
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
72
72
|
141
|
73
73
|
%%EOF
|
74
74
|
6 0 obj
|
75
|
-
<</Producer(HexaPDF version 0.
|
75
|
+
<</Producer(HexaPDF version 0.23.0)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -80,7 +80,7 @@ describe HexaPDF::Writer do
|
|
80
80
|
endstream
|
81
81
|
endobj
|
82
82
|
4 0 obj
|
83
|
-
<</Size 7/
|
83
|
+
<</Type/XRef/Size 7/Root<</Type/Catalog>>/Info 6 0 R/Prev 141/W[1 2 2]/Index[2 1 4 1 6 1]/Filter/FlateDecode/DecodeParms<</Columns 5/Predictor 12>>/Length 22>>stream
|
84
84
|
x\xDAcbdlg``b`\xB0\x04\x93\x93\x18\x18\x00\f\e\x01[
|
85
85
|
endstream
|
86
86
|
endobj
|
@@ -112,7 +112,7 @@ describe HexaPDF::Writer do
|
|
112
112
|
HexaPDF::Writer.write(doc, output_io, incremental: true)
|
113
113
|
assert_equal(output_io.string[0, @std_input_io.string.length], @std_input_io.string)
|
114
114
|
doc = HexaPDF::Document.new(io: output_io)
|
115
|
-
assert_equal(4, doc.revisions.
|
115
|
+
assert_equal(4, doc.revisions.count)
|
116
116
|
assert_equal(2, doc.revisions.current.each.to_a.size)
|
117
117
|
end
|
118
118
|
|
@@ -136,29 +136,58 @@ describe HexaPDF::Writer do
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
+
it "moves modified objects into the last revision" do
|
140
|
+
io = StringIO.new
|
141
|
+
io2 = StringIO.new
|
142
|
+
|
143
|
+
document = HexaPDF::Document.new
|
144
|
+
document.pages.add
|
145
|
+
HexaPDF::Writer.new(document, io).write
|
146
|
+
|
147
|
+
document = HexaPDF::Document.new(io: io)
|
148
|
+
document.pages.add
|
149
|
+
HexaPDF::Writer.new(document, io2).write_incremental
|
150
|
+
|
151
|
+
document = HexaPDF::Document.new(io: io2)
|
152
|
+
document.revisions.add
|
153
|
+
document.pages.add
|
154
|
+
HexaPDF::Writer.new(document, io).write
|
155
|
+
|
156
|
+
document = HexaPDF::Document.new(io: io)
|
157
|
+
assert_equal(3, document.revisions.count)
|
158
|
+
assert_equal(1, document.revisions.all[0].object(2)[:Kids].length)
|
159
|
+
assert_equal(2, document.revisions.all[1].object(2)[:Kids].length)
|
160
|
+
assert_equal(3, document.revisions.all[2].object(2)[:Kids].length)
|
161
|
+
end
|
162
|
+
|
139
163
|
it "creates an xref stream if no xref stream is in a revision but object streams are" do
|
140
164
|
document = HexaPDF::Document.new
|
141
165
|
document.add({Type: :ObjStm})
|
142
166
|
HexaPDF::Writer.new(document, StringIO.new).write
|
143
|
-
|
167
|
+
assert_equal(:XRef, document.object(4).type)
|
144
168
|
end
|
145
169
|
|
146
170
|
it "creates an xref stream if a previous revision had one" do
|
147
171
|
document = HexaPDF::Document.new
|
148
172
|
document.pages.add
|
149
|
-
|
173
|
+
io = StringIO.new
|
174
|
+
HexaPDF::Writer.new(document, io).write
|
175
|
+
|
176
|
+
document = HexaPDF::Document.new(io: io)
|
150
177
|
document.pages.add
|
151
178
|
document.add({Type: :ObjStm})
|
152
|
-
|
179
|
+
io2 = StringIO.new
|
180
|
+
HexaPDF::Writer.new(document, io2).write_incremental
|
181
|
+
|
182
|
+
document = HexaPDF::Document.new(io: io2)
|
153
183
|
document.pages.add
|
154
|
-
io
|
155
|
-
HexaPDF::Writer.new(document, io).write
|
184
|
+
HexaPDF::Writer.new(document, io).write_incremental
|
156
185
|
|
157
186
|
document = HexaPDF::Document.new(io: io)
|
158
187
|
assert_equal(3, document.revisions.count)
|
159
|
-
assert(document.revisions[0].none? {|obj| obj.type == :XRef })
|
160
|
-
assert(document.revisions[1].one? {|obj| obj.type == :XRef })
|
161
|
-
assert(document.revisions[2].one? {|obj| obj.type == :XRef })
|
188
|
+
assert(document.revisions.all[0].none? {|obj| obj.type == :XRef })
|
189
|
+
assert(document.revisions.all[1].one? {|obj| obj.type == :XRef })
|
190
|
+
assert(document.revisions.all[2].one? {|obj| obj.type == :XRef })
|
162
191
|
end
|
163
192
|
|
164
193
|
it "raises an error if the class is misused and an xref section contains invalid entries" do
|
@@ -115,6 +115,16 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
115
115
|
@field.flag(:password)
|
116
116
|
assert_raises(HexaPDF::Error) { @field.field_value = 'test' }
|
117
117
|
end
|
118
|
+
|
119
|
+
it "fails if it is a comb text field without a /MaxLen entry" do
|
120
|
+
@field.initialize_as_comb_text_field
|
121
|
+
assert_raises(HexaPDF::Error) { @field.field_value = 'test' }
|
122
|
+
end
|
123
|
+
|
124
|
+
it "fails if the value exceeds the length set by /MaxLen" do
|
125
|
+
@field[:MaxLen] = 5
|
126
|
+
assert_raises(HexaPDF::Error) { @field.field_value = 'testdf' }
|
127
|
+
end
|
118
128
|
end
|
119
129
|
|
120
130
|
it "sets and returns the default field value" do
|
@@ -197,5 +207,12 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
197
207
|
@field[:V] = nil
|
198
208
|
assert(@field.validate)
|
199
209
|
end
|
210
|
+
|
211
|
+
it "checks that /MaxLen is set for comb text fields" do
|
212
|
+
@field.initialize_as_comb_text_field
|
213
|
+
refute(@field.validate)
|
214
|
+
@field[:MaxLen] = 2
|
215
|
+
assert(@field.validate)
|
216
|
+
end
|
200
217
|
end
|
201
218
|
end
|
@@ -21,6 +21,14 @@ describe HexaPDF::Type::Catalog do
|
|
21
21
|
assert_equal(:Pages, pages.type)
|
22
22
|
end
|
23
23
|
|
24
|
+
it "creates the name dictionary on access" do
|
25
|
+
assert_nil(@catalog[:Names])
|
26
|
+
names = @catalog.names
|
27
|
+
assert_equal(:XXNames, names.type)
|
28
|
+
other = @catalog.names
|
29
|
+
assert_same(other, names)
|
30
|
+
end
|
31
|
+
|
24
32
|
describe "acro_form" do
|
25
33
|
it "returns an existing form object" do
|
26
34
|
@catalog[:AcroForm] = :test
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
require 'hexapdf/type/names'
|
6
|
+
|
7
|
+
describe HexaPDF::Type::Names do
|
8
|
+
before do
|
9
|
+
@doc = HexaPDF::Document.new
|
10
|
+
@names = @doc.add({}, type: :XXNames)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns the name tree for the /Dests entry" do
|
14
|
+
refute(@names.key?(:Dests))
|
15
|
+
dests = @names.destinations
|
16
|
+
assert_kind_of(HexaPDF::NameTreeNode, dests)
|
17
|
+
assert_same(dests, @names[:Dests])
|
18
|
+
assert_same(dests, @names.destinations)
|
19
|
+
end
|
20
|
+
end
|
@@ -88,10 +88,11 @@ describe HexaPDF::Type::XRefStream do
|
|
88
88
|
@obj[:Index] = [0, 5]
|
89
89
|
@obj[:W] = [1, 2, 2]
|
90
90
|
dict = @obj.trailer
|
91
|
-
assert_equal(
|
91
|
+
assert_equal(4, dict.length)
|
92
92
|
assert_equal(5, dict[:Size])
|
93
93
|
assert_equal(["a", "b"], dict[:ID])
|
94
94
|
assert_equal('x', dict[:Root])
|
95
|
+
assert_equal(:XRef, dict[:Type])
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
@@ -135,7 +135,17 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
135
135
|
assert_nil(@root.find_entry('non'))
|
136
136
|
end
|
137
137
|
|
138
|
-
it "works when no entry exists" do
|
138
|
+
it "works when no entry exists and neither /Names nor /Kids are set" do
|
139
|
+
assert_nil(@root.find_entry('non'))
|
140
|
+
end
|
141
|
+
|
142
|
+
it "works when no entry exists and /Names is set" do
|
143
|
+
@root[:Names] = []
|
144
|
+
assert_nil(@root.find_entry('non'))
|
145
|
+
end
|
146
|
+
|
147
|
+
it "works when no entry exists and /Kids is set" do
|
148
|
+
@root[:Kids] = []
|
139
149
|
assert_nil(@root.find_entry('non'))
|
140
150
|
end
|
141
151
|
end
|