hexapdf 0.42.0 → 0.44.0

Sign up to get free protection for your applications and to get access to all the features.
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