hexapdf 0.32.2 → 0.34.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 (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