hexapdf 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Rakefile +1 -1
  4. data/examples/030-pdfa.rb +1 -0
  5. data/lib/hexapdf/composer.rb +1 -0
  6. data/lib/hexapdf/dictionary.rb +3 -3
  7. data/lib/hexapdf/document/files.rb +7 -2
  8. data/lib/hexapdf/document/metadata.rb +12 -1
  9. data/lib/hexapdf/document.rb +14 -1
  10. data/lib/hexapdf/encryption.rb +17 -0
  11. data/lib/hexapdf/layout/box.rb +161 -61
  12. data/lib/hexapdf/layout/box_fitter.rb +4 -3
  13. data/lib/hexapdf/layout/column_box.rb +23 -25
  14. data/lib/hexapdf/layout/container_box.rb +3 -3
  15. data/lib/hexapdf/layout/frame.rb +13 -95
  16. data/lib/hexapdf/layout/image_box.rb +4 -4
  17. data/lib/hexapdf/layout/line.rb +4 -0
  18. data/lib/hexapdf/layout/list_box.rb +12 -20
  19. data/lib/hexapdf/layout/style.rb +5 -1
  20. data/lib/hexapdf/layout/table_box.rb +48 -55
  21. data/lib/hexapdf/layout/text_box.rb +38 -39
  22. data/lib/hexapdf/parser.rb +23 -17
  23. data/lib/hexapdf/type/acro_form/form.rb +78 -27
  24. data/lib/hexapdf/type/file_specification.rb +9 -5
  25. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  26. data/lib/hexapdf/version.rb +1 -1
  27. data/test/hexapdf/document/test_files.rb +5 -0
  28. data/test/hexapdf/document/test_metadata.rb +21 -0
  29. data/test/hexapdf/layout/test_box.rb +82 -37
  30. data/test/hexapdf/layout/test_box_fitter.rb +10 -3
  31. data/test/hexapdf/layout/test_column_box.rb +7 -13
  32. data/test/hexapdf/layout/test_container_box.rb +1 -1
  33. data/test/hexapdf/layout/test_frame.rb +0 -48
  34. data/test/hexapdf/layout/test_image_box.rb +14 -6
  35. data/test/hexapdf/layout/test_list_box.rb +25 -26
  36. data/test/hexapdf/layout/test_table_box.rb +39 -53
  37. data/test/hexapdf/layout/test_text_box.rb +65 -67
  38. data/test/hexapdf/test_composer.rb +6 -0
  39. data/test/hexapdf/test_dictionary.rb +6 -4
  40. data/test/hexapdf/test_parser.rb +20 -0
  41. data/test/hexapdf/type/acro_form/test_form.rb +63 -2
  42. data/test/hexapdf/type/test_file_specification.rb +2 -1
  43. metadata +2 -2
@@ -75,7 +75,7 @@ describe HexaPDF::Layout::TableBox::Cell do
75
75
  describe "fit" do
76
76
  it "fits a single box" do
77
77
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10))
78
- cell.fit(100, 100, @frame)
78
+ assert(cell.fit(100, 100, @frame).success?)
79
79
  assert_equal(100, cell.width)
80
80
  assert_equal(22, cell.height)
81
81
  assert_equal(32, cell.preferred_width)
@@ -84,7 +84,7 @@ describe HexaPDF::Layout::TableBox::Cell do
84
84
 
85
85
  it "fits a single box with horizontal aligning not being :left" do
86
86
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10, align: :center))
87
- cell.fit(100, 100, @frame)
87
+ assert(cell.fit(100, 100, @frame).success?)
88
88
  assert_equal(66, cell.preferred_width)
89
89
  end
90
90
 
@@ -92,7 +92,7 @@ describe HexaPDF::Layout::TableBox::Cell do
92
92
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10)
93
93
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
94
94
  cell = create_cell(children: [box1, box2])
95
- cell.fit(100, 100, @frame)
95
+ assert(cell.fit(100, 100, @frame).success?)
96
96
  assert_equal(100, cell.width)
97
97
  assert_equal(37, cell.height)
98
98
  assert_equal(62, cell.preferred_width)
@@ -103,13 +103,13 @@ describe HexaPDF::Layout::TableBox::Cell do
103
103
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, align: :center)
104
104
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
105
105
  cell = create_cell(children: [box1, box2])
106
- cell.fit(100, 100, @frame)
106
+ assert(cell.fit(100, 100, @frame).success?)
107
107
  assert_equal(66, cell.preferred_width)
108
108
  end
109
109
 
110
110
  it "fits the cell even if it has no content" do
111
111
  cell = create_cell(children: nil)
112
- cell.fit(100, 100, @frame)
112
+ assert(cell.fit(100, 100, @frame).success?)
113
113
  assert_equal(100, cell.width)
114
114
  assert_equal(12, cell.height)
115
115
  assert_equal(12, cell.preferred_width)
@@ -118,8 +118,8 @@ describe HexaPDF::Layout::TableBox::Cell do
118
118
 
119
119
  it "doesn't fit anything if the available width or height are too small" do
120
120
  cell = create_cell(children: nil)
121
- refute(cell.fit(10, 100, @frame))
122
- refute(cell.fit(100, 10, @frame))
121
+ assert(cell.fit(10, 100, @frame).failure?)
122
+ assert(cell.fit(100, 10, @frame).failure?)
123
123
  end
124
124
  end
125
125
 
@@ -370,8 +370,8 @@ describe HexaPDF::Layout::TableBox do
370
370
  HexaPDF::Layout::TableBox.new(cells: [@fixed_size_boxes[0, 2], @fixed_size_boxes[2, 2]], **kwargs)
371
371
  end
372
372
 
373
- def check_box(box, does_fit, width, height, cell_data = nil)
374
- assert(does_fit == box.fit(@frame.available_width, @frame.available_height, @frame), "box didn't fit")
373
+ def check_box(box, fit_status, width, height, cell_data = nil)
374
+ assert_equal(fit_status, box.fit(@frame.available_width, @frame.available_height, @frame).status)
375
375
  assert_equal(width, box.width, "box width")
376
376
  assert_equal(height, box.height, "box height")
377
377
  if cell_data
@@ -431,8 +431,8 @@ describe HexaPDF::Layout::TableBox do
431
431
  footer = lambda {|_| [[nil]] }
432
432
  box = create_box(header: header, footer: footer, cells: [[nil], [nil]],
433
433
  cell_style: {background_color: 'black'})
434
- refute(box.fit(100, 40, @frame))
435
- box_a, box_b = box.split(100, 40, nil)
434
+ assert(box.fit(100, 40, @frame).overflow?)
435
+ box_a, box_b = box.split
436
436
  assert_same(box_a, box)
437
437
  assert_equal('black', box_b.header_cells[0, 0].style.background_color)
438
438
  assert_equal('black', box_b.footer_cells[0, 0].style.background_color)
@@ -482,23 +482,13 @@ describe HexaPDF::Layout::TableBox do
482
482
  assert_equal(61, box.height)
483
483
  end
484
484
 
485
- it "cannot fit the table if the available width smaller than the initial width" do
486
- box = create_box(width: 200)
487
- refute(box.fit(@frame.available_width, @frame.available_height, @frame))
488
- end
489
-
490
- it "cannot fit the table if the available height smaller than the initial height" do
491
- box = create_box(height: 200)
492
- refute(box.fit(@frame.available_width, @frame.available_height, @frame))
493
- end
494
-
495
- it "cannot fit the table if the specified column widths are smaller than the available width" do
485
+ it "cannot fit the table if the specified column widths are greater than the available width" do
496
486
  box = create_box(column_widths: [200])
497
- refute(box.fit(@frame.available_width, @frame.available_height, @frame))
487
+ assert(box.fit(@frame.available_width, @frame.available_height, @frame).failure?)
498
488
  end
499
489
 
500
490
  it "fits a simple table" do
501
- check_box(create_box, true, 160, 45,
491
+ check_box(create_box, :success, 160, 45,
502
492
  [[0, 0, 79.5, 22], [79.5, 0, 79.5, 22], [0, 22, 79.5, 22], [79.5, 22, 79.5, 22]])
503
493
  end
504
494
 
@@ -507,7 +497,7 @@ describe HexaPDF::Layout::TableBox do
507
497
  [{col_span: 2, row_span: 2, content: @fixed_size_boxes[3]}, *@fixed_size_boxes[4, 2]],
508
498
  [{row_span: 2, content: @fixed_size_boxes[6]}, @fixed_size_boxes[7]],
509
499
  @fixed_size_boxes[8, 3]]
510
- check_box(create_box(cells: cells), true, 160, 89,
500
+ check_box(create_box(cells: cells), :success, 160, 89,
511
501
  [[0, 0, 39.75, 22], [39.75, 0, 79.5, 22], [39.75, 0, 79.50, 22], [119.25, 0, 39.75, 22],
512
502
  [0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 22, 39.75, 22], [119.25, 22, 39.75, 22],
513
503
  [0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 44, 39.75, 44], [119.25, 44, 39.75, 22],
@@ -518,7 +508,7 @@ describe HexaPDF::Layout::TableBox do
518
508
  result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
519
509
  header = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
520
510
  box = create_box(header: header, cell_style: {padding: 0, border: {width: 0}})
521
- box = check_box(box, true, 160, 40, result)
511
+ box = check_box(box, :success, 160, 40, result)
522
512
  assert_equal(result, cell_infos(box.header_cells))
523
513
  end
524
514
 
@@ -526,7 +516,7 @@ describe HexaPDF::Layout::TableBox do
526
516
  result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
527
517
  footer = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
528
518
  box = create_box(footer: footer, cell_style: {padding: 0, border: {width: 0}})
529
- box = check_box(box, true, 160, 40, result)
519
+ box = check_box(box, :success, 160, 40, result)
530
520
  assert_equal(result, cell_infos(box.footer_cells))
531
521
  end
532
522
 
@@ -535,14 +525,22 @@ describe HexaPDF::Layout::TableBox do
535
525
  cell_creator = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
536
526
  box = create_box(header: cell_creator, footer: cell_creator,
537
527
  cell_style: {padding: 0, border: {width: 0}})
538
- box = check_box(box, true, 160, 60, result)
528
+ box = check_box(box, :success, 160, 60, result)
539
529
  assert_equal(result, cell_infos(box.header_cells))
540
530
  assert_equal(result, cell_infos(box.footer_cells))
541
531
  end
542
532
 
533
+ it "fails if the header or footer rows don't fit" do
534
+ cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
535
+ [{header: cells_creator}, {footer: cells_creator}].each do |args|
536
+ box = create_box(**args, cell_style: {padding: 0, border: {width: 0}})
537
+ assert(box.fit(100, 15, @frame).failure?)
538
+ end
539
+ end
540
+
543
541
  it "partially fits a table if not enough height is available" do
544
542
  box = create_box(height: 10, cell_style: {padding: 0, border: {width: 0}})
545
- check_box(box, false, 160, 10,
543
+ check_box(box, :overflow, 160, 10,
546
544
  [[0, 0, 80, 10], [80, 0, 80, 10], [nil, nil, 80, 0], [nil, nil, 0, 0]])
547
545
  end
548
546
  end
@@ -550,8 +548,8 @@ describe HexaPDF::Layout::TableBox do
550
548
  describe "split" do
551
549
  it "splits the table if some rows could not be fit into the available region" do
552
550
  box = create_box
553
- refute(box.fit(100, 25, @frame))
554
- box_a, box_b = box.split(100, 25, @frame)
551
+ assert(box.fit(100, 25, @frame).overflow?)
552
+ box_a, box_b = box.split
555
553
  assert_same(box_a, box)
556
554
  assert(box_b.split_box?)
557
555
 
@@ -567,8 +565,8 @@ describe HexaPDF::Layout::TableBox do
567
565
  [HexaPDF::Layout::Box.new(width: 20, height: 150, &@draw_block)]]
568
566
  box = create_box(cells: cells)
569
567
 
570
- refute(box.fit(100, 100, @frame))
571
- box_a, box_b = box.split(100, 100, @frame)
568
+ assert(box.fit(100, 100, @frame).overflow?)
569
+ box_a, box_b = box.split
572
570
  assert_same(box_a, box)
573
571
  assert(box_b.split_box?)
574
572
  assert_equal(0, box_a.start_row_index)
@@ -576,8 +574,8 @@ describe HexaPDF::Layout::TableBox do
576
574
  assert_equal(1, box_b.start_row_index)
577
575
  assert_equal(-1, box_b.last_fitted_row_index)
578
576
 
579
- refute(box_b.fit(100, 100, @frame))
580
- box_c, box_d = box_b.split(100, 100, @frame)
577
+ assert(box_b.fit(100, 100, @frame).failure?)
578
+ box_c, box_d = box_b.split
581
579
  assert_nil(box_c)
582
580
  assert_same(box_d, box_b)
583
581
  assert(box_d.split_box?)
@@ -585,24 +583,12 @@ describe HexaPDF::Layout::TableBox do
585
583
  assert_equal(-1, box_d.last_fitted_row_index)
586
584
  end
587
585
 
588
- it "splits the table if the header or footer rows don't fit" do
589
- cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
590
- [{header: cells_creator}, {footer: cells_creator}].each do |args|
591
- box = create_box(**args)
592
- box.cells.style(padding: 0, border: {width: 0})
593
- refute(box.fit(100, 25, @frame))
594
- box_a, box_b = box.split(100, 25, @frame)
595
- assert_nil(box_a)
596
- assert_same(box_b, box)
597
- end
598
- end
599
-
600
586
  it "splits a table with a header or a footer" do
601
587
  cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
602
588
  [{header: cells_creator}, {footer: cells_creator}].each do |args|
603
589
  box = create_box(**args)
604
- refute(box.fit(100, 50, @frame))
605
- box_a, box_b = box.split(100, 50, @frame)
590
+ assert(box.fit(100, 50, @frame).overflow?)
591
+ box_a, box_b = box.split
606
592
  assert_same(box_a, box)
607
593
 
608
594
  assert_equal(0, box_a.start_row_index)
@@ -681,9 +667,9 @@ describe HexaPDF::Layout::TableBox do
681
667
 
682
668
  it "correctly works for split boxes" do
683
669
  box = create_box(cell_style: {padding: 0, border: {width: 0}})
684
- refute(box.fit(100, 10, @frame))
685
- _, split_box = box.split(100, 10, @frame)
686
- assert(split_box.fit(100, 100, @frame))
670
+ assert(box.fit(100, 10, @frame).overflow?)
671
+ _, split_box = box.split
672
+ assert(split_box.fit(100, 100, @frame).success?)
687
673
 
688
674
  box.draw(@canvas, 20, 10)
689
675
  split_box.draw(@canvas, 0, 50)
@@ -714,7 +700,7 @@ describe HexaPDF::Layout::TableBox do
714
700
  box = create_box(header: lambda {|_| [@fixed_size_boxes[10, 1]] },
715
701
  footer: lambda {|_| [@fixed_size_boxes[12, 1]] },
716
702
  cell_style: {padding: 0, border: {width: 0}})
717
- box.fit(100, 100, @frame)
703
+ assert(box.fit(100, 100, @frame).success?)
718
704
  box.draw(@canvas, 20, 10)
719
705
  operators = [[:save_graphics_state],
720
706
  [:concatenate_matrix, [1, 0, 0, 1, 20, 40]],
@@ -36,29 +36,31 @@ describe HexaPDF::Layout::TextBox do
36
36
  describe "fit" do
37
37
  it "fits into a rectangular area" do
38
38
  box = create_box([@inline_box] * 5, style: {padding: 10})
39
- assert(box.fit(100, 100, @frame))
39
+ assert(box.fit(100, 100, @frame).success?)
40
40
  assert_equal(70, box.width)
41
41
  assert_equal(30, box.height)
42
42
  end
43
43
 
44
44
  it "respects the set width and height" do
45
45
  box = create_box([@inline_box], width: 40, height: 50, style: {padding: 10})
46
- assert(box.fit(100, 100, @frame))
46
+ assert(box.fit(100, 100, @frame).success?)
47
47
  assert_equal(40, box.width)
48
48
  assert_equal(50, box.height)
49
49
  assert_equal([10], box.instance_variable_get(:@result).lines.map(&:width))
50
50
  end
51
51
 
52
52
  it "fits into the frame's outline" do
53
+ @frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
54
+ @frame.remove_area(Geom2D::Rectangle(80, 70, 20, 20))
53
55
  box = create_box([@inline_box] * 20, style: {position: :flow})
54
- assert(box.fit(100, 100, @frame))
56
+ assert(box.fit(100, 100, @frame).success?)
55
57
  assert_equal(100, box.width)
56
- assert_equal(20, box.height)
58
+ assert_equal(30, box.height)
57
59
  end
58
60
 
59
61
  it "takes the style option last_line_gap into account" do
60
62
  box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
61
- assert(box.fit(100, 100, @frame))
63
+ assert(box.fit(100, 100, @frame).success?)
62
64
  assert_equal(50, box.width)
63
65
  assert_equal(20, box.height)
64
66
  end
@@ -66,7 +68,7 @@ describe HexaPDF::Layout::TextBox do
66
68
  it "uses the whole available width when aligning to the center or right" do
67
69
  [:center, :right].each do |align|
68
70
  box = create_box([@inline_box], style: {text_align: align})
69
- assert(box.fit(100, 100, @frame))
71
+ assert(box.fit(100, 100, @frame).success?)
70
72
  assert_equal(100, box.width)
71
73
  end
72
74
  end
@@ -74,91 +76,62 @@ describe HexaPDF::Layout::TextBox do
74
76
  it "uses the whole available height when vertically aligning to the center or bottom" do
75
77
  [:center, :bottom].each do |valign|
76
78
  box = create_box([@inline_box], style: {text_valign: valign})
77
- assert(box.fit(100, 100, @frame))
79
+ assert(box.fit(100, 100, @frame).success?)
78
80
  assert_equal(100, box.height)
79
81
  end
80
82
  end
81
83
 
82
- it "respects the style property overflow when fitting too much text" do
84
+ it "can fit part of the box" do
83
85
  box = create_box([@inline_box] * 20, height: 15)
84
- refute(box.fit(100, 100, @frame))
85
- box.style.overflow = :truncate
86
- assert(box.fit(100, 100, @frame))
87
-
88
- box = create_box([@inline_box] * 20, style: {overflow: :truncate})
89
- refute(box.fit(100, 15, @frame))
86
+ assert(box.fit(100, 100, @frame).overflow?)
90
87
  end
91
88
 
92
- it "can't fit the text box if the set width is bigger than the available width" do
93
- box = create_box([@inline_box], width: 101)
94
- refute(box.fit(100, 100, @frame))
95
- end
96
-
97
- it "can't fit the text box if the set height is bigger than the available height" do
98
- box = create_box([@inline_box], height: 101)
99
- refute(box.fit(100, 100, @frame))
89
+ it "correctly handles text indentation for split boxes" do
90
+ [{}, {position: :flow}].each do |styles|
91
+ box = create_box([@inline_box] * 202, style: {text_indent: 50, **styles})
92
+ assert(box.fit(100, 100, @frame).overflow?)
93
+ _, box_b = box.split
94
+ assert_equal(107, box_b.instance_variable_get(:@items).length)
95
+ assert(box_b.fit(100, 100, @frame).overflow?)
96
+ _, box_b = box_b.split
97
+ assert_equal(7, box_b.instance_variable_get(:@items).length)
98
+ end
100
99
  end
101
- end
102
100
 
103
- describe "split" do
104
- it "works for an empty text box" do
101
+ it "fits an empty text box" do
105
102
  box = create_box([])
106
- assert_equal([box], box.split(100, 100, @frame))
103
+ assert(box.fit(100, 100, @frame).success?)
107
104
  end
108
105
 
109
- it "doesn't need to split the box if it completely fits" do
110
- box = create_box([@inline_box] * 5)
111
- assert_equal([box], box.split(50, 100, @frame))
112
- end
113
-
114
- it "works if no item of the text box fits" do
106
+ it "fails if no item of the text box fits due to the width" do
115
107
  box = create_box([@inline_box])
116
- assert_equal([nil, box], box.split(5, 20, @frame))
117
- end
118
-
119
- it "works if the whole text box doesn't fits" do
120
- box = create_box([@inline_box], width: 102)
121
- assert_equal([nil, box], box.split(100, 100, @frame))
122
-
123
- box = create_box([@inline_box], height: 102)
124
- assert_equal([nil, box], box.split(100, 100, @frame))
108
+ assert(box.fit(5, 20, @frame).failure?)
125
109
  end
126
110
 
127
- it "works if the box fits exactly (+/- float divergence)" do
128
- box = create_box([@inline_box] * 5)
129
- box.fit(50, 10, @frame)
130
- box.instance_variable_set(:@width, 50.00000000006)
131
- box.instance_variable_set(:@height, 10.00000000003)
132
- assert_equal([box], box.split(50, 10, @frame))
111
+ it "fails if no item of the text box fits due to the height" do
112
+ box = create_box([@inline_box])
113
+ assert(box.fit(20, 5, @frame).failure?)
133
114
  end
115
+ end
134
116
 
117
+ describe "split" do
135
118
  it "splits the box if necessary when using non-flowing text" do
136
119
  box = create_box([@inline_box] * 10)
137
- boxes = box.split(50, 10, @frame)
138
- assert_equal(2, boxes.length)
139
- assert_equal(box, boxes[0])
140
- refute(boxes[0].split_box?)
141
- assert(boxes[1].split_box?)
142
- assert_equal(5, boxes[1].instance_variable_get(:@items).length)
120
+ box.fit(50, 10, @frame)
121
+ box_a, box_b = box.split
122
+ assert_same(box, box_a)
123
+ refute(box_a.split_box?)
124
+ assert(box_b.split_box?)
125
+ assert_equal(5, box_b.instance_variable_get(:@items).length)
143
126
  end
144
127
 
145
128
  it "splits the box if necessary when using flowing text that results in a wider box" do
146
129
  @frame.remove_area(Geom2D::Polygon.new([[0, 100], [50, 100], [50, 10], [0, 10]]))
147
130
  box = create_box([@inline_box] * 60, style: {position: :flow})
148
- boxes = box.split(50, 100, @frame)
149
- assert_equal(2, boxes.length)
150
- assert_equal(box, boxes[0])
151
- assert_equal(5, boxes[1].instance_variable_get(:@items).length)
152
- end
153
-
154
- it "correctly handles text indentation for split boxes" do
155
- [{}, {position: :flow}].each do |styles|
156
- box = create_box([@inline_box] * 202, style: {text_indent: 50, **styles})
157
- boxes = box.split(100, 100, @frame)
158
- assert_equal(107, boxes[1].instance_variable_get(:@items).length)
159
- boxes = boxes[1].split(100, 100, @frame)
160
- assert_equal(7, boxes[1].instance_variable_get(:@items).length)
161
- end
131
+ box.fit(50, 100, @frame)
132
+ box_a, box_b = box.split
133
+ assert_same(box, box_a)
134
+ assert_equal(5, box_b.instance_variable_get(:@items).length)
162
135
  end
163
136
  end
164
137
 
@@ -190,8 +163,33 @@ describe HexaPDF::Layout::TextBox do
190
163
  [:restore_graphics_state]])
191
164
  end
192
165
 
166
+ it "correctly draws borders, backgrounds... for position :flow" do
167
+ @frame.remove_area(Geom2D::Rectangle(0, 0, 40, 100))
168
+ box = create_box([@inline_box], style: {position: :flow, border: {width: 1}})
169
+ box.fit(60, 100, @frame)
170
+ box.draw(@canvas, 0, 90)
171
+ assert_operators(@canvas.contents, [[:save_graphics_state],
172
+ [:append_rectangle, [40, 90, 10, 10]],
173
+ [:clip_path_non_zero],
174
+ [:end_path],
175
+ [:append_rectangle, [40.5, 90.5, 9.0, 9.0]],
176
+ [:stroke_path],
177
+ [:restore_graphics_state],
178
+ [:save_graphics_state],
179
+ [:restore_graphics_state],
180
+ [:save_graphics_state],
181
+ [:concatenate_matrix, [1, 0, 0, 1, 41, 89]],
182
+ [:save_graphics_state],
183
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
184
+ [:restore_graphics_state],
185
+ [:restore_graphics_state],
186
+ [:save_graphics_state],
187
+ [:restore_graphics_state]])
188
+ end
189
+
193
190
  it "draws nothing onto the canvas if the box is empty" do
194
191
  box = create_box([])
192
+ box.fit(100, 100, @frame)
195
193
  box.draw(@canvas, 5, 5)
196
194
  assert_operators(@canvas.contents, [])
197
195
  end
@@ -263,6 +263,12 @@ describe HexaPDF::Composer do
263
263
  [:restore_graphics_state]])
264
264
  end
265
265
 
266
+ it "handles truncated boxes correctly" do
267
+ box = create_box(height: 400, style: {overflow: :truncate})
268
+ box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
269
+ assert_same(box, @composer.draw_box(box))
270
+ end
271
+
266
272
  it "returns the last drawn box" do
267
273
  box = create_box(height: 400)
268
274
  assert_same(box, @composer.draw_box(box))
@@ -323,11 +323,13 @@ describe HexaPDF::Dictionary do
323
323
  end
324
324
  end
325
325
 
326
- describe "to_h" do
327
- it "returns a shallow copy of the value" do
328
- obj = @dict.to_h
326
+ describe "to_hash" do
327
+ it "returns a copy of the value where each entry is pre-processed" do
328
+ @dict[:value] = HexaPDF::Reference.new(1, 0)
329
+ obj = @dict.to_hash
329
330
  refute_equal(obj.object_id, @dict.value.object_id)
330
- assert_equal(obj, @dict.value)
331
+ assert_equal(:obj, obj[:Object])
332
+ assert_equal("deref", obj[:value])
331
333
  end
332
334
  end
333
335
 
@@ -358,6 +358,14 @@ describe HexaPDF::Parser do
358
358
  it "finds the startxref anywhere in file" do
359
359
  create_parser("startxref\n5\n%%EOF" << "\nhallo" * 5000)
360
360
  assert_equal(5, @parser.startxref_offset)
361
+ end
362
+
363
+ it "handles the case where %%EOF is the on the 1. line of the 1024 byte search block" do
364
+ create_parser("startxref\n5\n%%EOF\n" << "h" * 1018)
365
+ assert_equal(5, @parser.startxref_offset)
366
+ end
367
+
368
+ it "handles the case where %%EOF is the on the 2. line of the 1024 byte search block" do
361
369
  create_parser("startxref\n5\n%%EOF\n" << "h" * 1017)
362
370
  assert_equal(5, @parser.startxref_offset)
363
371
  end
@@ -367,6 +375,11 @@ describe HexaPDF::Parser do
367
375
  assert_equal(5, @parser.startxref_offset)
368
376
  end
369
377
 
378
+ it "handles the case of multiple %%EOF and the last one being invalid" do
379
+ create_parser("startxref\n5\n%%EOF\ntartxref\n3\n%%EOF")
380
+ assert_equal(5, @parser.startxref_offset)
381
+ end
382
+
370
383
  it "fails even in big files when nothing is found" do
371
384
  create_parser("\nhallo" * 5000)
372
385
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
@@ -402,6 +415,13 @@ describe HexaPDF::Parser do
402
415
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
403
416
  assert_match(/startxref on same line/, exp.message)
404
417
  end
418
+
419
+ it "fails on strict parsing if there are multiple %%EOF and the last one is invalid" do
420
+ @document.config['parser.on_correctable_error'] = proc { true }
421
+ create_parser("startxref\n5\n%%EOF\ntartxref\n3\n%%EOF")
422
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
423
+ assert_match(/missing startxref keyword/, exp.message)
424
+ end
405
425
  end
406
426
 
407
427
  describe "file_header_version" do
@@ -128,6 +128,12 @@ describe HexaPDF::Type::AcroForm::Form do
128
128
  @acro_form = @doc.acro_form(create: true)
129
129
  end
130
130
 
131
+ it "creates a pure namespace field" do
132
+ field = @acro_form.create_namespace_field('text')
133
+ assert_equal('text', field.full_field_name)
134
+ assert_nil(field.concrete_field_type)
135
+ end
136
+
131
137
  describe "handles the general case" do
132
138
  it "works for names with a dot" do
133
139
  @acro_form[:Fields] = [{T: "root"}]
@@ -142,8 +148,13 @@ describe HexaPDF::Type::AcroForm::Form do
142
148
  assert([field], @acro_form[:Fields])
143
149
  end
144
150
 
145
- it "fails if the parent field is not found" do
146
- assert_raises(HexaPDF::Error) { @acro_form.create_text_field("root.field") }
151
+ it "creates the parent fields as namespace fields if necessary" do
152
+ field = @acro_form.create_text_field("root.sub.field")
153
+ level1 = @acro_form.field_by_name('root')
154
+ assert_equal(1, level1[:Kids].size)
155
+ level2 = @acro_form.field_by_name('root.sub')
156
+ assert_equal(1, level2[:Kids].size)
157
+ assert_same(field, level2[:Kids][0])
147
158
  end
148
159
  end
149
160
 
@@ -241,6 +252,56 @@ describe HexaPDF::Type::AcroForm::Form do
241
252
  end
242
253
  end
243
254
 
255
+ describe "delete_field" do
256
+ before do
257
+ @field = @acro_form.create_signature_field("sig")
258
+ end
259
+
260
+ it "deletes a field via name" do
261
+ @acro_form.delete_field('sig')
262
+ assert_equal(0, @acro_form.root_fields.size)
263
+ end
264
+
265
+ it "deletes a field via field object" do
266
+ @acro_form.delete_field(@field)
267
+ assert_equal(0, @acro_form.root_fields.size)
268
+ end
269
+
270
+ it "deletes the set signature object" do
271
+ obj = @doc.add({})
272
+ @field.field_value = obj
273
+ @acro_form.delete_field(@field)
274
+ assert(obj.null?)
275
+ end
276
+
277
+ it "deletes all widget annotations from the document and the annotation array" do
278
+ widget1 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
279
+ widget2 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
280
+ refute(@doc.pages[1][:Annots].empty?)
281
+ @acro_form.delete_field(@field)
282
+ assert(@doc.pages[0][:Annots].empty?)
283
+ assert(@doc.pages[1][:Annots].empty?)
284
+ assert(@doc.object(widget1).null?)
285
+ assert(@doc.object(widget2).null?)
286
+ end
287
+
288
+ it "deletes the field from the field hierarchy" do
289
+ @acro_form.delete_field('sig')
290
+ refute(@acro_form.field_by_name('sig'))
291
+ assert(@acro_form[:Fields].empty?)
292
+
293
+ @acro_form.create_signature_field("sub.sub.sig")
294
+ @acro_form.delete_field("sub.sub.sig")
295
+ refute(@acro_form.field_by_name('sub.sub.sig'))
296
+ assert(@acro_form[:Fields][0][:Kids][0][:Kids].empty?)
297
+ end
298
+
299
+ it "deletes the field itself" do
300
+ @acro_form.delete_field('sig')
301
+ assert(@doc.object(@field).null?)
302
+ end
303
+ end
304
+
244
305
  describe "fill" do
245
306
  it "works for text field types" do
246
307
  field = @acro_form.create_text_field('test')
@@ -97,10 +97,11 @@ describe HexaPDF::Type::FileSpecification do
97
97
  end
98
98
 
99
99
  it "embeds the given file and registers it with the global name registry" do
100
- stream = @obj.embed(@file.path)
100
+ stream = @obj.embed(@file.path, mime_type: 'application/xml')
101
101
  assert_equal(stream, @obj[:EF][:UF])
102
102
  assert_equal(stream, @obj[:EF][:F])
103
103
  assert_equal(File.basename(@file.path), @obj.path)
104
+ assert_equal(:'application/xml', stream[:Subtype])
104
105
  assert_equal(@obj, @doc.catalog[:Names][:EmbeddedFiles].find_entry(@obj.path))
105
106
  assert_equal(:FlateDecode, stream[:Filter])
106
107
  assert_equal('embed-test', stream.stream)
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.42.0
4
+ version: 0.44.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-12 00:00:00.000000000 Z
11
+ date: 2024-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse