hexapdf 0.27.0 → 0.29.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 +100 -11
- data/examples/019-acro_form.rb +14 -3
- data/examples/023-images.rb +30 -0
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +2 -2
- data/lib/hexapdf/configuration.rb +13 -14
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +1 -5
- data/lib/hexapdf/dictionary_fields.rb +6 -2
- data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
- data/lib/hexapdf/digital_signature/handler.rb +138 -0
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
- data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
- data/lib/hexapdf/digital_signature/signatures.rb +210 -0
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
- data/lib/hexapdf/digital_signature/signing.rb +101 -0
- data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
- data/lib/hexapdf/digital_signature.rb +56 -0
- data/lib/hexapdf/document.rb +27 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +32 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +13 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +4 -0
- data/lib/hexapdf/tokenizer.rb +14 -8
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +33 -7
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/page.rb +56 -46
- data/lib/hexapdf/type.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +2 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
- data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
- data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
- data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
- data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
- data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
- data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
- data/test/hexapdf/digital_signature/test_signing.rb +53 -0
- data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
- data/test/hexapdf/document/test_pages.rb +2 -2
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -1
- data/test/hexapdf/test_document.rb +3 -9
- data/test/hexapdf/test_importer.rb +13 -6
- data/test/hexapdf/test_parser.rb +17 -0
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +43 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +3 -4
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +18 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +3 -0
- data/test/hexapdf/type/test_page.rb +67 -30
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- metadata +69 -16
- data/lib/hexapdf/document/signatures.rb +0 -546
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
- data/lib/hexapdf/type/signature/handler.rb +0 -140
- data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -18,12 +18,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
18
18
|
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
19
19
|
end
|
20
20
|
|
21
|
-
it "fails for unsupported button fields" do
|
22
|
-
@field.flag(:push_button)
|
23
|
-
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
24
|
-
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
25
|
-
end
|
26
|
-
|
27
21
|
it "fails for unsupported field types" do
|
28
22
|
@field[:FT] = :Unknown
|
29
23
|
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
@@ -141,8 +135,8 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
141
135
|
end
|
142
136
|
|
143
137
|
def execute
|
144
|
-
@generator.send(:draw_marker, @xform.canvas, @widget[:Rect], @widget.
|
145
|
-
@widget.marker_style)
|
138
|
+
@generator.send(:draw_marker, @xform.canvas, @widget[:Rect].width, @widget[:Rect].height,
|
139
|
+
@widget.border_style.width, @widget.marker_style)
|
146
140
|
end
|
147
141
|
|
148
142
|
it "handles the marker :circle specially for radio button widgets" do
|
@@ -196,6 +190,18 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
196
190
|
[:show_text, ["4"]],
|
197
191
|
[:end_text]])
|
198
192
|
end
|
193
|
+
|
194
|
+
it "draws the default marker if an empty string is specified as marker" do
|
195
|
+
@widget.marker_style(style: '', color: 0.5, size: 5)
|
196
|
+
execute
|
197
|
+
assert_operators(@xform.stream,
|
198
|
+
[[:set_font_and_size, [:F1, 5]],
|
199
|
+
[:set_device_gray_non_stroking_color, [0.5]],
|
200
|
+
[:begin_text],
|
201
|
+
[:set_text_matrix, [1, 0, 0, 1, 2.885, 8.2725]],
|
202
|
+
[:show_text, ["4"]],
|
203
|
+
[:end_text]])
|
204
|
+
end
|
199
205
|
end
|
200
206
|
end
|
201
207
|
|
@@ -213,7 +219,15 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
213
219
|
|
214
220
|
it "updates the widgets' /AS entry to point to the selected appearance" do
|
215
221
|
@generator.create_appearances
|
216
|
-
assert_equal(
|
222
|
+
assert_equal(:Off, @widget[:AS])
|
223
|
+
|
224
|
+
@field.field_value = :Yes
|
225
|
+
@generator.create_appearances
|
226
|
+
assert_equal(:Yes, @widget[:AS])
|
227
|
+
|
228
|
+
@field.delete(:V)
|
229
|
+
@generator.create_appearances
|
230
|
+
assert_equal(:Off, @widget[:AS])
|
217
231
|
end
|
218
232
|
|
219
233
|
it "set the print flag on the widgets" do
|
@@ -231,6 +245,33 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
231
245
|
assert_equal(12, @widget[:Rect].height)
|
232
246
|
end
|
233
247
|
|
248
|
+
describe "takes the rotation into account" do
|
249
|
+
def check_rotation(angle, width, height, matrix)
|
250
|
+
@widget[:MK] = {R: angle}
|
251
|
+
@field[:V] = :Yes
|
252
|
+
@generator.create_appearances
|
253
|
+
form = @widget[:AP][:N][@widget[:AS]]
|
254
|
+
assert_equal([0, 0, width, height], form[:BBox].value)
|
255
|
+
assert_equal(matrix, form[:Matrix].value)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "works for 0 degrees" do
|
259
|
+
check_rotation(-360, @widget[:Rect].width, @widget[:Rect].height, [1, 0, 0, 1, 0, 0])
|
260
|
+
end
|
261
|
+
|
262
|
+
it "works for 90 degrees" do
|
263
|
+
check_rotation(450, @widget[:Rect].height, @widget[:Rect].width, [0, 1, -1, 0, 0, 0])
|
264
|
+
end
|
265
|
+
|
266
|
+
it "works for 180 degrees" do
|
267
|
+
check_rotation(180, @widget[:Rect].width, @widget[:Rect].height, [0, -1, -1, 0, 0, 0])
|
268
|
+
end
|
269
|
+
|
270
|
+
it "works for 270 degrees" do
|
271
|
+
check_rotation(-90, @widget[:Rect].height, @widget[:Rect].width, [0, -1, 1, 0, 0, 0])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
234
275
|
it "creates the needed appearance streams" do
|
235
276
|
@widget[:AP][:N].delete(:Off)
|
236
277
|
@generator.create_appearances
|
@@ -292,61 +333,21 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
292
333
|
assert_equal(:Off, @widget[:AS])
|
293
334
|
end
|
294
335
|
|
295
|
-
it "set the print flag on the widgets" do
|
296
|
-
@generator.create_appearances
|
297
|
-
assert(@widget.flagged?(:print))
|
298
|
-
end
|
299
|
-
|
300
|
-
it "adjusts the /Rect if width is zero" do
|
301
|
-
@generator.create_appearances
|
302
|
-
assert_equal(12, @widget[:Rect].width)
|
303
|
-
end
|
304
|
-
|
305
|
-
it "adjusts the /Rect if height is zero" do
|
306
|
-
@generator.create_appearances
|
307
|
-
assert_equal(12, @widget[:Rect].height)
|
308
|
-
end
|
309
|
-
|
310
336
|
it "creates the needed appearance streams" do
|
311
337
|
@generator.create_appearances
|
312
338
|
assert_equal(:XObject, @widget[:AP][:N][:Off].type)
|
313
339
|
assert_equal(:XObject, @widget[:AP][:N][:radio].type)
|
314
340
|
end
|
341
|
+
end
|
315
342
|
|
316
|
-
|
317
|
-
|
318
|
-
@
|
319
|
-
|
320
|
-
|
321
|
-
[:set_device_gray_non_stroking_color, [1.0]],
|
322
|
-
[:append_rectangle, [0, 0, 12, 12]],
|
323
|
-
[:fill_path_non_zero],
|
324
|
-
[:append_rectangle, [0.5, 0.5, 11, 11]],
|
325
|
-
[:stroke_path], [:restore_graphics_state]])
|
326
|
-
end
|
327
|
-
|
328
|
-
it "creates the appearance stream according to the set value" do
|
329
|
-
@widget.marker_style(style: :check)
|
330
|
-
@generator.create_appearances
|
331
|
-
assert_operators(@widget[:AP][:N][:radio].stream,
|
332
|
-
[[:save_graphics_state],
|
333
|
-
[:set_device_gray_non_stroking_color, [1.0]],
|
334
|
-
[:append_rectangle, [0, 0, 12, 12]],
|
335
|
-
[:fill_path_non_zero],
|
336
|
-
[:append_rectangle, [0.5, 0.5, 11, 11]],
|
337
|
-
[:stroke_path], [:restore_graphics_state],
|
338
|
-
|
339
|
-
[:save_graphics_state],
|
340
|
-
[:set_font_and_size, [:F1, 10]],
|
341
|
-
[:begin_text],
|
342
|
-
[:set_text_matrix, [1, 0, 0, 1, 1.77, 2.545]],
|
343
|
-
[:show_text, ["4"]],
|
344
|
-
[:end_text],
|
345
|
-
[:restore_graphics_state]])
|
343
|
+
describe "push buttons" do
|
344
|
+
before do
|
345
|
+
@field.initialize_as_push_button
|
346
|
+
@widget = @field.create_widget(@page, Rect: [0, 0, 0, 0])
|
347
|
+
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
346
348
|
end
|
347
349
|
|
348
|
-
it "fails
|
349
|
-
@widget[:AP][:N].delete(:radio)
|
350
|
+
it "fails because it is not implemented yet" do
|
350
351
|
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
351
352
|
end
|
352
353
|
end
|
@@ -417,6 +418,37 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
417
418
|
assert_match(/test1/, form.contents)
|
418
419
|
end
|
419
420
|
|
421
|
+
describe "takes the rotation into account" do
|
422
|
+
before do
|
423
|
+
@widget[:Rect] = [0, 0, 100, 20]
|
424
|
+
end
|
425
|
+
|
426
|
+
def check_rotation(angle, width, height, matrix)
|
427
|
+
@widget[:MK] = {R: angle}
|
428
|
+
@field[:V] = 'test'
|
429
|
+
@generator.create_appearances
|
430
|
+
form = @widget[:AP][:N]
|
431
|
+
assert_equal([0, 0, width, height], form[:BBox].value)
|
432
|
+
assert_equal(matrix, form[:Matrix].value)
|
433
|
+
end
|
434
|
+
|
435
|
+
it "works for 0 degrees" do
|
436
|
+
check_rotation(-360, @widget[:Rect].width, @widget[:Rect].height, [1, 0, 0, 1, 0, 0])
|
437
|
+
end
|
438
|
+
|
439
|
+
it "works for 90 degrees" do
|
440
|
+
check_rotation(450, @widget[:Rect].height, @widget[:Rect].width, [0, 1, -1, 0, 0, 0])
|
441
|
+
end
|
442
|
+
|
443
|
+
it "works for 180 degrees" do
|
444
|
+
check_rotation(180, @widget[:Rect].width, @widget[:Rect].height, [0, -1, -1, 0, 0, 0])
|
445
|
+
end
|
446
|
+
|
447
|
+
it "works for 270 degrees" do
|
448
|
+
check_rotation(-90, @widget[:Rect].height, @widget[:Rect].width, [0, -1, 1, 0, 0, 0])
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
420
452
|
describe "font size calculation" do
|
421
453
|
before do
|
422
454
|
@widget[:Rect].height = 20
|
@@ -496,6 +528,53 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
496
528
|
end
|
497
529
|
end
|
498
530
|
|
531
|
+
describe "Javascript action AFNumber_Format" do
|
532
|
+
before do
|
533
|
+
@field.field_value = '1234567.898765'
|
534
|
+
@action = {S: :JavaScript, JS: ''}
|
535
|
+
@field[:AA] = {F: @action}
|
536
|
+
end
|
537
|
+
|
538
|
+
def assert_format(arg_string, result, range)
|
539
|
+
@action[:JS] = "AFNumber_Format(#{arg_string});"
|
540
|
+
@generator.create_appearances
|
541
|
+
assert_operators(@widget[:AP][:N].stream, result, range: range)
|
542
|
+
end
|
543
|
+
|
544
|
+
it "respects the set number of decimals" do
|
545
|
+
assert_format('0, 2, 0, 0, "E", false',
|
546
|
+
[:show_text, ["1.234.568E"]], 9)
|
547
|
+
assert_format('2, 2, 0, 0, "E", false',
|
548
|
+
[:show_text, ["1.234.567,90E"]], 9)
|
549
|
+
end
|
550
|
+
|
551
|
+
it "respects the digit separator style" do
|
552
|
+
["1,234,567.90", "1234567.90", "1.234.567,90", "1234567,90"].each_with_index do |result, style|
|
553
|
+
assert_format("2, #{style}, 0, 0, \"\", false", [:show_text, [result]], 9)
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
it "respects the negative value styling" do
|
558
|
+
@field.field_value = '-1234567.898'
|
559
|
+
["-E1234567,90", "E1234567,90", "(E1234567,90)", "(E1234567,90)"].each_with_index do |result, style|
|
560
|
+
assert_format("2, 3, #{style}, 0, \"E\", true",
|
561
|
+
[[:set_device_rgb_non_stroking_color, [style % 2, 0.0, 0.0]],
|
562
|
+
[:begin_text],
|
563
|
+
[:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
|
564
|
+
[:show_text, [result]]], 6..9)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
it "respects the specified currency string and position" do
|
569
|
+
assert_format('2, 3, 0, 0, " E", false', [:show_text, ["1234567,90 E"]], 9)
|
570
|
+
assert_format('2, 3, 0, 0, "E ", true', [:show_text, ["E 1234567,90"]], 9)
|
571
|
+
end
|
572
|
+
|
573
|
+
it "does nothing to the value if the Javascript method could not be determined " do
|
574
|
+
assert_format('2, 3, 0, 0, " E", false, a', [:show_text, ["1234567.898765"]], 8)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
499
578
|
it "creates the /N appearance stream according to the set string" do
|
500
579
|
@field.field_value = 'Text'
|
501
580
|
@field.set_default_appearance_string(font_color: "red")
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
10
10
|
@field = @doc.add({FT: :Btn, T: 'button'}, type: :XXAcroFormField, subtype: :Btn)
|
11
11
|
end
|
12
12
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
15
|
+
end
|
16
|
+
|
13
17
|
it "can be initialized as push button" do
|
14
18
|
@field.initialize_as_push_button
|
15
19
|
assert_nil(@field[:V])
|
@@ -232,6 +236,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
232
236
|
@field.create_appearances
|
233
237
|
yes = widget.appearance_dict.normal_appearance[:Yes]
|
234
238
|
off = widget.appearance_dict.normal_appearance[:Off]
|
239
|
+
widget.appearance_dict.normal_appearance[:Yes] = HexaPDF::Reference.new(yes.oid)
|
235
240
|
@field.create_appearances
|
236
241
|
assert_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
237
242
|
assert_same(off, widget.appearance_dict.normal_appearance[:Off])
|
@@ -252,7 +257,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
252
257
|
refute_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
253
258
|
end
|
254
259
|
|
255
|
-
it "fails for
|
260
|
+
it "fails for push buttons as they are not implemented yet" do
|
256
261
|
@field.flag(:push_button)
|
257
262
|
@field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
258
263
|
assert_raises(HexaPDF::Error) { @field.create_appearances }
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
|
|
10
10
|
@field = @doc.add({FT: :Ch, T: 'choice'}, type: :XXAcroFormField, subtype: :Ch)
|
11
11
|
end
|
12
12
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
15
|
+
end
|
16
|
+
|
13
17
|
it "can be initialized as list box" do
|
14
18
|
@field.initialize_as_list_box
|
15
19
|
assert_nil(@field[:V])
|
@@ -111,14 +111,14 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
111
111
|
@field[:Subtype] = :Widget
|
112
112
|
@field[:Rect] = [0, 0, 0, 0]
|
113
113
|
widgets = @field.each_widget.to_a
|
114
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
114
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
115
115
|
assert_same(@field.data, widgets.first.data)
|
116
116
|
end
|
117
117
|
|
118
118
|
it "yields all widgets in the /Kids array" do
|
119
119
|
@field[:Kids] = [{Subtype: :Widget, Rect: [0, 0, 0, 0], X: 1}]
|
120
120
|
widgets = @field.each_widget.to_a
|
121
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
121
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
122
122
|
assert_equal(1, widgets.first[:X])
|
123
123
|
end
|
124
124
|
|
@@ -128,8 +128,8 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
128
128
|
@doc.add({T: "b", Subtype: :Widget, Rect: [0, 0, 0, 0]}, type: :XXAcroFormField) <<
|
129
129
|
@doc.add({T: "a", X: 1, Subtype: :Widget, Rect: [0, 0, 0, 0]}, type: :XXAcroFormField)
|
130
130
|
|
131
|
-
widgets = @field.each_widget.to_a
|
132
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
131
|
+
widgets = @field.each_widget(direct_only: false).to_a
|
132
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
133
133
|
assert_equal(1, widgets.first[:X])
|
134
134
|
end
|
135
135
|
|
@@ -396,6 +396,24 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
396
396
|
end
|
397
397
|
end
|
398
398
|
|
399
|
+
describe "combining fields with the same name" do
|
400
|
+
before do
|
401
|
+
@acro_form[:Fields] = [
|
402
|
+
@doc.add({T: 'e', Subtype: :Widget, Rect: [0, 0, 0, 1]}),
|
403
|
+
@doc.add({T: 'e', Subtype: :Widget, Rect: [0, 0, 0, 2]}),
|
404
|
+
@doc.add({T: 'Tx2'}),
|
405
|
+
@doc.add({T: 'e', Kids: [{Subtype: :Widget, Rect: [0, 0, 0, 3]}]}),
|
406
|
+
]
|
407
|
+
end
|
408
|
+
|
409
|
+
it "merges fields with the same name into the first one" do
|
410
|
+
assert(@acro_form.validate)
|
411
|
+
assert_equal(2, @acro_form.root_fields.size)
|
412
|
+
assert_equal([[0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 0, 3]],
|
413
|
+
@acro_form.field_by_name('e').each_widget.map {|w| w[:Rect] })
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
399
417
|
describe "automatically creates the terminal fields; appearances" do
|
400
418
|
before do
|
401
419
|
@cb = @acro_form.create_check_box('test2')
|
@@ -20,6 +20,10 @@ describe HexaPDF::Type::AcroForm::SignatureField do
|
|
20
20
|
@field = @doc.wrap({}, type: :XXAcroFormField, subtype: :Sig)
|
21
21
|
end
|
22
22
|
|
23
|
+
it "identifies as an :XXAcroFormField type" do
|
24
|
+
assert_equal(:XXAcroFormField, @field.type)
|
25
|
+
end
|
26
|
+
|
23
27
|
it "sets the field value" do
|
24
28
|
@field.field_value = {Empty: :True}
|
25
29
|
assert_equal({Empty: :True}, @field[:V].value)
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
10
10
|
@field = @doc.add({FT: :Tx}, type: :XXAcroFormField, subtype: :Tx)
|
11
11
|
end
|
12
12
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
15
|
+
end
|
16
|
+
|
13
17
|
it "resolves /MaxLen as inheritable field" do
|
14
18
|
assert_nil(@field[:MaxLen])
|
15
19
|
|
@@ -164,11 +168,20 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
164
168
|
assert_same(stream, @field[:AP][:N].raw_stream)
|
165
169
|
@field.field_value = 'test'
|
166
170
|
refute_same(stream, @field[:AP][:N].raw_stream)
|
171
|
+
stream = @field[:AP][:N].raw_stream
|
167
172
|
|
168
173
|
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
169
174
|
assert_nil(widget[:AP])
|
170
175
|
@field.create_appearances
|
171
176
|
refute_nil(widget[:AP][:N])
|
177
|
+
|
178
|
+
@doc.clear_cache
|
179
|
+
@field.create_appearances
|
180
|
+
assert_same(stream, @field[:Kids][0][:AP][:N].raw_stream)
|
181
|
+
|
182
|
+
@doc.clear_cache
|
183
|
+
@field.field_value = 'other'
|
184
|
+
refute_same(stream, @field[:Kids][0][:AP][:N].raw_stream)
|
172
185
|
end
|
173
186
|
|
174
187
|
it "always creates a new appearance stream if force is true" do
|
@@ -15,6 +15,26 @@ describe HexaPDF::Type::FontTrueType do
|
|
15
15
|
BaseFont: :Something, FontDescriptor: font_descriptor})
|
16
16
|
end
|
17
17
|
|
18
|
+
describe "font_wrapper" do
|
19
|
+
it "returns the default value if the font is subset" do
|
20
|
+
@font[:BaseFont] = :'ABCDEF+Something'
|
21
|
+
assert_nil(@font.font_wrapper)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns the default value if the font has no embedded font file" do
|
25
|
+
assert_nil(@font.font_wrapper)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "uses a fully embedded TrueType font file" do
|
29
|
+
font_file = File.binread(File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf"))
|
30
|
+
@font[:FontDescriptor][:FontFile2] = @doc.add({}, stream: font_file)
|
31
|
+
font_wrapper = @font.font_wrapper
|
32
|
+
assert(font_wrapper)
|
33
|
+
assert_equal(font_file, font_wrapper.wrapped_font.io.string)
|
34
|
+
assert_same(font_wrapper, @font.font_wrapper)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
18
38
|
describe "validation" do
|
19
39
|
it "ignores some missing fields if the font name is one of the standard PDF fonts" do
|
20
40
|
@font[:BaseFont] = :'Arial,Bold'
|
@@ -104,7 +104,8 @@ describe HexaPDF::Type::ObjectStream do
|
|
104
104
|
assert_equal("", @obj.stream)
|
105
105
|
end
|
106
106
|
|
107
|
-
it "doesn't allow the Catalog entry to be compressed
|
107
|
+
it "doesn't allow the Catalog entry to be compressed" do
|
108
|
+
@doc.trailer.delete(:Encrypt)
|
108
109
|
@obj.add_object(HexaPDF::Dictionary.new({Type: :Catalog}, oid: 8))
|
109
110
|
@obj.write_objects(@revision)
|
110
111
|
assert_equal(0, @obj.value[:N])
|
@@ -225,29 +225,66 @@ describe HexaPDF::Type::Page do
|
|
225
225
|
@page.box(:art, [0, 0, 4, 5])
|
226
226
|
|
227
227
|
@page.rotate(90, flatten: true)
|
228
|
-
assert_equal([-
|
229
|
-
assert_equal([
|
230
|
-
assert_equal([-
|
231
|
-
assert_equal([-
|
232
|
-
assert_equal([-
|
228
|
+
assert_equal([-298, 50, -98, 200], @page.box(:media).value)
|
229
|
+
assert_equal([0, 0, 2, 1], @page.box(:crop).value)
|
230
|
+
assert_equal([-1, 0, 2, 2], @page.box(:bleed).value)
|
231
|
+
assert_equal([-2, 0, 2, 3], @page.box(:trim).value)
|
232
|
+
assert_equal([-3, 0, 2, 4], @page.box(:art).value)
|
233
233
|
end
|
234
234
|
|
235
235
|
it "works correctly for 90 degrees" do
|
236
236
|
@page.rotate(90, flatten: true)
|
237
|
-
assert_equal([
|
238
|
-
assert_equal(" q 0 1 -1 0
|
237
|
+
assert_equal([0, 0, 200, 150], @page.box(:media).value)
|
238
|
+
assert_equal(" q 0 1 -1 0 300 -50 cm Q ", @page.contents)
|
239
239
|
end
|
240
240
|
|
241
241
|
it "works correctly for 180 degrees" do
|
242
242
|
@page.rotate(180, flatten: true)
|
243
|
-
assert_equal([
|
244
|
-
assert_equal(" q -1 0 0 -1
|
243
|
+
assert_equal([0, 0, 150, 200], @page.box(:media).value)
|
244
|
+
assert_equal(" q -1 0 0 -1 200 300 cm Q ", @page.contents)
|
245
245
|
end
|
246
246
|
|
247
247
|
it "works correctly for 270 degrees" do
|
248
248
|
@page.rotate(270, flatten: true)
|
249
|
-
assert_equal([
|
250
|
-
assert_equal(" q 0 -1 1 0
|
249
|
+
assert_equal([0, 0, 200, 150], @page.box(:media).value)
|
250
|
+
assert_equal(" q 0 -1 1 0 -100 200 cm Q ", @page.contents)
|
251
|
+
end
|
252
|
+
|
253
|
+
describe "annotations" do
|
254
|
+
before do
|
255
|
+
@appearance = @doc.add({Type: :XObject, Subtype: :Form, BBox: [-10, -5, 50, 20]}, stream: "")
|
256
|
+
@annot = @doc.add({Type: :Annot, Subtype: :Widget, Rect: [100, 100, 160, 125],
|
257
|
+
QuadPoints: [0, 0, 100, 200, 300, 400, 500, 600],
|
258
|
+
AP: {N: @appearance}})
|
259
|
+
@page[:Annots] = [@annot]
|
260
|
+
end
|
261
|
+
|
262
|
+
it "rotates the /Rect entry" do
|
263
|
+
@page.rotate(90, flatten: true)
|
264
|
+
assert_equal([175, 50, 200, 110], @annot[:Rect].value)
|
265
|
+
end
|
266
|
+
|
267
|
+
it "rotates all (x,y) pairs in the /QuadPoints entry" do
|
268
|
+
@page.rotate(90, flatten: true)
|
269
|
+
assert_equal([300, -50, 100, 50, -100, 250, -300, 450],
|
270
|
+
@annot[:QuadPoints])
|
271
|
+
end
|
272
|
+
|
273
|
+
it "applies the needed matrix to the annotation's appearance stream's /Matrix entry" do
|
274
|
+
@page.rotate(90, flatten: true)
|
275
|
+
assert_equal([0, 1, -1, 0, 300, -50], @appearance[:Matrix])
|
276
|
+
|
277
|
+
@page.rotate(90, flatten: true)
|
278
|
+
assert_equal([-1, 0, 0, -1, 200, 300], @appearance[:Matrix])
|
279
|
+
end
|
280
|
+
|
281
|
+
it "modified the /R entry in the appearance characteristics dictionary of a widget annotation" do
|
282
|
+
@page.rotate(90, flatten: true)
|
283
|
+
assert_equal(90, @annot[:MK][:R])
|
284
|
+
|
285
|
+
@page.rotate(90, flatten: true)
|
286
|
+
assert_equal(180, @annot[:MK][:R])
|
287
|
+
end
|
251
288
|
end
|
252
289
|
end
|
253
290
|
|
@@ -494,25 +531,30 @@ describe HexaPDF::Type::Page do
|
|
494
531
|
@canvas = @page.canvas(type: :overlay)
|
495
532
|
end
|
496
533
|
|
497
|
-
it "does nothing if the page doesn't have any annotations" do
|
534
|
+
it "does nothing and returns the argument as array if the page doesn't have any annotations" do
|
535
|
+
annots = @page[:Annots]
|
536
|
+
|
498
537
|
@page.delete(:Annots)
|
499
538
|
result = @page.flatten_annotations
|
500
539
|
assert(result.empty?)
|
501
540
|
assert_operators(@canvas.contents, [])
|
541
|
+
|
542
|
+
result = @page.flatten_annotations(annots)
|
543
|
+
assert_kind_of(Array, result)
|
544
|
+
assert_equal([@annot1, @annot2], result)
|
545
|
+
assert_operators(@canvas.contents, [])
|
502
546
|
end
|
503
547
|
|
504
548
|
it "flattens all annotations of the page by default" do
|
505
549
|
result = @page.flatten_annotations
|
506
550
|
assert(result.empty?)
|
507
551
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
508
|
-
[:save_graphics_state],
|
509
552
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 110, 105]],
|
510
553
|
[:paint_xobject, [:XO1]],
|
511
554
|
[:restore_graphics_state],
|
512
555
|
[:save_graphics_state],
|
513
556
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
514
557
|
[:paint_xobject, [:XO1]],
|
515
|
-
[:restore_graphics_state],
|
516
558
|
[:restore_graphics_state]])
|
517
559
|
assert(@annot1.null?)
|
518
560
|
assert(@annot2.null?)
|
@@ -542,10 +584,8 @@ describe HexaPDF::Type::Page do
|
|
542
584
|
assert(result.empty?)
|
543
585
|
assert(@annot1.null?)
|
544
586
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
545
|
-
[:save_graphics_state],
|
546
587
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
547
588
|
[:paint_xobject, [:XO1]],
|
548
|
-
[:restore_graphics_state],
|
549
589
|
[:restore_graphics_state]])
|
550
590
|
end
|
551
591
|
|
@@ -555,10 +595,8 @@ describe HexaPDF::Type::Page do
|
|
555
595
|
assert(result.empty?)
|
556
596
|
assert(@annot1.null?)
|
557
597
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
558
|
-
[:save_graphics_state],
|
559
598
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
560
599
|
[:paint_xobject, [:XO1]],
|
561
|
-
[:restore_graphics_state],
|
562
600
|
[:restore_graphics_state]])
|
563
601
|
end
|
564
602
|
|
@@ -568,10 +606,8 @@ describe HexaPDF::Type::Page do
|
|
568
606
|
assert_equal([@annot1], result)
|
569
607
|
refute(@annot1.empty?)
|
570
608
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
571
|
-
[:save_graphics_state],
|
572
609
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
573
610
|
[:paint_xobject, [:XO1]],
|
574
|
-
[:restore_graphics_state],
|
575
611
|
[:restore_graphics_state]])
|
576
612
|
end
|
577
613
|
|
@@ -584,40 +620,41 @@ describe HexaPDF::Type::Page do
|
|
584
620
|
it "adjusts the position in case the form /Matrix has an offset" do
|
585
621
|
@appearance[:Matrix] = [1, 0, 0, 1, 15, 15]
|
586
622
|
@page.flatten_annotations
|
587
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 95, 90]], range:
|
623
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 95, 90]], range: 1)
|
588
624
|
end
|
589
625
|
|
590
626
|
it "adjusts the position for an appearance with a 90 degree rotation" do
|
591
627
|
@appearance[:Matrix] = [0, 1, -1, 0, 0, 0]
|
592
628
|
@annot1[:Rect] = [100, 100, 125, 160]
|
593
629
|
@page.flatten_annotations
|
594
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 120, 110]], range:
|
630
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 120, 110]], range: 1)
|
595
631
|
end
|
596
632
|
|
597
633
|
it "adjusts the position for an appearance with a -90 degree rotation" do
|
598
634
|
@appearance[:Matrix] = [0, -1, 1, 0, 0, 0]
|
599
635
|
@annot1[:Rect] = [100, 100, 125, 160]
|
600
636
|
@page.flatten_annotations
|
601
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 105, 150]], range:
|
637
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 105, 150]], range: 1)
|
602
638
|
end
|
603
639
|
|
604
640
|
it "adjusts the position for an appearance with a 180 degree rotation" do
|
605
641
|
@appearance[:Matrix] = [-1, 0, 0, -1, 0, 0]
|
606
642
|
@page.flatten_annotations
|
607
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 150, 120]], range:
|
643
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 150, 120]], range: 1)
|
608
644
|
end
|
609
645
|
|
610
|
-
it "
|
611
|
-
@appearance[:Matrix] = [
|
612
|
-
|
613
|
-
|
614
|
-
|
646
|
+
it "correctly positions and scales an appearance with a custom rotation" do
|
647
|
+
@appearance[:Matrix] = [0.707106, 0.707106, -0.707106, 0.707106, 10, 30]
|
648
|
+
@page.flatten_annotations
|
649
|
+
assert_operators(@canvas.contents,
|
650
|
+
[:concatenate_matrix, [0.998269, 0.0, 0.0, 0.415946, 111.21318, 80.60659]],
|
651
|
+
range: 1)
|
615
652
|
end
|
616
653
|
|
617
654
|
it "scales the appearance to fit into the annotations's rectangle" do
|
618
655
|
@annot1[:Rect] = [100, 100, 130, 150]
|
619
656
|
@page.flatten_annotations
|
620
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [0.5, 0, 0, 2, 110, 105]], range:
|
657
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [0.5, 0, 0, 2, 110, 105]], range: 1)
|
621
658
|
end
|
622
659
|
end
|
623
660
|
|
@@ -241,11 +241,13 @@ describe HexaPDF::Type::PageTreeNode do
|
|
241
241
|
|
242
242
|
it "moves the page to the correct location within the same parent node" do
|
243
243
|
@root.move_page(2, 4)
|
244
|
-
assert_equal([@pages[0], @pages[1], @pages[3], @pages[4], @pages[2], *@pages[5..-1]],
|
244
|
+
assert_equal([@pages[0], @pages[1], @pages[3], @pages[4], @pages[2], *@pages[5..-1]],
|
245
|
+
@root.each_page.to_a)
|
245
246
|
assert(@root.validate)
|
246
247
|
|
247
248
|
@root.move_page(4, 3)
|
248
|
-
assert_equal([@pages[0], @pages[1], @pages[3], @pages[2], @pages[4], *@pages[5..-1]],
|
249
|
+
assert_equal([@pages[0], @pages[1], @pages[3], @pages[2], @pages[4], *@pages[5..-1]],
|
250
|
+
@root.each_page.to_a)
|
249
251
|
assert(@root.validate)
|
250
252
|
end
|
251
253
|
|