hexapdf 0.13.0 → 0.14.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 +51 -0
- data/examples/019-acro_form.rb +41 -4
- data/lib/hexapdf/cli/split.rb +74 -14
- data/lib/hexapdf/document.rb +10 -4
- data/lib/hexapdf/layout/text_layouter.rb +2 -2
- data/lib/hexapdf/object.rb +22 -0
- data/lib/hexapdf/parser.rb +23 -1
- data/lib/hexapdf/pdf_array.rb +2 -2
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +127 -27
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -2
- data/lib/hexapdf/type/acro_form/choice_field.rb +64 -10
- data/lib/hexapdf/type/acro_form/form.rb +133 -10
- data/lib/hexapdf/type/acro_form/text_field.rb +68 -3
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/font.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +3 -3
- data/lib/hexapdf/type/form.rb +4 -1
- data/lib/hexapdf/type/page.rb +5 -5
- data/lib/hexapdf/utils/object_hash.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/layout/test_text_layouter.rb +9 -1
- data/test/hexapdf/test_document.rb +14 -6
- data/test/hexapdf/test_object.rb +27 -0
- data/test/hexapdf/test_parser.rb +46 -0
- data/test/hexapdf/test_pdf_array.rb +1 -1
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +286 -34
- data/test/hexapdf/type/acro_form/test_button_field.rb +15 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
- data/test/hexapdf/type/acro_form/test_form.rb +83 -11
- data/test/hexapdf/type/acro_form/test_text_field.rb +75 -1
- data/test/hexapdf/type/test_form.rb +7 -0
- data/test/hexapdf/utils/test_object_hash.rb +5 -0
- data/test/test_helper.rb +2 -0
- metadata +4 -3
@@ -233,6 +233,14 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
233
233
|
refute_same(off, widget.appearance.normal_appearance[:Off])
|
234
234
|
end
|
235
235
|
|
236
|
+
it "always generates appearances if force is true" do
|
237
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
238
|
+
@field.create_appearances
|
239
|
+
yes = widget.appearance.normal_appearance[:Yes]
|
240
|
+
@field.create_appearances(force: true)
|
241
|
+
refute_same(yes, widget.appearance.normal_appearance[:Yes])
|
242
|
+
end
|
243
|
+
|
236
244
|
it "fails for unsupported button types" do
|
237
245
|
@field.flag(:push_button)
|
238
246
|
@field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
@@ -263,6 +271,13 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
263
271
|
@field.update_widgets
|
264
272
|
assert_equal(:Yes, widget[:AS])
|
265
273
|
end
|
274
|
+
|
275
|
+
it "creates the appearances if necessary" do
|
276
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
277
|
+
assert_nil(widget[:AP][:N][:Yes])
|
278
|
+
@field.update_widgets
|
279
|
+
assert(widget[:AP][:N][:Yes])
|
280
|
+
end
|
266
281
|
end
|
267
282
|
|
268
283
|
describe "validation" do
|
@@ -36,23 +36,34 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
|
|
36
36
|
|
37
37
|
describe "field_value=" do
|
38
38
|
before do
|
39
|
-
@field.option_items = ["test", "other"]
|
39
|
+
@field.option_items = ["test", "something", "other", "neu"]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "updates the widgets to reflect the changed value" do
|
43
|
+
@field.initialize_as_combo_box
|
44
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
45
|
+
@field.set_default_appearance_string
|
46
|
+
@field.field_value = 'test'
|
47
|
+
assert(widget[:AP][:N])
|
40
48
|
end
|
41
49
|
|
42
50
|
describe "combo_box" do
|
43
51
|
before do
|
44
52
|
@field.initialize_as_combo_box
|
53
|
+
@field[:I] = 2
|
45
54
|
end
|
46
55
|
|
47
56
|
it "can set the value for an uneditable combo box" do
|
48
57
|
@field.field_value = 'test'
|
49
58
|
assert_equal("test", @field[:V])
|
59
|
+
assert_nil(@field[:I])
|
50
60
|
end
|
51
61
|
|
52
62
|
it "can set the value for an editable combo box" do
|
53
63
|
@field.flag(:edit)
|
54
64
|
@field.field_value = 'another'
|
55
65
|
assert_equal("another", @field[:V])
|
66
|
+
assert_nil(@field[:I])
|
56
67
|
end
|
57
68
|
|
58
69
|
it "fails if mulitple values are provided for a combo box" do
|
@@ -76,8 +87,18 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
|
|
76
87
|
|
77
88
|
it "can set a multiple values if the list box is a multi-select" do
|
78
89
|
@field.flag(:multi_select)
|
79
|
-
@field.field_value = ['
|
80
|
-
assert_equal(['
|
90
|
+
@field.field_value = ['other', 'test']
|
91
|
+
assert_equal(['other', 'test'], @field[:V].value)
|
92
|
+
assert_equal([0, 2], @field[:I].value)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "can read and set the top index" do
|
96
|
+
assert_raises(ArgumentError) { @field.list_box_top_index = 4 }
|
97
|
+
@field.delete(:Opt)
|
98
|
+
assert_raises(ArgumentError) { @field.list_box_top_index = 0 }
|
99
|
+
@field.option_items = [1, 2, 3, 4]
|
100
|
+
@field.list_box_top_index = 2
|
101
|
+
assert_equal(2, @field.list_box_top_index)
|
81
102
|
end
|
82
103
|
|
83
104
|
it "fails if mulitple values are provided but the list box is not a multi-select" do
|
@@ -97,10 +118,29 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
|
|
97
118
|
assert_raises(HexaPDF::Error) { @field.default_field_value = 'unknown' }
|
98
119
|
end
|
99
120
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
121
|
+
describe "option items" do
|
122
|
+
before do
|
123
|
+
@items = [["a", "Zx"], "\xFE\xFF".b << "Töne".encode('UTF-16BE').b, "H\xe4llo".b,]
|
124
|
+
end
|
125
|
+
|
126
|
+
it "sets the option items" do
|
127
|
+
@field.option_items = @items
|
128
|
+
assert_equal(@items, @field[:Opt].value)
|
129
|
+
|
130
|
+
@field.flag(:sort)
|
131
|
+
@field.option_items = @items
|
132
|
+
assert_equal(@items.values_at(2, 1, 0), @field[:Opt].value)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "can retrieve the option items" do
|
136
|
+
@field[:Opt] = @items
|
137
|
+
assert_equal(["Zx", "Töne", "Hällo"], @field.option_items)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "can retrieve the export values" do
|
141
|
+
@field[:Opt] = @items
|
142
|
+
assert_equal(["a", "Töne", "Hällo"], @field.export_values)
|
143
|
+
end
|
104
144
|
end
|
105
145
|
|
106
146
|
it "returns the correct concrete field type" do
|
@@ -121,8 +161,51 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
|
|
121
161
|
assert(@field[:AP][:N])
|
122
162
|
end
|
123
163
|
|
124
|
-
it "
|
125
|
-
|
164
|
+
it "works for list box fields" do
|
165
|
+
@field.initialize_as_list_box
|
166
|
+
@field.set_default_appearance_string
|
167
|
+
@field.create_appearances
|
168
|
+
assert(@field[:AP][:N])
|
169
|
+
end
|
170
|
+
|
171
|
+
it "only creates a new appearance if the involved dictionary values have changed per widget" do
|
172
|
+
@field.initialize_as_list_box
|
173
|
+
@field.set_default_appearance_string
|
174
|
+
@field.create_appearances
|
175
|
+
appearance_stream = @field[:AP][:N].raw_stream
|
176
|
+
|
177
|
+
@field.create_appearances
|
178
|
+
assert_same(appearance_stream, @field[:AP][:N].raw_stream)
|
179
|
+
|
180
|
+
do_check = lambda do
|
181
|
+
@field.create_appearances
|
182
|
+
refute_same(appearance_stream, @field[:AP][:N].raw_stream)
|
183
|
+
appearance_stream = @field[:AP][:N].raw_stream
|
184
|
+
end
|
185
|
+
|
186
|
+
@field.option_items = ['a', 'b', 'c']
|
187
|
+
do_check.call
|
188
|
+
|
189
|
+
@field.list_box_top_index = 2
|
190
|
+
do_check.call
|
191
|
+
|
192
|
+
@field.field_value = 'b'
|
193
|
+
do_check.call
|
194
|
+
|
195
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
196
|
+
assert_nil(widget[:AP])
|
197
|
+
@field.create_appearances
|
198
|
+
refute_nil(widget[:AP][:N])
|
199
|
+
end
|
200
|
+
|
201
|
+
it "force the creation of appearance streams when force: true" do
|
202
|
+
@field.initialize_as_list_box
|
203
|
+
@field.set_default_appearance_string
|
204
|
+
@field.create_appearances
|
205
|
+
appearance_stream = @field[:AP][:N].raw_stream
|
206
|
+
|
207
|
+
@field.create_appearances(force: true)
|
208
|
+
refute_same(appearance_stream, @field[:AP][:N].raw_stream)
|
126
209
|
end
|
127
210
|
end
|
128
211
|
|
@@ -115,6 +115,10 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
115
115
|
end
|
116
116
|
|
117
117
|
describe "create fields" do
|
118
|
+
before do
|
119
|
+
@acro_form = @doc.acro_form(create: true)
|
120
|
+
end
|
121
|
+
|
118
122
|
describe "handles the general case" do
|
119
123
|
it "works for names with a dot" do
|
120
124
|
@acro_form[:Fields] = [{T: "root"}]
|
@@ -134,9 +138,57 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
134
138
|
end
|
135
139
|
end
|
136
140
|
|
141
|
+
def applies_variable_text_properties(method, **args)
|
142
|
+
field = @acro_form.send(method, "field", **args, font: 'Times')
|
143
|
+
font_name, font_size = field.parse_default_appearance_string
|
144
|
+
assert_equal(:'Times-Roman', @acro_form.default_resources.font(font_name)[:BaseFont])
|
145
|
+
assert_equal(0, font_size)
|
146
|
+
|
147
|
+
field = @acro_form.send(method, "field", **args, font_size: 10)
|
148
|
+
font_name, font_size = field.parse_default_appearance_string
|
149
|
+
assert_equal(:Helvetica, @acro_form.default_resources.font(font_name)[:BaseFont])
|
150
|
+
assert_equal(10, font_size)
|
151
|
+
|
152
|
+
field = @acro_form.send(method, "field", **args, font: 'Courier', font_size: 10, align: :center)
|
153
|
+
font_name, font_size = field.parse_default_appearance_string
|
154
|
+
assert_equal(:Courier, @acro_form.default_resources.font(font_name)[:BaseFont])
|
155
|
+
assert_equal(10, font_size)
|
156
|
+
assert_equal(:center, field.text_alignment)
|
157
|
+
end
|
158
|
+
|
137
159
|
it "creates a text field" do
|
138
160
|
field = @acro_form.create_text_field("field")
|
139
161
|
assert_equal(:Tx, field.field_type)
|
162
|
+
applies_variable_text_properties(:create_text_field)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "creates a multiline text field" do
|
166
|
+
field = @acro_form.create_multiline_text_field("field")
|
167
|
+
assert_equal(:Tx, field.field_type)
|
168
|
+
assert(field.multiline_text_field?)
|
169
|
+
applies_variable_text_properties(:create_multiline_text_field)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "creates a comb text field" do
|
173
|
+
field = @acro_form.create_comb_text_field("field", max_chars: 9)
|
174
|
+
assert_equal(:Tx, field.field_type)
|
175
|
+
assert_equal(9, field[:MaxLen])
|
176
|
+
assert(field.comb_text_field?)
|
177
|
+
applies_variable_text_properties(:create_comb_text_field, max_chars: 9)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "creates a password field" do
|
181
|
+
field = @acro_form.create_password_field("field")
|
182
|
+
assert_equal(:Tx, field.field_type)
|
183
|
+
assert(field.password_field?)
|
184
|
+
applies_variable_text_properties(:create_password_field)
|
185
|
+
end
|
186
|
+
|
187
|
+
it "creates a file select field" do
|
188
|
+
field = @acro_form.create_file_select_field("field")
|
189
|
+
assert_equal(:Tx, field.field_type)
|
190
|
+
assert(field.file_select_field?)
|
191
|
+
applies_variable_text_properties(:create_file_select_field)
|
140
192
|
end
|
141
193
|
|
142
194
|
it "creates a check box" do
|
@@ -150,13 +202,19 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
150
202
|
end
|
151
203
|
|
152
204
|
it "creates a combo box" do
|
153
|
-
field = @acro_form.create_combo_box("field")
|
205
|
+
field = @acro_form.create_combo_box("field", option_items: ['a', 'b', 'c'], editable: true)
|
154
206
|
assert(field.combo_box?)
|
207
|
+
assert_equal(['a', 'b', 'c'], field.option_items)
|
208
|
+
assert(field.flagged?(:edit))
|
209
|
+
applies_variable_text_properties(:create_combo_box)
|
155
210
|
end
|
156
211
|
|
157
212
|
it "creates a list box" do
|
158
|
-
field = @acro_form.create_list_box("field")
|
213
|
+
field = @acro_form.create_list_box("field", option_items: ['a', 'b', 'c'], multi_select: true)
|
159
214
|
assert(field.list_box?)
|
215
|
+
assert_equal(['a', 'b', 'c'], field.option_items)
|
216
|
+
assert(field.flagged?(:multi_select))
|
217
|
+
applies_variable_text_properties(:create_list_box)
|
160
218
|
end
|
161
219
|
end
|
162
220
|
|
@@ -185,15 +243,29 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
185
243
|
assert(@acro_form[:NeedAppearances])
|
186
244
|
end
|
187
245
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
246
|
+
describe "create_appearances" do
|
247
|
+
before do
|
248
|
+
@tf = @acro_form.create_text_field('test')
|
249
|
+
@tf.set_default_appearance_string
|
250
|
+
@tf.create_widget(@doc.pages.add)
|
251
|
+
@cb = @acro_form.create_check_box('test2')
|
252
|
+
@cb.create_widget(@doc.pages.add)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "creates the appearances of all field widgets if necessary" do
|
256
|
+
@acro_form.create_appearances
|
257
|
+
assert(@tf.each_widget.all? {|w| w.appearance.normal_appearance.kind_of?(HexaPDF::Stream) })
|
258
|
+
assert(@cb.each_widget.all? {|w| w.appearance.normal_appearance[:Yes].kind_of?(HexaPDF::Stream) })
|
259
|
+
end
|
260
|
+
|
261
|
+
it "force the creation of appearances if force is true" do
|
262
|
+
@acro_form.create_appearances
|
263
|
+
text_stream = @tf[:AP][:N].raw_stream
|
264
|
+
@acro_form.create_appearances
|
265
|
+
assert_same(text_stream, @tf[:AP][:N].raw_stream)
|
266
|
+
@acro_form.create_appearances(force: true)
|
267
|
+
refute_same(text_stream, @tf[:AP][:N].raw_stream)
|
268
|
+
end
|
197
269
|
end
|
198
270
|
|
199
271
|
describe "perform_validation" do
|
@@ -21,6 +21,56 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
21
21
|
assert_equal(6, @field[:MaxLen])
|
22
22
|
end
|
23
23
|
|
24
|
+
it "can be initialized as a multiline text field" do
|
25
|
+
@field.flag(:comb)
|
26
|
+
@field.initialize_as_multiline_text_field
|
27
|
+
assert(@field.multiline_text_field?)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can be initialized as comb text field" do
|
31
|
+
@field.flag(:multiline)
|
32
|
+
@field.initialize_as_comb_text_field
|
33
|
+
assert(@field.comb_text_field?)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can be initialized as password field" do
|
37
|
+
@field.flag(:multiline)
|
38
|
+
@field[:V] = 'test'
|
39
|
+
@field.initialize_as_password_field
|
40
|
+
assert_nil(@field[:V])
|
41
|
+
assert(@field.password_field?)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "can be initialized as a file select field" do
|
45
|
+
@field.flag(:multiline)
|
46
|
+
@field.initialize_as_file_select_field
|
47
|
+
assert(@field.file_select_field?)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "can check whether the field is a multiline text field" do
|
51
|
+
refute(@field.multiline_text_field?)
|
52
|
+
@field.flag(:multiline)
|
53
|
+
assert(@field.multiline_text_field?)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "can check whether the field is a comb text field" do
|
57
|
+
refute(@field.comb_text_field?)
|
58
|
+
@field.flag(:comb)
|
59
|
+
assert(@field.comb_text_field?)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "can check whether the field is a password field" do
|
63
|
+
refute(@field.password_field?)
|
64
|
+
@field.flag(:password)
|
65
|
+
assert(@field.password_field?)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "can check whether the field is a file select field" do
|
69
|
+
refute(@field.file_select_field?)
|
70
|
+
@field.flag(:file_select)
|
71
|
+
assert(@field.file_select_field?)
|
72
|
+
end
|
73
|
+
|
24
74
|
describe "field_value" do
|
25
75
|
it "handles unset values" do
|
26
76
|
assert_nil(@field.field_value)
|
@@ -81,14 +131,38 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
81
131
|
end
|
82
132
|
|
83
133
|
describe "create_appearances" do
|
84
|
-
|
134
|
+
before do
|
85
135
|
@doc.acro_form(create: true)
|
86
136
|
@field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
87
137
|
@field.set_default_appearance_string
|
138
|
+
end
|
139
|
+
|
140
|
+
it "creates the needed streams" do
|
88
141
|
@field.create_appearances
|
89
142
|
assert(@field[:AP][:N])
|
90
143
|
end
|
91
144
|
|
145
|
+
it "doesn't create a new appearance stream if the field value hasn't changed, checked per widget" do
|
146
|
+
@field.create_appearances
|
147
|
+
stream = @field[:AP][:N].raw_stream
|
148
|
+
@field.create_appearances
|
149
|
+
assert_same(stream, @field[:AP][:N].raw_stream)
|
150
|
+
@field.field_value = 'test'
|
151
|
+
refute_same(stream, @field[:AP][:N].raw_stream)
|
152
|
+
|
153
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
154
|
+
assert_nil(widget[:AP])
|
155
|
+
@field.create_appearances
|
156
|
+
refute_nil(widget[:AP][:N])
|
157
|
+
end
|
158
|
+
|
159
|
+
it "always creates a new appearance stream if force is true" do
|
160
|
+
@field.create_appearances
|
161
|
+
stream = @field[:AP][:N].raw_stream
|
162
|
+
@field.create_appearances(force: true)
|
163
|
+
refute_same(stream, @field[:AP][:N].raw_stream)
|
164
|
+
end
|
165
|
+
|
92
166
|
it "uses the configuration option acro_form.appearance_generator" do
|
93
167
|
@doc.config['acro_form.appearance_generator'] = 'NonExistent'
|
94
168
|
assert_raises(Exception) { @field.create_appearances }
|
@@ -43,6 +43,13 @@ describe HexaPDF::Type::Form do
|
|
43
43
|
@form.contents = 'test'
|
44
44
|
assert_equal('test', @form.stream)
|
45
45
|
end
|
46
|
+
|
47
|
+
it "clears the cache to make sure that a new canvas can be created" do
|
48
|
+
@form[:BBox] = [0, 0, 100, 100]
|
49
|
+
canvas = @form.canvas
|
50
|
+
@form.contents = ''
|
51
|
+
refute_same(canvas, @form.canvas)
|
52
|
+
end
|
46
53
|
end
|
47
54
|
|
48
55
|
describe "resources" do
|
data/test/test_helper.rb
CHANGED
@@ -16,6 +16,8 @@ require 'zlib'
|
|
16
16
|
TEST_DATA_DIR = File.join(__dir__, 'data')
|
17
17
|
MINIMAL_PDF = File.read(File.join(TEST_DATA_DIR, 'minimal.pdf')).freeze
|
18
18
|
|
19
|
+
Minitest::Test.make_my_diffs_pretty!
|
20
|
+
|
19
21
|
module TestHelper
|
20
22
|
|
21
23
|
# Asserts that the method +name+ of +object+ gets invoked with the +expected_values+ when
|
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.14.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-12-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -631,6 +631,7 @@ files:
|
|
631
631
|
homepage: https://hexapdf.gettalong.org
|
632
632
|
licenses:
|
633
633
|
- AGPL-3.0
|
634
|
+
- Commercial License
|
634
635
|
metadata: {}
|
635
636
|
post_install_message:
|
636
637
|
rdoc_options: []
|
@@ -647,7 +648,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
647
648
|
- !ruby/object:Gem::Version
|
648
649
|
version: '0'
|
649
650
|
requirements: []
|
650
|
-
rubygems_version: 3.
|
651
|
+
rubygems_version: 3.2.3
|
651
652
|
signing_key:
|
652
653
|
specification_version: 4
|
653
654
|
summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|