hexapdf 0.24.2 → 0.25.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 +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
|