hexapdf 0.27.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -11
  3. data/examples/019-acro_form.rb +14 -3
  4. data/examples/023-images.rb +30 -0
  5. data/examples/024-digital-signatures.rb +23 -0
  6. data/lib/hexapdf/cli/info.rb +5 -1
  7. data/lib/hexapdf/cli/inspect.rb +2 -2
  8. data/lib/hexapdf/cli/split.rb +2 -2
  9. data/lib/hexapdf/configuration.rb +13 -14
  10. data/lib/hexapdf/content/canvas.rb +8 -3
  11. data/lib/hexapdf/dictionary.rb +1 -5
  12. data/lib/hexapdf/dictionary_fields.rb +6 -2
  13. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  14. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  15. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  16. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  17. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  18. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  19. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  20. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  21. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  22. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  23. data/lib/hexapdf/digital_signature.rb +56 -0
  24. data/lib/hexapdf/document.rb +27 -24
  25. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  26. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  27. data/lib/hexapdf/importer.rb +32 -27
  28. data/lib/hexapdf/layout/list_box.rb +1 -5
  29. data/lib/hexapdf/object.rb +5 -0
  30. data/lib/hexapdf/parser.rb +13 -0
  31. data/lib/hexapdf/revision.rb +15 -12
  32. data/lib/hexapdf/revisions.rb +4 -0
  33. data/lib/hexapdf/tokenizer.rb +14 -8
  34. data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
  35. data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
  36. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  37. data/lib/hexapdf/type/acro_form/field.rb +11 -5
  38. data/lib/hexapdf/type/acro_form/form.rb +33 -7
  39. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
  40. data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
  41. data/lib/hexapdf/type/annotations/widget.rb +3 -0
  42. data/lib/hexapdf/type/font_true_type.rb +14 -0
  43. data/lib/hexapdf/type/object_stream.rb +2 -2
  44. data/lib/hexapdf/type/outline.rb +1 -1
  45. data/lib/hexapdf/type/page.rb +56 -46
  46. data/lib/hexapdf/type.rb +0 -1
  47. data/lib/hexapdf/version.rb +1 -1
  48. data/lib/hexapdf/writer.rb +2 -3
  49. data/test/hexapdf/content/test_canvas.rb +5 -0
  50. data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
  51. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  52. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  53. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  54. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  55. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  56. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  57. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  58. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  59. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  60. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  61. data/test/hexapdf/document/test_pages.rb +2 -2
  62. data/test/hexapdf/encryption/test_aes.rb +1 -1
  63. data/test/hexapdf/filter/test_predictor.rb +0 -1
  64. data/test/hexapdf/layout/test_box.rb +2 -1
  65. data/test/hexapdf/layout/test_column_box.rb +1 -1
  66. data/test/hexapdf/layout/test_list_box.rb +1 -1
  67. data/test/hexapdf/test_dictionary_fields.rb +2 -1
  68. data/test/hexapdf/test_document.rb +3 -9
  69. data/test/hexapdf/test_importer.rb +13 -6
  70. data/test/hexapdf/test_parser.rb +17 -0
  71. data/test/hexapdf/test_revision.rb +15 -14
  72. data/test/hexapdf/test_revisions.rb +43 -0
  73. data/test/hexapdf/test_stream.rb +1 -1
  74. data/test/hexapdf/test_tokenizer.rb +3 -4
  75. data/test/hexapdf/test_writer.rb +3 -3
  76. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
  77. data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
  78. data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
  79. data/test/hexapdf/type/acro_form/test_field.rb +4 -4
  80. data/test/hexapdf/type/acro_form/test_form.rb +18 -0
  81. data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
  82. data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
  83. data/test/hexapdf/type/test_font_true_type.rb +20 -0
  84. data/test/hexapdf/type/test_object_stream.rb +2 -1
  85. data/test/hexapdf/type/test_outline.rb +3 -0
  86. data/test/hexapdf/type/test_page.rb +67 -30
  87. data/test/hexapdf/type/test_page_tree_node.rb +4 -2
  88. metadata +69 -16
  89. data/lib/hexapdf/document/signatures.rb +0 -546
  90. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  91. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  92. data/lib/hexapdf/type/signature/handler.rb +0 -140
  93. 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.border_style.width,
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(@field[:V], @widget[:AS])
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
- it "creates the /Off appearance stream" do
317
- @widget.marker_style(style: :cross)
318
- @generator.create_appearances
319
- assert_operators(@widget[:AP][:N][:Off].stream,
320
- [[:save_graphics_state],
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 if the appearance dictionaries are not set up" do
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 unsupported button types" do
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, *widgets)
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, *widgets)
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, *widgets)
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 when encryption is used" do
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])
@@ -64,6 +64,9 @@ describe HexaPDF::Type::Outline do
64
64
  assert(correctable)
65
65
  end
66
66
  refute(@outline.key?(:Count))
67
+
68
+ @outline[:Count] = 0
69
+ assert(@outline.validate(auto_correct: false))
67
70
  end
68
71
  end
69
72
  end
@@ -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([-300, 50, -100, 200], @page.box(:media).value)
229
- assert_equal([-2, 0, 0, 1], @page.box(:crop).value)
230
- assert_equal([-3, 0, 0, 2], @page.box(:bleed).value)
231
- assert_equal([-4, 0, 0, 3], @page.box(:trim).value)
232
- assert_equal([-5, 0, 0, 4], @page.box(:art).value)
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([-300, 50, -100, 200], @page.box(:media).value)
238
- assert_equal(" q 0 1 -1 0 0 0 cm Q ", @page.contents)
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([-200, -300, -50, -100], @page.box(:media).value)
244
- assert_equal(" q -1 0 0 -1 0 0 cm Q ", @page.contents)
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([100, -200, 300, -50], @page.box(:media).value)
250
- assert_equal(" q 0 -1 1 0 0 0 cm Q ", @page.contents)
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: 2)
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: 2)
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: 2)
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: 2)
643
+ assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 150, 120]], range: 1)
608
644
  end
609
645
 
610
- it "ignores an appearance with a rotation that is not a mulitple of 90" do
611
- @appearance[:Matrix] = [-1, 0.5, 0.5, -1, 0, 0]
612
- result = @page.flatten_annotations
613
- assert_equal([@annot1, @annot2], result)
614
- assert_operators(@canvas.contents, [[:save_graphics_state], [:restore_graphics_state]])
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: 2)
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]], @root.each_page.to_a)
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]], @root.each_page.to_a)
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