hexapdf 0.14.4 → 0.15.4

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.
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