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
@@ -4,11 +4,45 @@ require 'test_helper'
4
4
  require 'hexapdf/document'
5
5
  require 'hexapdf/layout/box'
6
6
 
7
+ describe HexaPDF::Layout::Box::FitResult do
8
+ it "shows the box's mask area on #draw when using debug output" do
9
+ doc = HexaPDF::Document.new(config: {'debug' => true})
10
+ canvas = doc.pages.add.canvas
11
+ box = HexaPDF::Layout::Box.create(width: 20, height: 20) {}
12
+ frame = HexaPDF::Layout::Frame.new(5, 10, 100, 150)
13
+ result = HexaPDF::Layout::Box::FitResult.new(box, frame: frame)
14
+ result.mask = Geom2D::Rectangle(0, 0, 20, 20)
15
+ result.x = result.y = 0
16
+ result.draw(canvas, dx: 10, dy: 15)
17
+ assert_equal(<<~CONTENTS, canvas.contents)
18
+ /OC /P1 BDC
19
+ q
20
+ 1 0 0 1 10 15 cm
21
+ 0.0 0.501961 0.0 rg
22
+ 0.0 0.392157 0.0 RG
23
+ /GS1 gs
24
+ 0 0 20 20 re
25
+ B
26
+ Q
27
+ EMC
28
+ q
29
+ 1 0 0 1 10 15 cm
30
+ Q
31
+ CONTENTS
32
+ ocg = doc.optional_content.ocgs.first
33
+ assert_equal([['Debug', ['Page 1', ocg]]], doc.optional_content.default_configuration[:Order])
34
+ assert_match(/10,15-20x20/, ocg.name)
35
+ end
36
+ end
37
+
7
38
  describe HexaPDF::Layout::Box do
8
39
  before do
9
40
  @frame = Object.new
10
41
  def @frame.x; 0; end
11
42
  def @frame.y; 100; end
43
+ def @frame.bottom; 40; end
44
+ def @frame.width; 150; end
45
+ def @frame.height; 150; end
12
46
  end
13
47
 
14
48
  def create_box(**args, &block)
@@ -78,7 +112,7 @@ describe HexaPDF::Layout::Box do
78
112
  describe "fit" do
79
113
  it "fits a fixed sized box" do
80
114
  box = create_box(width: 50, height: 50, style: {padding: 5})
81
- assert(box.fit(100, 100, @frame))
115
+ assert(box.fit(100, 100, @frame).success?)
82
116
  assert_equal(50, box.width)
83
117
  assert_equal(50, box.height)
84
118
  assert_equal(5, box.instance_variable_get(:@fit_x))
@@ -87,28 +121,53 @@ describe HexaPDF::Layout::Box do
87
121
 
88
122
  it "uses the maximum available width" do
89
123
  box = create_box(height: 50)
90
- assert(box.fit(100, 100, @frame))
124
+ assert(box.fit(100, 100, @frame).success?)
91
125
  assert_equal(100, box.width)
92
126
  assert_equal(50, box.height)
93
127
  end
94
128
 
95
129
  it "uses the maximum available height" do
96
130
  box = create_box(width: 50)
97
- assert(box.fit(100, 100, @frame))
131
+ assert(box.fit(100, 100, @frame).success?)
98
132
  assert_equal(50, box.width)
99
133
  assert_equal(100, box.height)
100
134
  end
101
135
 
136
+ it "use the frame's width and its remaining height for position=:flow boxes" do
137
+ box = create_box(style: {position: :flow})
138
+ box.define_singleton_method(:supports_position_flow?) { true }
139
+ assert(box.fit(100, 100, @frame).success?)
140
+ assert_equal(150, box.width)
141
+ assert_equal(60, box.height)
142
+ end
143
+
102
144
  it "uses float comparison" do
103
145
  box = create_box(width: 50.0000002, height: 49.9999996)
104
- assert(box.fit(50, 50, @frame))
146
+ assert(box.fit(50, 50, @frame).success?)
105
147
  assert_equal(50.0000002, box.width)
106
148
  assert_equal(49.9999996, box.height)
107
149
  end
108
150
 
109
- it "returns false if the box doesn't fit" do
151
+ it "fails if the box doesn't fit, position != :flow and its width is greater than the available width" do
110
152
  box = create_box(width: 101)
111
- refute(box.fit(100, 100, @frame))
153
+ assert(box.fit(100, 100, @frame).failure?)
154
+ end
155
+
156
+ it "fails if the box doesn't fit, position != :flow and its width is greater than the available width" do
157
+ box = create_box(height: 101)
158
+ assert(box.fit(100, 100, @frame).failure?)
159
+ end
160
+
161
+ it "fails if its content width is zero" do
162
+ box = create_box(height: 100)
163
+ box.style.padding = [0, 100]
164
+ assert(box.fit(150, 150, @frame).failure?)
165
+ end
166
+
167
+ it "fails if its content height is zero" do
168
+ box = create_box(width: 100)
169
+ box.style.padding = [100, 0]
170
+ assert(box.fit(150, 150, @frame).failure?)
112
171
  end
113
172
 
114
173
  it "can use the #content_width/#content_height helper methods" do
@@ -116,9 +175,9 @@ describe HexaPDF::Layout::Box do
116
175
  box.define_singleton_method(:fit_content) do |_aw, _ah, _frame|
117
176
  update_content_width { 10 }
118
177
  update_content_height { 20 }
119
- true
178
+ fit_result.success!
120
179
  end
121
- assert(box.fit(100, 100, @frame))
180
+ assert(box.fit(100, 100, @frame).success?)
122
181
  assert_equal(10, box.width)
123
182
  assert_equal(20, box.height)
124
183
 
@@ -126,49 +185,40 @@ describe HexaPDF::Layout::Box do
126
185
  box.define_singleton_method(:fit_content) do |_aw, _ah, _frame|
127
186
  update_content_width { 10 }
128
187
  update_content_height { 20 }
129
- true
188
+ fit_result.success!
130
189
  end
131
- assert(box.fit(100, 100, @frame))
190
+ assert(box.fit(100, 100, @frame).success?)
132
191
  assert_equal(30, box.width)
133
192
  assert_equal(50, box.height)
134
193
  end
135
194
  end
136
195
 
137
196
  describe "split" do
138
- before do
139
- @box = create_box(width: 100, height: 100)
140
- @box.fit(100, 100, @frame)
141
- end
142
-
143
197
  it "doesn't need to be split if it completely fits" do
144
- assert_equal([@box, nil], @box.split(100, 100, @frame))
198
+ box = create_box(width: 100, height: 100)
199
+ box.fit(100, 100, @frame)
200
+ assert_equal([box, nil], box.split)
145
201
  end
146
202
 
147
- it "can't be split if it doesn't (completely) fit and its width is greater than the available width" do
148
- @box.fit(90, 100, nil)
149
- assert_equal([nil, @box], @box.split(50, 150, @frame))
203
+ it "is not split if no part of it fits" do
204
+ box = create_box(width: 150)
205
+ box.fit(100, 100, @frame)
206
+ assert_equal([nil, box], box.split)
150
207
  end
151
208
 
152
- it "can't be split if it doesn't (completely) fit and its height is greater than the available height" do
153
- @box.fit(90, 100, nil)
154
- assert_equal([nil, @box], @box.split(150, 50, @frame))
155
- end
156
-
157
- it "can't be split if it doesn't (completely) fit and its content width is zero" do
158
- box = create_box(width: 0, height: 100)
159
- assert_equal([nil, box], box.split(150, 150, @frame))
160
- end
161
-
162
- it "can't be split if it doesn't (completely) fit and its content height is zero" do
163
- box = create_box(width: 100, height: 0)
164
- assert_equal([nil, box], box.split(150, 150, @frame))
209
+ it "is not split if a height for the box is specified and it doesn't completely fit" do
210
+ box = create_box(height: 50)
211
+ box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
212
+ box.fit(100, 100, @frame)
213
+ assert_equal([box, nil], box.split)
165
214
  end
166
215
 
167
- it "can't be split if it doesn't (completely) fit as the default implementation " \
216
+ it "can't be split if it doesn't completely fit as the default implementation " \
168
217
  "knows nothing about the content" do
169
- @box.style.position = :flow # make sure we would generally be splitable
170
- @box.fit(90, 100, nil)
171
- assert_equal([nil, @box], @box.split(150, 150, @frame))
218
+ box = create_box
219
+ box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
220
+ box.fit(100, 100, @frame)
221
+ assert_equal([nil, box], box.split)
172
222
  end
173
223
  end
174
224
 
@@ -177,7 +227,7 @@ describe HexaPDF::Layout::Box do
177
227
  box.fit(100, 100, @frame)
178
228
  cloned_box = box.send(:create_split_box)
179
229
  assert(cloned_box.split_box?)
180
- refute(cloned_box.instance_variable_get(:@fit_successful))
230
+ refute_same(box.fit_result, cloned_box.fit_result)
181
231
  assert_equal(0, cloned_box.width)
182
232
  assert_equal(0, cloned_box.height)
183
233
  end
@@ -240,6 +290,12 @@ describe HexaPDF::Layout::Box do
240
290
  refute(box.style.overlays?)
241
291
  end
242
292
 
293
+ it "raises an error if the style property :overflow is set to error and the box doesn't completely fit" do
294
+ box = create_box(height: 50, style: {overflow: :error})
295
+ box.fit_result.overflow!
296
+ assert_raises(HexaPDF::Error) { box.draw(@canvas, 0, 0) }
297
+ end
298
+
243
299
  it "wraps the box in optional content markers if the optional_content property is set" do
244
300
  box = create_box(properties: {'optional_content' => 'Text'})
245
301
  box.draw(@canvas, 0, 0)
@@ -50,6 +50,13 @@ describe HexaPDF::Layout::BoxFitter do
50
50
  check_result(10, 20, 0, 0, 100, 130, 100, 110, content_heights: [90, 40])
51
51
  end
52
52
 
53
+ it "correctly handles truncated boxes" do
54
+ box = HexaPDF::Layout::Box.new(height: 50) {}
55
+ box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
56
+ @box_fitter.fit(box)
57
+ check_result(10, 40, content_heights: [50, 0])
58
+ end
59
+
53
60
  it "fails when some boxes can't be fitted" do
54
61
  fit_box(9)
55
62
  fit_box(70)
@@ -22,7 +22,7 @@ describe HexaPDF::Layout::ColumnBox do
22
22
  end
23
23
 
24
24
  def check_box(box, width, height, fit_pos = nil)
25
- assert(box.fit(@frame.available_width, @frame.available_height, @frame), "box didn't fit")
25
+ assert(box.fit(@frame.available_width, @frame.available_height, @frame).success?, "box didn't fit")
26
26
  assert_equal(width, box.width, "box width")
27
27
  assert_equal(height, box.height, "box height")
28
28
  if fit_pos
@@ -73,9 +73,6 @@ describe HexaPDF::Layout::ColumnBox do
73
73
  box = create_box(columns: 1, children: @fixed_size_boxes[0..0], width: 50,
74
74
  style: {position: position})
75
75
  check_box(box, 50, 10)
76
-
77
- box = create_box(children: @fixed_size_boxes[0..0], width: 110)
78
- refute(box.fit(@frame.available_width, @frame.available_height, @frame))
79
76
  end
80
77
 
81
78
  it "respects the set initial height, position #{position}" do
@@ -86,9 +83,6 @@ describe HexaPDF::Layout::ColumnBox do
86
83
  box = create_box(children: @text_boxes[0..1], height: 50, equal_height: true,
87
84
  style: {position: position})
88
85
  check_box(box, 100, 50)
89
-
90
- box = create_box(children: @fixed_size_boxes[0..0], height: 110)
91
- refute(box.fit(@frame.available_width, @frame.available_height, @frame))
92
86
  end
93
87
 
94
88
  it "respects the border and padding around all columns, position #{position}" do
@@ -150,15 +144,15 @@ describe HexaPDF::Layout::ColumnBox do
150
144
 
151
145
  it "fails if the necessary width is larger than the available one" do
152
146
  box = create_box(children: @fixed_size_boxes[0..2], columns: 4, gaps: [40])
153
- refute(box.fit(100, 100, @frame))
147
+ assert(box.fit(100, 100, @frame).failure?)
154
148
  end
155
149
  end
156
150
  end
157
151
 
158
152
  it "splits the children if they are too big to fill the colums" do
159
- box = create_box(children: @fixed_size_boxes, width: 50, height: 50)
160
- box.fit(100, 100, @frame)
161
- box_a, box_b = box.split(100, 100, @frame)
153
+ box = create_box(children: @fixed_size_boxes, width: 50)
154
+ assert(box.fit(100, 50, @frame).overflow?)
155
+ box_a, box_b = box.split
162
156
  assert_same(box, box_a)
163
157
  assert(box_b.split_box?)
164
158
  assert_equal(5, box_b.children.size)
@@ -171,7 +165,7 @@ describe HexaPDF::Layout::ColumnBox do
171
165
 
172
166
  it "draws the result onto the canvas" do
173
167
  box = create_box(children: @fixed_size_boxes)
174
- box.fit(100, 100, @frame)
168
+ assert(box.fit(100, 100, @frame).success?)
175
169
  box.draw(@canvas, 0, 100 - box.height)
176
170
  operators = 90.step(to: 20, by: -10).map do |y|
177
171
  [[:save_graphics_state],
@@ -193,7 +187,7 @@ describe HexaPDF::Layout::ColumnBox do
193
187
 
194
188
  it "takes a different final location into account" do
195
189
  box = create_box(children: @fixed_size_boxes[0, 2], style: {padding: [2, 4, 6, 8]})
196
- box.fit(100, 100, @frame)
190
+ assert(box.fit(100, 100, @frame).success?)
197
191
  box.draw(@canvas, 20, 10)
198
192
  operators = [
199
193
  [:save_graphics_state],
@@ -15,7 +15,7 @@ describe HexaPDF::Layout::ContainerBox do
15
15
  end
16
16
 
17
17
  def check_box(box, width, height, fit_pos = nil)
18
- assert(box.fit(@frame.available_width, @frame.available_height, @frame), "box didn't fit")
18
+ assert(box.fit(@frame.available_width, @frame.available_height, @frame).success?, "box didn't fit")
19
19
  assert_equal(width, box.width, "box width")
20
20
  assert_equal(height, box.height, "box height")
21
21
  if fit_pos
@@ -5,37 +5,6 @@ require 'hexapdf/layout/frame'
5
5
  require 'hexapdf/layout/box'
6
6
  require 'hexapdf/document'
7
7
 
8
- describe HexaPDF::Layout::Frame::FitResult do
9
- it "shows the box's mask area on #draw when using debug output" do
10
- doc = HexaPDF::Document.new(config: {'debug' => true})
11
- canvas = doc.pages.add.canvas
12
- box = HexaPDF::Layout::Box.create(width: 20, height: 20) {}
13
- frame = HexaPDF::Layout::Frame.new(5, 10, 100, 150)
14
- result = HexaPDF::Layout::Frame::FitResult.new(frame, box)
15
- result.mask = Geom2D::Rectangle(0, 0, 20, 20)
16
- result.x = result.y = 0
17
- result.draw(canvas, dx: 10, dy: 15)
18
- assert_equal(<<~CONTENTS, canvas.contents)
19
- /OC /P1 BDC
20
- q
21
- 1 0 0 1 10 15 cm
22
- 0.0 0.501961 0.0 rg
23
- 0.0 0.392157 0.0 RG
24
- /GS1 gs
25
- 0 0 20 20 re
26
- B
27
- Q
28
- EMC
29
- q
30
- 1 0 0 1 10 15 cm
31
- Q
32
- CONTENTS
33
- ocg = doc.optional_content.ocgs.first
34
- assert_equal([['Debug', ['Page 1', ocg]]], doc.optional_content.default_configuration[:Order])
35
- assert_match(/10,15-20x20/, ocg.name)
36
- end
37
- end
38
-
39
8
  describe HexaPDF::Layout::Frame do
40
9
  before do
41
10
  @frame = HexaPDF::Layout::Frame.new(5, 10, 100, 150)
@@ -455,21 +424,13 @@ describe HexaPDF::Layout::Frame do
455
424
  refute(@frame.fit(box).success?)
456
425
  end
457
426
 
458
- it "handles (but doesn't draw) the box if the its height or width is zero" do
459
- result = Minitest::Mock.new
460
- box = Minitest::Mock.new
461
-
462
- result.expect(:box, box)
463
- box.expect(:height, 0)
464
- @frame.draw(@canvas, result)
465
-
466
- result.expect(:box, box)
467
- box.expect(:height, 5)
468
- result.expect(:box, box)
469
- box.expect(:width, 0)
470
- @frame.draw(@canvas, result)
471
-
472
- result.verify
427
+ it "doesn't do post-fitting tasks if fitting is a failure" do
428
+ box = HexaPDF::Layout::Box.create(width: 400)
429
+ result = @frame.fit(box)
430
+ assert(result.failure?)
431
+ assert_nil(result.x)
432
+ assert_nil(result.y)
433
+ assert_nil(result.mask)
473
434
  end
474
435
  end
475
436
 
@@ -9,6 +9,9 @@ describe HexaPDF::Layout::ImageBox do
9
9
  @image = HexaPDF::Stream.new({Subtype: :Image}, stream: '')
10
10
  @image.define_singleton_method(:width) { 40 }
11
11
  @image.define_singleton_method(:height) { 20 }
12
+ @frame = Object.new
13
+ def @frame.x; 0; end
14
+ def @frame.y; 100; end
12
15
  end
13
16
 
14
17
  def create_box(**kwargs)
@@ -25,35 +28,40 @@ describe HexaPDF::Layout::ImageBox do
25
28
  describe "fit" do
26
29
  it "fits with fixed dimensions" do
27
30
  box = create_box(width: 50, height: 30, style: {padding: [10, 4, 6, 2]})
28
- assert(box.fit(100, 100, nil))
31
+ assert(box.fit(100, 100, @frame).success?)
29
32
  assert_equal(50, box.width)
30
33
  assert_equal(30, box.height)
31
34
  end
32
35
 
33
36
  it "fits with a fixed width" do
34
37
  box = create_box(width: 60, style: {padding: [10, 4, 6, 2]})
35
- assert(box.fit(100, 100, nil))
38
+ assert(box.fit(100, 100, @frame).success?)
36
39
  assert_equal(60, box.width)
37
40
  assert_equal(43, box.height)
38
41
  end
39
42
 
40
43
  it "fits with a fixed height" do
41
44
  box = create_box(height: 40, style: {padding: [10, 4, 6, 2]})
42
- assert(box.fit(100, 100, nil))
45
+ assert(box.fit(100, 100, @frame).success?)
43
46
  assert_equal(54, box.width)
44
47
  assert_equal(40, box.height)
45
48
  end
46
49
 
47
50
  it "fits with auto-scaling to available space" do
48
51
  box = create_box(style: {padding: [10, 4, 6, 2]})
49
- assert(box.fit(100, 100, nil))
52
+ assert(box.fit(100, 100, @frame).success?)
50
53
  assert_equal(100, box.width)
51
54
  assert_equal(63, box.height)
52
55
 
53
- assert(box.fit(100, 30, nil))
56
+ assert(box.fit(100, 30, @frame).success?)
54
57
  assert_equal(34, box.width)
55
58
  assert_equal(30, box.height)
56
59
  end
60
+
61
+ it "fails if one of the calculated dimensions is larger than the available space" do
62
+ box = create_box(height: 60)
63
+ assert(box.fit(100, 100, @frame).failure?)
64
+ end
57
65
  end
58
66
 
59
67
  it "always returns false for empty?" do
@@ -63,7 +71,7 @@ describe HexaPDF::Layout::ImageBox do
63
71
  describe "draw" do
64
72
  it "draws the image" do
65
73
  box = create_box(height: 40, style: {padding: [10, 4, 6, 2]})
66
- box.fit(100, 100, nil)
74
+ box.fit(100, 100, @frame)
67
75
 
68
76
  @canvas = HexaPDF::Document.new.pages.add.canvas
69
77
  box.draw(@canvas, 0, 0)
@@ -19,8 +19,9 @@ describe HexaPDF::Layout::ListBox do
19
19
  HexaPDF::Layout::ListBox.new(content_indentation: 10, **kwargs)
20
20
  end
21
21
 
22
- def check_box(box, width, height, fit_pos = nil)
23
- assert(box.fit(@frame.available_width, @frame.available_height, @frame), "box didn't fit")
22
+ def check_box(box, width, height, status: :success, fit_pos: nil)
23
+ result = box.fit(@frame.available_width, @frame.available_height, @frame)
24
+ assert_equal(result.status, status)
24
25
  assert_equal(width, box.width, "box width")
25
26
  assert_equal(height, box.height, "box height")
26
27
  if fit_pos
@@ -73,45 +74,44 @@ describe HexaPDF::Layout::ListBox do
73
74
  check_box(box, 100, 40)
74
75
  end
75
76
 
76
- it "respects the set initial height and the property overflow=:truncate" do
77
- box = create_box(children: @text_boxes[0, 2], height: 20,
78
- style: {overflow: :truncate, position: position})
79
- check_box(box, 100, 20)
77
+ it "respects the set initial height even when it doesn't fit completely" do
78
+ box = create_box(children: @text_boxes[0, 2], height: 20, style: {position: position})
79
+ check_box(box, 100, 20, status: :overflow)
80
80
  end
81
81
 
82
82
  it "respects the border and padding around all list items, position #{position}" do
83
83
  box = create_box(children: @text_boxes[0, 2],
84
84
  style: {border: {width: [5, 4, 3, 2]}, padding: [5, 4, 3, 2], position: position})
85
- check_box(box, 100, 76, [[14, 60], [14, 30]])
85
+ check_box(box, 100, 76, fit_pos: [[14, 60], [14, 30]])
86
86
  end
87
87
  end
88
88
 
89
89
  it "uses the frame's current cursor position and available width/height when position=:default" do
90
90
  @frame.remove_area(Geom2D::Polygon([0, 0], [10, 0], [10, 90], [100, 90], [100, 100], [0, 100]))
91
91
  box = create_box(children: @text_boxes[0, 2])
92
- check_box(box, 90, 40, [[20, 70], [20, 50]])
92
+ check_box(box, 90, 40, fit_pos: [[20, 70], [20, 50]])
93
93
  end
94
94
 
95
95
  it "respects the frame's shape when style position=:flow" do
96
96
  @frame.remove_area(Geom2D::Polygon([0, 0], [0, 40], [40, 40], [40, 0]))
97
97
  box = create_box(children: @text_boxes[0, 4], style: {position: :flow})
98
- check_box(box, 100, 90, [[10, 80], [10, 60], [10, 40], [50, 10]])
98
+ check_box(box, 100, 90, fit_pos: [[10, 80], [10, 60], [10, 40], [50, 10]])
99
99
  end
100
100
 
101
101
  it "calculates the correct height if the marker is higher than the content" do
102
102
  box = create_box(children: @text_boxes[0, 1], content_indentation: 20,
103
103
  style: {font_size: 30})
104
- check_box(box, 100, 27, [[20, 80]])
104
+ check_box(box, 100, 27, fit_pos: [[20, 80]])
105
105
  end
106
106
 
107
107
  it "respects the content indentation" do
108
108
  box = create_box(children: @text_boxes[0, 1], content_indentation: 30)
109
- check_box(box, 100, 30, [[30, 70]])
109
+ check_box(box, 100, 30, fit_pos: [[30, 70]])
110
110
  end
111
111
 
112
112
  it "respects the spacing between list items" do
113
113
  box = create_box(children: @text_boxes[0, 2], item_spacing: 30)
114
- check_box(box, 100, 70, [[10, 80], [10, 30]])
114
+ check_box(box, 100, 70, fit_pos: [[10, 80], [10, 30]])
115
115
  end
116
116
 
117
117
  it "creates a new box for each marker even if the marker is the same" do
@@ -121,6 +121,11 @@ describe HexaPDF::Layout::ListBox do
121
121
  refute_same(results[0].marker, results[1].marker)
122
122
  end
123
123
 
124
+ it "fails if not even a part of the first list item fits" do
125
+ box = create_box(children: @text_boxes[0, 2], height: 5)
126
+ check_box(box, 100, 0, status: :failure)
127
+ end
128
+
124
129
  it "fails for unknown marker types" do
125
130
  box = create_box(children: @text_boxes[0, 1], marker_type: :unknown)
126
131
  assert_raises(HexaPDF::Error) { box.fit(100, 100, @frame) }
@@ -129,20 +134,20 @@ describe HexaPDF::Layout::ListBox do
129
134
 
130
135
  describe "split" do
131
136
  it "splits before a list item if no part of it will fit" do
132
- box = create_box(children: @text_boxes[0, 2], height: 20)
133
- box.fit(100, 100, @frame)
134
- box_a, box_b = box.split(100, 100, @frame)
137
+ box = create_box(children: @text_boxes[0, 3])
138
+ assert(box.fit(100, 22, @frame).overflow?)
139
+ box_a, box_b = box.split
135
140
  assert_same(box, box_a)
136
141
  assert_equal(:show_first_marker, box_b.split_box?)
137
142
  assert_equal(1, box_a.instance_variable_get(:@results)[0].box_fitter.fit_results.size)
138
- assert_equal(1, box_b.children.size)
143
+ assert_equal(2, box_b.children.size)
139
144
  assert_equal(2, box_b.start_number)
140
145
  end
141
146
 
142
147
  it "splits a list item if some part of it will fit" do
143
- box = create_box(children: @text_boxes[0, 2], height: 10)
144
- box.fit(100, 100, @frame)
145
- box_a, box_b = box.split(100, 100, @frame)
148
+ box = create_box(children: @text_boxes[0, 2])
149
+ assert(box.fit(100, 10, @frame).overflow?)
150
+ box_a, box_b = box.split
146
151
  assert_same(box, box_a)
147
152
  assert_equal(:hide_first_marker, box_b.split_box?)
148
153
  assert_equal(1, box_a.instance_variable_get(:@results)[0].box_fitter.fit_results.size)
@@ -152,8 +157,8 @@ describe HexaPDF::Layout::ListBox do
152
157
 
153
158
  it "splits a list item containg multiple boxes along box lines" do
154
159
  box = create_box(children: [@text_boxes[0], @text_boxes[1, 2]])
155
- box.fit(100, 40, @frame)
156
- box_a, box_b = box.split(100, 40, @frame)
160
+ assert(box.fit(100, 40, @frame).overflow?)
161
+ box_a, box_b = box.split
157
162
  assert_same(box, box_a)
158
163
  assert_equal(:hide_first_marker, box_b.split_box?)
159
164
  assert_equal(1, box_a.instance_variable_get(:@results)[1].box_fitter.fit_results.size)
@@ -338,11 +343,5 @@ describe HexaPDF::Layout::ListBox do
338
343
  assert_operators(@canvas.contents, [:set_font_and_size, [:F1, 5]], range: 1)
339
344
  assert_equal(:ZapfDingbats, @canvas.resources.font(:F1)[:BaseFont])
340
345
  end
341
-
342
- it "fails if the initial height is set and property overflow is set to :error" do
343
- box = create_box(children: @fixed_size_boxes[0, 2], height: 10)
344
- box.fit(100, 100, @frame)
345
- assert_raises(HexaPDF::Error) { box.draw(@canvas, 0, 100 - box.height) }
346
- end
347
346
  end
348
347
  end