hexapdf 0.24.2 → 0.25.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 +27 -0
- data/README.md +63 -14
- data/examples/022-outline.rb +30 -0
- data/lib/hexapdf/configuration.rb +3 -0
- data/lib/hexapdf/document/destinations.rb +90 -1
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/encryption/security_handler.rb +4 -1
- data/lib/hexapdf/layout/style.rb +41 -12
- data/lib/hexapdf/layout/text_layouter.rb +5 -0
- data/lib/hexapdf/revision.rb +3 -0
- data/lib/hexapdf/revisions.rb +11 -1
- data/lib/hexapdf/type/acro_form/form.rb +3 -12
- data/lib/hexapdf/type/annotation.rb +5 -16
- data/lib/hexapdf/type/catalog.rb +7 -0
- data/lib/hexapdf/type/font_descriptor.rb +4 -13
- data/lib/hexapdf/type/object_stream.rb +9 -1
- data/lib/hexapdf/type/outline.rb +122 -0
- data/lib/hexapdf/type/outline_item.rb +373 -0
- data/lib/hexapdf/type.rb +2 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_destinations.rb +87 -0
- data/test/hexapdf/encryption/test_security_handler.rb +4 -2
- data/test/hexapdf/layout/test_style.rb +1 -0
- data/test/hexapdf/layout/test_text_layouter.rb +6 -0
- data/test/hexapdf/test_document.rb +4 -0
- data/test/hexapdf/test_revisions.rb +47 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/test_catalog.rb +7 -0
- data/test/hexapdf/type/test_object_stream.rb +11 -10
- data/test/hexapdf/type/test_outline.rb +69 -0
- data/test/hexapdf/type/test_outline_item.rb +292 -0
- metadata +8 -3
@@ -267,6 +267,49 @@ describe HexaPDF::Revisions do
|
|
267
267
|
assert_equal(2, doc.revisions.count)
|
268
268
|
end
|
269
269
|
|
270
|
+
it "merges a completely empty revision with just a /XRefStm with the previous revision" do
|
271
|
+
io = StringIO.new(<<~EOF) # 2 28 3 47
|
272
|
+
%PDF-1.7
|
273
|
+
1 0 obj
|
274
|
+
10
|
275
|
+
endobj
|
276
|
+
|
277
|
+
2 0 obj
|
278
|
+
20
|
279
|
+
endobj
|
280
|
+
|
281
|
+
3 0 obj
|
282
|
+
<< /Type /XRef /Size 3 /Index [2 1] /W [1 1 1] /Filter /ASCIIHexDecode /Length 6
|
283
|
+
>>stream
|
284
|
+
011C00
|
285
|
+
endstream
|
286
|
+
endobj
|
287
|
+
|
288
|
+
xref
|
289
|
+
0 4
|
290
|
+
0000000000 65535 f
|
291
|
+
0000000009 00000 n
|
292
|
+
0000000000 65535 f
|
293
|
+
0000000047 00000 n
|
294
|
+
trailer
|
295
|
+
<< /Size 3 >>
|
296
|
+
startxref
|
297
|
+
170
|
298
|
+
%%EOF
|
299
|
+
|
300
|
+
xref
|
301
|
+
0 0
|
302
|
+
trailer
|
303
|
+
<< /Size 3 /Prev 170 /XRefStm 47>>
|
304
|
+
startxref
|
305
|
+
302
|
306
|
+
%%EOF
|
307
|
+
EOF
|
308
|
+
doc = HexaPDF::Document.new(io: io)
|
309
|
+
assert_equal(1, doc.revisions.count)
|
310
|
+
assert_equal(20, doc.object(2).value)
|
311
|
+
end
|
312
|
+
|
270
313
|
it "uses the reconstructed revision if errors are found when loading from an IO" do
|
271
314
|
io = StringIO.new(<<~EOF)
|
272
315
|
%PDF-1.7
|
@@ -300,5 +343,9 @@ describe HexaPDF::Revisions do
|
|
300
343
|
doc = HexaPDF::Document.new(io: io)
|
301
344
|
assert_equal(2, doc.revisions.count)
|
302
345
|
assert_same(doc.revisions.all[0].trailer.value, doc.revisions.all[1].trailer.value)
|
346
|
+
|
347
|
+
assert_raises(HexaPDF::MalformedPDFError) do
|
348
|
+
HexaPDF::Document.new(io: io, config: {'parser.try_xref_reconstruction' => false})
|
349
|
+
end
|
303
350
|
end
|
304
351
|
end
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ 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.25.0)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
@@ -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.25.0)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -206,7 +206,7 @@ describe HexaPDF::Writer do
|
|
206
206
|
<</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
|
207
207
|
endobj
|
208
208
|
5 0 obj
|
209
|
-
<</Producer(HexaPDF version 0.
|
209
|
+
<</Producer(HexaPDF version 0.25.0)>>
|
210
210
|
endobj
|
211
211
|
4 0 obj
|
212
212
|
<</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream
|
@@ -29,6 +29,13 @@ describe HexaPDF::Type::Catalog do
|
|
29
29
|
assert_same(other, names)
|
30
30
|
end
|
31
31
|
|
32
|
+
it "creates the document outline on access" do
|
33
|
+
assert_nil(@catalog[:Outlines])
|
34
|
+
outline = @catalog.outline
|
35
|
+
assert_equal(:Outlines, outline.type)
|
36
|
+
assert_same(outline, @catalog.outline)
|
37
|
+
end
|
38
|
+
|
32
39
|
describe "acro_form" do
|
33
40
|
it "returns an existing form object" do
|
34
41
|
@catalog[:AcroForm] = :test
|
@@ -26,39 +26,40 @@ describe HexaPDF::Type::ObjectStream do
|
|
26
26
|
def (@doc).trailer
|
27
27
|
@trailer ||= {Encrypt: HexaPDF::Object.new({}, oid: 9)}
|
28
28
|
end
|
29
|
-
@obj = HexaPDF::Type::ObjectStream.new({}, oid: 1, document: @doc
|
29
|
+
@obj = HexaPDF::Type::ObjectStream.new({N: 2, First: 8}, oid: 1, document: @doc,
|
30
|
+
stream: "1 0 5 2 5 [1 2]")
|
30
31
|
end
|
31
32
|
|
32
33
|
it "correctly parses stream data" do
|
33
|
-
@obj.value = {N: 2, First: 8}
|
34
|
-
@obj.stream = "1 0 5 2 5 [1 2]"
|
35
34
|
data = @obj.parse_stream
|
36
35
|
assert_equal([5, 1], data.object_by_index(0))
|
37
36
|
assert_equal([[1, 2], 5], data.object_by_index(1))
|
38
37
|
end
|
39
38
|
|
40
|
-
it "allows adding and deleting
|
39
|
+
it "allows adding and deleting objects as well as determining their index" do
|
41
40
|
@obj.add_object(5)
|
42
41
|
@obj.add_object(7)
|
43
42
|
@obj.add_object(9)
|
44
43
|
@obj.add_object(5)
|
45
|
-
assert_equal(
|
46
|
-
assert_equal(
|
47
|
-
assert_equal(
|
44
|
+
assert_equal(2, @obj.object_index(5))
|
45
|
+
assert_equal(3, @obj.object_index(7))
|
46
|
+
assert_equal(4, @obj.object_index(9))
|
48
47
|
|
49
48
|
@obj.delete_object(5)
|
50
49
|
@obj.delete_object(5)
|
51
|
-
assert_equal(
|
52
|
-
assert_equal(
|
50
|
+
assert_equal(2, @obj.object_index(9))
|
51
|
+
assert_equal(3, @obj.object_index(7))
|
53
52
|
assert_nil(@obj.object_index(5))
|
54
53
|
|
55
54
|
@obj.delete_object(7)
|
56
55
|
@obj.delete_object(9)
|
57
|
-
assert_nil(@obj.object_index(
|
56
|
+
assert_nil(@obj.object_index(7))
|
58
57
|
end
|
59
58
|
|
60
59
|
describe "write objects to stream" do
|
61
60
|
before do
|
61
|
+
@obj.delete_object(HexaPDF::Reference.new(1))
|
62
|
+
@obj.delete_object(HexaPDF::Reference.new(5))
|
62
63
|
@revision = Object.new
|
63
64
|
def @revision.object(obj); obj; end
|
64
65
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
require 'hexapdf/type/outline'
|
6
|
+
|
7
|
+
describe HexaPDF::Type::Outline do
|
8
|
+
before do
|
9
|
+
@doc = HexaPDF::Document.new
|
10
|
+
@outline = @doc.add({}, type: :Outlines)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "delegates add_item to the outline item wrapper" do
|
14
|
+
item = @outline.add_item("test", position: :first, text_color: "blue", flags: [:italic])
|
15
|
+
assert_equal("test", item.title)
|
16
|
+
assert_equal([0, 0, 1], item.text_color.components)
|
17
|
+
assert_equal([:italic], item.flags)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "recursively iterates over all items by delegating to the outline item wrapper" do
|
21
|
+
@outline.add_item("Item1") do |item1|
|
22
|
+
item1.add_item("Item2")
|
23
|
+
item1.add_item("Item3") do |item3|
|
24
|
+
item3.add_item("Item4")
|
25
|
+
end
|
26
|
+
item1.add_item("Item5")
|
27
|
+
end
|
28
|
+
assert_equal(%w[Item1 Item2 Item3 Item4 Item5], @outline.each_item.map(&:title))
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "perform_validation" do
|
32
|
+
before do
|
33
|
+
5.times { @outline.add_item("Test1") }
|
34
|
+
end
|
35
|
+
|
36
|
+
it "fixes a missing /First entry" do
|
37
|
+
@outline.delete(:First)
|
38
|
+
called = false
|
39
|
+
@outline.validate do |msg, correctable, _|
|
40
|
+
called = true
|
41
|
+
assert_match(/missing an endpoint reference/, msg)
|
42
|
+
assert(correctable)
|
43
|
+
end
|
44
|
+
assert(called)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "fixes a missing /Last entry" do
|
48
|
+
@outline.delete(:Last)
|
49
|
+
called = false
|
50
|
+
@outline.validate do |msg, correctable, _|
|
51
|
+
called = true
|
52
|
+
assert_match(/missing an endpoint reference/, msg)
|
53
|
+
assert(correctable)
|
54
|
+
end
|
55
|
+
assert(called)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "deletes the /Count entry if no /First and /Last entries exist" do
|
59
|
+
@outline.delete(:Last)
|
60
|
+
@outline.delete(:First)
|
61
|
+
assert_equal(5, @outline[:Count])
|
62
|
+
@outline.validate do |msg, correctable, _|
|
63
|
+
assert_match(/key \/Count set but no items exist/, msg)
|
64
|
+
assert(correctable)
|
65
|
+
end
|
66
|
+
refute(@outline.key?(:Count))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
require 'hexapdf/type/outline_item'
|
6
|
+
|
7
|
+
describe HexaPDF::Type::OutlineItem do
|
8
|
+
before do
|
9
|
+
@doc = HexaPDF::Document.new
|
10
|
+
@item = @doc.add({Title: "root", Count: 0}, type: :XXOutlineItem)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "title" do
|
14
|
+
it "returns the set title" do
|
15
|
+
@item[:Title] = 'Test'
|
16
|
+
assert_equal('Test', @item.title)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sets the title to the given value" do
|
20
|
+
@item.title('Test')
|
21
|
+
assert_equal('Test', @item[:Title])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "text_color" do
|
26
|
+
it "returns the default color if none is set" do
|
27
|
+
assert_equal([0, 0, 0], @item.text_color.components)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns the set color" do
|
31
|
+
@item[:C] = [0, 0.5, 1]
|
32
|
+
assert_equal([0, 0.5, 1], @item.text_color.components)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "sets the text color to the given value" do
|
36
|
+
@item.text_color([51, 51, 255])
|
37
|
+
assert_equal([0.2, 0.2, 1], @item[:C])
|
38
|
+
end
|
39
|
+
|
40
|
+
it "fails if a color in another color space is set" do
|
41
|
+
assert_raises(ArgumentError) { @item.text_color(5) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "destination" do
|
46
|
+
it "returns the set destination" do
|
47
|
+
@item[:Dest] = [5, :Fit]
|
48
|
+
assert_equal([5, :Fit], @item.destination)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "sets the destination to the given value" do
|
52
|
+
@item.destination(@doc.pages.add)
|
53
|
+
assert_equal([@doc.pages[0], :Fit], @item[:Dest])
|
54
|
+
end
|
55
|
+
|
56
|
+
it "deletes an existing action entry when setting a value" do
|
57
|
+
@item[:A] = {S: :GoTo}
|
58
|
+
@item.destination(@doc.pages.add)
|
59
|
+
refute(@item.key?(:A))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "action" do
|
64
|
+
it "returns the set action" do
|
65
|
+
@item[:A] = {S: :GoTo}
|
66
|
+
assert_equal({S: :GoTo}, @item.action.value)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "sets the action to the given value" do
|
70
|
+
@item.action({S: :GoTo})
|
71
|
+
assert_equal({S: :GoTo}, @item[:A].value)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "deletes an existing destination entry when setting a value" do
|
75
|
+
@item[:Dest] = [1, :Fit]
|
76
|
+
@item.action({S: :GoTo})
|
77
|
+
refute(@item.key?(:Dest))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "add" do
|
82
|
+
it "returns the created item" do
|
83
|
+
new_item = @item.add_item("Test")
|
84
|
+
assert_equal("Test", new_item.title)
|
85
|
+
assert_equal(0, new_item[:Count])
|
86
|
+
assert_same(@item, new_item[:Parent])
|
87
|
+
assert(new_item.indirect?)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "sets the item's text color" do
|
91
|
+
new_item = @item.add_item("Test", text_color: "red")
|
92
|
+
assert_equal([1, 0, 0], new_item.text_color.components)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "sets the item's flags" do
|
96
|
+
new_item = @item.add_item("Test", flags: [:bold, :italic])
|
97
|
+
assert_equal([:italic, :bold], new_item.flags)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "doesn't set the item's /Count when it should not be open" do
|
101
|
+
new_item = @item.add_item("Test", open: false)
|
102
|
+
refute(new_item.key?(:Count))
|
103
|
+
end
|
104
|
+
|
105
|
+
it "sets the item's destination if given" do
|
106
|
+
new_item = @item.add_item("Test", destination: @doc.pages.add)
|
107
|
+
assert_equal([@doc.pages[0], :Fit], new_item.destination)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "sets the item's action if given" do
|
111
|
+
new_item = @item.add_item("Test", action: {S: :GoTo, D: [1, :Fit]})
|
112
|
+
assert_equal({S: :GoTo, D: [1, :Fit]}, new_item.action.value)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "yields the item" do
|
116
|
+
yielded_item = nil
|
117
|
+
new_item = @item.add_item("Test") {|i| yielded_item = i }
|
118
|
+
assert_same(new_item, yielded_item)
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "position" do
|
122
|
+
it "works for an empty item" do
|
123
|
+
new_item = @item.add_item("Test")
|
124
|
+
assert_same(new_item, @item[:First])
|
125
|
+
assert_same(new_item, @item[:Last])
|
126
|
+
assert_nil(new_item[:Next])
|
127
|
+
assert_nil(new_item[:Prev])
|
128
|
+
end
|
129
|
+
|
130
|
+
it "inserts an item at the last position with at least one existing sub-item" do
|
131
|
+
first_item = @item.add_item("Test")
|
132
|
+
second_item = @item.add_item("Test", position: :last)
|
133
|
+
assert_same(first_item, @item[:First])
|
134
|
+
assert_same(second_item, @item[:Last])
|
135
|
+
assert_same(second_item, first_item[:Next])
|
136
|
+
assert_same(first_item, second_item[:Prev])
|
137
|
+
end
|
138
|
+
|
139
|
+
it "inserts an item at the first position with at least one existing sub-item" do
|
140
|
+
second_item = @item.add_item("Test")
|
141
|
+
first_item = @item.add_item("Test", position: :first)
|
142
|
+
assert_same(first_item, @item[:First])
|
143
|
+
assert_same(second_item, @item[:Last])
|
144
|
+
assert_same(second_item, first_item[:Next])
|
145
|
+
assert_same(first_item, second_item[:Prev])
|
146
|
+
end
|
147
|
+
|
148
|
+
it "inserts an item at an arbitrary positive index" do
|
149
|
+
5.times {|i| @item.add_item("Test#{i}") }
|
150
|
+
@item.add_item("Test", position: 3)
|
151
|
+
item = @item[:First]
|
152
|
+
%w[Test0 Test1 Test2 Test Test3 Test4].each do |title|
|
153
|
+
assert_equal(title, item.title)
|
154
|
+
item = item[:Next]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
it "inserts an item at an arbitrary negative index" do
|
159
|
+
5.times {|i| @item.add_item("Test#{i}") }
|
160
|
+
@item.add_item("Test", position: -3)
|
161
|
+
item = @item[:First]
|
162
|
+
%w[Test0 Test1 Test2 Test Test3 Test4].each do |title|
|
163
|
+
assert_equal(title, item.title)
|
164
|
+
item = item[:Next]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it "raises an out of bounds error for invalid integer values" do
|
169
|
+
5.times {|i| @item.add_item("Test#{i}") }
|
170
|
+
assert_raises(ArgumentError) { @item.add_item("Test", position: 10) }
|
171
|
+
assert_raises(ArgumentError) { @item.add_item("Test", position: -10) }
|
172
|
+
end
|
173
|
+
|
174
|
+
it "raises an error for an invalid value" do
|
175
|
+
assert_raises(ArgumentError) { @item.add_item("Test", position: :luck) }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
it "calculcates the /Count values correctly" do
|
180
|
+
[
|
181
|
+
[[true, true], [6, 4, 0, 1, 0, 0, 0]],
|
182
|
+
[[true, false], [5, 3, 0, -1, 0, 0, 0]],
|
183
|
+
[[false, true], [2, -4, 0, 1, 0, 0, 0]],
|
184
|
+
[[false, false], [2, -3, 0, -1, 0, 0, 0]],
|
185
|
+
].each do |(states, result)|
|
186
|
+
# reset list
|
187
|
+
@item[:First] = @item[:Last] = nil
|
188
|
+
@item[:Count] = 0
|
189
|
+
|
190
|
+
items = [@item]
|
191
|
+
@item.add_item("Document", open: states[0]) do |idoc|
|
192
|
+
items << idoc
|
193
|
+
items << idoc.add_item("Section 1", open: false)
|
194
|
+
idoc.add_item("Section 2", open: states[1]) do |isec|
|
195
|
+
items << isec
|
196
|
+
items << isec.add_item("Subsection 1")
|
197
|
+
end
|
198
|
+
items << idoc.add_item("Section 3")
|
199
|
+
end
|
200
|
+
items << @item.add_item("Summary")
|
201
|
+
items.each_with_index {|item, index| assert_equal(result.shift, item[:Count] || 0, "item#{index}") }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "recursively iterates over all descendant items" do
|
207
|
+
@item.add_item("Item1") do |item1|
|
208
|
+
item1.add_item("Item2")
|
209
|
+
item1.add_item("Item3") do |item3|
|
210
|
+
item3.add_item("Item4")
|
211
|
+
end
|
212
|
+
item1.add_item("Item5")
|
213
|
+
end
|
214
|
+
assert_equal(%w[Item1 Item2 Item3 Item4 Item5], @item.each_item.map(&:title))
|
215
|
+
end
|
216
|
+
|
217
|
+
describe "perform_validation" do
|
218
|
+
before do
|
219
|
+
5.times { @item.add_item("Test1") }
|
220
|
+
@item[:Parent] = @doc.add({})
|
221
|
+
end
|
222
|
+
|
223
|
+
it "fixes a missing /First entry" do
|
224
|
+
@item.delete(:First)
|
225
|
+
called = false
|
226
|
+
@item.validate do |msg, correctable, _|
|
227
|
+
called = true
|
228
|
+
assert_match(/missing an endpoint reference/, msg)
|
229
|
+
assert(correctable)
|
230
|
+
end
|
231
|
+
assert(called)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "fixes a missing /Last entry" do
|
235
|
+
@item.delete(:Last)
|
236
|
+
called = false
|
237
|
+
@item.validate do |msg, correctable, _|
|
238
|
+
called = true
|
239
|
+
assert_match(/missing an endpoint reference/, msg)
|
240
|
+
assert(correctable)
|
241
|
+
end
|
242
|
+
assert(called)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "deletes the /Count entry if no /First and /Last entries exist" do
|
246
|
+
@item.delete(:Last)
|
247
|
+
@item.delete(:First)
|
248
|
+
assert_equal(5, @item[:Count])
|
249
|
+
@item.validate do |msg, correctable, _|
|
250
|
+
assert_match(/\/Count set but no descendants/, msg)
|
251
|
+
assert(correctable)
|
252
|
+
end
|
253
|
+
refute(@item.key?(:Count))
|
254
|
+
end
|
255
|
+
|
256
|
+
it "fails validation if the previous item's /Next points somewhere else" do
|
257
|
+
item = @item[:First][:Next]
|
258
|
+
item[:Prev][:Next] = item[:Next]
|
259
|
+
item.validate do |msg, correctable, _|
|
260
|
+
assert_match(/\/Prev points to item whose \/Next points somewhere else/, msg)
|
261
|
+
refute(correctable)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
it "corrects the previous item's missing /Next entry" do
|
266
|
+
item = @item[:First][:Next]
|
267
|
+
item[:Prev].delete(:Next)
|
268
|
+
item.validate do |msg, correctable, _|
|
269
|
+
assert_match(/\/Prev points to item without \/Next/, msg)
|
270
|
+
assert(correctable)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
it "fails validation if the next item's /Prev points somewhere else" do
|
275
|
+
item = @item[:First][:Next]
|
276
|
+
item[:Next][:Prev] = item[:Prev]
|
277
|
+
item.validate do |msg, correctable, _|
|
278
|
+
assert_match(/\/Next points to item whose \/Prev points somewhere else/, msg)
|
279
|
+
refute(correctable)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it "corrects the next item's missing /Prev entry" do
|
284
|
+
item = @item[:First][:Next]
|
285
|
+
item[:Next].delete(:Prev)
|
286
|
+
item.validate do |msg, correctable, _|
|
287
|
+
assert_match(/\/Next points to item without \/Prev/, msg)
|
288
|
+
assert(correctable)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -233,6 +233,7 @@ files:
|
|
233
233
|
- examples/019-acro_form.rb
|
234
234
|
- examples/020-column_box.rb
|
235
235
|
- examples/021-list_box.rb
|
236
|
+
- examples/022-outline.rb
|
236
237
|
- examples/emoji-smile.png
|
237
238
|
- examples/emoji-wink.png
|
238
239
|
- examples/machupicchu.jpg
|
@@ -421,6 +422,8 @@ files:
|
|
421
422
|
- lib/hexapdf/type/info.rb
|
422
423
|
- lib/hexapdf/type/names.rb
|
423
424
|
- lib/hexapdf/type/object_stream.rb
|
425
|
+
- lib/hexapdf/type/outline.rb
|
426
|
+
- lib/hexapdf/type/outline_item.rb
|
424
427
|
- lib/hexapdf/type/page.rb
|
425
428
|
- lib/hexapdf/type/page_tree_node.rb
|
426
429
|
- lib/hexapdf/type/resources.rb
|
@@ -664,6 +667,8 @@ files:
|
|
664
667
|
- test/hexapdf/type/test_info.rb
|
665
668
|
- test/hexapdf/type/test_names.rb
|
666
669
|
- test/hexapdf/type/test_object_stream.rb
|
670
|
+
- test/hexapdf/type/test_outline.rb
|
671
|
+
- test/hexapdf/type/test_outline_item.rb
|
667
672
|
- test/hexapdf/type/test_page.rb
|
668
673
|
- test/hexapdf/type/test_page_tree_node.rb
|
669
674
|
- test/hexapdf/type/test_resources.rb
|
@@ -698,7 +703,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
698
703
|
- !ruby/object:Gem::Version
|
699
704
|
version: '0'
|
700
705
|
requirements: []
|
701
|
-
rubygems_version: 3.
|
706
|
+
rubygems_version: 3.3.3
|
702
707
|
signing_key:
|
703
708
|
specification_version: 4
|
704
709
|
summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|