Class: PDF::SimpleTable

### Home Classes Methods

### In Files

- lib/pdf/simpletable.rb

### Parent

Object

### Namespace

- CLASS PDF::SimpleTable::Column

### Methods

- ::new
- #render_on

### Included Modules

- Transaction::Simple

### Files

- ChangeLog

### Class/Module Index

Quicksearch

- PDF
- PDF::Charts
- PDF::Charts::StdDev
- PDF::Charts::StdDev::Label
- PDF::Charts::StdDev::Marker
- PDF::Charts::StdDev::Scale
- PDF::Math
- PDF::QuickRef
- PDF::SimpleTable
- PDF::SimpleTable::Column
- PDF::SimpleTable::Column::Heading
- PDF::TechBook
- PDF::TechBook::TagTocDots
- PDF::TechBook::TagXref
- PDF::Writer
- PDF::Writer::ARC4
- PDF::Writer::Complex
- PDF::Writer::External
- PDF::Writer::FontMetrics
- PDF::Writer::Graphics
- PDF::Writer::Graphics::ImageInfo
- PDF::Writer::Graphics::ImageInfo::Formats
- PDF::Writer::Graphics::ImageInfo::Type
- PDF::Writer::Lang
- PDF::Writer::Lang::EN
- PDF::Writer::OHash
- PDF::Writer::Object
- PDF::Writer::Object::Action
- PDF::Writer::Object::Annotation
- PDF::Writer::Object::Catalog
- PDF::Writer::Object::Contents
- PDF::Writer::Object::Destination
- PDF::Writer::Object::Encryption
- PDF::Writer::Object::Font
- PDF::Writer::Object::FontDescriptor
- PDF::Writer::Object::FontEncoding
- PDF::Writer::Object::Info
- PDF::Writer::Object::Outline
- PDF::Writer::Object::Outlines
- PDF::Writer::Object::Pages
- PDF::Writer::Object::Procset
- PDF::Writer::Object::ViewerPreferences
- PDF::Writer::OffsetReader
- PDF::Writer::PolygonPoint
- PDF::Writer::State
- PDF::Writer::StateStack
- PDF::Writer::StrokeStyle

No matching classes.

# PDF::SimpleTable

This class will create tables with a relatively simple API and internal implementation.

### Constants

WIDTH_FACTOR

### Attributes

bold_headings[RW]

Makes the heading text bold if `true`. Defaults to `false`.

column_gap[RW]

The space, in PDF user units, on the left and right sides of each cell. Default 5 units.

column_order[RW]

An array that defines the order of the columns in the table. The values in this array are the column names in data. The columns will be presented in the order defined here.

columns[RW]

An array that defines columns and column options for the table. The entries should be PDF::SimpleTable::Column objects.

data[RW]

An array of Hash entries. Each row is a Hash where the keys are the names of the columns as specified in column_order and the values are the values of the cell.

font_size[RW]

The font size of the data cells, in points. Defaults to 10 points.

header_gap[RW]

The number of PDF user units to leave open at the top of a page after a page break. This is typically used for a repeating page header, etc. Defaults to zero units.

heading_color[RW]

The text colour of the heading. Defaults to Color::RGB::Black.

heading_font_size[RW]

The font size of the heading cells, in points. Defaults to 12 points.

inner_line_style[RW]

Defines the inner line style. The default style is a solid line with a thickness of 1 unit.

line_color[RW]

The colour of the table lines. Defaults to Color::RGB::Black.

maximum_width[RW]

Specifies the maximum width of the table. The table will not grow larger than this width under any circumstances.

Defaults to zero, which indicates that there is no maximum width (aside from the margin size).

minimum_space[RW]

The minimum space between the bottom of each row and the bottom margin. If the amount of space is less than this, a new page will be started. Default is 100 PDF user units.

orientation[RW]

The orientation of the table relative to position.

`:left` | The table is to the left of position.
`:right` | The table is to the right of position.
`:center` | The table is centred at position.
offset | The left of the table is offset from position.

outer_line_style[RW]

Defines the outer line style. The default style is a solid line with a thickness of 1 unit.

position[RW]

The `x` position of the table. This will be one of:

`:left` | Aligned with the left margin.
`:right` | Aligned with the right margin.
`:center` | Centered between the margins. Default.
offset | The absolute position of the table, relative from the left margin.

protect_rows[RW]

The number of rows to hold with the heading on the page. If there are less than this number of rows on the page, then move the whole lot onto the next page. Default is one row.

row_gap[RW]

The space, in PDF user units, added to the top and bottom of each row between the text and the lines of the cell. Default 2 units.

shade_color[RW]

The main row shading colour. Defaults to Color::RGB::Grey80. Used with shade_rows of `:shaded` and `:striped`.

shade_color2[RW]

The alternate row shading colour, used with shade_rows of `:striped`. Defaults to Color::RGB::Grey70.

shade_heading_color[RW]

Defines the colour of the background shading for the heading if shade_headings is `true`. Default is Color::RGB::Grey90.

shade_headings[RW]

Places a background colour in the heading if `true`.

shade_rows[RW]

Controls row shading.

`:none` | No row shading; all rows are the standard background colour.
`:shaded` | Alternate lines will be shaded; half of the rows will be the standard background colour; the rest of the rows will be shaded with shade_color. Default
`:striped` | Alternate lines will be shaded; half of the rows will be shaded with shade_color; the rest of the rows will be shaded with shade_color2.

show_headings[RW]

Displays the headings for the table if `true`. The default is `true`.

show_lines[RW]

Whether to display the lines on the table or not. Valid values are:

`:none` | Displays no lines.
`:outer` | Displays outer lines only. Default
`:inner` | Displays inner lines only.
`:all` | Displays all lines, inner and outer.

split_rows[RW]

Allows a table's rows to be split across page boundaries if `true`. This defaults to `false`.

text_color[RW]

The text colour of the body cells. Defaults to Color::RGB::Black.

title[RW]

The title to be put on the top of the table.

title_color[RW]

The text colour of the title. Defaults to Color::RGB::Black.

title_font_size[RW]

The font size of the title, in points. Defaults to 12 points.

title_gap[RW]

The gap, in PDF units, between the title and the table. Defaults to 5 units.

width[RW]

Specifies the width of the table. If the table is smaller than the provided width, columns are proportionally stretched to fit the width of the table. If the table is wider than the provided width, columns are proportionally shrunk to fit the width of the table. Content may need to wrap in this case.

Defaults to zero, which indicates that the size whould be determined automatically based on the content and the margins.

### Public Class Methods

new() click to toggle source

    # File lib/pdf/simpletable.rb, line 69
    def initialize
      @column_order = []
      @data         = []
      @columns      = {}

      @show_lines           = :outer
      @show_headings        = true
      @shade_rows           = :shaded
      @shade_color          = Color::RGB::Grey80
      @shade_color2         = Color::RGB::Grey70
      @shade_headings       = false
      @shade_heading_color  = Color::RGB::Grey90
      @font_size            = 10
      @heading_font_size    = 12
      @title_font_size      = 12
      @title_gap            = 5
      @title_color          = Color::RGB::Black
      @heading_color        = Color::RGB::Black
      @text_color           = Color::RGB::Black
      @line_color           = Color::RGB::Black
      @position             = :center
      @orientation          = :center
      @bold_headings        = false

      @cols                 = PDF::Writer::OHash.new
      @width                = 0
      @maximum_width        = 0

      @gap                  = 5
      @row_gap              = 2
      @column_gap           = 5
      @header_gap           = 0

      @minimum_space        = 0
      @protect_rows         = 1
      @split_rows           = false

      @inner_line_style     = PDF::Writer::StrokeStyle.new(1)
      @outer_line_style     = PDF::Writer::StrokeStyle.new(1)

      yield self if block_given?
    end

### Public Instance Methods

render_on(pdf) click to toggle source

Render the table on the PDF::Writer document provided.

    # File lib/pdf/simpletable.rb, line 238
    def render_on(pdf)
      if @column_order.empty?
        raise TypeError, PDF::Writer::Lang[:simpletable_columns_undefined]
      end
      if @data.empty?
        raise TypeError, PDF::Writer::Lang[:simpletable_data_empty]
      end

      low_y = descender = y0 = y1 = y = nil

      @cols = PDF::Writer::OHash.new
      @column_order.each do |name|
        col = @columns[name]
        if col
          @cols[name] = col
        else
          @cols[name] = PDF::SimpleTable::Column.new(name)
        end
      end

      @gap = 2 * @column_gap

      max_width = __find_table_max_width__(pdf)
      pos, t, x, adjustment_width, set_width = __find_table_positions__(pdf, max_width)

      # if max_width is specified, and the table is too wide, and the width
      # has not been set, then set the width.
      if @width.zero? and @maximum_width.nonzero? and ((t - x) > @maximum_width)
        @width = @maximum_width
      end

      if @width and (adjustment_width > 0) and (set_width < @width)
          # First find the current widths of the columns involved in this
          # mystery
        cols0 = PDF::Writer::OHash.new
        cols1 = PDF::Writer::OHash.new

        xq = presentWidth = 0
        last = nil

        pos.each do |name, colpos|
          if @cols[last].nil? or
            @cols[last].width.nil? or
            @cols[last].width <= 0
            unless last.nil? or last.empty?
              cols0[last] = colpos - xq - @gap
              presentWidth += (colpos - xq - @gap)
            end
          else
            cols1[last] = colpos - xq
          end
          last = name
          xq = colpos
        end

        # cols0 contains the widths of all the columns which are not set
        needed_width = @width - set_width

          # If needed width is negative then add it equally to each column,
          # else get more tricky.
        if presentWidth < needed_width
          diff = (needed_width - presentWidth) / cols0.size.to_f
          cols0.each_key { |name| cols0[name] += diff }
        else
          cnt = 0
          loop do
            break if (presentWidth <= needed_width) or (cnt >= 100)
            cnt += 1 # insurance policy
              # Find the widest columns and the next to widest width
            aWidest = []
            nWidest = widest = 0
            cols0.each do |name, w|
              if w > widest
                aWidest = [ name ]
                nWidest = widest
                widest = w
              elsif w == widest
                aWidest << name
              end
            end

            # Then figure out what the width of the widest columns would
            # have to be to take up all the slack.
            newWidestWidth = widest - (presentWidth - needed_width) / aWidest.size.to_f
            if newWidestWidth > nWidest
              aWidest.each { |name| cols0[name] = newWidestWidth }
              presentWidth = needed_width
            else
              # There is no space, reduce the size of the widest ones down
              # to the next size down, and we will go round again
              aWidest.each { |name| cols0[name] = nWidest }
              presentWidth -= (widest - nWidest) * aWidest.size
            end
          end
        end

          # cols0 now contains the new widths of the constrained columns. now
          # need to update the pos and max_width arrays
        xq = 0
        pos.each do |name, colpos|
          pos[name] = xq

          if @cols[name].nil? or
            @cols[name].width.nil? or
            @cols[name].width <= 0
            if not cols0[name].nil?
              xq += cols0[name] + @gap
              max_width[name] = cols0[name]
            end
          else
            xq += cols1[name] unless cols1[name].nil?
          end
        end

        t = x + @width
        pos[:__last_column__] = t
      end

      # now adjust the table to the correct location across the page
      case @position
      when :left
        xref = pdf.absolute_left_margin
      when :right
        xref = pdf.absolute_right_margin
      when :center
        xref = pdf.margin_x_middle
      else
        xref = @position
      end

      case @orientation
      when :left
        dx = xref - t
      when :right
        dx = xref
      when :center
        dx = xref - (t / 2.0)
      else
        dx = xref + @orientation
      end

      pos.each { |k, v| pos[k] = v + dx }

      base_x0 = x0 = x + dx
      base_x1 = x1 = t + dx

      base_left_margin = pdf.absolute_left_margin
      base_pos = pos.dup

        # Ok, just about ready to make me a table.
      pdf.fill_color @text_color
      pdf.stroke_color @shade_color

      middle = (x0 + x1) / 2.0

        # Start a transaction. This transaction will be used to regress the
        # table if there are not enough rows protected.
      tg = Transaction::Simple::Group.new(pdf, self)
      tg.start_transaction(:table)
      moved_once = false if @protect_rows.nonzero?

      abortTable = true
      loop do # while abortTable
        break unless abortTable
        abortTable = false

        dm = pdf.absolute_left_margin - base_left_margin
        base_pos.each { |k, v| pos[k] = v + dm }
        x0 = base_x0 + dm
        x1 = base_x1 + dm
        middle = (x0 + x1) / 2.0

          # If the title is set, then render it.
        unless @title.nil? or @title.empty?
          w = pdf.text_width(@title, @title_font_size)
          _y = pdf.y - pdf.font_height(@title_font_size)
          if _y < pdf.absolute_bottom_margin
            pdf.start_new_page

              # margins may have changed on the new page
            dm = pdf.absolute_left_margin - base_left_margin
            base_pos.each { |k, v| pos[k] = v + dm }
            x0 = base_x0 + dm
            x1 = base_x1 + dm
            middle = (x0 + x1) / 2.0
          end

          pdf.y -= pdf.font_height(@title_font_size)
          pdf.fill_color @title_color
          pdf.add_text(middle - w / 2.0, pdf.y, title, @title_font_size)
          pdf.y -= @title_gap
        end

          # Margins may have changed on the new_page.
        dm = pdf.absolute_left_margin - base_left_margin
        base_pos.each { |k, v| pos[k] = v + dm }
        x0 = base_x0 + dm
        x1 = base_x1 + dm
        middle = (x0 + x1) / 2.0

        y = pdf.y  # simplifies the code a bit
        low_y = y if low_y.nil? or y < low_y

          # Make the table
        height = pdf.font_height @font_size
        descender = pdf.font_descender @font_size

        y0 = y + descender
        dy = 0

        if @show_headings
          # This function will move the start of the table to a new page if
          # it does not fit on this one.
          hOID = __open_new_object__(pdf) if @shade_headings
          pdf.fill_color @heading_color
          _height, y = __table_column_headings__(pdf, pos, max_width, height,
            descender, @row_gap, @heading_font_size, y)
          pdf.fill_color @text_color
          y0 = y + _height
          y1 = y

          if @shade_headings
            pdf.close_object
            pdf.fill_color! @shade_heading_color
            pdf.rectangle(x0 - @gap / 2.0, y, x1 - x0, _height).fill
            pdf.reopen_object(hOID)
            pdf.close_object
            pdf.restore_state
          end

            # Margins may have changed on the new_page
          dm = pdf.absolute_left_margin - base_left_margin
          base_pos.each { |k, v| pos[k] = v + dm }
          x0 = base_x0 + dm
          x1 = base_x1 + dm
          middle = (x0 + x1) / 2.0
        else
          y1 = y0
        end

        first_line = true

        # open an object here so that the text can be put in over the
        # shading
        tOID = __open_new_object__(pdf) unless :none == @shade_rows

        cnt = 0
        cnt = 1 unless @shade_headings
        newPage = false
        @data.each do |row|
          cnt += 1
            # Start a transaction that will be used for this row to prevent it
            # from being split.
          unless @split_rows
            pageStart = pdf.pageset.size

            columnStart = pdf.column_number if pdf.columns?

            tg.start_transaction(:row)
            row_orig = row
            y_orig = y
            y0_orig = y0
            y1_orig = y1
          end # unless @split_rows

          ok = false
          second_turn = false
          loop do # while !abortTable and !ok
            break if abortTable or ok

            mx = 0
            newRow = true

            loop do # while !abortTable and (newPage or newRow)
              break if abortTable or not (newPage or newRow)

              y -= height
              low_y = y if low_y.nil? or y < low_y

              if newPage or y < (pdf.absolute_bottom_margin + @minimum_space)
                  # check that enough rows are with the heading
                moved_once = abortTable = true if @protect_rows.nonzero? and not moved_once and cnt <= @protect_rows

                y2 = y - mx + (2 * height) + descender - (newRow ? 1 : 0) * height

                unless :none == @show_lines
                  y0 = y1 unless @show_headings

                  __table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2,
                    @line_color, @inner_line_style, @outer_line_style,
                    @show_lines)
                end

                unless :none == @shade_rows
                  pdf.close_object
                  pdf.restore_state
                end

                pdf.start_new_page
                pdf.save_state

                  # and the margins may have changed, this is due to the
                  # possibility of the columns being turned on as the columns are
                  # managed by manipulating the margins
                dm = pdf.absolute_left_margin - base_left_margin
                base_pos.each { |k, v| pos[k] = v + dm }
                x0 = base_x0 + dm
                x1 = base_x1 + dm

                tOID = __open_new_object__(pdf) unless :none == @shade_rows

                pdf.fill_color! @text_color

                y = pdf.absolute_top_margin - @header_gap
                low_y = y
                y0 = y + descender
                mx = 0

                if @show_headings
                  old_y = y

                  pdf.fill_color @heading_color
                  _height, y = __table_column_headings__(pdf, pos, max_width,
                    height, descender, @row_gap, @heading_font_size, y)
                  pdf.fill_color @text_color

                  y0 = y + _height
                  y1 = y

                  if @shade_headings
                    pdf.fill_color! @shade_heading_color
                    pdf.rectangle(x0 - @gap / 2, y, x1 - x0, _height).fill
                    pdf.fill_color @heading_color
                    __table_column_headings__(pdf, pos, max_width, height,
                                              descender, @row_gap,
                                              @heading_font_size, old_y)
                    pdf.fill_color @text_color
                  end

                  dm = pdf.absolute_left_margin - base_left_margin
                  base_pos.each { |k, v| pos[k] = v + dm }
                  x0 = base_x0 + dm
                  x1 = base_x1 + dm
                  middle = (x0 + x1) / 2.0
                else
                  y1 = y0
                end

                first_line = true
                y -= height
                low_y = y if low_y.nil? or y < low_y
              end

              newRow = false

                # Write the actual data. If these cells need to be split over
                # a page, then newPage will be set, and the remaining text
                # will be placed in leftOvers
              newPage = false
              leftOvers = PDF::Writer::OHash.new

              @cols.each do |name, column|
                pdf.pointer = y + height
                colNewPage = false

                unless row[name].nil?
                  lines = row[name].to_s.split(/\n/)
                  if column and column.link_name
                    lines.map! do |kk|
                      link = row[column.link_name]
                      if link
                        "<c:alink uri='#{link}'>#{kk}</c:alink>"
                      else
                        kk
                      end
                    end
                  end
                else
                  lines = []
                end

                pdf.y -= @row_gap

                lines.each do |line|
                  pdf.send(:preprocess_text, line)
                  start = true

                  loop do
                    break if (line.nil? or line.empty?) and not start
                    start = false

                    _y = pdf.y - height if not colNewPage

                      # a new page is required
                    newPage = colNewPage = true if _y < pdf.absolute_bottom_margin

                    if colNewPage
                      if leftOvers[name].nil?
                        leftOvers[name] = [line]
                      else
                        leftOvers[name] << "\n#{line}"
                      end
                      line = nil
                    else
                      if column and column.justification
                        just = column.justification
                      end
                      just ||= :left

                      pdf.y = _y
                      line = pdf.add_text_wrap(pos[name], pdf.y,
                                               max_width[name], line,
                                               @font_size, just)
                    end
                  end
                end

                dy = y + height - pdf.y + @row_gap
                mx = dy - height * (newPage ? 1 : 0) if (dy - height * (newPage ? 1 : 0)) > mx
              end

                # Set row to leftOvers so that they will be processed onto the
                # new page
              row = leftOvers

              # Now add the shading underneath
              unless :none == @shade_rows
                pdf.close_object

                if (cnt % 2).zero?
                  pdf.fill_color!(@shade_color)
                  pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
                elsif (cnt % 2).nonzero? and :striped == @shade_rows
                  pdf.fill_color!(@shade_color2)
                  pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
                end
                pdf.reopen_object(tOID)
              end

              if :inner == @show_lines or :all == @show_lines
                # draw a line on the top of the block
                pdf.save_state
                pdf.stroke_color! @line_color
                if first_line
                  pdf.stroke_style @outer_line_style
                  first_line = false
                else
                  pdf.stroke_style @inner_line_style
                end
                pdf.line(x0 - @gap / 2.0, y + descender + height, x1 - @gap / 2.0, y + descender + height).stroke
                pdf.restore_state
              end
            end

            y = y - mx + height
            pdf.y = y
            low_y = y if low_y.nil? or y < low_y

              # checking row split over pages
            unless @split_rows
              if (((pdf.pageset.size != pageStart) or (pdf.columns? and columnStart != pdf.column_number)) and not second_turn)
                # then we need to go back and try that again!
                newPage = second_turn = true
                tg.rewind_transaction(:row)
                row = row_orig
                low_y = y = y_orig
                y0 = y0_orig
                y1 = y1_orig
                ok = false

                dm = pdf.absolute_left_margin - base_left_margin
                base_pos.each { |k, v| pos[k] = v + dm }
                x0 = base_x0 + dm
                x1 = base_x1 + dm
              else
                tg.commit_transaction(:row)
                ok = true
              end
            else
              ok = true # don't go 'round the loop if splitting rows is allowed
            end
          end

          if abortTable
              # abort_transaction if not ok only the outer transaction should
              # be operational.
            tg.rewind_transaction(:table)
            pdf.start_new_page
              # fix a bug where a moved table will take up the whole page.
            low_y = nil
            pdf.save_state
            break
          end
        end
      end

      if low_y <= y
        y2 = low_y + descender
      else
        y2 = y + descender
      end

      unless :none == @show_lines
        y0 = y1 unless @show_headings

        __table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2, @line_color,
          @inner_line_style, @outer_line_style, @show_lines)
      end

      # close the object for drawing the text on top
      unless :none == @shade_rows
        pdf.close_object
        pdf.restore_state
      end

      pdf.y = low_y

        # Table has been put on the page, the rows guarded as required; commit.
      tg.commit_transaction(:table)

      y
    rescue Exception => ex
      begin
        tg.abort_transaction(:table) if tg.transaction_open?
      rescue
        nil
      end
      raise ex
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.