hexapdf 0.12.3 → 0.13.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 +38 -0
- data/lib/hexapdf/cli/command.rb +4 -2
- data/lib/hexapdf/cli/image2pdf.rb +2 -1
- data/lib/hexapdf/cli/info.rb +51 -2
- data/lib/hexapdf/cli/inspect.rb +30 -8
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/configuration.rb +15 -0
- data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
- data/lib/hexapdf/dictionary.rb +4 -4
- data/lib/hexapdf/dictionary_fields.rb +1 -9
- data/lib/hexapdf/document.rb +31 -12
- data/lib/hexapdf/document/files.rb +0 -1
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +1 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
- data/lib/hexapdf/font/cmap.rb +1 -4
- data/lib/hexapdf/font/true_type/table/head.rb +1 -0
- data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
- data/lib/hexapdf/image_loader/png.rb +3 -2
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/style.rb +23 -23
- data/lib/hexapdf/layout/text_shaper.rb +3 -2
- data/lib/hexapdf/object.rb +30 -25
- data/lib/hexapdf/parser.rb +65 -3
- data/lib/hexapdf/pdf_array.rb +9 -2
- data/lib/hexapdf/revisions.rb +29 -21
- data/lib/hexapdf/serializer.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +6 -4
- data/lib/hexapdf/type/acro_form/choice_field.rb +4 -4
- data/lib/hexapdf/type/acro_form/field.rb +35 -5
- data/lib/hexapdf/type/acro_form/form.rb +6 -4
- data/lib/hexapdf/type/acro_form/text_field.rb +2 -1
- data/lib/hexapdf/type/actions/uri.rb +3 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -4
- data/lib/hexapdf/type/catalog.rb +2 -2
- data/lib/hexapdf/type/file_specification.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +3 -1
- data/lib/hexapdf/type/font_true_type.rb +6 -2
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/form.rb +2 -1
- data/lib/hexapdf/type/image.rb +2 -2
- data/lib/hexapdf/type/page.rb +16 -7
- data/lib/hexapdf/type/page_tree_node.rb +29 -5
- data/lib/hexapdf/type/resources.rb +1 -0
- data/lib/hexapdf/type/trailer.rb +2 -3
- data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
- data/test/hexapdf/content/test_canvas.rb +3 -3
- data/test/hexapdf/content/test_color_space.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +4 -4
- data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
- data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
- data/test/hexapdf/layout/test_text_layouter.rb +3 -4
- data/test/hexapdf/test_configuration.rb +2 -2
- data/test/hexapdf/test_dictionary.rb +3 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -2
- data/test/hexapdf/test_document.rb +4 -4
- data/test/hexapdf/test_object.rb +44 -26
- data/test/hexapdf/test_parser.rb +115 -55
- data/test/hexapdf/test_pdf_array.rb +7 -0
- data/test/hexapdf/test_revisions.rb +35 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -2
- data/test/hexapdf/type/acro_form/test_field.rb +39 -0
- data/test/hexapdf/type/acro_form/test_form.rb +4 -4
- data/test/hexapdf/type/acro_form/test_text_field.rb +2 -0
- data/test/hexapdf/type/test_font_simple.rb +2 -1
- data/test/hexapdf/type/test_font_true_type.rb +6 -0
- data/test/hexapdf/type/test_form.rb +1 -1
- data/test/hexapdf/type/test_page.rb +8 -1
- data/test/hexapdf/type/test_page_tree_node.rb +42 -0
- data/test/hexapdf/utils/test_bit_field.rb +2 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
- metadata +5 -12
@@ -107,6 +107,13 @@ describe HexaPDF::PDFArray do
|
|
107
107
|
assert_equal([1, :data, @array[2]], @array[0, 5])
|
108
108
|
end
|
109
109
|
|
110
|
+
it "allows deleting an object" do
|
111
|
+
obj = @array.value[1]
|
112
|
+
assert_same(obj, @array.delete(obj))
|
113
|
+
ref = HexaPDF::Object.new(:test, oid: 1)
|
114
|
+
assert_equal(ref, @array.delete(ref))
|
115
|
+
end
|
116
|
+
|
110
117
|
describe "slice!" do
|
111
118
|
it "allows deleting a single element" do
|
112
119
|
@array.slice!(2)
|
@@ -158,4 +158,39 @@ describe HexaPDF::Revisions do
|
|
158
158
|
doc = HexaPDF::Document.new(io: io)
|
159
159
|
assert_equal(2, doc.revisions.count)
|
160
160
|
end
|
161
|
+
|
162
|
+
it "uses the reconstructed revision if errors are found when loading from an IO" do
|
163
|
+
io = StringIO.new(<<~EOF)
|
164
|
+
%PDF-1.7
|
165
|
+
1 0 obj
|
166
|
+
10
|
167
|
+
endobj
|
168
|
+
|
169
|
+
xref
|
170
|
+
0 2
|
171
|
+
0000000000 65535 f
|
172
|
+
0000000009 00000 n
|
173
|
+
trailer
|
174
|
+
<< /Size 5 >>
|
175
|
+
startxref
|
176
|
+
28
|
177
|
+
%%EOF
|
178
|
+
|
179
|
+
2 0 obj
|
180
|
+
300
|
181
|
+
endobj
|
182
|
+
|
183
|
+
xref
|
184
|
+
2 1
|
185
|
+
0000000301 00000 n
|
186
|
+
trailer
|
187
|
+
<< /Size 3 /Prev 100>>
|
188
|
+
startxref
|
189
|
+
139
|
190
|
+
%%EOF
|
191
|
+
EOF
|
192
|
+
doc = HexaPDF::Document.new(io: io)
|
193
|
+
assert_equal(2, doc.revisions.count)
|
194
|
+
assert_same(doc.revisions[0].trailer.value, doc.revisions[1].trailer.value)
|
195
|
+
end
|
161
196
|
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.13.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.13.0)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -478,7 +478,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
478
478
|
[:restore_graphics_state],
|
479
479
|
[:end_marked_content]])
|
480
480
|
end
|
481
|
-
|
482
481
|
end
|
483
482
|
|
484
483
|
describe "choice fields" do
|
@@ -495,7 +494,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
495
494
|
|
496
495
|
describe "font resolution in case the referenced font is not usable" do
|
497
496
|
before do
|
498
|
-
@doc.config['acro_form.fallback_font'] = ['Times', variant: :none]
|
497
|
+
@doc.config['acro_form.fallback_font'] = ['Times', {variant: :none}]
|
499
498
|
@field[:V] = 'Test'
|
500
499
|
end
|
501
500
|
|
@@ -99,6 +99,12 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
99
99
|
refute(@field.terminal_field?)
|
100
100
|
end
|
101
101
|
|
102
|
+
it "can check whether a widget is embedded in the field" do
|
103
|
+
refute(@field.embedded_widget?)
|
104
|
+
@field[:Subtype] = :Wdiget
|
105
|
+
assert(@field.embedded_widget?)
|
106
|
+
end
|
107
|
+
|
102
108
|
describe "each_widget" do
|
103
109
|
it "yields a wrapped instance of self if a single widget is embedded" do
|
104
110
|
@field[:Subtype] = :Widget
|
@@ -175,6 +181,39 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
175
181
|
end
|
176
182
|
end
|
177
183
|
|
184
|
+
describe "delete_widget" do
|
185
|
+
before do
|
186
|
+
@page = @doc.pages.add
|
187
|
+
end
|
188
|
+
|
189
|
+
it "does nothing if the provided widget doesn't belong to the field" do
|
190
|
+
wrong_widget = @doc.add({Subtype: :Widget})
|
191
|
+
|
192
|
+
@field.create_widget(@page)
|
193
|
+
@field.delete_widget(wrong_widget)
|
194
|
+
assert_equal(:Widget, @field[:Subtype])
|
195
|
+
|
196
|
+
@field.create_widget(@page)
|
197
|
+
@field.delete_widget(wrong_widget)
|
198
|
+
assert_equal(2, @field[:Kids].size)
|
199
|
+
end
|
200
|
+
|
201
|
+
it "deletes the widget if it is embedded" do
|
202
|
+
widget = @field.create_widget(@page)
|
203
|
+
@field.delete_widget(widget)
|
204
|
+
refute(@field.key?(:Subtype))
|
205
|
+
assert(@page[:Annots].empty?)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "deletes the widget if it is not embedded" do
|
209
|
+
@field.create_widget(@page)
|
210
|
+
widget2 = @field.create_widget(@page)
|
211
|
+
@field.delete_widget(widget2)
|
212
|
+
assert_equal(1, @field[:Kids].size)
|
213
|
+
assert_equal(@field[:Kids].value, @page[:Annots].value)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
178
217
|
describe "perform_validation" do
|
179
218
|
before do
|
180
219
|
@field[:FT] = :Tx
|
@@ -7,7 +7,7 @@ require 'hexapdf/type/acro_form/form'
|
|
7
7
|
describe HexaPDF::Type::AcroForm::Form do
|
8
8
|
before do
|
9
9
|
@doc = HexaPDF::Document.new
|
10
|
-
@acro_form = @doc.add({}, type: :XXAcroForm)
|
10
|
+
@acro_form = @doc.add({Fields: []}, type: :XXAcroForm)
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "signature flags" do
|
@@ -49,7 +49,7 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
49
49
|
root_fields = @acro_form.find_root_fields
|
50
50
|
assert_equal(result, root_fields.map(&:value))
|
51
51
|
assert_kind_of(HexaPDF::Type::AcroForm::TextField, root_fields[0])
|
52
|
-
|
52
|
+
assert_equal([], @acro_form[:Fields].value)
|
53
53
|
|
54
54
|
@acro_form.find_root_fields!
|
55
55
|
assert_equal(result, @acro_form[:Fields].value.map(&:value))
|
@@ -204,9 +204,9 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
204
204
|
|
205
205
|
it "checks whether the font used in /DA is available in /DR" do
|
206
206
|
@acro_form[:DA] = '/F2 0 Tf /F1 0 Tf'
|
207
|
-
refute(@acro_form.validate {|msg
|
207
|
+
refute(@acro_form.validate {|msg| assert_match(/DR must also be present/, msg) })
|
208
208
|
@acro_form.default_resources[:Font] = {}
|
209
|
-
refute(@acro_form.validate {|msg
|
209
|
+
refute(@acro_form.validate {|msg| assert_match(/font.*is not.*resource/, msg) })
|
210
210
|
@acro_form.default_resources[:Font][:F1] = :yes
|
211
211
|
assert(@acro_form.validate)
|
212
212
|
end
|
@@ -161,8 +161,9 @@ describe HexaPDF::Type::FontSimple do
|
|
161
161
|
end
|
162
162
|
|
163
163
|
describe "validation" do
|
164
|
+
before { assert(@font.validate) }
|
165
|
+
|
164
166
|
it "validates the existence of required keys" do
|
165
|
-
assert(@font.validate)
|
166
167
|
@font.delete(:FirstChar)
|
167
168
|
refute(@font.validate)
|
168
169
|
end
|
@@ -16,6 +16,12 @@ describe HexaPDF::Type::FontTrueType do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe "validation" do
|
19
|
+
it "ignores some missing fields if the font name is one of the standard PDF fonts" do
|
20
|
+
@font[:BaseFont] = :'Arial,Bold'
|
21
|
+
[:FirstChar, :LastChar, :Widths, :FontDescriptor].each {|field| @font.delete(field) }
|
22
|
+
assert(@font.validate)
|
23
|
+
end
|
24
|
+
|
19
25
|
it "requires that the FontDescriptor key is set" do
|
20
26
|
assert(@font.validate)
|
21
27
|
@font.delete(:FontDescriptor)
|
@@ -49,7 +49,7 @@ describe HexaPDF::Type::Form do
|
|
49
49
|
it "creates the resource dictionary if it is not found" do
|
50
50
|
resources = @form.resources
|
51
51
|
assert_equal(:XXResources, resources.type)
|
52
|
-
assert_equal({}, resources.value)
|
52
|
+
assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
|
53
53
|
end
|
54
54
|
|
55
55
|
it "returns the already used resource dictionary" do
|
@@ -277,7 +277,7 @@ describe HexaPDF::Type::Page do
|
|
277
277
|
page = @doc.add({Type: :Page, Parent: @doc.pages.root})
|
278
278
|
resources = page.resources
|
279
279
|
assert_equal(:XXResources, resources.type)
|
280
|
-
assert_equal({}, resources.value)
|
280
|
+
assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
|
281
281
|
end
|
282
282
|
|
283
283
|
it "returns the already used resource dictionary" do
|
@@ -319,6 +319,13 @@ describe HexaPDF::Type::Page do
|
|
319
319
|
end
|
320
320
|
end
|
321
321
|
|
322
|
+
it "returns all ancestor page tree nodes of a page" do
|
323
|
+
root = @doc.add({Type: :Pages})
|
324
|
+
kid = @doc.add({Type: :Pages, Parent: root})
|
325
|
+
page = @doc.add({Type: :Page, Parent: kid})
|
326
|
+
assert_equal([kid, root], page.ancestor_nodes)
|
327
|
+
end
|
328
|
+
|
322
329
|
describe "canvas" do
|
323
330
|
before do
|
324
331
|
@page = @doc.pages.add
|
@@ -206,6 +206,48 @@ describe HexaPDF::Type::PageTreeNode do
|
|
206
206
|
end
|
207
207
|
end
|
208
208
|
|
209
|
+
describe "move_page" do
|
210
|
+
before do
|
211
|
+
define_multilevel_page_tree
|
212
|
+
end
|
213
|
+
|
214
|
+
it "moves the page to the first place" do
|
215
|
+
@root.move_page(@pages[1], 0)
|
216
|
+
assert_equal([@pages[1], @pages[0], *@pages[2..-1]], @root.each_page.to_a)
|
217
|
+
assert(@root.validate)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "moves the page to the correct location with a positive index" do
|
221
|
+
@root.move_page(1, 3)
|
222
|
+
assert_equal([@pages[0], @pages[2], @pages[1], *@pages[3..-1]], @root.each_page.to_a)
|
223
|
+
assert(@root.validate)
|
224
|
+
end
|
225
|
+
|
226
|
+
it "moves the page to the last place" do
|
227
|
+
@root.move_page(1, -1)
|
228
|
+
assert_equal([@pages[0], *@pages[2..-1], @pages[1]], @root.each_page.to_a)
|
229
|
+
assert(@root.validate)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "fails if the index to the moving page is invalid" do
|
233
|
+
assert_raises(HexaPDF::Error) { @root.move_page(10, 0) }
|
234
|
+
end
|
235
|
+
|
236
|
+
it "fails if the moving page was deleted/is null" do
|
237
|
+
@doc.delete(@pages[0])
|
238
|
+
assert_raises(HexaPDF::Error) { @root.move_page(@pages[0], 3) }
|
239
|
+
end
|
240
|
+
|
241
|
+
it "fails if the page was not yet added to a page tree" do
|
242
|
+
page = @doc.add({Type: :Page})
|
243
|
+
assert_raises(HexaPDF::Error) { @root.move_page(page, 3) }
|
244
|
+
end
|
245
|
+
|
246
|
+
it "fails if the page is not part of the page tree" do
|
247
|
+
assert_raises(HexaPDF::Error) { @kid1.move_page(@pages[6], 3) }
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
209
251
|
describe "each_page" do
|
210
252
|
before do
|
211
253
|
define_multilevel_page_tree
|
@@ -13,9 +13,9 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
13
13
|
|
14
14
|
def add_multilevel_entries
|
15
15
|
@kid11 = @doc.add({Limits: ['c', 'f'], Names: ['c', 1, 'f', 1]}, type: HexaPDF::NameTreeNode)
|
16
|
-
@kid12 = @doc.add({Limits: ['i', 'm'], Names: ['i', 1, 'm', 1]})
|
16
|
+
@kid12 = @doc.add({Limits: ['i', 'm'], Names: ['i', 1, 'm', 1]}, type: HexaPDF::NameTreeNode)
|
17
17
|
ref = HexaPDF::Reference.new(@kid11.oid, @kid11.gen)
|
18
|
-
@kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]})
|
18
|
+
@kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]}, type: HexaPDF::NameTreeNode)
|
19
19
|
@kid21 = @doc.add({Limits: ['o', 'q'], Names: ['o', 1, 'q', 1]}, type: HexaPDF::NameTreeNode)
|
20
20
|
@kid221 = @doc.add({Limits: ['s', 'u'], Names: ['s', 1, 'u', 1]}, type: HexaPDF::NameTreeNode)
|
21
21
|
@kid22 = @doc.add({Limits: ['s', 'u'], Kids: [@kid221]}, type: HexaPDF::NameTreeNode)
|
@@ -73,11 +73,11 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
73
73
|
@root.add_entry('p', 1)
|
74
74
|
@root.add_entry('r', 1)
|
75
75
|
@root.add_entry('v', 1)
|
76
|
-
assert_equal(['a', 'm'], @kid1[:Limits])
|
76
|
+
assert_equal(['a', 'm'], @kid1[:Limits].value)
|
77
77
|
assert_equal(['a', 'f'], @kid11[:Limits].value)
|
78
78
|
assert_equal(['a', 1, 'c', 1, 'e', 1, 'f', 1], @kid11[:Names].value)
|
79
|
-
assert_equal(['g', 'm'], @kid12[:Limits])
|
80
|
-
assert_equal(['g', 1, 'i', 1, 'j', 1, 'm', 1], @kid12[:Names])
|
79
|
+
assert_equal(['g', 'm'], @kid12[:Limits].value)
|
80
|
+
assert_equal(['g', 1, 'i', 1, 'j', 1, 'm', 1], @kid12[:Names].value)
|
81
81
|
assert_equal(['n', 'v'], @kid2[:Limits].value)
|
82
82
|
assert_equal(['n', 'q'], @kid21[:Limits].value)
|
83
83
|
assert_equal(['n', 1, 'o', 1, 'p', 1, 'q', 1], @kid21[:Names].value)
|
@@ -193,15 +193,16 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
193
193
|
end
|
194
194
|
|
195
195
|
it "checks that all kid objects are indirect objects" do
|
196
|
-
@root[:Kids][0] = HexaPDF::Reference.new(@kid1.oid, @kid1.gen)
|
196
|
+
@root[:Kids][0] = ref = HexaPDF::Reference.new(@kid1.oid, @kid1.gen)
|
197
197
|
assert(@root.validate)
|
198
198
|
|
199
|
-
@root[:Kids][0] =
|
199
|
+
@root[:Kids][0] = ref
|
200
200
|
@kid1.oid = 0
|
201
|
-
|
201
|
+
assert(@root.validate do |message, c|
|
202
202
|
assert_match(/must be indirect objects/, message)
|
203
|
-
|
203
|
+
assert(c)
|
204
204
|
end)
|
205
|
+
assert(@kid1.indirect?)
|
205
206
|
end
|
206
207
|
|
207
208
|
it "checks that leaf node containers have an even number of entries" do
|
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.13.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: 2020-
|
11
|
+
date: 2020-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -78,20 +78,14 @@ dependencies:
|
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: '0
|
82
|
-
- - ">="
|
83
|
-
- !ruby/object:Gem::Version
|
84
|
-
version: 0.82.0
|
81
|
+
version: '1.0'
|
85
82
|
type: :development
|
86
83
|
prerelease: false
|
87
84
|
version_requirements: !ruby/object:Gem::Requirement
|
88
85
|
requirements:
|
89
86
|
- - "~>"
|
90
87
|
- !ruby/object:Gem::Version
|
91
|
-
version: '0
|
92
|
-
- - ">="
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
version: 0.82.0
|
88
|
+
version: '1.0'
|
95
89
|
description: |
|
96
90
|
HexaPDF is a pure Ruby library with an accompanying application for working with PDF
|
97
91
|
files.
|
@@ -653,8 +647,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
653
647
|
- !ruby/object:Gem::Version
|
654
648
|
version: '0'
|
655
649
|
requirements: []
|
656
|
-
|
657
|
-
rubygems_version: 2.7.6.2
|
650
|
+
rubygems_version: 3.0.3
|
658
651
|
signing_key:
|
659
652
|
specification_version: 4
|
660
653
|
summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|