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