hexapdf 0.33.0 → 0.34.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/examples/026-optional_content.rb +55 -0
  4. data/examples/027-composer_optional_content.rb +83 -0
  5. data/lib/hexapdf/cli/command.rb +7 -1
  6. data/lib/hexapdf/cli/fonts.rb +1 -1
  7. data/lib/hexapdf/cli/inspect.rb +2 -4
  8. data/lib/hexapdf/composer.rb +3 -2
  9. data/lib/hexapdf/configuration.rb +21 -1
  10. data/lib/hexapdf/content/canvas.rb +52 -0
  11. data/lib/hexapdf/content/operator.rb +3 -1
  12. data/lib/hexapdf/dictionary.rb +1 -0
  13. data/lib/hexapdf/dictionary_fields.rb +1 -2
  14. data/lib/hexapdf/digital_signature/verification_result.rb +1 -2
  15. data/lib/hexapdf/document/layout.rb +3 -0
  16. data/lib/hexapdf/document/pages.rb +1 -1
  17. data/lib/hexapdf/document.rb +7 -0
  18. data/lib/hexapdf/encryption/ruby_aes.rb +10 -20
  19. data/lib/hexapdf/layout/box.rb +23 -3
  20. data/lib/hexapdf/layout/column_box.rb +2 -1
  21. data/lib/hexapdf/layout/frame.rb +23 -6
  22. data/lib/hexapdf/layout/inline_box.rb +20 -9
  23. data/lib/hexapdf/layout/list_box.rb +34 -20
  24. data/lib/hexapdf/layout/page_style.rb +2 -1
  25. data/lib/hexapdf/layout/style.rb +46 -6
  26. data/lib/hexapdf/layout/table_box.rb +9 -7
  27. data/lib/hexapdf/layout/text_box.rb +9 -2
  28. data/lib/hexapdf/layout/text_fragment.rb +28 -2
  29. data/lib/hexapdf/layout/text_layouter.rb +21 -5
  30. data/lib/hexapdf/stream.rb +1 -2
  31. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  32. data/lib/hexapdf/type/actions.rb +1 -0
  33. data/lib/hexapdf/type/annotations/text.rb +1 -2
  34. data/lib/hexapdf/type/catalog.rb +10 -1
  35. data/lib/hexapdf/type/cid_font.rb +15 -1
  36. data/lib/hexapdf/type/form.rb +75 -5
  37. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  38. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  39. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  40. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  41. data/lib/hexapdf/type/page.rb +40 -16
  42. data/lib/hexapdf/type/page_label.rb +4 -8
  43. data/lib/hexapdf/type.rb +4 -0
  44. data/lib/hexapdf/utils/pdf_doc_encoding.rb +0 -1
  45. data/lib/hexapdf/version.rb +1 -1
  46. data/test/hexapdf/content/test_canvas.rb +49 -0
  47. data/test/hexapdf/content/test_operator.rb +3 -1
  48. data/test/hexapdf/document/test_layout.rb +7 -2
  49. data/test/hexapdf/document/test_pages.rb +6 -6
  50. data/test/hexapdf/layout/test_box.rb +13 -4
  51. data/test/hexapdf/layout/test_frame.rb +13 -1
  52. data/test/hexapdf/layout/test_inline_box.rb +17 -8
  53. data/test/hexapdf/layout/test_list_box.rb +48 -31
  54. data/test/hexapdf/layout/test_style.rb +10 -0
  55. data/test/hexapdf/layout/test_table_box.rb +32 -26
  56. data/test/hexapdf/layout/test_text_box.rb +8 -0
  57. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  58. data/test/hexapdf/layout/test_text_layouter.rb +32 -5
  59. data/test/hexapdf/test_composer.rb +30 -0
  60. data/test/hexapdf/test_dictionary.rb +10 -0
  61. data/test/hexapdf/test_document.rb +4 -0
  62. data/test/hexapdf/test_writer.rb +3 -3
  63. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  64. data/test/hexapdf/type/test_catalog.rb +11 -0
  65. data/test/hexapdf/type/test_form.rb +119 -0
  66. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  67. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  68. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  69. data/test/hexapdf/type/test_page.rb +7 -5
  70. metadata +14 -3
@@ -6,7 +6,9 @@ require 'hexapdf/layout/list_box'
6
6
 
7
7
  describe HexaPDF::Layout::ListBox do
8
8
  before do
9
- @frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
9
+ @doc = HexaPDF::Document.new
10
+ @page = @doc.pages.add
11
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100, context: @page)
10
12
  inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {}
11
13
  @text_boxes = 5.times.map do
12
14
  HexaPDF::Layout::TextBox.new(items: [inline_box] * 15, style: {position: :default})
@@ -23,8 +25,8 @@ describe HexaPDF::Layout::ListBox do
23
25
  assert_equal(height, box.height, "box height")
24
26
  if fit_pos
25
27
  results = box.instance_variable_get(:@results)
26
- results.each_with_index do |box_fitter, item_index|
27
- box_fitter.fit_results.each_with_index do |fit_result, result_index|
28
+ results.each_with_index do |item_result, item_index|
29
+ item_result.box_fitter.fit_results.each_with_index do |fit_result, result_index|
28
30
  x, y = fit_pos.shift
29
31
  assert_equal(x, fit_result.x, "item #{item_index}, result #{result_index}, x")
30
32
  assert_equal(y, fit_result.y, "item #{item_index}, result #{result_index}, y")
@@ -55,7 +57,7 @@ describe HexaPDF::Layout::ListBox do
55
57
  it "is empty if nothing could be fit" do
56
58
  box = create_box(children: [@text_boxes[0]], width: 5)
57
59
  box.fit(@frame.available_width, @frame.available_height, @frame)
58
- assert(create_box.empty?)
60
+ assert(box.empty?)
59
61
  end
60
62
  end
61
63
 
@@ -90,6 +92,12 @@ describe HexaPDF::Layout::ListBox do
90
92
  check_box(box, 100, 90, [[10, 80], [10, 60], [10, 40], [50, 10]])
91
93
  end
92
94
 
95
+ it "calculates the correct height if the marker is higher than the content" do
96
+ box = create_box(children: @text_boxes[0, 1], content_indentation: 20,
97
+ style: {font_size: 30})
98
+ check_box(box, 100, 27, [[20, 80]])
99
+ end
100
+
93
101
  it "respects the content indentation" do
94
102
  box = create_box(children: @text_boxes[0, 1], content_indentation: 30)
95
103
  check_box(box, 100, 30, [[30, 70]])
@@ -99,6 +107,11 @@ describe HexaPDF::Layout::ListBox do
99
107
  box = create_box(children: @text_boxes[0, 2], item_spacing: 30)
100
108
  check_box(box, 100, 70, [[10, 80], [10, 30]])
101
109
  end
110
+
111
+ it "fails for unknown item types" do
112
+ box = create_box(children: @text_boxes[0, 1], item_type: :unknown)
113
+ assert_raises(HexaPDF::Error) { box.fit(100, 100, @frame) }
114
+ end
102
115
  end
103
116
 
104
117
  describe "split" do
@@ -108,7 +121,7 @@ describe HexaPDF::Layout::ListBox do
108
121
  box_a, box_b = box.split(100, 100, @frame)
109
122
  assert_same(box, box_a)
110
123
  assert_equal(:show_first_marker, box_b.split_box?)
111
- assert_equal(1, box_a.instance_variable_get(:@results)[0].fit_results.size)
124
+ assert_equal(1, box_a.instance_variable_get(:@results)[0].box_fitter.fit_results.size)
112
125
  assert_equal(1, box_b.children.size)
113
126
  assert_equal(2, box_b.start_number)
114
127
  end
@@ -119,7 +132,7 @@ describe HexaPDF::Layout::ListBox do
119
132
  box_a, box_b = box.split(100, 100, @frame)
120
133
  assert_same(box, box_a)
121
134
  assert_equal(:hide_first_marker, box_b.split_box?)
122
- assert_equal(1, box_a.instance_variable_get(:@results)[0].fit_results.size)
135
+ assert_equal(1, box_a.instance_variable_get(:@results)[0].box_fitter.fit_results.size)
123
136
  assert_equal(2, box_b.children.size)
124
137
  assert_equal(1, box_b.start_number)
125
138
  end
@@ -127,20 +140,22 @@ describe HexaPDF::Layout::ListBox do
127
140
 
128
141
  describe "draw" do
129
142
  before do
130
- @canvas = HexaPDF::Document.new.pages.add.canvas
143
+ @canvas = @page.canvas
131
144
  draw_block = lambda {|canvas, box| }
132
145
  @fixed_size_boxes = 5.times.map { HexaPDF::Layout::Box.new(width: 20, height: 10, &draw_block) }
133
146
  end
134
147
 
135
148
  it "draws the result" do
136
- box = create_box(children: @fixed_size_boxes[0, 2])
149
+ box = create_box(children: @fixed_size_boxes[0, 2],
150
+ style: {font_size: 11, fill_color: 0.5})
137
151
  box.fit(100, 100, @frame)
138
152
  box.draw(@canvas, 0, 100 - box.height)
139
153
  operators = [
140
154
  [:save_graphics_state],
141
- [:set_font_and_size, [:F1, 10]],
155
+ [:set_font_and_size, [:F1, 11]],
156
+ [:set_device_gray_non_stroking_color, [0.5]],
142
157
  [:begin_text],
143
- [:set_text_matrix, [1, 0, 0, 1, 1.5, 93.17]],
158
+ [:set_text_matrix, [1, 0, 0, 1, 1.15, 92.487]],
144
159
  [:show_text, ["\x95".b]],
145
160
  [:end_text],
146
161
  [:restore_graphics_state],
@@ -149,9 +164,10 @@ describe HexaPDF::Layout::ListBox do
149
164
  [:restore_graphics_state],
150
165
 
151
166
  [:save_graphics_state],
152
- [:set_font_and_size, [:F1, 10]],
167
+ [:set_font_and_size, [:F1, 11]],
168
+ [:set_device_gray_non_stroking_color, [0.5]],
153
169
  [:begin_text],
154
- [:set_text_matrix, [1, 0, 0, 1, 1.5, 83.17]],
170
+ [:set_text_matrix, [1, 0, 0, 1, 1.15, 82.487]],
155
171
  [:show_text, ["\x95".b]],
156
172
  [:end_text],
157
173
  [:restore_graphics_state],
@@ -162,16 +178,18 @@ describe HexaPDF::Layout::ListBox do
162
178
  assert_operators(@canvas.contents, operators)
163
179
  end
164
180
 
165
- it "draws a cicle as marker" do
166
- box = create_box(children: @fixed_size_boxes[0, 1], item_type: :circle)
181
+ it "draws a circle as marker" do
182
+ box = create_box(children: @fixed_size_boxes[0, 1], item_type: :circle,
183
+ style: {font_size: 11, fill_color: 0.5})
167
184
  box.fit(100, 100, @frame)
168
185
  box.draw(@canvas, 0, 100 - box.height)
169
186
  operators = [
170
187
  [:save_graphics_state],
171
- [:set_font_and_size, [:F1, 5]],
172
- [:set_text_rise, [-5.555556]],
188
+ [:set_font_and_size, [:F1, 5.5]],
189
+ [:set_text_rise, [-6.111111]],
190
+ [:set_device_gray_non_stroking_color, [0.5]],
173
191
  [:begin_text],
174
- [:set_text_matrix, [1, 0, 0, 1, 0.635, 100]],
192
+ [:set_text_matrix, [1, 0, 0, 1, 0.1985, 100]],
175
193
  [:show_text, ["m".b]],
176
194
  [:end_text],
177
195
  [:restore_graphics_state],
@@ -183,15 +201,17 @@ describe HexaPDF::Layout::ListBox do
183
201
  end
184
202
 
185
203
  it "draws a square as marker" do
186
- box = create_box(children: @fixed_size_boxes[0, 1], item_type: :square)
204
+ box = create_box(children: @fixed_size_boxes[0, 1], item_type: :square,
205
+ style: {font_size: 11, fill_color: 0.5})
187
206
  box.fit(100, 100, @frame)
188
207
  box.draw(@canvas, 0, 100 - box.height)
189
208
  operators = [
190
209
  [:save_graphics_state],
191
- [:set_font_and_size, [:F1, 5]],
192
- [:set_text_rise, [-5.555556]],
210
+ [:set_font_and_size, [:F1, 5.5]],
211
+ [:set_text_rise, [-6.111111]],
212
+ [:set_device_gray_non_stroking_color, [0.5]],
193
213
  [:begin_text],
194
- [:set_text_matrix, [1, 0, 0, 1, 1.195, 100]],
214
+ [:set_text_matrix, [1, 0, 0, 1, 0.8145, 100]],
195
215
  [:show_text, ["n".b]],
196
216
  [:end_text],
197
217
  [:restore_graphics_state],
@@ -204,14 +224,16 @@ describe HexaPDF::Layout::ListBox do
204
224
 
205
225
  it "draws decimal numbers as marker" do
206
226
  box = create_box(children: @fixed_size_boxes[0, 2], item_type: :decimal,
227
+ style: {font_size: 11, fill_color: 0.5},
207
228
  content_indentation: 20)
208
229
  box.fit(100, 100, @frame)
209
230
  box.draw(@canvas, 0, 100 - box.height)
210
231
  operators = [
211
232
  [:save_graphics_state],
212
- [:set_font_and_size, [:F1, 10]],
233
+ [:set_font_and_size, [:F1, 11]],
234
+ [:set_device_gray_non_stroking_color, [0.5]],
213
235
  [:begin_text],
214
- [:set_text_matrix, [1, 0, 0, 1, 7.5, 93.17]],
236
+ [:set_text_matrix, [1, 0, 0, 1, 6.75, 92.487]],
215
237
  [:show_text, ["1.".b]],
216
238
  [:end_text],
217
239
  [:restore_graphics_state],
@@ -220,9 +242,10 @@ describe HexaPDF::Layout::ListBox do
220
242
  [:restore_graphics_state],
221
243
 
222
244
  [:save_graphics_state],
223
- [:set_font_and_size, [:F1, 10]],
245
+ [:set_font_and_size, [:F1, 11]],
246
+ [:set_device_gray_non_stroking_color, [0.5]],
224
247
  [:begin_text],
225
- [:set_text_matrix, [1, 0, 0, 1, 7.5, 83.17]],
248
+ [:set_text_matrix, [1, 0, 0, 1, 6.75, 82.487]],
226
249
  [:show_text, ["2.".b]],
227
250
  [:end_text],
228
251
  [:restore_graphics_state],
@@ -272,11 +295,5 @@ describe HexaPDF::Layout::ListBox do
272
295
  ]
273
296
  assert_operators(@canvas.contents, operators)
274
297
  end
275
-
276
- it "fails for unknown item types" do
277
- box = create_box(children: @fixed_size_boxes[0, 1], item_type: :unknown)
278
- box.fit(100, 100, @frame)
279
- assert_raises(HexaPDF::Error) { box.draw(@canvas, 0, 0) }
280
- end
281
298
  end
282
299
  end
@@ -580,7 +580,10 @@ describe HexaPDF::Layout::Style::LinkLayer do
580
580
  it "fails if more than one possible target is chosen" do
581
581
  assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, uri: true) }
582
582
  assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, file: true) }
583
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, action: true) }
583
584
  assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(uri: true, file: true) }
585
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(uri: true, action: true) }
586
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(file: true, action: true) }
584
587
  end
585
588
 
586
589
  it "fails if an invalid border is provided" do
@@ -656,6 +659,12 @@ describe HexaPDF::Layout::Style::LinkLayer do
656
659
  assert_nil(annot[:Dest])
657
660
  end
658
661
 
662
+ it "works for actions" do
663
+ annot = call_link(action: {Type: :Action, S: :SetOCGState})
664
+ assert_equal({Type: :Action, S: :SetOCGState}, annot[:A].value)
665
+ assert_nil(annot[:Dest])
666
+ end
667
+
659
668
  it "works for destinations set via the 'link' custom box property" do
660
669
  @box.properties['link'] = [@canvas.context, :FitH]
661
670
  annot = call_link({})
@@ -773,6 +782,7 @@ describe HexaPDF::Layout::Style do
773
782
  refute(@style.subscript)
774
783
  refute(@style.superscript)
775
784
  refute(@style.last_line_gap)
785
+ refute(@style.fill_horizontal)
776
786
  assert_kind_of(HexaPDF::Layout::Style::Layers, @style.underlays)
777
787
  assert_kind_of(HexaPDF::Layout::Style::Layers, @style.overlays)
778
788
  end
@@ -5,6 +5,10 @@ require 'hexapdf/document'
5
5
  require 'hexapdf/layout/table_box'
6
6
 
7
7
  describe HexaPDF::Layout::TableBox::Cell do
8
+ before do
9
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 0, 0)
10
+ end
11
+
8
12
  def create_cell(**kwargs)
9
13
  HexaPDF::Layout::TableBox::Cell.new(row: 1, column: 1, **kwargs)
10
14
  end
@@ -46,7 +50,7 @@ describe HexaPDF::Layout::TableBox::Cell do
46
50
 
47
51
  it "returns true if the cell has no content" do
48
52
  cell = create_cell(children: nil, style: {border: {width: 0}})
49
- cell.fit(100, 100, nil)
53
+ cell.fit(100, 100, @frame)
50
54
  assert(cell.empty?)
51
55
  end
52
56
  end
@@ -54,7 +58,7 @@ describe HexaPDF::Layout::TableBox::Cell do
54
58
  describe "update_height" do
55
59
  it "updates the height to the correct one" do
56
60
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 10, height: 10))
57
- cell.fit(100, 100, nil)
61
+ cell.fit(100, 100, @frame)
58
62
  assert_equal(22, cell.height)
59
63
  cell.update_height(50)
60
64
  assert_equal(50, cell.height)
@@ -62,7 +66,7 @@ describe HexaPDF::Layout::TableBox::Cell do
62
66
 
63
67
  it "fails if the given height is smaller than the one determined during #fit" do
64
68
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 10, height: 10))
65
- cell.fit(100, 100, nil)
69
+ cell.fit(100, 100, @frame)
66
70
  err = assert_raises(HexaPDF::Error) { cell.update_height(5) }
67
71
  assert_match(/at least as big/, err.message)
68
72
  end
@@ -71,7 +75,7 @@ describe HexaPDF::Layout::TableBox::Cell do
71
75
  describe "fit" do
72
76
  it "fits a single box" do
73
77
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10))
74
- cell.fit(100, 100, nil)
78
+ cell.fit(100, 100, @frame)
75
79
  assert_equal(100, cell.width)
76
80
  assert_equal(22, cell.height)
77
81
  assert_equal(32, cell.preferred_width)
@@ -80,7 +84,7 @@ describe HexaPDF::Layout::TableBox::Cell do
80
84
 
81
85
  it "fits a single box with horizontal aligning not being :left" do
82
86
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center))
83
- cell.fit(100, 100, nil)
87
+ cell.fit(100, 100, @frame)
84
88
  assert_equal(66, cell.preferred_width)
85
89
  end
86
90
 
@@ -88,7 +92,7 @@ describe HexaPDF::Layout::TableBox::Cell do
88
92
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10)
89
93
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
90
94
  cell = create_cell(children: [box1, box2])
91
- cell.fit(100, 100, nil)
95
+ cell.fit(100, 100, @frame)
92
96
  assert_equal(100, cell.width)
93
97
  assert_equal(37, cell.height)
94
98
  assert_equal(62, cell.preferred_width)
@@ -99,13 +103,13 @@ describe HexaPDF::Layout::TableBox::Cell do
99
103
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center)
100
104
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
101
105
  cell = create_cell(children: [box1, box2])
102
- cell.fit(100, 100, nil)
106
+ cell.fit(100, 100, @frame)
103
107
  assert_equal(66, cell.preferred_width)
104
108
  end
105
109
 
106
110
  it "fits the cell even if it has no content" do
107
111
  cell = create_cell(children: nil)
108
- cell.fit(100, 100, nil)
112
+ cell.fit(100, 100, @frame)
109
113
  assert_equal(100, cell.width)
110
114
  assert_equal(12, cell.height)
111
115
  assert_equal(12, cell.preferred_width)
@@ -114,8 +118,8 @@ describe HexaPDF::Layout::TableBox::Cell do
114
118
 
115
119
  it "doesn't fit anything if the available width or height are too small" do
116
120
  cell = create_cell(children: nil)
117
- refute(cell.fit(10, 100, nil))
118
- refute(cell.fit(100, 10, nil))
121
+ refute(cell.fit(10, 100, @frame))
122
+ refute(cell.fit(100, 10, @frame))
119
123
  end
120
124
  end
121
125
 
@@ -129,7 +133,7 @@ describe HexaPDF::Layout::TableBox::Cell do
129
133
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center, &draw_block)
130
134
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15, &draw_block)
131
135
  box = create_cell(children: [box1, box2])
132
- box.fit(100, 100, nil)
136
+ box.fit(100, 100, @frame)
133
137
  box.draw(@canvas, 10, 75)
134
138
  operators = [[:save_graphics_state],
135
139
  [:append_rectangle, [9.5, 74.5, 101.0, 38.0]],
@@ -153,7 +157,7 @@ describe HexaPDF::Layout::TableBox::Cell do
153
157
 
154
158
  it "works for a cell without content" do
155
159
  box = create_cell(children: nil, style: {border: {width: 0}})
156
- box.fit(100, 100, nil)
160
+ box.fit(100, 100, @frame)
157
161
  box.draw(@canvas, 10, 75)
158
162
  assert_operators(@canvas.contents, [])
159
163
  end
@@ -355,7 +359,9 @@ end
355
359
 
356
360
  describe HexaPDF::Layout::TableBox do
357
361
  before do
358
- @frame = HexaPDF::Layout::Frame.new(0, 0, 160, 100)
362
+ @doc = HexaPDF::Document.new
363
+ @page = @doc.pages.add
364
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 160, 100, context: @page)
359
365
  draw_block = lambda {|canvas, _box| canvas.move_to(0, 0).end_path }
360
366
  @fixed_size_boxes = 15.times.map { HexaPDF::Layout::Box.new(width: 20, height: 10, &draw_block) }
361
367
  end
@@ -425,7 +431,7 @@ describe HexaPDF::Layout::TableBox do
425
431
  footer = lambda {|_| [[nil]] }
426
432
  box = create_box(header: header, footer: footer, cells: [[nil], [nil]],
427
433
  cell_style: {background_color: 'black'})
428
- refute(box.fit(100, 40, nil))
434
+ refute(box.fit(100, 40, @frame))
429
435
  box_a, box_b = box.split(100, 40, nil)
430
436
  assert_same(box_a, box)
431
437
  assert_equal('black', box_b.header_cells[0, 0].style.background_color)
@@ -544,8 +550,8 @@ describe HexaPDF::Layout::TableBox do
544
550
  describe "split" do
545
551
  it "splits the table if some rows could not be fit into the available region" do
546
552
  box = create_box
547
- refute(box.fit(100, 25, nil))
548
- box_a, box_b = box.split(100, 25, nil)
553
+ refute(box.fit(100, 25, @frame))
554
+ box_a, box_b = box.split(100, 25, @frame)
549
555
  assert_same(box_a, box)
550
556
  assert(box_b.split_box?)
551
557
 
@@ -560,8 +566,8 @@ describe HexaPDF::Layout::TableBox do
560
566
  [{header: cells_creator}, {footer: cells_creator}].each do |args|
561
567
  box = create_box(**args)
562
568
  box.cells.style(padding: 0, border: {width: 0})
563
- refute(box.fit(100, 25, nil))
564
- box_a, box_b = box.split(100, 25, nil)
569
+ refute(box.fit(100, 25, @frame))
570
+ box_a, box_b = box.split(100, 25, @frame)
565
571
  assert_nil(box_a)
566
572
  assert_same(box_b, box)
567
573
  end
@@ -571,8 +577,8 @@ describe HexaPDF::Layout::TableBox do
571
577
  cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
572
578
  [{header: cells_creator}, {footer: cells_creator}].each do |args|
573
579
  box = create_box(**args)
574
- refute(box.fit(100, 50, nil))
575
- box_a, box_b = box.split(100, 50, nil)
580
+ refute(box.fit(100, 50, @frame))
581
+ box_a, box_b = box.split(100, 50, @frame)
576
582
  assert_same(box_a, box)
577
583
 
578
584
  assert_equal(0, box_a.start_row_index)
@@ -591,12 +597,12 @@ describe HexaPDF::Layout::TableBox do
591
597
 
592
598
  describe "draw_content" do
593
599
  before do
594
- @canvas = HexaPDF::Document.new.pages.add.canvas
600
+ @canvas = @page.canvas
595
601
  end
596
602
 
597
603
  it "draws the result onto the canvas" do
598
604
  box = create_box
599
- box.fit(100, 100, nil)
605
+ box.fit(100, 100, @frame)
600
606
  box.draw(@canvas, 20, 10)
601
607
  operators = [[:save_graphics_state],
602
608
  [:append_rectangle, [20.0, 32.0, 50.5, 23.0]],
@@ -651,9 +657,9 @@ describe HexaPDF::Layout::TableBox do
651
657
 
652
658
  it "correctly works for split boxes" do
653
659
  box = create_box(cell_style: {padding: 0, border: {width: 0}})
654
- refute(box.fit(100, 10, nil))
655
- _, split_box = box.split(100, 10, nil)
656
- assert(split_box.fit(100, 100, nil))
660
+ refute(box.fit(100, 10, @frame))
661
+ _, split_box = box.split(100, 10, @frame)
662
+ assert(split_box.fit(100, 100, @frame))
657
663
 
658
664
  box.draw(@canvas, 20, 10)
659
665
  split_box.draw(@canvas, 0, 50)
@@ -684,7 +690,7 @@ describe HexaPDF::Layout::TableBox do
684
690
  box = create_box(header: lambda {|_| [@fixed_size_boxes[10, 1]] },
685
691
  footer: lambda {|_| [@fixed_size_boxes[12, 1]] },
686
692
  cell_style: {padding: 0, border: {width: 0}})
687
- box.fit(100, 100, nil)
693
+ box.fit(100, 100, @frame)
688
694
  box.draw(@canvas, 20, 10)
689
695
  operators = [[:save_graphics_state],
690
696
  [:concatenate_matrix, [1, 0, 0, 1, 20, 40]],
@@ -25,6 +25,14 @@ describe HexaPDF::Layout::TextBox do
25
25
  end
26
26
  end
27
27
 
28
+ it "returns the text contents as string" do
29
+ doc = HexaPDF::Document.new
30
+ font = doc.fonts.add("Times")
31
+ box = create_box([HexaPDF::Layout::TextFragment.create('Test ', font: font), @inline_box,
32
+ HexaPDF::Layout::TextFragment.create('here', font: font)])
33
+ assert_equal('Test here', box.text)
34
+ end
35
+
28
36
  describe "fit" do
29
37
  it "fits into a rectangular area" do
30
38
  box = create_box([@inline_box] * 5, style: {padding: 10})
@@ -51,6 +51,10 @@ describe HexaPDF::Layout::TextFragment do
51
51
  end
52
52
  end
53
53
 
54
+ it "returns the text value of the items as string" do
55
+ assert_equal("Hal lo\u{00a0}d\n", setup_fragment(@font.decode_utf8("Hal lo\u{00a0}d\n")).text)
56
+ end
57
+
54
58
  it "allows duplicating with only its attributes while also setting new items" do
55
59
  setup_fragment([20])
56
60
  @fragment.properties['key'] = :value
@@ -392,6 +396,35 @@ describe HexaPDF::Layout::TextFragment do
392
396
  end
393
397
  end
394
398
 
399
+ describe "fill_horizontal!" do
400
+ before do
401
+ @fragment = HexaPDF::Layout::TextFragment.create('ab', fill_horizontal: 1, font: @font)
402
+ end
403
+
404
+ it "returns the fragment if the given width is too small" do
405
+ assert_same(@fragment, @fragment.fill_horizontal!(0.1))
406
+ end
407
+
408
+ it "repeats all items of the fragment" do
409
+ fragment = @fragment.fill_horizontal!(@fragment.width * 2)
410
+ assert_equal([*(@fragment.items * 2), 0], fragment.items)
411
+ assert_in_delta(@fragment.width * 2, fragment.width)
412
+ end
413
+
414
+ it "adds, after repeating, items from the start of the fragment to fill the available space" do
415
+ fragment = @fragment.fill_horizontal!(90)
416
+ assert_equal([*(@fragment.items * 9), @fragment.items[0], 3.3333333333332673], fragment.items)
417
+ assert_in_delta(90, fragment.width)
418
+ end
419
+
420
+ it "sets the character spacing correctly to account for the remaining space after filling with items" do
421
+ fragment = @fragment.fill_horizontal!(90)
422
+ refute_same(@fragment.style, fragment.style)
423
+ assert_equal(0.033333333333332674, fragment.style.character_spacing)
424
+ assert_in_delta(90, fragment.width)
425
+ end
426
+ end
427
+
395
428
  it "can be inspected" do
396
429
  frag = setup_fragment(@font.decode_utf8("H") << 5)
397
430
  assert_match(/:H/, frag.inspect)
@@ -262,7 +262,7 @@ module CommonLineWrappingTests
262
262
  end
263
263
 
264
264
  it "handles prohibited breakpoint penalties with non-zero width" do
265
- item = boxes(20).first
265
+ item = boxes(20).first.item
266
266
  result = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(5000, item)] + boxes(30))
267
267
  assert_line_wrapping(result, [70, 60])
268
268
  end
@@ -295,11 +295,34 @@ module CommonLineWrappingTests
295
295
  assert_equal(2, lines.count)
296
296
  end
297
297
 
298
+ it "handles items with fill_horizontal correctly" do
299
+ doc = HexaPDF::Document.new
300
+ font = doc.fonts.add("Times")
301
+ box1 = HexaPDF::Layout::TextLayouter::Box.new(
302
+ HexaPDF::Layout::TextFragment.create('.', font: font, fill_horizontal: 1)
303
+ )
304
+ box2 = HexaPDF::Layout::TextLayouter::Box.new(
305
+ HexaPDF::Layout::TextFragment.create('.', font: font, fill_horizontal: 2)
306
+ )
307
+ items = [box1, *boxes(10), box2]
308
+ rest, lines = call(items, 40)
309
+ assert_equal(0, rest.size)
310
+ assert_equal(1, lines.size)
311
+
312
+ line = lines.first
313
+ refute_same(items[0].item, line.items[0])
314
+ assert_same(items[1].item, line.items[1])
315
+ refute_same(items[2].item, line.items[2])
316
+ assert_equal(10, line.items[0].width)
317
+ assert_equal(10, line.items[1].width)
318
+ assert_equal(20, line.items[2].width)
319
+ end
298
320
  end
299
321
 
300
322
  describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
301
323
  before do
302
324
  @obj = HexaPDF::Layout::TextLayouter::SimpleLineWrapping
325
+ @mock_frame = nil
303
326
  end
304
327
 
305
328
  describe "fixed width wrapping" do
@@ -308,7 +331,9 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
308
331
  def call(items, width = 100, &block)
309
332
  lines = []
310
333
  block ||= proc { true }
311
- rest = @obj.call(items, proc { width }) {|line, item| lines << line; block.call(line, item) }
334
+ rest = @obj.call(items, proc { width }, @mock_frame) do |line, item|
335
+ lines << line; block.call(line, item)
336
+ end
312
337
  [rest, lines]
313
338
  end
314
339
  end
@@ -319,7 +344,9 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
319
344
  def call(items, width = 100, &block)
320
345
  lines = []
321
346
  block ||= proc { true }
322
- rest = @obj.call(items, proc {|_| width }) {|line, i| lines << line; block.call(line, i) }
347
+ rest = @obj.call(items, proc {|_| width }, @mock_frame) do |line, i|
348
+ lines << line; block.call(line, i)
349
+ end
323
350
  [rest, lines]
324
351
  end
325
352
 
@@ -334,7 +361,7 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
334
361
  end
335
362
  end
336
363
  lines = []
337
- rest = @obj.call(boxes([20, 10], [10, 10], [20, 15], [40, 10]), width_block) do |line|
364
+ rest = @obj.call(boxes([20, 10], [10, 10], [20, 15], [40, 10]), width_block, @mock_frame) do |line|
338
365
  height += line.height
339
366
  lines << line
340
367
  true
@@ -357,7 +384,7 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
357
384
  lines = []
358
385
  item = HexaPDF::Layout::InlineBox.create(width: 20, height: 10) {}
359
386
  items = boxes([20, 10]) + [penalty(0, item)] + boxes([40, 15])
360
- rest = @obj.call(items, width_block) do |line|
387
+ rest = @obj.call(items, width_block, @mock_frame) do |line|
361
388
  height += line.height
362
389
  lines << line
363
390
  true
@@ -119,6 +119,26 @@ describe HexaPDF::Composer do
119
119
  end
120
120
  end
121
121
 
122
+ describe "page_style" do
123
+ it "returns the page style if no argument or block is given" do
124
+ page_style = @composer.page_style(:default)
125
+ assert_kind_of(HexaPDF::Layout::PageStyle, page_style)
126
+ assert_equal(:A4, page_style.page_size)
127
+ end
128
+
129
+ it "sets a page style using the given attributes" do
130
+ @composer.page_style(:other, page_size: :A3)
131
+ assert_equal(:A3, @composer.page_style(:other).page_size)
132
+ end
133
+
134
+ it "sets a page style using default attributes but with a block" do
135
+ @composer.page_style(:other) {|canvas, style| style.frame = :hallo }
136
+ style = @composer.page_style(:other)
137
+ style.create_page(@composer.document)
138
+ assert_equal(:hallo, style.frame)
139
+ end
140
+ end
141
+
122
142
  describe "text/formatted_text/image/box/method_missing" do
123
143
  before do
124
144
  test_self = self
@@ -243,6 +263,16 @@ describe HexaPDF::Composer do
243
263
  [:restore_graphics_state]])
244
264
  end
245
265
 
266
+ it "returns the last drawn box" do
267
+ box = create_box(height: 400)
268
+ assert_same(box, @composer.draw_box(box))
269
+
270
+ box = create_box(height: 400)
271
+ split_box = create_box(height: 100)
272
+ box.define_singleton_method(:split) {|*| [box, split_box] }
273
+ assert_same(split_box, @composer.draw_box(box))
274
+ end
275
+
246
276
  it "raises an error if a box doesn't fit onto an empty page" do
247
277
  assert_raises(HexaPDF::Error) do
248
278
  @composer.draw_box(create_box(height: 800))
@@ -94,6 +94,16 @@ describe HexaPDF::Dictionary do
94
94
  obj = @test_class.new(nil)
95
95
  assert_equal(:MyType, obj.value[:Type])
96
96
  end
97
+
98
+ it "doesn't set the default values for required fields if the type class might be wrong" do
99
+ @test_class.define_type(:MyType)
100
+ obj = @test_class.new({})
101
+ assert_equal([], obj.value[:Array])
102
+ obj = @test_class.new({Type: :MyType})
103
+ assert_equal([], obj.value[:Array])
104
+ obj = @test_class.new({Type: :OtherType})
105
+ refute(obj.key?(:Array))
106
+ end
97
107
  end
98
108
 
99
109
  describe "[]" do
@@ -561,6 +561,10 @@ describe HexaPDF::Document do
561
561
  assert_kind_of(HexaPDF::Type::Outline, @doc.outline)
562
562
  end
563
563
 
564
+ it "returns the optional content properties" do
565
+ assert_kind_of(HexaPDF::Type::OptionalContentProperties, @doc.optional_content)
566
+ end
567
+
564
568
  it "can be inspected and the output is not too large" do
565
569
  assert_match(/HexaPDF::Document:\d+/, @doc.inspect)
566
570
  end
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.33.0)>>
43
+ <</Producer(HexaPDF version 0.34.1)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.33.0)>>
75
+ <</Producer(HexaPDF version 0.34.1)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -214,7 +214,7 @@ describe HexaPDF::Writer do
214
214
  <</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
215
215
  endobj
216
216
  5 0 obj
217
- <</Producer(HexaPDF version 0.33.0)>>
217
+ <</Producer(HexaPDF version 0.34.1)>>
218
218
  endobj
219
219
  4 0 obj
220
220
  <</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream