hexapdf 0.43.0 → 0.45.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 +36 -0
- data/examples/027-composer_optional_content.rb +6 -4
- data/examples/030-pdfa.rb +13 -11
- data/lib/hexapdf/composer.rb +23 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/canvas_composer.rb +1 -0
- data/lib/hexapdf/document/files.rb +7 -2
- data/lib/hexapdf/document/layout.rb +15 -3
- data/lib/hexapdf/document/metadata.rb +12 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/layout/box.rb +180 -66
- data/lib/hexapdf/layout/box_fitter.rb +1 -0
- data/lib/hexapdf/layout/column_box.rb +18 -28
- data/lib/hexapdf/layout/container_box.rb +6 -6
- data/lib/hexapdf/layout/frame.rb +13 -94
- data/lib/hexapdf/layout/image_box.rb +4 -4
- data/lib/hexapdf/layout/list_box.rb +13 -31
- data/lib/hexapdf/layout/style.rb +8 -4
- data/lib/hexapdf/layout/table_box.rb +55 -58
- data/lib/hexapdf/layout/text_box.rb +84 -71
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +7 -8
- data/lib/hexapdf/parser.rb +5 -2
- data/lib/hexapdf/rectangle.rb +4 -4
- data/lib/hexapdf/type/file_specification.rb +9 -5
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas_composer.rb +13 -8
- data/test/hexapdf/document/test_files.rb +5 -0
- data/test/hexapdf/document/test_layout.rb +16 -0
- data/test/hexapdf/document/test_metadata.rb +21 -0
- data/test/hexapdf/layout/test_box.rb +93 -37
- data/test/hexapdf/layout/test_box_fitter.rb +7 -0
- 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 +7 -46
- data/test/hexapdf/layout/test_image_box.rb +14 -6
- data/test/hexapdf/layout/test_list_box.rb +26 -27
- data/test/hexapdf/layout/test_table_box.rb +47 -54
- data/test/hexapdf/layout/test_text_box.rb +83 -83
- data/test/hexapdf/test_composer.rb +20 -5
- data/test/hexapdf/test_parser.rb +8 -0
- data/test/hexapdf/test_serializer.rb +1 -0
- 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,23 +103,30 @@ 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)
|
116
116
|
assert_equal(12, cell.preferred_height)
|
117
117
|
end
|
118
118
|
|
119
|
-
it "doesn't fit
|
119
|
+
it "doesn't fit children that are too big" do
|
120
|
+
cell = create_cell(children: HexaPDF::Layout::Box.create(width: 300, height: 20))
|
121
|
+
assert(cell.fit(100, 100, @frame).failure?)
|
122
|
+
cell = create_cell(children: [HexaPDF::Layout::Box.create(width: 300, height: 20)])
|
123
|
+
assert(cell.fit(100, 100, @frame).failure?)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "doesn't fit anything if the available width or height are too small even if there are no children" do
|
120
127
|
cell = create_cell(children: nil)
|
121
|
-
|
122
|
-
|
128
|
+
assert(cell.fit(10, 100, @frame).failure?)
|
129
|
+
assert(cell.fit(100, 10, @frame).failure?)
|
123
130
|
end
|
124
131
|
end
|
125
132
|
|
@@ -370,8 +377,8 @@ describe HexaPDF::Layout::TableBox do
|
|
370
377
|
HexaPDF::Layout::TableBox.new(cells: [@fixed_size_boxes[0, 2], @fixed_size_boxes[2, 2]], **kwargs)
|
371
378
|
end
|
372
379
|
|
373
|
-
def check_box(box,
|
374
|
-
|
380
|
+
def check_box(box, fit_status, width, height, cell_data = nil)
|
381
|
+
assert_equal(fit_status, box.fit(@frame.available_width, @frame.available_height, @frame).status)
|
375
382
|
assert_equal(width, box.width, "box width")
|
376
383
|
assert_equal(height, box.height, "box height")
|
377
384
|
if cell_data
|
@@ -431,8 +438,8 @@ describe HexaPDF::Layout::TableBox do
|
|
431
438
|
footer = lambda {|_| [[nil]] }
|
432
439
|
box = create_box(header: header, footer: footer, cells: [[nil], [nil]],
|
433
440
|
cell_style: {background_color: 'black'})
|
434
|
-
|
435
|
-
box_a, box_b = box.split
|
441
|
+
assert(box.fit(100, 40, @frame).overflow?)
|
442
|
+
box_a, box_b = box.split
|
436
443
|
assert_same(box_a, box)
|
437
444
|
assert_equal('black', box_b.header_cells[0, 0].style.background_color)
|
438
445
|
assert_equal('black', box_b.footer_cells[0, 0].style.background_color)
|
@@ -482,23 +489,13 @@ describe HexaPDF::Layout::TableBox do
|
|
482
489
|
assert_equal(61, box.height)
|
483
490
|
end
|
484
491
|
|
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
|
492
|
+
it "cannot fit the table if the specified column widths are greater than the available width" do
|
496
493
|
box = create_box(column_widths: [200])
|
497
|
-
|
494
|
+
assert(box.fit(@frame.available_width, @frame.available_height, @frame).failure?)
|
498
495
|
end
|
499
496
|
|
500
497
|
it "fits a simple table" do
|
501
|
-
check_box(create_box,
|
498
|
+
check_box(create_box, :success, 160, 45,
|
502
499
|
[[0, 0, 79.5, 22], [79.5, 0, 79.5, 22], [0, 22, 79.5, 22], [79.5, 22, 79.5, 22]])
|
503
500
|
end
|
504
501
|
|
@@ -507,7 +504,7 @@ describe HexaPDF::Layout::TableBox do
|
|
507
504
|
[{col_span: 2, row_span: 2, content: @fixed_size_boxes[3]}, *@fixed_size_boxes[4, 2]],
|
508
505
|
[{row_span: 2, content: @fixed_size_boxes[6]}, @fixed_size_boxes[7]],
|
509
506
|
@fixed_size_boxes[8, 3]]
|
510
|
-
check_box(create_box(cells: cells),
|
507
|
+
check_box(create_box(cells: cells), :success, 160, 89,
|
511
508
|
[[0, 0, 39.75, 22], [39.75, 0, 79.5, 22], [39.75, 0, 79.50, 22], [119.25, 0, 39.75, 22],
|
512
509
|
[0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 22, 39.75, 22], [119.25, 22, 39.75, 22],
|
513
510
|
[0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 44, 39.75, 44], [119.25, 44, 39.75, 22],
|
@@ -518,7 +515,7 @@ describe HexaPDF::Layout::TableBox do
|
|
518
515
|
result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
|
519
516
|
header = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
|
520
517
|
box = create_box(header: header, cell_style: {padding: 0, border: {width: 0}})
|
521
|
-
box = check_box(box,
|
518
|
+
box = check_box(box, :success, 160, 40, result)
|
522
519
|
assert_equal(result, cell_infos(box.header_cells))
|
523
520
|
end
|
524
521
|
|
@@ -526,7 +523,7 @@ describe HexaPDF::Layout::TableBox do
|
|
526
523
|
result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
|
527
524
|
footer = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
|
528
525
|
box = create_box(footer: footer, cell_style: {padding: 0, border: {width: 0}})
|
529
|
-
box = check_box(box,
|
526
|
+
box = check_box(box, :success, 160, 40, result)
|
530
527
|
assert_equal(result, cell_infos(box.footer_cells))
|
531
528
|
end
|
532
529
|
|
@@ -535,14 +532,22 @@ describe HexaPDF::Layout::TableBox do
|
|
535
532
|
cell_creator = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
|
536
533
|
box = create_box(header: cell_creator, footer: cell_creator,
|
537
534
|
cell_style: {padding: 0, border: {width: 0}})
|
538
|
-
box = check_box(box,
|
535
|
+
box = check_box(box, :success, 160, 60, result)
|
539
536
|
assert_equal(result, cell_infos(box.header_cells))
|
540
537
|
assert_equal(result, cell_infos(box.footer_cells))
|
541
538
|
end
|
542
539
|
|
540
|
+
it "fails if the header or footer rows don't fit" do
|
541
|
+
cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
|
542
|
+
[{header: cells_creator}, {footer: cells_creator}].each do |args|
|
543
|
+
box = create_box(**args, cell_style: {padding: 0, border: {width: 0}})
|
544
|
+
assert(box.fit(100, 15, @frame).failure?)
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
543
548
|
it "partially fits a table if not enough height is available" do
|
544
549
|
box = create_box(height: 10, cell_style: {padding: 0, border: {width: 0}})
|
545
|
-
check_box(box,
|
550
|
+
check_box(box, :overflow, 160, 10,
|
546
551
|
[[0, 0, 80, 10], [80, 0, 80, 10], [nil, nil, 80, 0], [nil, nil, 0, 0]])
|
547
552
|
end
|
548
553
|
end
|
@@ -550,8 +555,8 @@ describe HexaPDF::Layout::TableBox do
|
|
550
555
|
describe "split" do
|
551
556
|
it "splits the table if some rows could not be fit into the available region" do
|
552
557
|
box = create_box
|
553
|
-
|
554
|
-
box_a, box_b = box.split
|
558
|
+
assert(box.fit(100, 25, @frame).overflow?)
|
559
|
+
box_a, box_b = box.split
|
555
560
|
assert_same(box_a, box)
|
556
561
|
assert(box_b.split_box?)
|
557
562
|
|
@@ -567,8 +572,8 @@ describe HexaPDF::Layout::TableBox do
|
|
567
572
|
[HexaPDF::Layout::Box.new(width: 20, height: 150, &@draw_block)]]
|
568
573
|
box = create_box(cells: cells)
|
569
574
|
|
570
|
-
|
571
|
-
box_a, box_b = box.split
|
575
|
+
assert(box.fit(100, 100, @frame).overflow?)
|
576
|
+
box_a, box_b = box.split
|
572
577
|
assert_same(box_a, box)
|
573
578
|
assert(box_b.split_box?)
|
574
579
|
assert_equal(0, box_a.start_row_index)
|
@@ -576,8 +581,8 @@ describe HexaPDF::Layout::TableBox do
|
|
576
581
|
assert_equal(1, box_b.start_row_index)
|
577
582
|
assert_equal(-1, box_b.last_fitted_row_index)
|
578
583
|
|
579
|
-
|
580
|
-
box_c, box_d = box_b.split
|
584
|
+
assert(box_b.fit(100, 100, @frame).failure?)
|
585
|
+
box_c, box_d = box_b.split
|
581
586
|
assert_nil(box_c)
|
582
587
|
assert_same(box_d, box_b)
|
583
588
|
assert(box_d.split_box?)
|
@@ -585,24 +590,12 @@ describe HexaPDF::Layout::TableBox do
|
|
585
590
|
assert_equal(-1, box_d.last_fitted_row_index)
|
586
591
|
end
|
587
592
|
|
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
593
|
it "splits a table with a header or a footer" do
|
601
594
|
cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
|
602
595
|
[{header: cells_creator}, {footer: cells_creator}].each do |args|
|
603
596
|
box = create_box(**args)
|
604
|
-
|
605
|
-
box_a, box_b = box.split
|
597
|
+
assert(box.fit(100, 50, @frame).overflow?)
|
598
|
+
box_a, box_b = box.split
|
606
599
|
assert_same(box_a, box)
|
607
600
|
|
608
601
|
assert_equal(0, box_a.start_row_index)
|
@@ -681,9 +674,9 @@ describe HexaPDF::Layout::TableBox do
|
|
681
674
|
|
682
675
|
it "correctly works for split boxes" do
|
683
676
|
box = create_box(cell_style: {padding: 0, border: {width: 0}})
|
684
|
-
|
685
|
-
_, split_box = box.split
|
686
|
-
assert(split_box.fit(100, 100, @frame))
|
677
|
+
assert(box.fit(100, 10, @frame).overflow?)
|
678
|
+
_, split_box = box.split
|
679
|
+
assert(split_box.fit(100, 100, @frame).success?)
|
687
680
|
|
688
681
|
box.draw(@canvas, 20, 10)
|
689
682
|
split_box.draw(@canvas, 0, 50)
|
@@ -714,7 +707,7 @@ describe HexaPDF::Layout::TableBox do
|
|
714
707
|
box = create_box(header: lambda {|_| [@fixed_size_boxes[10, 1]] },
|
715
708
|
footer: lambda {|_| [@fixed_size_boxes[12, 1]] },
|
716
709
|
cell_style: {padding: 0, border: {width: 0}})
|
717
|
-
box.fit(100, 100, @frame)
|
710
|
+
assert(box.fit(100, 100, @frame).success?)
|
718
711
|
box.draw(@canvas, 20, 10)
|
719
712
|
operators = [[:save_graphics_state],
|
720
713
|
[:concatenate_matrix, [1, 0, 0, 1, 20, 40]],
|
@@ -36,39 +36,40 @@ 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
|
-
box = create_box([@inline_box], width:
|
46
|
-
|
47
|
-
|
45
|
+
box = create_box([@inline_box] * 5, width: 44, height: 50,
|
46
|
+
style: {padding: 10, text_align: :right, text_valign: :bottom})
|
47
|
+
assert(box.fit(100, 100, @frame).success?)
|
48
|
+
assert_equal(44, box.width)
|
48
49
|
assert_equal(50, box.height)
|
49
|
-
assert_equal([10], box.instance_variable_get(:@result).lines.map(&:width))
|
50
|
+
assert_equal([20, 20, 10], box.instance_variable_get(:@result).lines.map(&:width))
|
50
51
|
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
53
|
+
describe "style option last_line_gap" do
|
54
|
+
it "is taken into account" do
|
55
|
+
box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
|
56
|
+
assert(box.fit(100, 100, @frame).success?)
|
57
|
+
assert_equal(50, box.width)
|
58
|
+
assert_equal(20, box.height)
|
59
|
+
end
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
it "will have no effect for fixed-height boxes" do
|
62
|
+
box = create_box([@inline_box] * 5, height: 40, style: {last_line_gap: true, line_spacing: :double})
|
63
|
+
assert(box.fit(100, 100, @frame).success?)
|
64
|
+
assert_equal(50, box.width)
|
65
|
+
assert_equal(40, box.height)
|
66
|
+
end
|
66
67
|
end
|
67
68
|
|
68
|
-
it "uses the whole available width when aligning to the center or
|
69
|
-
[:center, :right].each do |align|
|
69
|
+
it "uses the whole available width when aligning to the center, right or justified" do
|
70
|
+
[:center, :right, :justify].each do |align|
|
70
71
|
box = create_box([@inline_box], style: {text_align: align})
|
71
|
-
assert(box.fit(100, 100, @frame))
|
72
|
+
assert(box.fit(100, 100, @frame).success?)
|
72
73
|
assert_equal(100, box.width)
|
73
74
|
end
|
74
75
|
end
|
@@ -76,91 +77,89 @@ describe HexaPDF::Layout::TextBox do
|
|
76
77
|
it "uses the whole available height when vertically aligning to the center or bottom" do
|
77
78
|
[:center, :bottom].each do |valign|
|
78
79
|
box = create_box([@inline_box], style: {text_valign: valign})
|
79
|
-
assert(box.fit(100, 100, @frame))
|
80
|
+
assert(box.fit(100, 100, @frame).success?)
|
80
81
|
assert_equal(100, box.height)
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
84
|
-
it "
|
85
|
+
it "can fit part of the box" do
|
85
86
|
box = create_box([@inline_box] * 20, height: 15)
|
86
|
-
|
87
|
-
box.style.overflow = :truncate
|
88
|
-
assert(box.fit(100, 100, @frame))
|
89
|
-
|
90
|
-
box = create_box([@inline_box] * 20, style: {overflow: :truncate})
|
91
|
-
refute(box.fit(100, 15, @frame))
|
92
|
-
end
|
93
|
-
|
94
|
-
it "can't fit the text box if the set width is bigger than the available width" do
|
95
|
-
box = create_box([@inline_box], width: 101)
|
96
|
-
refute(box.fit(100, 100, @frame))
|
87
|
+
assert(box.fit(100, 100, @frame).overflow?)
|
97
88
|
end
|
98
89
|
|
99
|
-
it "
|
100
|
-
|
101
|
-
|
90
|
+
it "correctly handles text indentation for split boxes" do
|
91
|
+
[{}, {position: :flow}].each do |styles|
|
92
|
+
box = create_box([@inline_box] * 202, style: {text_indent: 50, **styles})
|
93
|
+
assert(box.fit(100, 100, @frame).overflow?)
|
94
|
+
_, box_b = box.split
|
95
|
+
assert_equal(107, box_b.instance_variable_get(:@items).length)
|
96
|
+
assert(box_b.fit(100, 100, @frame).overflow?)
|
97
|
+
_, box_b = box_b.split
|
98
|
+
assert_equal(7, box_b.instance_variable_get(:@items).length)
|
99
|
+
end
|
102
100
|
end
|
103
|
-
end
|
104
101
|
|
105
|
-
|
106
|
-
it "works for an empty text box" do
|
102
|
+
it "fits an empty text box" do
|
107
103
|
box = create_box([])
|
108
|
-
|
104
|
+
assert(box.fit(100, 100, @frame).success?)
|
109
105
|
end
|
110
106
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
107
|
+
describe "position :flow" do
|
108
|
+
it "fits into the frame's outline" do
|
109
|
+
@frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
|
110
|
+
@frame.remove_area(Geom2D::Rectangle(80, 70, 20, 20))
|
111
|
+
box = create_box([@inline_box] * 20, style: {position: :flow})
|
112
|
+
assert(box.fit(100, 100, @frame).success?)
|
113
|
+
assert_equal(100, box.width)
|
114
|
+
assert_equal(30, box.height)
|
115
|
+
end
|
115
116
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
it "respects a set initial height" do
|
118
|
+
box = create_box([@inline_box] * 20, height: 13, style: {position: :flow})
|
119
|
+
assert(box.fit(100, 100, @frame).overflow?)
|
120
|
+
assert_equal(100, box.width)
|
121
|
+
assert_equal(13, box.height)
|
122
|
+
end
|
120
123
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
+
it "respects top/bottom padding/border" do
|
125
|
+
@frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
|
126
|
+
box = create_box([@inline_box] * 20, style: {position: :flow, padding: 10, border: {width: 2}})
|
127
|
+
assert(box.fit(100, 100, @frame).success?)
|
128
|
+
assert_equal(124, box.width)
|
129
|
+
assert_equal(54, box.height)
|
130
|
+
assert_equal([80, 100, 20], box.instance_variable_get(:@result).lines.map(&:width))
|
131
|
+
end
|
132
|
+
end
|
124
133
|
|
125
|
-
|
126
|
-
|
134
|
+
it "fails if no item of the text box fits due to the width" do
|
135
|
+
box = create_box([@inline_box])
|
136
|
+
assert(box.fit(5, 20, @frame).failure?)
|
127
137
|
end
|
128
138
|
|
129
|
-
it "
|
130
|
-
box = create_box([@inline_box]
|
131
|
-
box.fit(
|
132
|
-
box.instance_variable_set(:@width, 50.00000000006)
|
133
|
-
box.instance_variable_set(:@height, 10.00000000003)
|
134
|
-
assert_equal([box], box.split(50, 10, @frame))
|
139
|
+
it "fails if no item of the text box fits due to the height" do
|
140
|
+
box = create_box([@inline_box])
|
141
|
+
assert(box.fit(20, 5, @frame).failure?)
|
135
142
|
end
|
143
|
+
end
|
136
144
|
|
145
|
+
describe "split" do
|
137
146
|
it "splits the box if necessary when using non-flowing text" do
|
138
147
|
box = create_box([@inline_box] * 10)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
refute(
|
143
|
-
assert(
|
144
|
-
assert_equal(5,
|
148
|
+
box.fit(50, 10, @frame)
|
149
|
+
box_a, box_b = box.split
|
150
|
+
assert_same(box, box_a)
|
151
|
+
refute(box_a.split_box?)
|
152
|
+
assert(box_b.split_box?)
|
153
|
+
assert_equal(5, box_b.instance_variable_get(:@items).length)
|
145
154
|
end
|
146
155
|
|
147
156
|
it "splits the box if necessary when using flowing text that results in a wider box" do
|
148
157
|
@frame.remove_area(Geom2D::Polygon.new([[0, 100], [50, 100], [50, 10], [0, 10]]))
|
149
158
|
box = create_box([@inline_box] * 60, style: {position: :flow})
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
assert_equal(5,
|
154
|
-
end
|
155
|
-
|
156
|
-
it "correctly handles text indentation for split boxes" do
|
157
|
-
[{}, {position: :flow}].each do |styles|
|
158
|
-
box = create_box([@inline_box] * 202, style: {text_indent: 50, **styles})
|
159
|
-
boxes = box.split(100, 100, @frame)
|
160
|
-
assert_equal(107, boxes[1].instance_variable_get(:@items).length)
|
161
|
-
boxes = boxes[1].split(100, 100, @frame)
|
162
|
-
assert_equal(7, boxes[1].instance_variable_get(:@items).length)
|
163
|
-
end
|
159
|
+
box.fit(50, 100, @frame)
|
160
|
+
box_a, box_b = box.split
|
161
|
+
assert_same(box, box_a)
|
162
|
+
assert_equal(5, box_b.instance_variable_get(:@items).length)
|
164
163
|
end
|
165
164
|
end
|
166
165
|
|
@@ -196,12 +195,12 @@ describe HexaPDF::Layout::TextBox do
|
|
196
195
|
@frame.remove_area(Geom2D::Rectangle(0, 0, 40, 100))
|
197
196
|
box = create_box([@inline_box], style: {position: :flow, border: {width: 1}})
|
198
197
|
box.fit(60, 100, @frame)
|
199
|
-
box.draw(@canvas, 0,
|
198
|
+
box.draw(@canvas, 0, 88)
|
200
199
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
201
|
-
[:append_rectangle, [40,
|
200
|
+
[:append_rectangle, [40, 88, 12, 12]],
|
202
201
|
[:clip_path_non_zero],
|
203
202
|
[:end_path],
|
204
|
-
[:append_rectangle, [40.5,
|
203
|
+
[:append_rectangle, [40.5, 88.5, 11.0, 11.0]],
|
205
204
|
[:stroke_path],
|
206
205
|
[:restore_graphics_state],
|
207
206
|
[:save_graphics_state],
|
@@ -218,6 +217,7 @@ describe HexaPDF::Layout::TextBox do
|
|
218
217
|
|
219
218
|
it "draws nothing onto the canvas if the box is empty" do
|
220
219
|
box = create_box([])
|
220
|
+
box.fit(100, 100, @frame)
|
221
221
|
box.draw(@canvas, 5, 5)
|
222
222
|
assert_operators(@canvas.contents, [])
|
223
223
|
end
|
@@ -119,6 +119,13 @@ describe HexaPDF::Composer do
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
+
describe "styles" do
|
123
|
+
it "delegates to layout.styles" do
|
124
|
+
@composer.styles(base: {font_size: 30}, other: {font_size: 40})
|
125
|
+
assert_equal([:base, :other], @composer.document.layout.styles.keys)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
122
129
|
describe "page_style" do
|
123
130
|
it "returns the page style if no argument or block is given" do
|
124
131
|
page_style = @composer.page_style(:default)
|
@@ -225,8 +232,9 @@ describe HexaPDF::Composer do
|
|
225
232
|
first_page_contents = @composer.canvas.contents
|
226
233
|
@composer.draw_box(create_box(height: 400))
|
227
234
|
|
228
|
-
box = create_box
|
229
|
-
box.define_singleton_method(:
|
235
|
+
box = create_box
|
236
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
237
|
+
box.define_singleton_method(:split_content) do |*|
|
230
238
|
[box, HexaPDF::Layout::Box.new(height: 100) {}]
|
231
239
|
end
|
232
240
|
@composer.draw_box(box)
|
@@ -235,7 +243,7 @@ describe HexaPDF::Composer do
|
|
235
243
|
[:concatenate_matrix, [1, 0, 0, 1, 36, 405.889764]],
|
236
244
|
[:restore_graphics_state],
|
237
245
|
[:save_graphics_state],
|
238
|
-
[:concatenate_matrix, [1, 0, 0, 1, 36,
|
246
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
|
239
247
|
[:restore_graphics_state]])
|
240
248
|
assert_operators(@composer.canvas.contents,
|
241
249
|
[[:save_graphics_state],
|
@@ -263,13 +271,20 @@ describe HexaPDF::Composer do
|
|
263
271
|
[:restore_graphics_state]])
|
264
272
|
end
|
265
273
|
|
274
|
+
it "handles truncated boxes correctly" do
|
275
|
+
box = create_box(height: 400, style: {overflow: :truncate})
|
276
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
277
|
+
assert_same(box, @composer.draw_box(box))
|
278
|
+
end
|
279
|
+
|
266
280
|
it "returns the last drawn box" do
|
267
281
|
box = create_box(height: 400)
|
268
282
|
assert_same(box, @composer.draw_box(box))
|
269
283
|
|
270
|
-
box = create_box(height: 400)
|
271
284
|
split_box = create_box(height: 100)
|
272
|
-
box
|
285
|
+
box = create_box
|
286
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
287
|
+
box.define_singleton_method(:split_content) {|*| [box, split_box] }
|
273
288
|
assert_same(split_box, @composer.draw_box(box))
|
274
289
|
end
|
275
290
|
|
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
|
@@ -89,6 +89,7 @@ describe HexaPDF::Serializer do
|
|
89
89
|
assert_serialized('/ ', :"")
|
90
90
|
assert_serialized('/H#c3#b6#c3#9fgang', :Hößgang)
|
91
91
|
assert_serialized('/H#e8lp', "H\xE8lp".force_encoding('BINARY').intern)
|
92
|
+
assert_serialized('/#00#09#0a#0c#0d#20', :"\x00\t\n\f\r ")
|
92
93
|
end
|
93
94
|
|
94
95
|
it "serializes arrays" do
|
@@ -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.45.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-
|
11
|
+
date: 2024-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|