Class: PDF::Wrapper::Table

Inherits:
Object
  • Object
show all
Defined in:
lib/pdf/wrapper/table.rb

Overview

This class is used to hold all the data and options for a table that will be added to a PDF::Wrapper document. Tables are a collection of cells, each one individually rendered to the document in a location that makes it appear to be a table.

To begin working with a table, pass in a 2d array of data to display, along with optional headings, then pass the object to Wrapper#table

table = Table.new do |t|
  t.headers = ["Words", "Numbers"]
  t.data = [['one',  1],
            ['two',  2],
            ['three',3]]
end
pdf.table(table)

For all but the most basic tables, you will probably want to tweak at least some of the options for some of the cells. The options available are the same as those that are valid for the Wrapper#cell method, including things like font, font size, color and alignment.

Options can be specified at the table, column, row and cell level. When it comes time to render each cell, the options are merged together so that cell options override row ones, row ones override column ones and column ones override table wide ones.

By default, no options are defined at all, and the document defaults will be used.

For example:

table = Table.new(:font_size => 10) do |t|
  t.headers = ["Words", "Numbers"]
  t.data = [['one',  1],
            ['two',  2],
            ['three',3]]
  t.row_options 0, :color => :green
  t.row_options 2, :color => :red
  t.col_options 0, :color => :blue
  t.cell_options 2, 2, :font_size => 18
  t.manual_column_width 2, 40
end
pdf.table(table)

Displaying Headings

By default, the column headings will be displayed at the top of the table, and at the start of each new page the table wraps on to. Use the show_headers= option to change this behaviour. Valid values are nil for never, :once for just the at the top of the table, and :page for the default.

Complex Cells

By default, any cell content described in the data array is converted to a string and wrapped in a TextCell object. If you need to, it is possible to define your cells as cell-like objects manually to get more control.

The following two calls are equivilant:

data = [[1,2]]
pdf.table(data)

data = [[PDF::Wrapper::TextCell.new(2),PDF::Wrapper::TextCell.new(2)]]
pdf.table(data)

An alternative to a text-only cell is a cell with text and an image. These cells must be initialised with a filename and cell dimensions (width and height) as calculating automatic dimensions is difficult.

data = [
  ["James", PDF::Wrapper::TextImageCell.new("Healy","photo-jim.jpg",100,100)],
  ["Jess", PDF::Wrapper::TextImageCell.new("Healy","photo-jess.jpg",100,100)],
]
pdf.table(data)

If TextImageCell doesn’t meet your needs, you are free to define your own cell-like object and use that.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) {|_self| ... } ⇒ Table

Returns a new instance of Table.

Yields:

  • (_self)

Yield Parameters:



123
124
125
126
127
128
129
130
131
132
# File 'lib/pdf/wrapper/table.rb', line 123

def initialize(opts = {})

  # default table options
  @table_options  = opts
  @manual_col_widths = {}
  @show_headers  = :page

  yield self if block_given?
  self
end

Instance Attribute Details

#cellsObject (readonly)

Returns the value of attribute cells.



120
121
122
# File 'lib/pdf/wrapper/table.rb', line 120

def cells
  @cells
end

#show_headersObject

Returns the value of attribute show_headers.



121
122
123
# File 'lib/pdf/wrapper/table.rb', line 121

def show_headers
  @show_headers
end

#widthObject

Returns the value of attribute width.



121
122
123
# File 'lib/pdf/wrapper/table.rb', line 121

def width
  @width
end

#wrapperObject (readonly)

Returns the value of attribute wrapper.



120
121
122
# File 'lib/pdf/wrapper/table.rb', line 120

def wrapper
  @wrapper
end

Instance Method Details

#cell(col_idx, row_idx) ⇒ Object

access a particular cell at location x, y



243
244
245
# File 'lib/pdf/wrapper/table.rb', line 243

def cell(col_idx, row_idx)
  @cells[row_idx][col_idx]
end

#cell_options(col_idx, row_idx, opts = nil) ⇒ Object

set or retrieve options that apply to a single cell For a list of valid options, see Wrapper#cell.

Raises:

  • (ArgumentError)


249
250
251
252
253
# File 'lib/pdf/wrapper/table.rb', line 249

def cell_options(col_idx, row_idx, opts = nil)
  raise ArgumentError, "#{col_idx},#{row_idx} is not a valid cell reference" unless @cells[row_idx] && @cells[row_idx][col_idx]
  @cells[row_idx][col_idx].options = @cells[row_idx][col_idx].options.merge(opts) if opts
  @cells[row_idx][col_idx].options
end

#col_countObject

Returns the number of columns in the table



313
314
315
# File 'lib/pdf/wrapper/table.rb', line 313

def col_count
  @cells.first.size.to_f
end

#col_options(spec, opts) ⇒ Object

set options that apply to 1 or more columns For a list of valid options, see Wrapper#cell.

spec

Which columns to add the options to. :odd, :even, a range, an Array of numbers or a number



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/pdf/wrapper/table.rb', line 258

def col_options(spec, opts)
  each_column do |col_idx|
    if (spec == :even && (col_idx % 2) == 0) ||
       (spec == :odd  && (col_idx % 2) == 1) ||
       (spec.class == Range && spec.include?(col_idx)) ||
       (spec.class == Array && spec.include?(col_idx)) ||
       (spec.respond_to?(:to_i) && spec.to_i == col_idx)

      cells_in_col(col_idx).each do |cell|
        cell.options.merge!(opts)
      end
    end
  end
  self
end

#data=(d) ⇒ Object

Set the table data.

The single argument should be a 2d array like:

[[ "one", "two"],
 [ "one", "two"]]

The cells in the array can be any object with to_s() defined, or a Cell-like object (such as a TextCell or TextImageCell).

Raises:

  • (ArgumentError)


144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/pdf/wrapper/table.rb', line 144

def data=(d)
  row_sizes = d.map { |row| row.size }.compact.uniq
  raise ArgumentError, "" if row_sizes.size > 1

  @cells = d.collect do |row|
    row.collect do |data|
      if data.kind_of?(Wrapper::TextCell) || data.kind_of?(Wrapper::TextImageCell)
        data
      else
        Wrapper::TextCell.new(data.to_s)
      end
    end
  end
  each_cell { |cell| cell.options.merge!(@table_options)}
  @cells
end

#draw(wrapper, tablex, tabley) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/pdf/wrapper/table.rb', line 199

def draw(wrapper, tablex, tabley)
  @wrapper = wrapper

  calculate_dimensions

  # move to the start of our table (the top left)
  wrapper.move_to(tablex, tabley)

  # draw the header cells
  draw_table_headers if self.headers && (self.show_headers == :page || self.show_headers == :once)

  x, y = wrapper.current_point

  # loop over each row in the table
  self.cells.each_with_index do |row, row_idx|

    # calc the height of the current row
    h = row.first.height

    if y + h > wrapper.absolute_bottom_margin
      wrapper.start_new_page
      y = wrapper.margin_top

      # draw the header cells
      draw_table_headers if self.headers && (self.show_headers == :page)
      x, y = wrapper.current_point
    end

    # loop over each column in the current row and paint it
    row.each_with_index do |cell, col_idx|
      cell.draw(wrapper, x, y)
      x += cell.width
      wrapper.move_to(x, y)
    end

    # move to the start of the next row
    y += h
    x = tablex
    wrapper.move_to(x, y)
  end
end

#each_cell(&block) ⇒ Object

iterate over each cell in the table. Yields a cell object.



319
320
321
322
323
324
325
# File 'lib/pdf/wrapper/table.rb', line 319

def each_cell(&block)
  each_row do |row_idx|
    cells_in_row(row_idx).each do |cell|
      yield cell
    end
  end
end

#headers(h = nil, opts = {}) ⇒ Object

Retrieve or set the table’s optional column headers.

With no arguments, the currents headers will be returned

t.headers
=> ["col one", "col two"]

The first argument is an array of text to use as column headers

t.headers ["col one", "col two]

The optional second argument sets the cell options for the header cells. See PDF::Wrapper#cell for a list of possible options.

t.headers ["col one", "col two], :color => :block, :fill_color => :black

If the options hash is left unspecified, the default table options will be used.



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/pdf/wrapper/table.rb', line 180

def headers(h = nil, opts = {})
  return @headers if h.nil?

  if @cells && @cells.first.size != h.size
    raise ArgumentError, "header column count does not match data column count"
  end

  @headers = h.collect do |data|
    if data.kind_of?(Wrapper::TextCell) || data.kind_of?(Wrapper::TextImageCell)
      data
    else
      Wrapper::TextCell.new(data.to_s)
    end
  end
  @headers.each { |cell| cell.options.merge!(@table_options)}
  @headers.each { |cell| cell.options.merge!(opts)}
  @headers
end

#manual_col_width(spec, width) ⇒ Object

Manually set the width for 1 or more columns

spec

Which columns to set the width for. :odd, :even, a range, an Array of numbers or a number



278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/pdf/wrapper/table.rb', line 278

def manual_col_width(spec, width)
  width = width.to_f
  each_column do |col_idx|
    if (spec == :even && (col_idx % 2) == 0) ||
       (spec == :odd  && (col_idx % 2) == 1) ||
       (spec.class == Range && spec.include?(col_idx)) ||
       (spec.class == Array && spec.include?(col_idx)) ||
       (spec.respond_to?(:to_i) && spec.to_i == col_idx)

      @manual_col_widths[col_idx] = width
    end
  end
  self
end

#row_options(spec, opts) ⇒ Object

set options that apply to 1 or more rows For a list of valid options, see Wrapper#cell.

spec

Which columns to add the options to. :odd, :even, a range, an Array of numbers or a number



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/pdf/wrapper/table.rb', line 296

def row_options(spec, opts)
  each_row do |row_idx|
    if (spec == :even && (row_idx % 2) == 0) ||
       (spec == :odd  && (row_idx % 2) == 1) ||
       (spec.class == Range && spec.include?(row_idx)) ||
       (spec.class == Array && spec.include?(row_idx)) ||
       (spec.respond_to?(:to_i) && spec.to_i == row_idx)

      cells_in_row(row_idx).each do |cell|
        cell.options.merge!(opts)
      end
    end
  end
  self
end