hexapdf 0.21.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 +79 -1
- data/Rakefile +1 -1
- data/lib/hexapdf/cli/form.rb +30 -3
- data/lib/hexapdf/cli/inspect.rb +18 -5
- data/lib/hexapdf/cli/modify.rb +23 -3
- data/lib/hexapdf/composer.rb +24 -2
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document/destinations.rb +396 -0
- data/lib/hexapdf/document.rb +38 -89
- data/lib/hexapdf/encryption/aes.rb +9 -5
- 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/form.rb +11 -5
- data/lib/hexapdf/type/acro_form/text_field.rb +8 -0
- data/lib/hexapdf/type/catalog.rb +9 -1
- data/lib/hexapdf/type/image.rb +47 -3
- 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_aes.rb +8 -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_dictionary_fields.rb +1 -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_form.rb +2 -1
- 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_image.rb +45 -9
- 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 +6 -3
@@ -0,0 +1,338 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
|
6
|
+
describe HexaPDF::Document::Destinations::Destination do
|
7
|
+
def destination(dest)
|
8
|
+
HexaPDF::Document::Destinations::Destination.new(dest)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can be asked whether the referenced page is in a remote document" do
|
12
|
+
assert(destination([5, :Fit]).remote?)
|
13
|
+
refute(destination([HexaPDF::Dictionary.new({}), :Fit]).remote?)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns the page object" do
|
17
|
+
assert_equal(:page, destination([:page, :Fit]).page)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "type :xyz" do
|
21
|
+
before do
|
22
|
+
@dest = destination([:page, :XYZ, :left, :top, :zoom])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns the type of the destination" do
|
26
|
+
assert_equal(:xyz, @dest.type)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns the argument left" do
|
30
|
+
assert_equal(:left, @dest.left)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns the argument top" do
|
34
|
+
assert_equal(:top, @dest.top)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns the argument zoom" do
|
38
|
+
assert_equal(:zoom, @dest.zoom)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises an error if the bottom and right properties are accessed" do
|
42
|
+
assert_raises(HexaPDF::Error) { @dest.bottom }
|
43
|
+
assert_raises(HexaPDF::Error) { @dest.right }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "type :fit_page" do
|
48
|
+
before do
|
49
|
+
@dest = destination([:page, :Fit])
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns the type of the destination" do
|
53
|
+
assert_equal(:fit_page, @dest.type)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "raises an error if the top, left, bottom, right, zoom properties are accessed" do
|
57
|
+
assert_raises(HexaPDF::Error) { @dest.top }
|
58
|
+
assert_raises(HexaPDF::Error) { @dest.left }
|
59
|
+
assert_raises(HexaPDF::Error) { @dest.bottom }
|
60
|
+
assert_raises(HexaPDF::Error) { @dest.right }
|
61
|
+
assert_raises(HexaPDF::Error) { @dest.zoom }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "type :fit_page_horizontal" do
|
66
|
+
before do
|
67
|
+
@dest = destination([:page, :FitH, :top])
|
68
|
+
end
|
69
|
+
|
70
|
+
it "returns the type of the destination" do
|
71
|
+
assert_equal(:fit_page_horizontal, @dest.type)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns the argument top" do
|
75
|
+
assert_equal(:top, @dest.top)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "raises an error if the left, bottom, right, zoom properties are accessed" do
|
79
|
+
assert_raises(HexaPDF::Error) { @dest.left }
|
80
|
+
assert_raises(HexaPDF::Error) { @dest.bottom }
|
81
|
+
assert_raises(HexaPDF::Error) { @dest.right }
|
82
|
+
assert_raises(HexaPDF::Error) { @dest.zoom }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "type :fit_page_vertical" do
|
87
|
+
before do
|
88
|
+
@dest = destination([:page, :FitV, :left])
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns the type of the destination" do
|
92
|
+
assert_equal(:fit_page_vertical, @dest.type)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "returns the argument left" do
|
96
|
+
assert_equal(:left, @dest.left)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "raises an error if the top, bottom, right, zoom properties are accessed" do
|
100
|
+
assert_raises(HexaPDF::Error) { @dest.top }
|
101
|
+
assert_raises(HexaPDF::Error) { @dest.bottom }
|
102
|
+
assert_raises(HexaPDF::Error) { @dest.right }
|
103
|
+
assert_raises(HexaPDF::Error) { @dest.zoom }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "type :fit_rectangle" do
|
108
|
+
before do
|
109
|
+
@dest = destination([:page, :FitR, :left, :bottom, :right, :top])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "returns the type of the destination" do
|
113
|
+
assert_equal(:fit_rectangle, @dest.type)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns the argument left" do
|
117
|
+
assert_equal(:left, @dest.left)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "returns the argument top" do
|
121
|
+
assert_equal(:top, @dest.top)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns the argument right" do
|
125
|
+
assert_equal(:right, @dest.right)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns the argument bottom" do
|
129
|
+
assert_equal(:bottom, @dest.bottom)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "raises an error if the zoom property is accessed" do
|
133
|
+
assert_raises(HexaPDF::Error) { @dest.zoom }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "type :fit_bounding_box" do
|
138
|
+
before do
|
139
|
+
@dest = destination([:page, :FitB])
|
140
|
+
end
|
141
|
+
|
142
|
+
it "returns the type of the destination" do
|
143
|
+
assert_equal(:fit_bounding_box, @dest.type)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "raises an error if the bottom and right properties are accessed" do
|
147
|
+
assert_raises(HexaPDF::Error) { @dest.left }
|
148
|
+
assert_raises(HexaPDF::Error) { @dest.bottom }
|
149
|
+
assert_raises(HexaPDF::Error) { @dest.right }
|
150
|
+
assert_raises(HexaPDF::Error) { @dest.top }
|
151
|
+
assert_raises(HexaPDF::Error) { @dest.zoom }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "type :fit_bounding_box_horizontal" do
|
156
|
+
before do
|
157
|
+
@dest = destination([:page, :FitBH, :top])
|
158
|
+
end
|
159
|
+
|
160
|
+
it "returns the type of the destination" do
|
161
|
+
assert_equal(:fit_bounding_box_horizontal, @dest.type)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "returns the argument top" do
|
165
|
+
assert_equal(:top, @dest.top)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "raises an error if the left, bottom, right, zoom properties are accessed" do
|
169
|
+
assert_raises(HexaPDF::Error) { @dest.left }
|
170
|
+
assert_raises(HexaPDF::Error) { @dest.bottom }
|
171
|
+
assert_raises(HexaPDF::Error) { @dest.right }
|
172
|
+
assert_raises(HexaPDF::Error) { @dest.zoom }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "type :fit_bounding_box_vertical::" do
|
177
|
+
before do
|
178
|
+
@dest = destination([:page, :FitBV, :left])
|
179
|
+
end
|
180
|
+
|
181
|
+
it "returns the type of the destination" do
|
182
|
+
assert_equal(:fit_bounding_box_vertical, @dest.type)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "returns the argument left" do
|
186
|
+
assert_equal(:left, @dest.left)
|
187
|
+
end
|
188
|
+
|
189
|
+
it "raises an error if the left, bottom, right, zoom properties are accessed" do
|
190
|
+
assert_raises(HexaPDF::Error) { @dest.top }
|
191
|
+
assert_raises(HexaPDF::Error) { @dest.bottom }
|
192
|
+
assert_raises(HexaPDF::Error) { @dest.right }
|
193
|
+
assert_raises(HexaPDF::Error) { @dest.zoom }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe HexaPDF::Document::Destinations do
|
199
|
+
before do
|
200
|
+
@doc = HexaPDF::Document.new
|
201
|
+
@page = @doc.pages.add
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "create_xyz" do
|
205
|
+
it "creates the destination" do
|
206
|
+
dest = @doc.destinations.create_xyz(@page, left: 1, top: 2, zoom: 3)
|
207
|
+
assert_equal([@page, :XYZ, 1, 2, 3], dest)
|
208
|
+
end
|
209
|
+
|
210
|
+
it "creates the destination and registers it under the given name" do
|
211
|
+
dest = @doc.destinations.create_xyz(@page, name: 'xyz')
|
212
|
+
assert_equal([@page, :XYZ, nil, nil, nil], @doc.destinations[dest])
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "create_fit_page" do
|
217
|
+
it "creates the destination" do
|
218
|
+
dest = @doc.destinations.create_fit_page( @page)
|
219
|
+
assert_equal([@page, :Fit], dest)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "creates the destination and registers it under the given name" do
|
223
|
+
dest = @doc.destinations.create_fit_page(@page, name: 'xyz')
|
224
|
+
assert_equal([@page, :Fit], @doc.destinations[dest])
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe "create_fit_page_horizontal" do
|
229
|
+
it "creates the destination" do
|
230
|
+
dest = @doc.destinations.create_fit_page_horizontal(@page, top: 2)
|
231
|
+
assert_equal([@page, :FitH, 2], dest)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "creates the destination and registers it under the given name" do
|
235
|
+
dest = @doc.destinations.create_fit_page_horizontal(@page, name: 'xyz')
|
236
|
+
assert_equal([@page, :FitH, nil], @doc.destinations[dest])
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe "create_fit_page_vertical" do
|
241
|
+
it "creates the destination" do
|
242
|
+
dest = @doc.destinations.create_fit_page_vertical(@page, left: 2)
|
243
|
+
assert_equal([@page, :FitV, 2], dest)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "creates the destination and registers it under the given name" do
|
247
|
+
dest = @doc.destinations.create_fit_page_vertical(@page, name: 'xyz')
|
248
|
+
assert_equal([@page, :FitV, nil], @doc.destinations[dest])
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
describe "create_fit_rectangle" do
|
253
|
+
it "creates the destination" do
|
254
|
+
dest = @doc.destinations.create_fit_rectangle(@page, left: 1, bottom: 2, right: 3, top: 4)
|
255
|
+
assert_equal([@page, :FitR, 1, 2, 3, 4], dest)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "creates the destination and registers it under the given name" do
|
259
|
+
dest = @doc.destinations.create_fit_rectangle(@page, name: 'xyz', left: 1, bottom: 2, right: 3, top: 4)
|
260
|
+
assert_equal([@page, :FitR, 1, 2, 3, 4], @doc.destinations[dest])
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe "create_fit_bounding_box" do
|
265
|
+
it "creates the destination" do
|
266
|
+
dest = @doc.destinations.create_fit_bounding_box(@page)
|
267
|
+
assert_equal([@page, :FitB], dest)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "creates the destination and registers it under the given name" do
|
271
|
+
dest = @doc.destinations.create_fit_bounding_box(@page, name: 'xyz')
|
272
|
+
assert_equal([@page, :FitB], @doc.destinations[dest])
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe "create_fit_bounding_box_horizontal" do
|
277
|
+
it "creates the destination" do
|
278
|
+
dest = @doc.destinations.create_fit_bounding_box_horizontal(@page, top: 2)
|
279
|
+
assert_equal([@page, :FitBH, 2], dest)
|
280
|
+
end
|
281
|
+
|
282
|
+
it "creates the destination and registers it under the given name" do
|
283
|
+
dest = @doc.destinations.create_fit_bounding_box_horizontal(@page, name: 'xyz')
|
284
|
+
assert_equal([@page, :FitBH, nil], @doc.destinations[dest])
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
describe "create_fit_bounding_box_vertical" do
|
289
|
+
it "creates the destination" do
|
290
|
+
dest = @doc.destinations.create_fit_bounding_box_vertical(@page, left: 2)
|
291
|
+
assert_equal([@page, :FitBV, 2], dest)
|
292
|
+
end
|
293
|
+
|
294
|
+
it "creates the destination and registers it under the given name" do
|
295
|
+
dest = @doc.destinations.create_fit_bounding_box_vertical(@page, name: 'xyz')
|
296
|
+
assert_equal([@page, :FitBV, nil], @doc.destinations[dest])
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
it "adds a destination array to the destinations name tree and allows to retrieve it" do
|
301
|
+
@doc.destinations.add('abc', [:page, :Fit])
|
302
|
+
assert_equal([:page, :Fit], @doc.destinations['abc'])
|
303
|
+
end
|
304
|
+
|
305
|
+
it "deletes a named destination" do
|
306
|
+
@doc.destinations.add('abc', [:page, :Fit])
|
307
|
+
assert(@doc.destinations['abc'])
|
308
|
+
@doc.destinations.delete('abc')
|
309
|
+
refute(@doc.destinations['abc'])
|
310
|
+
end
|
311
|
+
|
312
|
+
describe "each" do
|
313
|
+
before do
|
314
|
+
3.times {|i| @doc.destinations.add("abc#{i}", [:page, :Fit]) }
|
315
|
+
end
|
316
|
+
|
317
|
+
it "returns an enumerator if no block is given" do
|
318
|
+
enum = @doc.destinations.each
|
319
|
+
assert_equal('abc0', enum.next.first)
|
320
|
+
assert_equal('abc1', enum.next.first)
|
321
|
+
assert_equal('abc2', enum.next.first)
|
322
|
+
assert_raises(StopIteration) { enum.next }
|
323
|
+
end
|
324
|
+
|
325
|
+
it "iterates over all name-destination pairs in order" do
|
326
|
+
result = [
|
327
|
+
['abc0', :fit_page],
|
328
|
+
['abc1', :fit_page],
|
329
|
+
['abc2', :fit_page],
|
330
|
+
]
|
331
|
+
@doc.destinations.each do |name, dest|
|
332
|
+
exp_name, exp_type = result.shift
|
333
|
+
assert_equal(exp_name, name)
|
334
|
+
assert_equal(exp_type, dest.type)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
@@ -48,6 +48,10 @@ describe HexaPDF::Encryption::AES do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
it "handles invalid files with missing 16 byte padding" do
|
52
|
+
assert_equal('', @algorithm_class.decrypt('some key' * 2, 'iv' * 8))
|
53
|
+
end
|
54
|
+
|
51
55
|
it "fails on decryption if not enough bytes are provided" do
|
52
56
|
assert_raises(HexaPDF::EncryptionError) do
|
53
57
|
@algorithm_class.decrypt('some' * 4, 'no iv')
|
@@ -97,6 +101,10 @@ describe HexaPDF::Encryption::AES do
|
|
97
101
|
end
|
98
102
|
|
99
103
|
it "decryption works if the padding is invalid" do
|
104
|
+
f = Fiber.new { 'a' * 16 }
|
105
|
+
result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
|
106
|
+
assert_equal('', result)
|
107
|
+
|
100
108
|
f = Fiber.new { 'a' * 32 }
|
101
109
|
result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
|
102
110
|
assert_equal('a' * 16, result)
|
@@ -302,9 +302,9 @@ describe HexaPDF::Encryption::SecurityHandler do
|
|
302
302
|
@handler = TestHandler.new(@document)
|
303
303
|
|
304
304
|
assert_equal("Something",
|
305
|
-
@handler.decrypt(@document.revisions[0].trailer[:Encrypt])[:Key])
|
305
|
+
@handler.decrypt(@document.revisions.all[0].trailer[:Encrypt])[:Key])
|
306
306
|
assert_equal("Otherthing",
|
307
|
-
@handler.decrypt(@document.revisions[1].trailer[:Encrypt])[:Key])
|
307
|
+
@handler.decrypt(@document.revisions.all[1].trailer[:Encrypt])[:Key])
|
308
308
|
end
|
309
309
|
|
310
310
|
it "defers handling encryption to a Crypt filter is specified" do
|
@@ -196,8 +196,15 @@ describe HexaPDF::Layout::Frame do
|
|
196
196
|
[[[10, 10], [110, 10], [110, 60], [60, 60], [60, 110], [10, 110]]])
|
197
197
|
end
|
198
198
|
|
199
|
+
it "draws the box in the center" do
|
200
|
+
check_box({width: 50, height: 50, position: :float, position_hint: :center},
|
201
|
+
[35, 60],
|
202
|
+
[[[10, 10], [110, 10], [110, 110], [85, 110], [85, 60], [35, 60],
|
203
|
+
[35, 110], [10, 110]]])
|
204
|
+
end
|
205
|
+
|
199
206
|
describe "with margin" do
|
200
|
-
[:left, :right].each do |hint|
|
207
|
+
[:left, :center, :right].each do |hint|
|
201
208
|
it "ignores all margins if the box fills the whole frame, with position hint #{hint}" do
|
202
209
|
check_box({margin: 10, position: :float, position_hint: hint},
|
203
210
|
[10, 10], [])
|
@@ -219,6 +226,13 @@ describe HexaPDF::Layout::Frame do
|
|
219
226
|
[[[20, 10], [110, 10], [110, 110], [90, 110], [90, 50], [20, 50]]])
|
220
227
|
end
|
221
228
|
|
229
|
+
it "uses the left and the right margin if aligned center" do
|
230
|
+
check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :center},
|
231
|
+
[35, 60],
|
232
|
+
[[[10, 10], [110, 10], [110, 110], [95, 110], [95, 50], [25, 50],
|
233
|
+
[25, 110], [10, 110]]])
|
234
|
+
end
|
235
|
+
|
222
236
|
it "ignores the right, but not the left margin if aligned right to the frame border" do
|
223
237
|
check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :right},
|
224
238
|
[60, 60],
|
@@ -53,6 +53,22 @@ describe HexaPDF::Layout::TextBox do
|
|
53
53
|
assert_equal(20, box.height)
|
54
54
|
end
|
55
55
|
|
56
|
+
it "uses the whole available width when aligning to the center or right" do
|
57
|
+
[:center, :right].each do |align|
|
58
|
+
box = create_box([@inline_box], style: {align: align})
|
59
|
+
assert(box.fit(100, 100, @frame))
|
60
|
+
assert_equal(100, box.width)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "uses the whole available height when vertically aligning to the center or bottom" do
|
65
|
+
[:center, :bottom].each do |valign|
|
66
|
+
box = create_box([@inline_box], style: {valign: valign})
|
67
|
+
assert(box.fit(100, 100, @frame))
|
68
|
+
assert_equal(100, box.height)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
56
72
|
it "can't fit the text box if the set width is bigger than the available width" do
|
57
73
|
box = create_box([@inline_box], width: 101)
|
58
74
|
refute(box.fit(100, 100, @frame))
|
@@ -646,6 +646,13 @@ describe HexaPDF::Layout::TextLayouter do
|
|
646
646
|
assert_equal(100 - 20 * 2 + 20, result.lines[0].y_offset)
|
647
647
|
assert_equal(100, result.height)
|
648
648
|
end
|
649
|
+
|
650
|
+
it "doesn't vertically align when layouting in variable-width mode" do
|
651
|
+
@style.valign = :bottom
|
652
|
+
result = @layouter.fit(@items, proc { 40 }, 100)
|
653
|
+
assert_equal(result.lines[0].y_max, result.lines[0].y_offset)
|
654
|
+
assert_equal(40, result.height)
|
655
|
+
end
|
649
656
|
end
|
650
657
|
|
651
658
|
it "post-processes lines for justification if needed" do
|
@@ -52,7 +52,7 @@ describe HexaPDF::Task::Optimize do
|
|
52
52
|
describe "compact" do
|
53
53
|
it "compacts the document" do
|
54
54
|
@doc.task(:optimize, compact: true)
|
55
|
-
assert_equal(1, @doc.revisions.
|
55
|
+
assert_equal(1, @doc.revisions.count)
|
56
56
|
assert_equal(2, @doc.each(only_current: false).to_a.size)
|
57
57
|
refute_equal(@obj2, @doc.object(@obj2))
|
58
58
|
refute_equal(@obj3, @doc.object(@obj3))
|
@@ -81,8 +81,8 @@ describe HexaPDF::Task::Optimize do
|
|
81
81
|
end
|
82
82
|
|
83
83
|
it "compacts and deletes xref streams" do
|
84
|
-
@doc.add({Type: :XRef},
|
85
|
-
@doc.add({Type: :XRef},
|
84
|
+
@doc.revisions.all[0].add(@doc.wrap({Type: :XRef}, oid: @doc.revisions.next_oid))
|
85
|
+
@doc.revisions.all[1].add(@doc.wrap({Type: :XRef}, oid: @doc.revisions.next_oid))
|
86
86
|
@doc.task(:optimize, compact: true, xref_streams: :delete)
|
87
87
|
assert_no_xrefstms
|
88
88
|
assert_default_deleted
|
@@ -108,7 +108,9 @@ describe HexaPDF::Task::Optimize do
|
|
108
108
|
assert_objstms_generated
|
109
109
|
assert_default_deleted
|
110
110
|
assert_nil(@doc.object(objstm).value)
|
111
|
-
|
111
|
+
objstms = @doc.revisions.current.find_all {|obj| obj.type == :ObjStm }
|
112
|
+
assert_equal(2, objstms.size)
|
113
|
+
assert_equal(400, objstms[0].instance_variable_get(:@objects).size)
|
112
114
|
end
|
113
115
|
|
114
116
|
it "deletes object and xref streams" do
|
@@ -158,6 +160,17 @@ describe HexaPDF::Task::Optimize do
|
|
158
160
|
@doc.task(:optimize, compress_pages: true)
|
159
161
|
assert_equal("10 10 m\nq\nQ\nBI\n/Name 5 ID\ndataEI\n", page.contents)
|
160
162
|
end
|
163
|
+
|
164
|
+
it "uses parser.on_correctable_error to defer a decision regarding invalid operations" do
|
165
|
+
page = @doc.pages.add
|
166
|
+
page.contents = "10 20-30 m"
|
167
|
+
@doc.task(:optimize, compress_pages: true)
|
168
|
+
assert_equal("", page.contents)
|
169
|
+
|
170
|
+
@doc.config['parser.on_correctable_error'] = proc { true }
|
171
|
+
page.contents = "10 20-30 m"
|
172
|
+
assert_raises(HexaPDF::Error) { @doc.task(:optimize, compress_pages: true) }
|
173
|
+
end
|
161
174
|
end
|
162
175
|
|
163
176
|
describe "prune_page_resources" do
|
@@ -90,7 +90,7 @@ describe HexaPDF::Composer do
|
|
90
90
|
it "creates a new style if it does not exist based on the base argument" do
|
91
91
|
@composer.style(:base, font_size: 20)
|
92
92
|
assert_equal(20, @composer.style(:newstyle, subscript: true).font_size)
|
93
|
-
refute(
|
93
|
+
refute(@composer.style(:base).subscript)
|
94
94
|
assert_equal(10, @composer.style(:another_new, base: nil).font_size)
|
95
95
|
assert(@composer.style(:yet_another_new, base: :newstyle).subscript)
|
96
96
|
end
|
@@ -240,6 +240,13 @@ describe HexaPDF::Composer do
|
|
240
240
|
assert(box.style.subscript)
|
241
241
|
assert_same(@composer.document.images.add(image_path), box.image)
|
242
242
|
end
|
243
|
+
|
244
|
+
it "allows using a form XObject" do
|
245
|
+
form = @composer.document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, 10, 10]})
|
246
|
+
@composer.image(form, width: 10)
|
247
|
+
assert_equal(796, @composer.y)
|
248
|
+
assert_equal(36, @composer.x)
|
249
|
+
end
|
243
250
|
end
|
244
251
|
|
245
252
|
describe "draw_box" do
|
@@ -318,4 +325,20 @@ describe HexaPDF::Composer do
|
|
318
325
|
end
|
319
326
|
end
|
320
327
|
end
|
328
|
+
|
329
|
+
describe "create_stamp" do
|
330
|
+
it "creates and returns a form XObject" do
|
331
|
+
stamp = @composer.create_stamp(10, 5)
|
332
|
+
assert_kind_of(HexaPDF::Type::Form, stamp)
|
333
|
+
assert_equal(10, stamp.width)
|
334
|
+
assert_equal(5, stamp.height)
|
335
|
+
end
|
336
|
+
|
337
|
+
it "allows using a block to draw on the canvas of the form XObject" do
|
338
|
+
stamp = @composer.create_stamp(10, 10) do |canvas|
|
339
|
+
canvas.line_width(5)
|
340
|
+
end
|
341
|
+
assert_equal("5 w\n", stamp.canvas.contents)
|
342
|
+
end
|
343
|
+
end
|
321
344
|
end
|