This class will create tables with a relatively simple API and internal implementation.
The space, in PDF user units, on the left and right sides of each cell. Default 5 units.
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.
An array that defines columns and column options for the table. The entries should be PDF::SimpleTable::Column objects.
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.
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.
Defines the inner line style. The default style is a solid line with a thickness of 1 unit.
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).
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.
Defines the outer line style. The default style is a solid line with a thickness of 1 unit.
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. |
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.
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.
The main row shading colour. Defaults to Color::RGB::Grey80. Used with shade_rows of :shaded and :striped.
The alternate row shading colour, used with shade_rows of :striped. Defaults to Color::RGB::Grey70.
Defines the colour of the background shading for the heading if shade_headings is true. Default is Color::RGB::Grey90.
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. |
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. |
Allows a table’s rows to be split across page boundaries if true. This defaults to false.
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.
# 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
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
Generated with the Darkfish Rdoc Generator 2.