lib/hexapdf/layout/table_box.rb in hexapdf-0.46.0 vs lib/hexapdf/layout/table_box.rb in hexapdf-0.47.0
- old
+ new
@@ -380,11 +380,18 @@
# 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.
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 = []
+
@cells[start_row..-1].each.with_index(start_row) do |columns, row_index|
+ # 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_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
@@ -394,27 +401,67 @@
end
unless cell.fit(available_cell_width, available_height, frame).success?
row_fit = false
break
end
- cell.left = column_info[cell.column].first
- cell.top = height - available_height
- row_height = cell.preferred_height if row_height < cell.preferred_height
+ 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
- if row_fit
- seen = {}
- columns.each do |cell|
- next if seen[cell]
- cell.update_height(cell.row == row_index ? row_height : cell.height + row_height)
- seen[cell] = true
- 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
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.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]
end