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