lib/prawn/table/cells.rb in prawn-0.15.0 vs lib/prawn/table/cells.rb in prawn-1.0.0.rc1
- old
+ new
@@ -6,10 +6,18 @@
#
# This is free software. Please see the LICENSE and COPYING files for details.
module Prawn
class Table
+
+ # Returns a Cells object that can be used to select and style cells. See
+ # the Cells documentation for things you can do with cells.
+ #
+ def cells
+ @cell_proxy ||= Cells.new(@cells)
+ end
+
# Selects the given rows (0-based) for styling. Returns a Cells object --
# see the documentation on Cells for things you can do with cells.
#
def rows(row_spec)
cells.rows(row_spec)
@@ -35,58 +43,42 @@
#
# table.rows(1..3).columns(2..4).background_color = 'ff0000'
#
class Cells < Array
- # @group Experimental API
-
# Limits selection to the given row or rows. +row_spec+ can be anything
# that responds to the === operator selecting a set of 0-based row
# numbers; most commonly a number or a range.
#
# table.row(0) # selects first row
# table.rows(3..4) # selects rows four and five
#
def rows(row_spec)
- index_cells unless defined?(@indexed) && @indexed
- row_spec = transform_spec(row_spec, @first_row, @row_count)
+ index_cells unless @indexed
+ row_spec = transform_spec(row_spec, @row_count)
Cells.new(@rows[row_spec] ||= select { |c|
row_spec.respond_to?(:include?) ?
row_spec.include?(c.row) : row_spec === c.row })
end
alias_method :row, :rows
-
- # Returns the number of rows in the list.
- #
- def row_count
- index_cells unless defined?(@indexed) && @indexed
- @row_count
- end
-
+
# Limits selection to the given column or columns. +col_spec+ can be
# anything that responds to the === operator selecting a set of 0-based
# column numbers; most commonly a number or a range.
#
# table.column(0) # selects first column
# table.columns(3..4) # selects columns four and five
#
def columns(col_spec)
- index_cells unless defined?(@indexed) && @indexed
- col_spec = transform_spec(col_spec, @first_column, @column_count)
+ index_cells unless @indexed
+ col_spec = transform_spec(col_spec, @column_count)
Cells.new(@columns[col_spec] ||= select { |c|
- col_spec.respond_to?(:include?) ?
+ col_spec.respond_to?(:include?) ?
col_spec.include?(c.column) : col_spec === c.column })
end
alias_method :column, :columns
- # Returns the number of columns in the list.
- #
- def column_count
- index_cells unless defined?(@indexed) && @indexed
- @column_count
- end
-
# Allows you to filter the given cells by arbitrary properties.
#
# table.column(4).filter { |cell| cell.content =~ /Yes/ }.
# background_color = '00ff00'
#
@@ -94,43 +86,17 @@
Cells.new(select(&block))
end
# Retrieves a cell based on its 0-based row and column. Returns an
# individual Cell, not a Cells collection.
- #
+ #
# table.cells[0, 0].content # => "First cell content"
#
def [](row, col)
- return nil if empty?
- index_cells unless defined?(@indexed) && @indexed
- row_array, col_array = @rows[@first_row + row] || [], @columns[@first_column + col] || []
- if row_array.length < col_array.length
- row_array.find { |c| c.column == @first_column + col }
- else
- col_array.find { |c| c.row == @first_row + row }
- end
+ find { |c| c.row == row && c.column == col }
end
- # Puts a cell in the collection at the given position. Internal use only.
- #
- def []=(row, col, cell) # :nodoc:
- cell.extend(Cell::InTable)
- cell.row = row
- cell.column = col
-
- if defined?(@indexed) && @indexed
- (@rows[row] ||= []) << cell
- (@columns[col] ||= []) << cell
- @first_row = row if !@first_row || row < @first_row
- @first_column = col if !@first_column || col < @first_column
- @row_count = @rows.size
- @column_count = @columns.size
- end
-
- self << cell
- end
-
# Supports setting multiple properties at once.
#
# table.cells.style(:padding => 0, :border_width => 2)
#
# is the same as:
@@ -142,68 +108,73 @@
# This allows you to set more complicated properties:
#
# table.cells.style { |cell| cell.border_width += 12 }
#
def style(options={}, &block)
- each do |cell|
- next if cell.is_a?(Cell::SpanDummy)
- cell.style(options, &block)
- end
+ each { |cell| cell.style(options, &block) }
end
# Returns the total width of all columns in the selected set.
#
def width
- widths = {}
- each do |cell|
- per_cell_width = cell.width_ignoring_span.to_f / cell.colspan
- cell.colspan.times do |n|
- widths[cell.column+n] = [widths[cell.column+n], per_cell_width].
- compact.max
- end
+ column_widths = {}
+ each do |cell|
+ column_widths[cell.column] =
+ [column_widths[cell.column], cell.width].compact.max
end
- widths.values.inject(0, &:+)
+ column_widths.values.inject(0) { |sum, width| sum + width }
end
# Returns minimum width required to contain cells in the set.
#
def min_width
- aggregate_cell_values(:column, :avg_spanned_min_width, :max)
+ column_min_widths = {}
+ each do |cell|
+ column_min_widths[cell.column] =
+ [column_min_widths[cell.column], cell.min_width].compact.max
+ end
+ column_min_widths.values.inject(0) { |sum, width| sum + width }
end
# Returns maximum width that can contain cells in the set.
#
def max_width
- aggregate_cell_values(:column, :max_width_ignoring_span, :max)
+ column_max_widths = {}
+ each do |cell|
+ column_max_widths[cell.column] =
+ [column_max_widths[cell.column], cell.max_width].compact.min
+ end
+ column_max_widths.values.inject(0) { |sum, width| sum + width }
end
# Returns the total height of all rows in the selected set.
#
def height
- aggregate_cell_values(:row, :height_ignoring_span, :max)
+ row_heights = {}
+ each do |cell|
+ row_heights[cell.row] =
+ [row_heights[cell.row], cell.height].compact.max
+ end
+ row_heights.values.inject(0) { |sum, width| sum + width }
end
# Supports setting arbitrary properties on a group of cells.
#
# table.cells.row(3..6).background_color = 'cc0000'
#
def method_missing(id, *args, &block)
- if id.to_s =~ /=\z/
- each { |c| c.send(id, *args, &block) if c.respond_to?(id) }
- else
- super
- end
+ each { |c| c.send(id, *args, &block) }
end
protected
-
+
# Defers indexing until rows() or columns() is actually called on the
# Cells object. Without this, we would needlessly index the leaf nodes of
# the object graph, the ones that are only there to be iterated over.
#
# Make sure to call this before using @rows or @columns.
- #
+ #
def index_cells
@rows = {}
@columns = {}
each do |cell|
@@ -212,92 +183,27 @@
@columns[cell.column] ||= []
@columns[cell.column] << cell
end
- @first_row = @rows.keys.min
- @first_column = @columns.keys.min
-
@row_count = @rows.size
@column_count = @columns.size
@indexed = true
end
- # Sum up a min/max value over rows or columns in the cells selected.
- # Takes the min/max (per +aggregate+) of the result of sending +meth+ to
- # each cell, grouped by +row_or_column+.
- #
- def aggregate_cell_values(row_or_column, meth, aggregate)
- values = {}
-
- #calculate values for all cells that do not span accross multiple cells
- #this ensures that we don't have a problem if the first line includes
- #a cell that spans across multiple cells
- each do |cell|
- #don't take spanned cells
- if cell.colspan == 1 and cell.class != Prawn::Table::Cell::SpanDummy
- index = cell.send(row_or_column)
- values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
- end
- end
-
- #if there are only colspanned or rowspanned cells in a table
- spanned_width_needs_fixing = true
-
- each do |cell|
- index = cell.send(row_or_column)
- if cell.colspan > 1
- #calculate current (old) return value before we do anything
- old_sum = 0
- cell.colspan.times { |i|
- old_sum += values[index+i] unless values[index+i].nil?
- }
-
- #calculate future return value
- new_sum = cell.send(meth) * cell.colspan
-
- #due to float rounding errors we need to ignore a small difference in the new
- #and the old sum the same had to be done in
- #the column_width_calculator#natural_width
- spanned_width_needs_fixing = ((new_sum - old_sum) > Prawn::FLOAT_PRECISION)
-
- if spanned_width_needs_fixing
- #not entirely sure why we need this line, but with it the tests pass
- values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
- #overwrite the old values with the new ones, but only if all entries existed
- entries_exist = true
- cell.colspan.times { |i| entries_exist = false if values[index+i].nil? }
- cell.colspan.times { |i|
- values[index+i] = cell.send(meth) if entries_exist
- }
- end
- else
- if spanned_width_needs_fixing && cell.class == Prawn::Table::Cell::SpanDummy
- values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
- end
- end
- end
- values.values.inject(0, &:+)
- end
-
# Transforms +spec+, a column / row specification, into an object that
# can be compared against a row or column number using ===. Normalizes
- # negative indices to be positive, given a total size of +total+. The
- # first row/column is indicated by +first+; this value is considered row
- # or column 0.
+ # negative indices to be positive, given a total size of +total+.
#
- def transform_spec(spec, first, total)
+ def transform_spec(spec, total)
case spec
when Range
- transform_spec(spec.begin, first, total) ..
- transform_spec(spec.end, first, total)
+ transform_spec(spec.begin, total)..transform_spec(spec.end, total)
when Integer
- spec < 0 ? (first + total + spec) : first + spec
- when Enumerable
- spec.map { |x| first + x }
+ spec < 0 ? (total + spec) : spec
else # pass through
- raise "Don't understand spec #{spec.inspect}"
+ spec
end
end
end
end
end