hexapdf 0.42.0 → 0.44.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/Rakefile +1 -1
- data/examples/030-pdfa.rb +1 -0
- data/lib/hexapdf/composer.rb +1 -0
- data/lib/hexapdf/dictionary.rb +3 -3
- data/lib/hexapdf/document/files.rb +7 -2
- data/lib/hexapdf/document/metadata.rb +12 -1
- data/lib/hexapdf/document.rb +14 -1
- data/lib/hexapdf/encryption.rb +17 -0
- data/lib/hexapdf/layout/box.rb +161 -61
- data/lib/hexapdf/layout/box_fitter.rb +4 -3
- data/lib/hexapdf/layout/column_box.rb +23 -25
- data/lib/hexapdf/layout/container_box.rb +3 -3
- data/lib/hexapdf/layout/frame.rb +13 -95
- data/lib/hexapdf/layout/image_box.rb +4 -4
- data/lib/hexapdf/layout/line.rb +4 -0
- data/lib/hexapdf/layout/list_box.rb +12 -20
- data/lib/hexapdf/layout/style.rb +5 -1
- data/lib/hexapdf/layout/table_box.rb +48 -55
- data/lib/hexapdf/layout/text_box.rb +38 -39
- data/lib/hexapdf/parser.rb +23 -17
- data/lib/hexapdf/type/acro_form/form.rb +78 -27
- data/lib/hexapdf/type/file_specification.rb +9 -5
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_files.rb +5 -0
- data/test/hexapdf/document/test_metadata.rb +21 -0
- data/test/hexapdf/layout/test_box.rb +82 -37
- data/test/hexapdf/layout/test_box_fitter.rb +10 -3
- data/test/hexapdf/layout/test_column_box.rb +7 -13
- data/test/hexapdf/layout/test_container_box.rb +1 -1
- data/test/hexapdf/layout/test_frame.rb +0 -48
- data/test/hexapdf/layout/test_image_box.rb +14 -6
- data/test/hexapdf/layout/test_list_box.rb +25 -26
- data/test/hexapdf/layout/test_table_box.rb +39 -53
- data/test/hexapdf/layout/test_text_box.rb +65 -67
- data/test/hexapdf/test_composer.rb +6 -0
- data/test/hexapdf/test_dictionary.rb +6 -4
- data/test/hexapdf/test_parser.rb +20 -0
- data/test/hexapdf/type/acro_form/test_form.rb +63 -2
- data/test/hexapdf/type/test_file_specification.rb +2 -1
- 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
|
-
|
122
|
-
|
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,
|
374
|
-
|
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
|
-
|
435
|
-
box_a, box_b = box.split
|
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
|
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
|
-
|
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,
|
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),
|
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,
|
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,
|
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,
|
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,
|
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
|
-
|
554
|
-
box_a, box_b = box.split
|
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
|
-
|
571
|
-
box_a, box_b = box.split
|
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
|
-
|
580
|
-
box_c, box_d = box_b.split
|
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
|
-
|
605
|
-
box_a, box_b = box.split
|
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
|
-
|
685
|
-
_, split_box = box.split
|
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(
|
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 "
|
84
|
+
it "can fit part of the box" do
|
83
85
|
box = create_box([@inline_box] * 20, height: 15)
|
84
|
-
|
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 "
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
104
|
-
it "works for an empty text box" do
|
101
|
+
it "fits an empty text box" do
|
105
102
|
box = create_box([])
|
106
|
-
|
103
|
+
assert(box.fit(100, 100, @frame).success?)
|
107
104
|
end
|
108
105
|
|
109
|
-
it "
|
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
|
-
|
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 "
|
128
|
-
box = create_box([@inline_box]
|
129
|
-
box.fit(
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
refute(
|
141
|
-
assert(
|
142
|
-
assert_equal(5,
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
assert_equal(5,
|
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 "
|
327
|
-
it "returns a
|
328
|
-
|
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,
|
331
|
+
assert_equal(:obj, obj[:Object])
|
332
|
+
assert_equal("deref", obj[:value])
|
331
333
|
end
|
332
334
|
end
|
333
335
|
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -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 "
|
146
|
-
|
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.
|
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
|
11
|
+
date: 2024-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|