Class: HexaPDF::Layout::TableBox::Cells
- Inherits:
-
Object
- Object
- HexaPDF::Layout::TableBox::Cells
- Defined in:
- lib/hexapdf/layout/table_box.rb
Overview
Represents the cells of a TableBox.
This class is a wrapper around an array of arrays and provides some utility methods for managing and styling the cells.
Table data transformation into correct form
One of the main purposes of this class is to transform the cell data provided on initialization into the representation a TableBox instance can work with.
The data argument for ::new is an array of arrays representing the rows of the table. Each row array may contain one of the following items:
-
A single Box instance defining the content of the cell.
-
An array of Box instances defining the content of the cell.
-
A hash which defines the content of the cell as well as, optionally, additional information through the following keys:
:content-
The content for the cell. This may be a single Box or an array of Box instances.
:row_span-
An integer specifying the number of rows this cell should span.
:col_span-
An integer specifying the number of columsn this cell should span.
:min_height-
A number specifying the minimum height of the table cell.
:properties-
A hash of properties (see Box#properties) to be set on the cell itself.
All other key-value pairs are taken to be cell styling information (like
:background_color) and assigned to the cell style.
Additionally, the first item in the data argument is treated specially if it is not an array:
-
If it is a hash, it is assumed to be style properties to be set on all created cell instances.
-
If it is a callable object, it needs to accept a cell as argument and is called for all created cell instances.
Any properties or styling information retrieved from the respective item in data takes precedence over the above globally specified information.
Here is an example input data array:
data = [[box1, {col_span: 2, content: box2}, box3],
[box4, box5, {col_span: 2, row_span: 2, content: [box6.1, box6.2]}],
[box7, box8]]
And this is what the table will look like:
| box1 | box2 | box 3 |
| box4 | box5 | box6.1 box6.2 |
| box7 | box8 | |
Instance Method Summary collapse
-
#[](row, column) ⇒ Object
Returns the cell (a Cell instance) in the given row and column.
-
#draw_rows(start_row, end_row, canvas, x, y) ⇒ Object
Draws the rows from
start_rowtoend_rowon the givencanvas, with the top-left corner of the resulting table being at (x,y). -
#each_row(&block) ⇒ Object
Iterates over each row.
-
#fit_rows(start_row, available_height, column_info, frame) ⇒ Object
Fits all rows starting from
start_rowinto an area with the givenavailable_height, using the column information incolumn_info. -
#initialize(data, cell_style: nil) ⇒ Cells
constructor
Creates a new Cells instance with the given
datawhich cannot be changed afterwards. -
#number_of_columns ⇒ Object
Returns the number of columns.
-
#number_of_rows ⇒ Object
Returns the number of rows.
-
#style(**properties, &block) ⇒ Object
Applies the given style properties to all cells and optionally yields all cells for more complex customization.
Constructor Details
#initialize(data, cell_style: nil) ⇒ Cells
Creates a new Cells instance with the given data which cannot be changed afterwards.
The optional cell_style argument can either be a hash of style properties to be assigned to every cell or a block accepting a cell for more control over e.g. style assignment. If the data has such a cell style as its first item, the cell_style argument is not used.
See the class documentation for details on the data argument.
381 382 383 384 385 |
# File 'lib/hexapdf/layout/table_box.rb', line 381 def initialize(data, cell_style: nil) @cells = [] @number_of_columns = 0 assign_data(data, cell_style) end |
Instance Method Details
#[](row, column) ⇒ Object
Returns the cell (a Cell instance) in the given row and column.
Note that the same cell instance may be returned for different (row, column) arguments if the cell spans more than one row and/or column.
391 392 393 |
# File 'lib/hexapdf/layout/table_box.rb', line 391 def [](row, column) row == @overridden_row_index ? @overridden_row_cells[column] : @cells[row]&.[](column) end |
#draw_rows(start_row, end_row, canvas, x, y) ⇒ Object
Draws the rows from start_row to end_row on the given canvas, with the top-left corner of the resulting table being at (x, y).
549 550 551 552 553 554 555 556 557 |
# File 'lib/hexapdf/layout/table_box.rb', line 549 def draw_rows(start_row, end_row, canvas, x, y) @cells[start_row..end_row].each.with_index(start_row) do |columns, row_index| columns = @overridden_row_cells if row_index == @overridden_row_index columns.each_with_index do |cell, col_index| next if cell.row != row_index || cell.column != col_index cell.draw(canvas, x + cell.left, y - cell.top - cell.height) end end end |
#each_row(&block) ⇒ Object
Iterates over each row.
406 407 408 409 410 411 412 413 414 415 416 |
# File 'lib/hexapdf/layout/table_box.rb', line 406 def each_row(&block) return to_enum(__method__) unless block_given? if @overridden_row_index @cells[0...@overridden_row_index].each(&block) block&.call(@overridden_row_cells) @cells[(@overridden_row_index + 1)..-1].each(&block) else @cells.each(&block) end end |
#fit_rows(start_row, available_height, column_info, frame) ⇒ Object
Fits all rows starting from start_row into an area with the given available_height, using the column information in column_info. Returns the used height as well as the row index of the last row that fit (which may be -1 if no row fits).
The column_info argument needs to be an array of arrays of the form [x_pos, width] containing the horizontal positions and widths of each column.
The frame argument is further handed down to the Cell instances for fitting.
The fitting of a cell is done through the Cell#fit method which stores the result in the cell itself. Furthermore, Cell#left and Cell#top are also assigned correctly.
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 |
# File 'lib/hexapdf/layout/table_box.rb', line 440 def fit_rows(start_row, available_height, column_info, frame) height = available_height last_fitted_row_index = -1 row_heights = {} zero_height_rows = {} row_spans = [] split_row_cells = nil @cells[start_row..-1].each.with_index(start_row) do |columns, row_index| columns = @overridden_row_cells if row_index == @overridden_row_index # Rows containing row-spanning cells aren't supported for cell-splitting row_has_row_spans = columns.any? {|cell| cell.row != row_index || cell.row_span > 1 } # 1. Fit all columns of the row and record the max height of all non-row-span cells. If # a row has zero height (usually because it only has row-span cells), record that # information. Additionally store all cells with row-spans. row_fit = true row_has_split_cells = false row_height = 0 columns.each_with_index do |cell, col_index| next if cell.row != row_index || cell.column != col_index available_cell_width = if cell.col_span > 1 column_info[cell.column, cell.col_span].map(&:last).sum else column_info[cell.column].last end cell_fit_result = cell.fit(available_cell_width, available_height, frame) unless cell_fit_result.success? if !row_has_row_spans && cell_fit_result.overflow? row_has_split_cells = true else row_fit = false break end end if row_height < cell.preferred_height && cell.row_span == 1 row_height = cell.preferred_height end row_spans << cell if cell.row_span > 1 end zero_height_rows[row_index] = true if row_height == 0 if row_fit # 2. If all cells of the row fit, we subtract the recorded row height of the # non-row-span cells from the available height for the next pass. last_fitted_row_index = row_index row_heights[row_index] = row_height available_height -= row_height # 3. We look at all row-span cells that end at the current row index. If the row-span # cell is larger than the sum of the row heights, we proportionally enlarge the # stored height of each spanned row and subtract the difference from the available # height for the next pass. If the row span contains initially zero-height rows, # only those rows are enlarged. Row-span cells themselves are not updated at this # point! row_spans.each do |cell| upper_row_index = cell.row + cell.row_span - 1 next unless upper_row_index == row_index rows = cell.row.upto(upper_row_index) row_span_height = rows.sum {|ri| row_heights[ri] } if row_span_height < cell.preferred_height zero_height_rows_in_span = rows.select {|ri| zero_height_rows[ri] } rows = zero_height_rows_in_span if zero_height_rows_in_span.size > 0 adjustment = (cell.preferred_height - row_span_height) / rows.size.to_f rows.each {|ri| row_heights[ri] += adjustment } available_height -= cell.preferred_height - row_span_height end end if row_has_split_cells split_row_cells = create_split_row_cells(columns) break end else last_fitted_row_index = columns.min_by(&:row).row - 1 if height != available_height break end end if last_fitted_row_index >= 0 # 4. Once all possible rows have been fitted and the heights of the rows are fixed, the # final height and top-left corner of each cell needs to be set. running_height = 0 @cells[start_row..last_fitted_row_index].each.with_index(start_row) do |columns, row_index| columns = @overridden_row_cells if row_index == @overridden_row_index columns.each_with_index do |cell, col_index| next if cell.row != row_index || cell.column != col_index cell.left = column_info[cell.column].first cell.top = running_height if cell.row_span == 1 cell.update_height(row_heights[row_index]) else new_height = cell.row.upto(cell.row + cell.row_span - 1).sum {|ri| row_heights[ri] } cell.update_height(new_height) end end running_height += row_heights[row_index] end end [height - available_height, last_fitted_row_index < start_row ? -1 : last_fitted_row_index, split_row_cells] end |
#number_of_columns ⇒ Object
Returns the number of columns.
401 402 403 |
# File 'lib/hexapdf/layout/table_box.rb', line 401 def number_of_columns @number_of_columns end |
#number_of_rows ⇒ Object
Returns the number of rows.
396 397 398 |
# File 'lib/hexapdf/layout/table_box.rb', line 396 def number_of_rows @cells.size end |
#style(**properties, &block) ⇒ Object
Applies the given style properties to all cells and optionally yields all cells for more complex customization.
420 421 422 423 424 425 426 427 |
# File 'lib/hexapdf/layout/table_box.rb', line 420 def style(**properties, &block) @cells.each do |columns| columns.each do |cell| cell.style.update(**properties) block&.call(cell) end end end |