Class: Prawn::Table::ColumnWidthCalculator

Inherits:
Object
  • Object
show all
Defined in:
lib/prawn/table/column_width_calculator.rb

Instance Method Summary collapse

Constructor Details

#initialize(cells) ⇒ ColumnWidthCalculator

Returns a new instance of ColumnWidthCalculator.



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/prawn/table/column_width_calculator.rb', line 7

def initialize(cells)
  @cells = cells

  @widths_by_column        = Hash.new(0)
  @rows_with_a_span_dummy  = Hash.new(false)

  #calculate for each row if it includes a Cell:SpanDummy
  @cells.each do |cell|
    @rows_with_a_span_dummy[cell.row] = true if cell.is_a?(Cell::SpanDummy)
  end
end

Instance Method Details

#aggregate_cell_values(row_or_column, meth, aggregate) ⇒ Object

get column widths (either min or max depending on meth) used in cells.rb

Parameters:

  • row_or_column
    • you may call this on either rows or columns

  • meth
    • min/max

  • aggregate
    • functions from cell.rb to be used to aggregate e.g. avg_spanned_min_width



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/prawn/table/column_width_calculator.rb', line 123

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
  @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

  @cells.each do |cell|
    index = cell.send(row_or_column)
    if cell.colspan > 1
      #special treatment if some but not all spanned indices in the values array have been calculated
      #only applies to rows
      values = fill_values_if_needed(values, cell, index, meth) if row_or_column == :column
      #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

  return values.values.inject(0, &:+)
end

#fill_values_if_needed(values, cell, index, meth) ⇒ Object

helper method column widths are stored in the values array a cell may span cells whose value is only partly given this function handles this special case

Parameters:

  • values
    • The columns widths calculated up until now

  • cell
    • The current cell

  • index
    • The current column

  • meth
    • Meth (min/max); used to calculate values to be filled



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/prawn/table/column_width_calculator.rb', line 37

def fill_values_if_needed(values, cell, index, meth)
  #have all spanned indices been filled with a value?
  #e.g. values[0], values[1] and values[2] don't return nil given a index of 0 and a colspan of 3
  number_of_nil_values = 0
  cell.colspan.times do |i|
    number_of_nil_values += 1 if values[index+i].nil?
  end

  #nothing to do? because
  #a) all values are filled
  return values if number_of_nil_values == 0
  #b) no values are filled
  return values if number_of_nil_values == cell.colspan
  #c) I am not sure why this line is needed FIXXME
  #some test cases manage to this line even though there is no dummy cell in the row
  #I'm not sure if this is a sign for a further underlying bug.
  return values unless has_a_span_dummy?(cell.row)
  #fill up the values array

  #calculate the new sum
  new_sum = cell.send(meth) * cell.colspan
  #substract any calculated values
  cell.colspan.times do |i|
    new_sum -= values[index+i] unless values[index+i].nil?
  end

  #calculate value for the remaining - not yet filled - cells.
  new_value = new_sum.to_f / number_of_nil_values
  #fill the not yet filled cells
  cell.colspan.times do |i|
    values[index+i] = new_value if values[index+i].nil?
  end
  return values
end

#has_a_span_dummy?(row) ⇒ Boolean

does this row include a Cell:SpanDummy?

Parameters:

  • row
    • the row that should be checked for Cell:SpanDummy elements

Returns:

  • (Boolean)


23
24
25
# File 'lib/prawn/table/column_width_calculator.rb', line 23

def has_a_span_dummy?(row)
  @rows_with_a_span_dummy[row]
end

#natural_widthsObject



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/prawn/table/column_width_calculator.rb', line 72

def natural_widths
  #calculate natural column width for all rows that do not include a span dummy
  @cells.each do |cell|
    unless has_a_span_dummy?(cell.row)
      @widths_by_column[cell.column] =
        [@widths_by_column[cell.column], cell.width.to_f].max
    end
  end

  #integrate natural column widths for all rows that do include a span dummy
  @cells.each do |cell|
    next unless has_a_span_dummy?(cell.row)
    #the width of a SpanDummy cell will be calculated by the "mother" cell
    next if cell.is_a?(Cell::SpanDummy)

    if cell.colspan == 1
      @widths_by_column[cell.column] =
        [@widths_by_column[cell.column], cell.width.to_f].max
    else
      #calculate the current with of all cells that will be spanned by the current cell
      current_width_of_spanned_cells =
        @widths_by_column.to_a[cell.column..(cell.column + cell.colspan - 1)]
                         .collect{|key, value| value}.inject(0, :+)

      #update the Hash only if the new with is at least equal to the old one
      #due to arithmetic errors we need to ignore a small difference in the new and the old sum
      #the same had to be done in the column_widht_calculator#natural_width
      update_hash = ((cell.width.to_f - current_width_of_spanned_cells) >
                     Prawn::FLOAT_PRECISION)

      if update_hash
        # Split the width of colspanned cells evenly by columns
        width_per_column = cell.width.to_f / cell.colspan
        # Update the Hash
        cell.colspan.times do |i|
          @widths_by_column[cell.column + i] = width_per_column
        end
      end
    end
  end

  @widths_by_column.sort_by { |col, _| col }.map { |_, w| w }
end