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