hexapdf 0.32.2 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -1
  3. data/README.md +9 -0
  4. data/examples/002-graphics.rb +15 -17
  5. data/examples/003-arcs.rb +9 -9
  6. data/examples/009-text_layouter_alignment.rb +1 -1
  7. data/examples/010-text_layouter_inline_boxes.rb +2 -2
  8. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  9. data/examples/012-text_layouter_styling.rb +7 -7
  10. data/examples/013-text_layouter_shapes.rb +1 -1
  11. data/examples/014-text_in_polygon.rb +1 -1
  12. data/examples/015-boxes.rb +8 -7
  13. data/examples/016-frame_automatic_box_placement.rb +2 -2
  14. data/examples/017-frame_text_flow.rb +2 -1
  15. data/examples/018-composer.rb +1 -1
  16. data/examples/020-column_box.rb +2 -1
  17. data/examples/025-table_box.rb +46 -0
  18. data/examples/026-optional_content.rb +55 -0
  19. data/examples/027-composer_optional_content.rb +83 -0
  20. data/lib/hexapdf/cli/command.rb +12 -3
  21. data/lib/hexapdf/cli/fonts.rb +1 -1
  22. data/lib/hexapdf/cli/form.rb +5 -5
  23. data/lib/hexapdf/cli/inspect.rb +5 -7
  24. data/lib/hexapdf/composer.rb +106 -53
  25. data/lib/hexapdf/configuration.rb +65 -40
  26. data/lib/hexapdf/content/canvas.rb +445 -267
  27. data/lib/hexapdf/content/color_space.rb +72 -25
  28. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  32. data/lib/hexapdf/content/graphic_object.rb +6 -7
  33. data/lib/hexapdf/content/graphics_state.rb +54 -45
  34. data/lib/hexapdf/content/operator.rb +54 -54
  35. data/lib/hexapdf/content/parser.rb +2 -2
  36. data/lib/hexapdf/content/processor.rb +15 -15
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +5 -0
  39. data/lib/hexapdf/dictionary.rb +7 -5
  40. data/lib/hexapdf/dictionary_fields.rb +43 -16
  41. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  42. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  43. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  44. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  45. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  46. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  47. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  48. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  49. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  50. data/lib/hexapdf/digital_signature/verification_result.rb +3 -4
  51. data/lib/hexapdf/digital_signature.rb +7 -2
  52. data/lib/hexapdf/document/destinations.rb +12 -11
  53. data/lib/hexapdf/document/files.rb +1 -1
  54. data/lib/hexapdf/document/fonts.rb +1 -1
  55. data/lib/hexapdf/document/layout.rb +170 -39
  56. data/lib/hexapdf/document/pages.rb +4 -3
  57. data/lib/hexapdf/document.rb +96 -55
  58. data/lib/hexapdf/encryption/aes.rb +5 -5
  59. data/lib/hexapdf/encryption/arc4.rb +1 -1
  60. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  61. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  62. data/lib/hexapdf/encryption/identity.rb +1 -1
  63. data/lib/hexapdf/encryption/ruby_aes.rb +11 -21
  64. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  65. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  66. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  67. data/lib/hexapdf/encryption.rb +7 -2
  68. data/lib/hexapdf/error.rb +18 -0
  69. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  70. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  71. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  72. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  73. data/lib/hexapdf/filter/pass_through.rb +1 -1
  74. data/lib/hexapdf/filter/predictor.rb +1 -1
  75. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  76. data/lib/hexapdf/filter.rb +55 -6
  77. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  78. data/lib/hexapdf/font/cmap.rb +1 -1
  79. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  82. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  83. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  84. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  85. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  86. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  87. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  88. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  89. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  90. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  91. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  92. data/lib/hexapdf/font_loader.rb +3 -0
  93. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  94. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  95. data/lib/hexapdf/image_loader/png.rb +2 -2
  96. data/lib/hexapdf/image_loader.rb +1 -1
  97. data/lib/hexapdf/importer.rb +13 -0
  98. data/lib/hexapdf/layout/box.rb +32 -5
  99. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  100. data/lib/hexapdf/layout/column_box.rb +20 -5
  101. data/lib/hexapdf/layout/frame.rb +53 -18
  102. data/lib/hexapdf/layout/image_box.rb +5 -0
  103. data/lib/hexapdf/layout/inline_box.rb +21 -9
  104. data/lib/hexapdf/layout/list_box.rb +50 -20
  105. data/lib/hexapdf/layout/page_style.rb +6 -5
  106. data/lib/hexapdf/layout/style.rb +64 -9
  107. data/lib/hexapdf/layout/table_box.rb +684 -0
  108. data/lib/hexapdf/layout/text_box.rb +12 -3
  109. data/lib/hexapdf/layout/text_fragment.rb +29 -3
  110. data/lib/hexapdf/layout/text_layouter.rb +32 -8
  111. data/lib/hexapdf/layout.rb +1 -0
  112. data/lib/hexapdf/name_tree_node.rb +1 -1
  113. data/lib/hexapdf/number_tree_node.rb +1 -1
  114. data/lib/hexapdf/object.rb +18 -7
  115. data/lib/hexapdf/parser.rb +7 -7
  116. data/lib/hexapdf/pdf_array.rb +1 -1
  117. data/lib/hexapdf/rectangle.rb +1 -1
  118. data/lib/hexapdf/reference.rb +1 -1
  119. data/lib/hexapdf/revision.rb +1 -1
  120. data/lib/hexapdf/revisions.rb +3 -3
  121. data/lib/hexapdf/serializer.rb +15 -15
  122. data/lib/hexapdf/stream.rb +5 -4
  123. data/lib/hexapdf/tokenizer.rb +14 -14
  124. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  125. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  126. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  127. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  128. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  129. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  130. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  131. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  132. data/lib/hexapdf/type/acro_form.rb +1 -1
  133. data/lib/hexapdf/type/action.rb +1 -1
  134. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  135. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  136. data/lib/hexapdf/type/actions/launch.rb +1 -1
  137. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  138. data/lib/hexapdf/type/actions/uri.rb +1 -1
  139. data/lib/hexapdf/type/actions.rb +2 -1
  140. data/lib/hexapdf/type/annotation.rb +3 -3
  141. data/lib/hexapdf/type/annotations/link.rb +1 -1
  142. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  143. data/lib/hexapdf/type/annotations/text.rb +2 -3
  144. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  145. data/lib/hexapdf/type/annotations.rb +1 -1
  146. data/lib/hexapdf/type/catalog.rb +11 -2
  147. data/lib/hexapdf/type/cid_font.rb +18 -4
  148. data/lib/hexapdf/type/embedded_file.rb +1 -1
  149. data/lib/hexapdf/type/file_specification.rb +2 -2
  150. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  151. data/lib/hexapdf/type/font_simple.rb +2 -2
  152. data/lib/hexapdf/type/font_type0.rb +3 -3
  153. data/lib/hexapdf/type/font_type3.rb +1 -1
  154. data/lib/hexapdf/type/form.rb +76 -6
  155. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  156. data/lib/hexapdf/type/icon_fit.rb +1 -1
  157. data/lib/hexapdf/type/image.rb +1 -1
  158. data/lib/hexapdf/type/info.rb +1 -1
  159. data/lib/hexapdf/type/mark_information.rb +1 -1
  160. data/lib/hexapdf/type/names.rb +2 -2
  161. data/lib/hexapdf/type/object_stream.rb +2 -1
  162. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  163. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  164. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  165. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  166. data/lib/hexapdf/type/outline.rb +1 -1
  167. data/lib/hexapdf/type/outline_item.rb +1 -1
  168. data/lib/hexapdf/type/page.rb +46 -21
  169. data/lib/hexapdf/type/page_label.rb +5 -9
  170. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  171. data/lib/hexapdf/type/resources.rb +1 -1
  172. data/lib/hexapdf/type/trailer.rb +2 -2
  173. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  174. data/lib/hexapdf/type/xref_stream.rb +2 -2
  175. data/lib/hexapdf/type.rb +4 -0
  176. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -2
  177. data/lib/hexapdf/version.rb +1 -1
  178. data/lib/hexapdf/writer.rb +4 -4
  179. data/lib/hexapdf/xref_section.rb +2 -2
  180. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  181. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  182. data/test/hexapdf/content/test_canvas.rb +49 -1
  183. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  184. data/test/hexapdf/document/test_files.rb +2 -2
  185. data/test/hexapdf/document/test_layout.rb +105 -2
  186. data/test/hexapdf/document/test_pages.rb +6 -6
  187. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  188. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  189. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  190. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  191. data/test/hexapdf/layout/test_box.rb +14 -5
  192. data/test/hexapdf/layout/test_column_box.rb +65 -21
  193. data/test/hexapdf/layout/test_frame.rb +27 -15
  194. data/test/hexapdf/layout/test_image_box.rb +4 -0
  195. data/test/hexapdf/layout/test_inline_box.rb +17 -3
  196. data/test/hexapdf/layout/test_list_box.rb +84 -33
  197. data/test/hexapdf/layout/test_page_style.rb +3 -2
  198. data/test/hexapdf/layout/test_style.rb +60 -0
  199. data/test/hexapdf/layout/test_table_box.rb +728 -0
  200. data/test/hexapdf/layout/test_text_box.rb +26 -0
  201. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  202. data/test/hexapdf/layout/test_text_layouter.rb +36 -5
  203. data/test/hexapdf/test_composer.rb +10 -0
  204. data/test/hexapdf/test_dictionary.rb +10 -0
  205. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  206. data/test/hexapdf/test_document.rb +5 -0
  207. data/test/hexapdf/test_filter.rb +8 -0
  208. data/test/hexapdf/test_importer.rb +9 -0
  209. data/test/hexapdf/test_object.rb +16 -5
  210. data/test/hexapdf/test_stream.rb +7 -0
  211. data/test/hexapdf/test_writer.rb +3 -3
  212. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  213. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  214. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  215. data/test/hexapdf/type/test_catalog.rb +11 -0
  216. data/test/hexapdf/type/test_form.rb +119 -0
  217. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  218. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  219. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  220. data/test/hexapdf/type/test_page.rb +20 -6
  221. metadata +28 -8
@@ -0,0 +1,728 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/layout/table_box'
6
+
7
+ describe HexaPDF::Layout::TableBox::Cell do
8
+ before do
9
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 0, 0)
10
+ end
11
+
12
+ def create_cell(**kwargs)
13
+ HexaPDF::Layout::TableBox::Cell.new(row: 1, column: 1, **kwargs)
14
+ end
15
+
16
+ describe "initialize" do
17
+ it "creates a new instance with the given arguments" do
18
+ cell = create_cell(children: [:a], row: 5, column: 3, row_span: 7, col_span: 2,
19
+ style: {background_color: 'blue', padding: 3, border: {width: 3}})
20
+ assert_equal([:a], cell.children)
21
+ assert_equal(5, cell.row)
22
+ assert_equal(3, cell.column)
23
+ assert_equal(7, cell.row_span)
24
+ assert_equal(2, cell.col_span)
25
+ assert_equal('blue', cell.style.background_color)
26
+ assert_equal(3, cell.style.padding.left)
27
+ assert_equal(3, cell.style.border.width.left)
28
+ end
29
+
30
+ it "uses defaults for attributes that were not given" do
31
+ cell = create_cell
32
+ assert_equal(1, cell.row_span)
33
+ assert_equal(1, cell.col_span)
34
+ end
35
+
36
+ it "uses defaults for the border and padding" do
37
+ cell = create_cell
38
+ assert(cell.style.border.width.simple?)
39
+ assert_equal(1, cell.style.border.width.left)
40
+ assert(cell.style.padding.simple?)
41
+ assert_equal(5, cell.style.padding.left)
42
+ end
43
+ end
44
+
45
+ describe "empty?" do
46
+ it "returns true if the cell has not been fit yet" do
47
+ cell = create_cell(children: [:a], style: {border: {width: 0}})
48
+ assert(cell.empty?)
49
+ end
50
+
51
+ it "returns true if the cell has no content" do
52
+ cell = create_cell(children: nil, style: {border: {width: 0}})
53
+ cell.fit(100, 100, @frame)
54
+ assert(cell.empty?)
55
+ end
56
+ end
57
+
58
+ describe "update_height" do
59
+ it "updates the height to the correct one" do
60
+ cell = create_cell(children: HexaPDF::Layout::Box.create(width: 10, height: 10))
61
+ cell.fit(100, 100, @frame)
62
+ assert_equal(22, cell.height)
63
+ cell.update_height(50)
64
+ assert_equal(50, cell.height)
65
+ end
66
+
67
+ it "fails if the given height is smaller than the one determined during #fit" do
68
+ cell = create_cell(children: HexaPDF::Layout::Box.create(width: 10, height: 10))
69
+ cell.fit(100, 100, @frame)
70
+ err = assert_raises(HexaPDF::Error) { cell.update_height(5) }
71
+ assert_match(/at least as big/, err.message)
72
+ end
73
+ end
74
+
75
+ describe "fit" do
76
+ it "fits a single box" do
77
+ cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10))
78
+ cell.fit(100, 100, @frame)
79
+ assert_equal(100, cell.width)
80
+ assert_equal(22, cell.height)
81
+ assert_equal(32, cell.preferred_width)
82
+ assert_equal(22, cell.preferred_height)
83
+ end
84
+
85
+ it "fits a single box with horizontal aligning not being :left" do
86
+ cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center))
87
+ cell.fit(100, 100, @frame)
88
+ assert_equal(66, cell.preferred_width)
89
+ end
90
+
91
+ it "fits multiple boxes" do
92
+ box1 = HexaPDF::Layout::Box.create(width: 20, height: 10)
93
+ box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
94
+ cell = create_cell(children: [box1, box2])
95
+ cell.fit(100, 100, @frame)
96
+ assert_equal(100, cell.width)
97
+ assert_equal(37, cell.height)
98
+ assert_equal(62, cell.preferred_width)
99
+ assert_equal(37, cell.preferred_height)
100
+ end
101
+
102
+ it "fits multiple boxes with horizontal aligning not being :left" do
103
+ box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center)
104
+ box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
105
+ cell = create_cell(children: [box1, box2])
106
+ cell.fit(100, 100, @frame)
107
+ assert_equal(66, cell.preferred_width)
108
+ end
109
+
110
+ it "fits the cell even if it has no content" do
111
+ cell = create_cell(children: nil)
112
+ cell.fit(100, 100, @frame)
113
+ assert_equal(100, cell.width)
114
+ assert_equal(12, cell.height)
115
+ assert_equal(12, cell.preferred_width)
116
+ assert_equal(12, cell.preferred_height)
117
+ end
118
+
119
+ it "doesn't fit anything if the available width or height are too small" do
120
+ cell = create_cell(children: nil)
121
+ refute(cell.fit(10, 100, @frame))
122
+ refute(cell.fit(100, 10, @frame))
123
+ end
124
+ end
125
+
126
+ describe "draw" do
127
+ before do
128
+ @canvas = HexaPDF::Document.new.pages.add.canvas
129
+ end
130
+
131
+ it "draws the boxes at the correct location" do
132
+ draw_block = lambda {|canvas, _| canvas.move_to(0, 0).end_path }
133
+ box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center, &draw_block)
134
+ box2 = HexaPDF::Layout::Box.create(width: 50, height: 15, &draw_block)
135
+ box = create_cell(children: [box1, box2])
136
+ box.fit(100, 100, @frame)
137
+ box.draw(@canvas, 10, 75)
138
+ operators = [[:save_graphics_state],
139
+ [:append_rectangle, [9.5, 74.5, 101.0, 38.0]],
140
+ [:clip_path_non_zero],
141
+ [:end_path],
142
+ [:append_rectangle, [10.0, 75.0, 100.0, 37.0]],
143
+ [:stroke_path],
144
+ [:restore_graphics_state],
145
+ [:save_graphics_state],
146
+ [:concatenate_matrix, [1, 0, 0, 1, 50.0, 96]],
147
+ [:move_to, [0, 0]],
148
+ [:end_path],
149
+ [:restore_graphics_state],
150
+ [:save_graphics_state],
151
+ [:concatenate_matrix, [1, 0, 0, 1, 16, 81]],
152
+ [:move_to, [0, 0]],
153
+ [:end_path],
154
+ [:restore_graphics_state]]
155
+ assert_operators(@canvas.contents, operators)
156
+ end
157
+
158
+ it "works for a cell without content" do
159
+ box = create_cell(children: nil, style: {border: {width: 0}})
160
+ box.fit(100, 100, @frame)
161
+ box.draw(@canvas, 10, 75)
162
+ assert_operators(@canvas.contents, [])
163
+ end
164
+ end
165
+
166
+ it "returns a useful representation when inspecting" do
167
+ cell = create_cell(row: 3, column: 2, row_span: 2, col_span: 3, children: [:a, "b"])
168
+ assert_equal("<Cell (3,2) 2x3 [Symbol, String]>", cell.inspect)
169
+ end
170
+ end
171
+
172
+ describe HexaPDF::Layout::TableBox::Cells do
173
+ def create_cells(data, cell_style = nil)
174
+ HexaPDF::Layout::TableBox::Cells.new(data, cell_style: cell_style)
175
+ end
176
+
177
+ describe "intialize" do
178
+ it "works with simple data" do
179
+ cells = create_cells([[:a]])
180
+ assert_equal(1, cells.number_of_columns)
181
+ assert_equal(1, cells.number_of_rows)
182
+ assert_equal(:a, cells[0, 0].children)
183
+
184
+ cells = create_cells([[:a, :b, :c]])
185
+ assert_equal(3, cells.number_of_columns)
186
+ assert_equal(1, cells.number_of_rows)
187
+ assert_equal(:a, cells[0, 0].children)
188
+ assert_equal(:b, cells[0, 1].children)
189
+ assert_equal(:c, cells[0, 2].children)
190
+
191
+ cells = create_cells([[:a], [:b], [:c]])
192
+ assert_equal(1, cells.number_of_columns)
193
+ assert_equal(3, cells.number_of_rows)
194
+ assert_equal(:a, cells[0, 0].children)
195
+ assert_equal(:b, cells[1, 0].children)
196
+ assert_equal(:c, cells[2, 0].children)
197
+
198
+ cells = create_cells([[:a, :b], [:c, :d, :e], [:f]])
199
+ assert_equal(3, cells.number_of_columns)
200
+ assert_equal(3, cells.number_of_rows)
201
+ assert_equal(:a, cells[0, 0].children)
202
+ assert_equal(:b, cells[0, 1].children)
203
+ assert_nil(cells[0, 2])
204
+ assert_equal(:c, cells[1, 0].children)
205
+ assert_equal(:d, cells[1, 1].children)
206
+ assert_equal(:e, cells[1, 2].children)
207
+ assert_equal(:f, cells[2, 0].children)
208
+ assert_nil(cells[2, 1])
209
+ assert_nil(cells[2, 2])
210
+ end
211
+
212
+ it "can handle column spans" do
213
+ cells = create_cells([[{col_span: 2, content: :a}, :b], [:c, {col_span: 3, content: :d}]])
214
+ assert_equal(4, cells.number_of_columns)
215
+ assert_equal(2, cells.number_of_rows)
216
+ assert_equal(:a, cells[0, 0].children)
217
+ assert_same(cells[0, 0], cells[0, 1])
218
+ assert_equal(:b, cells[0, 2].children)
219
+ assert_equal(:c, cells[1, 0].children)
220
+ assert_equal(:d, cells[1, 1].children)
221
+ assert_same(cells[1, 1], cells[1, 2])
222
+ assert_same(cells[1, 1], cells[1, 3])
223
+ end
224
+
225
+ it "can handle row spans" do
226
+ cells = create_cells([[{row_span: 2, content: :a}, :b], [{row_span: 2, content: :c}], [:d]])
227
+ assert_equal(2, cells.number_of_columns)
228
+ assert_equal(3, cells.number_of_rows)
229
+ assert_equal(:a, cells[0, 0].children)
230
+ assert_equal(:b, cells[0, 1].children)
231
+ assert_same(cells[0, 0], cells[1, 0])
232
+ assert_equal(:c, cells[1, 1].children)
233
+ assert_equal(:d, cells[2, 0].children)
234
+ assert_same(cells[1, 1], cells[2, 1])
235
+ end
236
+
237
+ it "can handle column and row spans concurrently" do
238
+ cells = create_cells([[:a, {col_span: 2, content: :b}, :c],
239
+ [{col_span: 2, row_span: 2, content: :d}, :e, :f],
240
+ [{row_span: 2, content: :g}, :h],
241
+ [:i, :j, :k]])
242
+ assert_equal(:a, cells[0, 0].children)
243
+ assert_equal(:b, cells[0, 1].children)
244
+ assert_same(cells[0, 1], cells[0, 2])
245
+ assert_equal(:c, cells[0, 3].children)
246
+ assert_equal(:d, cells[1, 0].children)
247
+ assert_same(cells[1, 0], cells[1, 1])
248
+ assert_equal(:e, cells[1, 2].children)
249
+ assert_equal(:f, cells[1, 3].children)
250
+ assert_same(cells[1, 0], cells[2, 0])
251
+ assert_same(cells[1, 0], cells[2, 1])
252
+ assert_equal(:g, cells[2, 2].children)
253
+ assert_equal(:h, cells[2, 3].children)
254
+ assert_equal(:i, cells[3, 0].children)
255
+ assert_equal(:j, cells[3, 1].children)
256
+ assert_same(cells[2, 2], cells[3, 2])
257
+ assert_equal(:k, cells[3, 3].children)
258
+ end
259
+
260
+ it "sets the correct information on the created cells" do
261
+ cells = create_cells([[:a, {col_span: 2, content: :b}],
262
+ [{col_span: 2, row_span: 2, content: :c}, {row_span: 2, content: :d}]])
263
+ assert_equal(0, cells[0, 0].row)
264
+ assert_equal(0, cells[0, 0].column)
265
+ assert_equal(1, cells[0, 0].row_span)
266
+ assert_equal(1, cells[0, 0].col_span)
267
+ assert_equal(0, cells[0, 1].row)
268
+ assert_equal(1, cells[0, 1].column)
269
+ assert_equal(1, cells[0, 1].row_span)
270
+ assert_equal(2, cells[0, 1].col_span)
271
+ assert_equal(1, cells[1, 0].row)
272
+ assert_equal(0, cells[1, 0].column)
273
+ assert_equal(2, cells[1, 0].row_span)
274
+ assert_equal(2, cells[1, 0].col_span)
275
+ assert_equal(1, cells[1, 2].row)
276
+ assert_equal(2, cells[1, 2].column)
277
+ assert_equal(2, cells[1, 2].row_span)
278
+ assert_equal(1, cells[1, 2].col_span)
279
+ end
280
+
281
+ it "allows setting cell styles and properties" do
282
+ cells = create_cells([[{content: :a, background_color: 'black', properties: {'x' => 'y'}}]])
283
+ assert_equal('black', cells[0, 0].style.background_color)
284
+ assert_equal('y', cells[0, 0].properties['x'])
285
+ end
286
+
287
+ it "allows setting styles for all cells using a hash as first item in data" do
288
+ cells = create_cells([{background_color: 'black'}, [:a, :b], [:c, :d]])
289
+ assert_equal('black', cells[0, 0].style.background_color)
290
+ assert_equal('black', cells[1, 0].style.background_color)
291
+ end
292
+
293
+ it "allows setting styles for all cells using a proc as first item in data" do
294
+ block = lambda {|cell| cell.style.background_color = 'black' if cell.row == 0 }
295
+ cells = create_cells([block, [:a, :b], [:c, :d]])
296
+ assert_equal('black', cells[0, 0].style.background_color)
297
+ assert_nil(cells[1, 0].style.background_color)
298
+ end
299
+
300
+ it "allows setting styles for all cells using a hash as the cell_style argument" do
301
+ cells = create_cells([[:a, :b], [:c, :d]], {background_color: 'black'})
302
+ assert_equal('black', cells[0, 0].style.background_color)
303
+ assert_equal('black', cells[1, 0].style.background_color)
304
+ end
305
+
306
+ it "allows setting styles for all cells using a proc as the cell_style argument" do
307
+ block = lambda {|cell| cell.style.background_color = 'black' if cell.row == 0 }
308
+ cells = create_cells([[:a, :b], [:c, :d]], block)
309
+ assert_equal('black', cells[0, 0].style.background_color)
310
+ assert_nil(cells[1, 0].style.background_color)
311
+ end
312
+
313
+ it "only uses the styling informtion from data if a cell_style argument is also provided" do
314
+ cells = create_cells([{background_color: 'yellow'}, [:a, :b], [:c, :d]], {background_color: 'black'})
315
+ assert_equal('yellow', cells[0, 0].style.background_color)
316
+ end
317
+
318
+ it "makes sure that cell styling information overrides global styling information" do
319
+ block = lambda do |cell|
320
+ cell.style.background_color = 'yellow'
321
+ cell.properties['key'] = :value
322
+ end
323
+ cells = create_cells([block, [{background_color: 'black', properties: {'key' => 5}, content: :a}, :b],
324
+ [:c, :d]])
325
+ assert_equal('black', cells[0, 0].style.background_color)
326
+ assert_equal(5, cells[0, 0].properties['key'])
327
+ end
328
+ end
329
+
330
+ describe "each_row" do
331
+ it "allows iterating over rows" do
332
+ cells = create_cells([[:a, :b], [:c], [:d, :e]])
333
+ assert_equal([[:a, :b], [:c], [:d, :e]], cells.each_row.map {|cols| cols.map(&:children) })
334
+ end
335
+ end
336
+
337
+ describe "style" do
338
+ it "assigns the style properties to all cells" do
339
+ cells = create_cells([[:a, :b], [:c, :d]])
340
+ cells.style(background_color: 'blue')
341
+ assert_equal('blue', cells[0, 0].style.background_color)
342
+ assert_equal('blue', cells[0, 1].style.background_color)
343
+ assert_equal('blue', cells[1, 0].style.background_color)
344
+ assert_equal('blue', cells[1, 1].style.background_color)
345
+ end
346
+
347
+ it "calls the given block for all cells" do
348
+ cells = create_cells([[:a, :b], [:c, :d]])
349
+ cells.style(background_color: 'blue') {|cell| cell.style.background_color = 'red' if cell.row == 0 }
350
+ assert_equal('red', cells[0, 0].style.background_color)
351
+ assert_equal('red', cells[0, 1].style.background_color)
352
+ assert_equal('blue', cells[1, 0].style.background_color)
353
+ assert_equal('blue', cells[1, 1].style.background_color)
354
+ end
355
+ end
356
+
357
+ #fit_rows and draw_rows are tested through TableBox#fit/#draw
358
+ end
359
+
360
+ describe HexaPDF::Layout::TableBox do
361
+ before do
362
+ @doc = HexaPDF::Document.new
363
+ @page = @doc.pages.add
364
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 160, 100, context: @page)
365
+ draw_block = lambda {|canvas, _box| canvas.move_to(0, 0).end_path }
366
+ @fixed_size_boxes = 15.times.map { HexaPDF::Layout::Box.new(width: 20, height: 10, &draw_block) }
367
+ end
368
+
369
+ def create_box(**kwargs)
370
+ HexaPDF::Layout::TableBox.new(cells: [@fixed_size_boxes[0, 2], @fixed_size_boxes[2, 2]], **kwargs)
371
+ end
372
+
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")
375
+ assert_equal(width, box.width, "box width")
376
+ assert_equal(height, box.height, "box height")
377
+ if cell_data
378
+ cells = box.cells.each_row.to_a.flatten
379
+ assert_equal(cells.size, cell_data.size)
380
+ cell_data.each_with_index do |(left, top, cwidth, cheight), index|
381
+ cell = cells[index]
382
+ if left.nil?
383
+ assert_nil(cell.left, "cell #{index} left")
384
+ else
385
+ assert_equal(left, cell.left, "cell #{index} left")
386
+ end
387
+ if top.nil?
388
+ assert_nil(cell.top, "cell #{index} top")
389
+ else
390
+ assert_equal(top, cell.top, "cell #{index} top")
391
+ end
392
+ assert_equal(cwidth, cell.width, "cell #{index} width")
393
+ assert_equal(cheight, cell.height, "cell #{index} height")
394
+ end
395
+ end
396
+ box
397
+ end
398
+
399
+ def cell_infos(cells)
400
+ cells.each_row.map {|cols| cols.map {|c| [c.left, c.top, c.width, c.height] } }.flatten(1)
401
+ end
402
+
403
+ describe "initialize" do
404
+ it "creates a new instance with the default arguments" do
405
+ box = create_box(cells: [[:a, :b], [:c]])
406
+ assert_equal([[:a, :b], [:c]], box.cells.each_row.map {|cols| cols.map(&:children) })
407
+ assert_equal([], box.column_widths)
408
+ assert_nil(box.header_cells)
409
+ assert_nil(box.footer_cells)
410
+ assert_equal(0, box.start_row_index)
411
+ assert_equal(-1, box.last_fitted_row_index)
412
+ refute(box.supports_position_flow?)
413
+ end
414
+
415
+ it "creates a new instance with the given arguments" do
416
+ header = lambda {|_| [[:h1, :h2]] }
417
+ footer = lambda {|_| [[:f1], [:f2]] }
418
+ box = create_box(cells: [[:a, :b], [:c]], column_widths: [-2, -1], header: header, footer: footer,
419
+ cell_style: {background_color: 'black'})
420
+ assert_equal([[:a, :b], [:c]], box.cells.each_row.map {|cols| cols.map(&:children) })
421
+ assert_equal([[:h1, :h2]], box.header_cells.each_row.map {|cols| cols.map(&:children) })
422
+ assert_equal([[:f1], [:f2]], box.footer_cells.each_row.map {|cols| cols.map(&:children) })
423
+ assert_equal([-2, -1], box.column_widths)
424
+ [box.cells[0, 0], box.header_cells[0, 0], box.footer_cells[0, 0]].each do |cell|
425
+ assert_equal('black', cell.style.background_color)
426
+ end
427
+ end
428
+
429
+ it "also applies the cell_style information to header and footer cells of split boxes" do
430
+ header = lambda {|_| [[nil]] }
431
+ footer = lambda {|_| [[nil]] }
432
+ box = create_box(header: header, footer: footer, cells: [[nil], [nil]],
433
+ cell_style: {background_color: 'black'})
434
+ refute(box.fit(100, 40, @frame))
435
+ box_a, box_b = box.split(100, 40, nil)
436
+ assert_same(box_a, box)
437
+ assert_equal('black', box_b.header_cells[0, 0].style.background_color)
438
+ assert_equal('black', box_b.footer_cells[0, 0].style.background_color)
439
+ end
440
+
441
+ it "allows providing a Cells instance instead of an array of arrays" do
442
+ box = create_box(cells: HexaPDF::Layout::TableBox::Cells.new([[:a, :b]]))
443
+ assert_equal(:a, box.cells[0, 0].children)
444
+ end
445
+ end
446
+
447
+ describe "empty?" do
448
+ it "is empty if nothing is fit yet" do
449
+ assert(create_box.empty?)
450
+ end
451
+
452
+ it "is empty if not as single row fits" do
453
+ box = create_box(column_widths: [5])
454
+ box.fit(@frame.available_width, @frame.available_height, @frame)
455
+ assert(box.empty?)
456
+ end
457
+
458
+ it "is not empty if at least one box fits" do
459
+ box = create_box
460
+ box.fit(@frame.available_width, @frame.available_height, @frame)
461
+ refute(box.empty?)
462
+ end
463
+ end
464
+
465
+ describe "fit" do
466
+ it "respects the set initial width" do
467
+ box = create_box(width: 50)
468
+ box.fit(@frame.available_width, @frame.available_height, @frame)
469
+ assert_equal(50, box.width)
470
+ end
471
+
472
+ it "respects the set initial height" do
473
+ box = create_box(height: 50)
474
+ box.fit(@frame.available_width, @frame.available_height, @frame)
475
+ assert_equal(50, box.height)
476
+ end
477
+
478
+ it "respects the border and padding" do
479
+ box = create_box(column_widths: [40, 40], style: {border: {width: [5, 4, 3, 2]}, padding: [5, 4, 3, 2]})
480
+ box.fit(@frame.available_width, @frame.available_height, @frame)
481
+ assert_equal(93, box.width)
482
+ assert_equal(61, box.height)
483
+ end
484
+
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
496
+ box = create_box(column_widths: [200])
497
+ refute(box.fit(@frame.available_width, @frame.available_height, @frame))
498
+ end
499
+
500
+ it "fits a simple table" do
501
+ check_box(create_box, true, 160, 45,
502
+ [[0, 0, 79.5, 22], [79.5, 0, 79.5, 22], [0, 22, 79.5, 22], [79.5, 22, 79.5, 22]])
503
+ end
504
+
505
+ it "fits a table with column and row spans" do
506
+ cells = [[@fixed_size_boxes[0], {col_span: 2, content: @fixed_size_boxes[1]}, @fixed_size_boxes[2]],
507
+ [{col_span: 2, row_span: 2, content: @fixed_size_boxes[3]}, *@fixed_size_boxes[4, 2]],
508
+ [{row_span: 2, content: @fixed_size_boxes[6]}, @fixed_size_boxes[7]],
509
+ @fixed_size_boxes[8, 3]]
510
+ check_box(create_box(cells: cells), true, 160, 89,
511
+ [[0, 0, 39.75, 22], [39.75, 0, 79.5, 22], [39.75, 0, 79.50, 22], [119.25, 0, 39.75, 22],
512
+ [0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 22, 39.75, 22], [119.25, 22, 39.75, 22],
513
+ [0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 44, 39.75, 44], [119.25, 44, 39.75, 22],
514
+ [0, 66, 39.75, 22], [39.75, 66, 39.75, 22], [79.5, 44, 39.75, 44], [119.25, 66, 39.75, 22]])
515
+ end
516
+
517
+ it "fits a table with header rows" do
518
+ result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
519
+ header = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
520
+ box = create_box(header: header, cell_style: {padding: 0, border: {width: 0}})
521
+ box = check_box(box, true, 160, 40, result)
522
+ assert_equal(result, cell_infos(box.header_cells))
523
+ end
524
+
525
+ it "fits a table with footer rows" do
526
+ result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
527
+ footer = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
528
+ box = create_box(footer: footer, cell_style: {padding: 0, border: {width: 0}})
529
+ box = check_box(box, true, 160, 40, result)
530
+ assert_equal(result, cell_infos(box.footer_cells))
531
+ end
532
+
533
+ it "fits a table with header and footer rows" do
534
+ result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
535
+ cell_creator = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
536
+ box = create_box(header: cell_creator, footer: cell_creator,
537
+ cell_style: {padding: 0, border: {width: 0}})
538
+ box = check_box(box, true, 160, 60, result)
539
+ assert_equal(result, cell_infos(box.header_cells))
540
+ assert_equal(result, cell_infos(box.footer_cells))
541
+ end
542
+
543
+ it "partially fits a table if not enough height is available" do
544
+ box = create_box(height: 10, cell_style: {padding: 0, border: {width: 0}})
545
+ check_box(box, false, 160, 10,
546
+ [[0, 0, 80, 10], [80, 0, 80, 10], [nil, nil, 80, 0], [nil, nil, 0, 0]])
547
+ end
548
+ end
549
+
550
+ describe "split" do
551
+ it "splits the table if some rows could not be fit into the available region" do
552
+ box = create_box
553
+ refute(box.fit(100, 25, @frame))
554
+ box_a, box_b = box.split(100, 25, @frame)
555
+ assert_same(box_a, box)
556
+ assert(box_b.split_box?)
557
+
558
+ assert_equal(0, box_a.start_row_index)
559
+ assert_equal(0, box_a.last_fitted_row_index)
560
+ assert_equal(1, box_b.start_row_index)
561
+ assert_equal(-1, box_b.last_fitted_row_index)
562
+ end
563
+
564
+ it "splits the table if the header or footer rows don't fit" do
565
+ cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
566
+ [{header: cells_creator}, {footer: cells_creator}].each do |args|
567
+ box = create_box(**args)
568
+ box.cells.style(padding: 0, border: {width: 0})
569
+ refute(box.fit(100, 25, @frame))
570
+ box_a, box_b = box.split(100, 25, @frame)
571
+ assert_nil(box_a)
572
+ assert_same(box_b, box)
573
+ end
574
+ end
575
+
576
+ it "splits a table with a header or a footer" do
577
+ cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
578
+ [{header: cells_creator}, {footer: cells_creator}].each do |args|
579
+ box = create_box(**args)
580
+ refute(box.fit(100, 50, @frame))
581
+ box_a, box_b = box.split(100, 50, @frame)
582
+ assert_same(box_a, box)
583
+
584
+ assert_equal(0, box_a.start_row_index)
585
+ assert_equal(0, box_a.last_fitted_row_index)
586
+ assert_equal(1, box_b.start_row_index)
587
+ assert_equal(-1, box_b.last_fitted_row_index)
588
+ assert_nil(box_b.instance_variable_get(:@special_cells_fit_not_successful))
589
+ if args.key?(:header)
590
+ refute_same(box_a.header_cells, box_b.header_cells)
591
+ else
592
+ refute_same(box_a.footer_cells, box_b.footer_cells)
593
+ end
594
+ end
595
+ end
596
+ end
597
+
598
+ describe "draw_content" do
599
+ before do
600
+ @canvas = @page.canvas
601
+ end
602
+
603
+ it "draws the result onto the canvas" do
604
+ box = create_box
605
+ box.fit(100, 100, @frame)
606
+ box.draw(@canvas, 20, 10)
607
+ operators = [[:save_graphics_state],
608
+ [:append_rectangle, [20.0, 32.0, 50.5, 23.0]],
609
+ [:clip_path_non_zero],
610
+ [:end_path],
611
+ [:append_rectangle, [20.5, 32.5, 49.5, 22.0]],
612
+ [:stroke_path],
613
+ [:restore_graphics_state],
614
+ [:save_graphics_state],
615
+ [:concatenate_matrix, [1, 0, 0, 1, 26.5, 38.5]],
616
+ [:move_to, [0, 0]],
617
+ [:end_path],
618
+ [:restore_graphics_state],
619
+ [:save_graphics_state],
620
+ [:append_rectangle, [69.5, 32.0, 50.5, 23.0]],
621
+ [:clip_path_non_zero],
622
+ [:end_path],
623
+ [:append_rectangle, [70.0, 32.5, 49.5, 22.0]],
624
+ [:stroke_path],
625
+ [:restore_graphics_state],
626
+ [:save_graphics_state],
627
+ [:concatenate_matrix, [1, 0, 0, 1, 76.0, 38.5]],
628
+ [:move_to, [0, 0]],
629
+ [:end_path],
630
+ [:restore_graphics_state],
631
+ [:save_graphics_state],
632
+ [:append_rectangle, [20.0, 10.0, 50.5, 23.0]],
633
+ [:clip_path_non_zero],
634
+ [:end_path],
635
+ [:append_rectangle, [20.5, 10.5, 49.5, 22.0]],
636
+ [:stroke_path],
637
+ [:restore_graphics_state],
638
+ [:save_graphics_state],
639
+ [:concatenate_matrix, [1, 0, 0, 1, 26.5, 16.5]],
640
+ [:move_to, [0, 0]],
641
+ [:end_path],
642
+ [:restore_graphics_state],
643
+ [:save_graphics_state],
644
+ [:append_rectangle, [69.5, 10.0, 50.5, 23.0]],
645
+ [:clip_path_non_zero],
646
+ [:end_path],
647
+ [:append_rectangle, [70.0, 10.5, 49.5, 22.0]],
648
+ [:stroke_path],
649
+ [:restore_graphics_state],
650
+ [:save_graphics_state],
651
+ [:concatenate_matrix, [1, 0, 0, 1, 76.0, 16.5]],
652
+ [:move_to, [0, 0]],
653
+ [:end_path],
654
+ [:restore_graphics_state]]
655
+ assert_operators(@canvas.contents, operators)
656
+ end
657
+
658
+ it "correctly works for split boxes" do
659
+ box = create_box(cell_style: {padding: 0, border: {width: 0}})
660
+ refute(box.fit(100, 10, @frame))
661
+ _, split_box = box.split(100, 10, @frame)
662
+ assert(split_box.fit(100, 100, @frame))
663
+
664
+ box.draw(@canvas, 20, 10)
665
+ split_box.draw(@canvas, 0, 50)
666
+ operators = [[:save_graphics_state],
667
+ [:concatenate_matrix, [1, 0, 0, 1, 20.0, 10]],
668
+ [:move_to, [0, 0]],
669
+ [:end_path],
670
+ [:restore_graphics_state],
671
+ [:save_graphics_state],
672
+ [:concatenate_matrix, [1, 0, 0, 1, 70.0, 10]],
673
+ [:move_to, [0, 0]],
674
+ [:end_path],
675
+ [:restore_graphics_state],
676
+ [:save_graphics_state],
677
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 50]],
678
+ [:move_to, [0, 0]],
679
+ [:end_path],
680
+ [:restore_graphics_state],
681
+ [:save_graphics_state],
682
+ [:concatenate_matrix, [1, 0, 0, 1, 50.0, 50]],
683
+ [:move_to, [0, 0]],
684
+ [:end_path],
685
+ [:restore_graphics_state]]
686
+ assert_operators(@canvas.contents, operators)
687
+ end
688
+
689
+ it "correctly works for tables with headers and footers" do
690
+ box = create_box(header: lambda {|_| [@fixed_size_boxes[10, 1]] },
691
+ footer: lambda {|_| [@fixed_size_boxes[12, 1]] },
692
+ cell_style: {padding: 0, border: {width: 0}})
693
+ box.fit(100, 100, @frame)
694
+ box.draw(@canvas, 20, 10)
695
+ operators = [[:save_graphics_state],
696
+ [:concatenate_matrix, [1, 0, 0, 1, 20, 40]],
697
+ [:move_to, [0, 0]],
698
+ [:end_path],
699
+ [:restore_graphics_state],
700
+ [:save_graphics_state],
701
+ [:concatenate_matrix, [1, 0, 0, 1, 20, 30]],
702
+ [:move_to, [0, 0]],
703
+ [:end_path],
704
+ [:restore_graphics_state],
705
+ [:save_graphics_state],
706
+ [:concatenate_matrix, [1, 0, 0, 1, 70.0, 30]],
707
+ [:move_to, [0, 0]],
708
+ [:end_path],
709
+ [:restore_graphics_state],
710
+ [:save_graphics_state],
711
+ [:concatenate_matrix, [1, 0, 0, 1, 20, 20]],
712
+ [:move_to, [0, 0]],
713
+ [:end_path],
714
+ [:restore_graphics_state],
715
+ [:save_graphics_state],
716
+ [:concatenate_matrix, [1, 0, 0, 1, 70.0, 20]],
717
+ [:move_to, [0, 0]],
718
+ [:end_path],
719
+ [:restore_graphics_state],
720
+ [:save_graphics_state],
721
+ [:concatenate_matrix, [1, 0, 0, 1, 20.0, 10]],
722
+ [:move_to, [0, 0]],
723
+ [:end_path],
724
+ [:restore_graphics_state]]
725
+ assert_operators(@canvas.contents, operators)
726
+ end
727
+ end
728
+ end