hexapdf 0.22.0 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|