hexapdf 0.43.0 → 0.45.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/examples/027-composer_optional_content.rb +6 -4
  4. data/examples/030-pdfa.rb +13 -11
  5. data/lib/hexapdf/composer.rb +23 -0
  6. data/lib/hexapdf/content/canvas.rb +3 -3
  7. data/lib/hexapdf/content/canvas_composer.rb +1 -0
  8. data/lib/hexapdf/document/files.rb +7 -2
  9. data/lib/hexapdf/document/layout.rb +15 -3
  10. data/lib/hexapdf/document/metadata.rb +12 -1
  11. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  12. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  13. data/lib/hexapdf/layout/box.rb +180 -66
  14. data/lib/hexapdf/layout/box_fitter.rb +1 -0
  15. data/lib/hexapdf/layout/column_box.rb +18 -28
  16. data/lib/hexapdf/layout/container_box.rb +6 -6
  17. data/lib/hexapdf/layout/frame.rb +13 -94
  18. data/lib/hexapdf/layout/image_box.rb +4 -4
  19. data/lib/hexapdf/layout/list_box.rb +13 -31
  20. data/lib/hexapdf/layout/style.rb +8 -4
  21. data/lib/hexapdf/layout/table_box.rb +55 -58
  22. data/lib/hexapdf/layout/text_box.rb +84 -71
  23. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  24. data/lib/hexapdf/layout/text_layouter.rb +7 -8
  25. data/lib/hexapdf/parser.rb +5 -2
  26. data/lib/hexapdf/rectangle.rb +4 -4
  27. data/lib/hexapdf/type/file_specification.rb +9 -5
  28. data/lib/hexapdf/type/form.rb +2 -2
  29. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  30. data/lib/hexapdf/version.rb +1 -1
  31. data/test/hexapdf/content/test_canvas_composer.rb +13 -8
  32. data/test/hexapdf/document/test_files.rb +5 -0
  33. data/test/hexapdf/document/test_layout.rb +16 -0
  34. data/test/hexapdf/document/test_metadata.rb +21 -0
  35. data/test/hexapdf/layout/test_box.rb +93 -37
  36. data/test/hexapdf/layout/test_box_fitter.rb +7 -0
  37. data/test/hexapdf/layout/test_column_box.rb +7 -13
  38. data/test/hexapdf/layout/test_container_box.rb +1 -1
  39. data/test/hexapdf/layout/test_frame.rb +7 -46
  40. data/test/hexapdf/layout/test_image_box.rb +14 -6
  41. data/test/hexapdf/layout/test_list_box.rb +26 -27
  42. data/test/hexapdf/layout/test_table_box.rb +47 -54
  43. data/test/hexapdf/layout/test_text_box.rb +83 -83
  44. data/test/hexapdf/test_composer.rb +20 -5
  45. data/test/hexapdf/test_parser.rb +8 -0
  46. data/test/hexapdf/test_serializer.rb +1 -0
  47. data/test/hexapdf/type/test_file_specification.rb +2 -1
  48. 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 anything if the available width or height are too small" do
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
- refute(cell.fit(10, 100, @frame))
122
- refute(cell.fit(100, 10, @frame))
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, does_fit, width, height, cell_data = nil)
374
- assert(does_fit == box.fit(@frame.available_width, @frame.available_height, @frame), "box didn't fit")
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
- refute(box.fit(100, 40, @frame))
435
- box_a, box_b = box.split(100, 40, nil)
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 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
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
- refute(box.fit(@frame.available_width, @frame.available_height, @frame))
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, true, 160, 45,
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), true, 160, 89,
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, true, 160, 40, result)
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, true, 160, 40, result)
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, true, 160, 60, result)
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, false, 160, 10,
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
- refute(box.fit(100, 25, @frame))
554
- box_a, box_b = box.split(100, 25, @frame)
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
- refute(box.fit(100, 100, @frame))
571
- box_a, box_b = box.split(100, 100, @frame)
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
- refute(box_b.fit(100, 100, @frame))
580
- box_c, box_d = box_b.split(100, 100, @frame)
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
- refute(box.fit(100, 50, @frame))
605
- box_a, box_b = box.split(100, 50, @frame)
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
- refute(box.fit(100, 10, @frame))
685
- _, split_box = box.split(100, 10, @frame)
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: 40, height: 50, style: {padding: 10})
46
- assert(box.fit(100, 100, @frame))
47
- assert_equal(40, box.width)
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
- 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))
55
- box = create_box([@inline_box] * 20, style: {position: :flow})
56
- assert(box.fit(100, 100, @frame))
57
- assert_equal(100, box.width)
58
- assert_equal(30, box.height)
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
- it "takes the style option last_line_gap into account" do
62
- box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
63
- assert(box.fit(100, 100, @frame))
64
- assert_equal(50, box.width)
65
- assert_equal(20, box.height)
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 right" do
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 "respects the style property overflow when fitting too much text" do
85
+ it "can fit part of the box" do
85
86
  box = create_box([@inline_box] * 20, height: 15)
86
- refute(box.fit(100, 100, @frame))
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 "can't fit the text box if the set height is bigger than the available height" do
100
- box = create_box([@inline_box], height: 101)
101
- refute(box.fit(100, 100, @frame))
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
- describe "split" do
106
- it "works for an empty text box" do
102
+ it "fits an empty text box" do
107
103
  box = create_box([])
108
- assert_equal([box], box.split(100, 100, @frame))
104
+ assert(box.fit(100, 100, @frame).success?)
109
105
  end
110
106
 
111
- it "doesn't need to split the box if it completely fits" do
112
- box = create_box([@inline_box] * 5)
113
- assert_equal([box], box.split(50, 100, @frame))
114
- end
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
- it "works if no item of the text box fits" do
117
- box = create_box([@inline_box])
118
- assert_equal([nil, box], box.split(5, 20, @frame))
119
- end
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
- it "works if the whole text box doesn't fits" do
122
- box = create_box([@inline_box], width: 102)
123
- assert_equal([nil, box], box.split(100, 100, @frame))
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
- box = create_box([@inline_box], height: 102)
126
- assert_equal([nil, box], box.split(100, 100, @frame))
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 "works if the box fits exactly (+/- float divergence)" do
130
- box = create_box([@inline_box] * 5)
131
- box.fit(50, 10, @frame)
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
- boxes = box.split(50, 10, @frame)
140
- assert_equal(2, boxes.length)
141
- assert_equal(box, boxes[0])
142
- refute(boxes[0].split_box?)
143
- assert(boxes[1].split_box?)
144
- assert_equal(5, boxes[1].instance_variable_get(:@items).length)
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
- boxes = box.split(50, 100, @frame)
151
- assert_equal(2, boxes.length)
152
- assert_equal(box, boxes[0])
153
- assert_equal(5, boxes[1].instance_variable_get(:@items).length)
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, 90)
198
+ box.draw(@canvas, 0, 88)
200
199
  assert_operators(@canvas.contents, [[:save_graphics_state],
201
- [:append_rectangle, [40, 90, 10, 10]],
200
+ [:append_rectangle, [40, 88, 12, 12]],
202
201
  [:clip_path_non_zero],
203
202
  [:end_path],
204
- [:append_rectangle, [40.5, 90.5, 9.0, 9.0]],
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(height: 400)
229
- box.define_singleton_method(:split) do |*|
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, 5.889764]],
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.define_singleton_method(:split) {|*| [box, split_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
 
@@ -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.43.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-05-26 00:00:00.000000000 Z
11
+ date: 2024-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse