hexapdf 0.14.4 → 0.15.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/lib/hexapdf/cli/form.rb +30 -8
  4. data/lib/hexapdf/configuration.rb +18 -3
  5. data/lib/hexapdf/content/canvas.rb +1 -0
  6. data/lib/hexapdf/encryption/standard_security_handler.rb +16 -0
  7. data/lib/hexapdf/error.rb +4 -3
  8. data/lib/hexapdf/parser.rb +18 -6
  9. data/lib/hexapdf/revision.rb +16 -0
  10. data/lib/hexapdf/type/acro_form.rb +1 -0
  11. data/lib/hexapdf/type/acro_form/appearance_generator.rb +29 -17
  12. data/lib/hexapdf/type/acro_form/button_field.rb +8 -4
  13. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  14. data/lib/hexapdf/type/acro_form/form.rb +37 -0
  15. data/lib/hexapdf/type/acro_form/signature_field.rb +223 -0
  16. data/lib/hexapdf/type/annotation.rb +18 -9
  17. data/lib/hexapdf/type/annotations/widget.rb +3 -1
  18. data/lib/hexapdf/type/font_descriptor.rb +9 -2
  19. data/lib/hexapdf/type/page.rb +81 -0
  20. data/lib/hexapdf/utils/graphics_helpers.rb +4 -4
  21. data/lib/hexapdf/version.rb +1 -1
  22. data/test/hexapdf/content/test_canvas.rb +21 -0
  23. data/test/hexapdf/encryption/test_standard_security_handler.rb +27 -0
  24. data/test/hexapdf/test_parser.rb +23 -3
  25. data/test/hexapdf/test_revision.rb +21 -0
  26. data/test/hexapdf/test_writer.rb +2 -2
  27. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +21 -2
  28. data/test/hexapdf/type/acro_form/test_button_field.rb +13 -7
  29. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  30. data/test/hexapdf/type/acro_form/test_form.rb +46 -2
  31. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  32. data/test/hexapdf/type/annotations/test_widget.rb +2 -0
  33. data/test/hexapdf/type/test_annotation.rb +24 -10
  34. data/test/hexapdf/type/test_font_descriptor.rb +7 -0
  35. data/test/hexapdf/type/test_page.rb +187 -49
  36. data/test/hexapdf/utils/test_graphics_helpers.rb +8 -0
  37. metadata +4 -2
@@ -44,6 +44,13 @@ describe HexaPDF::Type::FontDescriptor do
44
44
  refute(@font_desc.validate)
45
45
  end
46
46
 
47
+ it "deletes the /FontWeight value if it doesn't contain a valid value" do
48
+ @font_desc[:FontWeight] = 350
49
+ refute(@font_desc.validate(auto_correct: false))
50
+ assert(@font_desc.validate)
51
+ refute(@font_desc.key?(:FontWeight))
52
+ end
53
+
47
54
  it "updates the /Descent value if it is not a negative number" do
48
55
  @font_desc[:Descent] = 5
49
56
  refute(@font_desc.validate(auto_correct: false))
@@ -26,7 +26,7 @@ describe HexaPDF::Type::Page do
26
26
  end
27
27
 
28
28
  # Asserts that the page's contents contains the operators.
29
- def assert_operators(page, operators)
29
+ def assert_page_operators(page, operators)
30
30
  processor = TestHelper::OperatorRecorder.new
31
31
  page.process_contents(processor)
32
32
  assert_equal(operators, processor.recorded_ops)
@@ -293,8 +293,8 @@ describe HexaPDF::Type::Page do
293
293
  it "parses the contents and processes it" do
294
294
  page = @doc.pages.add
295
295
  page[:Contents] = @doc.wrap({}, stream: 'q 10 w Q')
296
- assert_operators(page, [[:save_graphics_state], [:set_line_width, [10]],
297
- [:restore_graphics_state]])
296
+ assert_page_operators(page, [[:save_graphics_state], [:set_line_width, [10]],
297
+ [:restore_graphics_state]])
298
298
  end
299
299
  end
300
300
 
@@ -333,60 +333,60 @@ describe HexaPDF::Type::Page do
333
333
 
334
334
  it "works correctly if invoked on an empty page, using type :page in first invocation" do
335
335
  @page.canvas.line_width = 10
336
- assert_operators(@page, [[:set_line_width, [10]]])
336
+ assert_page_operators(@page, [[:set_line_width, [10]]])
337
337
 
338
338
  @page.canvas(type: :overlay).line_width = 5
339
- assert_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
340
- [:save_graphics_state], [:set_line_width, [10]],
341
- [:restore_graphics_state], [:save_graphics_state],
342
- [:set_line_width, [5]], [:restore_graphics_state]])
339
+ assert_page_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
340
+ [:save_graphics_state], [:set_line_width, [10]],
341
+ [:restore_graphics_state], [:save_graphics_state],
342
+ [:set_line_width, [5]], [:restore_graphics_state]])
343
343
 
344
344
  @page.canvas(type: :underlay).line_width = 2
345
- assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
346
- [:restore_graphics_state], [:save_graphics_state],
347
- [:set_line_width, [10]],
348
- [:restore_graphics_state], [:save_graphics_state],
349
- [:set_line_width, [5]], [:restore_graphics_state]])
345
+ assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
346
+ [:restore_graphics_state], [:save_graphics_state],
347
+ [:set_line_width, [10]],
348
+ [:restore_graphics_state], [:save_graphics_state],
349
+ [:set_line_width, [5]], [:restore_graphics_state]])
350
350
  end
351
351
 
352
352
  it "works correctly if invoked on an empty page, using type :underlay in first invocation" do
353
353
  @page.canvas(type: :underlay).line_width = 2
354
- assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
355
- [:restore_graphics_state], [:save_graphics_state],
356
- [:restore_graphics_state], [:save_graphics_state],
357
- [:restore_graphics_state]])
354
+ assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
355
+ [:restore_graphics_state], [:save_graphics_state],
356
+ [:restore_graphics_state], [:save_graphics_state],
357
+ [:restore_graphics_state]])
358
358
 
359
359
  @page.canvas.line_width = 10
360
- assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
361
- [:restore_graphics_state], [:save_graphics_state],
362
- [:set_line_width, [10]], [:restore_graphics_state],
363
- [:save_graphics_state], [:restore_graphics_state]])
360
+ assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
361
+ [:restore_graphics_state], [:save_graphics_state],
362
+ [:set_line_width, [10]], [:restore_graphics_state],
363
+ [:save_graphics_state], [:restore_graphics_state]])
364
364
 
365
365
  @page.canvas(type: :overlay).line_width = 5
366
- assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
367
- [:restore_graphics_state], [:save_graphics_state],
368
- [:set_line_width, [10]],
369
- [:restore_graphics_state], [:save_graphics_state],
370
- [:set_line_width, [5]], [:restore_graphics_state]])
366
+ assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
367
+ [:restore_graphics_state], [:save_graphics_state],
368
+ [:set_line_width, [10]],
369
+ [:restore_graphics_state], [:save_graphics_state],
370
+ [:set_line_width, [5]], [:restore_graphics_state]])
371
371
  end
372
372
 
373
373
  it "works correctly if invoked on a page with existing contents" do
374
374
  @page.contents = "10 w"
375
375
 
376
376
  @page.canvas(type: :overlay).line_width = 5
377
- assert_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
378
- [:save_graphics_state], [:set_line_width, [10]],
379
- [:restore_graphics_state],
380
- [:save_graphics_state], [:set_line_width, [5]],
381
- [:restore_graphics_state]])
377
+ assert_page_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
378
+ [:save_graphics_state], [:set_line_width, [10]],
379
+ [:restore_graphics_state],
380
+ [:save_graphics_state], [:set_line_width, [5]],
381
+ [:restore_graphics_state]])
382
382
 
383
383
  @page.canvas(type: :underlay).line_width = 2
384
- assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
385
- [:restore_graphics_state], [:save_graphics_state],
386
- [:set_line_width, [10]],
387
- [:restore_graphics_state],
388
- [:save_graphics_state], [:set_line_width, [5]],
389
- [:restore_graphics_state]])
384
+ assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
385
+ [:restore_graphics_state], [:save_graphics_state],
386
+ [:set_line_width, [10]],
387
+ [:restore_graphics_state],
388
+ [:save_graphics_state], [:set_line_width, [5]],
389
+ [:restore_graphics_state]])
390
390
  end
391
391
 
392
392
  it "works correctly if the page has its origin not at (0,0)" do
@@ -395,20 +395,20 @@ describe HexaPDF::Type::Page do
395
395
  @page.canvas(type: :page).line_width = 2
396
396
  @page.canvas(type: :overlay).line_width = 2
397
397
 
398
- assert_operators(@page, [[:save_graphics_state],
399
- [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
400
- [:set_line_width, [2]],
401
- [:restore_graphics_state],
398
+ assert_page_operators(@page, [[:save_graphics_state],
399
+ [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
400
+ [:set_line_width, [2]],
401
+ [:restore_graphics_state],
402
402
 
403
- [:save_graphics_state],
404
- [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
405
- [:set_line_width, [2]],
406
- [:restore_graphics_state],
403
+ [:save_graphics_state],
404
+ [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
405
+ [:set_line_width, [2]],
406
+ [:restore_graphics_state],
407
407
 
408
- [:save_graphics_state],
409
- [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
410
- [:set_line_width, [2]],
411
- [:restore_graphics_state]])
408
+ [:save_graphics_state],
409
+ [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
410
+ [:set_line_width, [2]],
411
+ [:restore_graphics_state]])
412
412
  end
413
413
 
414
414
  it "fails if the page canvas is requested for a page with existing contents" do
@@ -446,4 +446,142 @@ describe HexaPDF::Type::Page do
446
446
  refute_same(form.raw_stream, page[:Contents].raw_stream)
447
447
  end
448
448
  end
449
+
450
+ describe "flatten_annotations" do
451
+ before do
452
+ @page = @doc.pages.add
453
+ @appearance = @doc.add({Type: :XObject, Subtype: :Form, BBox: [-10, -5, 50, 20]}, stream: "")
454
+ @annot1 = @doc.add({Type: :Annot, Subtype: :Text, Rect: [100, 100, 160, 125], AP: {N: @appearance}})
455
+ @annot2 = @doc.add({Rect: [10, 10, 70, 35], AP: {N: @appearance}})
456
+ @page[:Annots] = [@annot1, @annot2]
457
+ @canvas = @page.canvas(type: :overlay)
458
+ end
459
+
460
+ it "does nothing if the page doesn't have any annotations" do
461
+ @page.delete(:Annots)
462
+ result = @page.flatten_annotations
463
+ assert(result.empty?)
464
+ assert_operators(@canvas.contents, [])
465
+ end
466
+
467
+ it "flattens all annotations of the page by default" do
468
+ result = @page.flatten_annotations
469
+ assert(result.empty?)
470
+ assert_operators(@canvas.contents, [[:save_graphics_state],
471
+ [:save_graphics_state],
472
+ [:concatenate_matrix, [1.0, 0, 0, 1.0, 110, 105]],
473
+ [:paint_xobject, [:XO1]],
474
+ [:restore_graphics_state],
475
+ [:save_graphics_state],
476
+ [:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
477
+ [:paint_xobject, [:XO1]],
478
+ [:restore_graphics_state],
479
+ [:restore_graphics_state]])
480
+ assert(@annot1.null?)
481
+ assert(@annot2.null?)
482
+ end
483
+
484
+ it "only deletes the widget annotation of a form field even if it is embedded in the field object" do
485
+ form = @doc.acro_form(create: true)
486
+ field = form.create_text_field('test')
487
+ widget = field.create_widget(@page, Rect: [200, 200, 250, 215])
488
+ field.field_value = 'hello'
489
+
490
+ assert_same(widget.data, field.data)
491
+ result = @page.flatten_annotations([widget])
492
+ assert(result.empty?)
493
+ refute(field.null?)
494
+ end
495
+
496
+ it "does nothing if a given annotation is not part of the page" do
497
+ annots = [{Test: :Object}]
498
+ result = @page.flatten_annotations(annots)
499
+ assert_equal(annots, result)
500
+ end
501
+
502
+ it "deletes hidden annotations but doesn't include them in the content stream" do
503
+ @annot1.flag(:hidden)
504
+ result = @page.flatten_annotations
505
+ assert(result.empty?)
506
+ assert(@annot1.null?)
507
+ assert_operators(@canvas.contents, [[:save_graphics_state],
508
+ [:save_graphics_state],
509
+ [:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
510
+ [:paint_xobject, [:XO1]],
511
+ [:restore_graphics_state],
512
+ [:restore_graphics_state]])
513
+ end
514
+
515
+ it "deletes invisible annotations but doesn't include them in the content stream" do
516
+ @annot1.flag(:invisible)
517
+ result = @page.flatten_annotations
518
+ assert(result.empty?)
519
+ assert(@annot1.null?)
520
+ assert_operators(@canvas.contents, [[:save_graphics_state],
521
+ [:save_graphics_state],
522
+ [:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
523
+ [:paint_xobject, [:XO1]],
524
+ [:restore_graphics_state],
525
+ [:restore_graphics_state]])
526
+ end
527
+
528
+ it "ignores annotations without appearane stream" do
529
+ @annot1.delete(:AP)
530
+ result = @page.flatten_annotations
531
+ assert_equal([@annot1], result)
532
+ refute(@annot1.empty?)
533
+ assert_operators(@canvas.contents, [[:save_graphics_state],
534
+ [:save_graphics_state],
535
+ [:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
536
+ [:paint_xobject, [:XO1]],
537
+ [:restore_graphics_state],
538
+ [:restore_graphics_state]])
539
+ end
540
+
541
+ it "adjusts the position to counter the translation in #canvas based on the page's media box" do
542
+ @page[:MediaBox] = [-15, -15, 100, 100]
543
+ @page.flatten_annotations
544
+ assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 15, 15]], range: 1)
545
+ end
546
+
547
+ it "adjusts the position in case the form /Matrix has an offset" do
548
+ @appearance[:Matrix] = [1, 0, 0, 1, 15, 15]
549
+ @page.flatten_annotations
550
+ assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 95, 90]], range: 2)
551
+ end
552
+
553
+ it "adjusts the position for an appearance with a 90 degree rotation" do
554
+ @appearance[:Matrix] = [0, 1, -1, 0, 0, 0]
555
+ @annot1[:Rect] = [100, 100, 125, 160]
556
+ @page.flatten_annotations
557
+ assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 120, 110]], range: 2)
558
+ end
559
+
560
+ it "adjusts the position for an appearance with a -90 degree rotation" do
561
+ @appearance[:Matrix] = [0, -1, 1, 0, 0, 0]
562
+ @annot1[:Rect] = [100, 100, 125, 160]
563
+ @page.flatten_annotations
564
+ assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 105, 150]], range: 2)
565
+ end
566
+
567
+ it "adjusts the position for an appearance with a 180 degree rotation" do
568
+ @appearance[:Matrix] = [-1, 0, 0, -1, 0, 0]
569
+ @page.flatten_annotations
570
+ assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 150, 120]], range: 2)
571
+ end
572
+
573
+ it "ignores an appearance with a rotation that is not a mulitple of 90" do
574
+ @appearance[:Matrix] = [-1, 0.5, 0.5, -1, 0, 0]
575
+ result = @page.flatten_annotations
576
+ assert_equal([@annot1, @annot2], result)
577
+ assert_operators(@canvas.contents, [[:save_graphics_state], [:restore_graphics_state]])
578
+ end
579
+
580
+ it "scales the appearance to fit into the annotations's rectangle" do
581
+ @annot1[:Rect] = [100, 100, 130, 150]
582
+ @page.flatten_annotations
583
+ assert_operators(@canvas.contents, [:concatenate_matrix, [0.5, 0, 0, 2, 110, 105]], range: 2)
584
+ end
585
+
586
+ end
449
587
  end
@@ -15,9 +15,17 @@ describe HexaPDF::Utils::GraphicsHelpers do
15
15
  assert_equal([10, 12], calculate_dimensions(5, 6, rwidth: 10))
16
16
  end
17
17
 
18
+ it "returns the requested width and the given height if width is zero" do
19
+ assert_equal([10, 6], calculate_dimensions(0, 6, rwidth: 10))
20
+ end
21
+
18
22
  it "returns the requested height and an adjusted width" do
19
23
  assert_equal([10, 12], calculate_dimensions(5, 6, rheight: 12))
20
24
  end
25
+
26
+ it "returns the requested height and the given width if height is zero" do
27
+ assert_equal([5, 12], calculate_dimensions(5, 0, rheight: 12))
28
+ end
21
29
  end
22
30
 
23
31
  describe "point_on_line" 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.14.4
4
+ version: 0.15.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-27 00:00:00.000000000 Z
11
+ date: 2021-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -359,6 +359,7 @@ files:
359
359
  - lib/hexapdf/type/acro_form/choice_field.rb
360
360
  - lib/hexapdf/type/acro_form/field.rb
361
361
  - lib/hexapdf/type/acro_form/form.rb
362
+ - lib/hexapdf/type/acro_form/signature_field.rb
362
363
  - lib/hexapdf/type/acro_form/text_field.rb
363
364
  - lib/hexapdf/type/acro_form/variable_text_field.rb
364
365
  - lib/hexapdf/type/action.rb
@@ -595,6 +596,7 @@ files:
595
596
  - test/hexapdf/type/acro_form/test_choice_field.rb
596
597
  - test/hexapdf/type/acro_form/test_field.rb
597
598
  - test/hexapdf/type/acro_form/test_form.rb
599
+ - test/hexapdf/type/acro_form/test_signature_field.rb
598
600
  - test/hexapdf/type/acro_form/test_text_field.rb
599
601
  - test/hexapdf/type/acro_form/test_variable_text_field.rb
600
602
  - test/hexapdf/type/actions/test_launch.rb