Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow draw a table in multiple pages when table's width is larger than a page #53

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 139 additions & 19 deletions lib/prawn/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
require_relative 'table/cell/subtable'
require_relative 'table/cell/image'
require_relative 'table/cell/span_dummy'
require_relative 'table/page'

module Prawn
module Errors
Expand Down Expand Up @@ -144,9 +145,11 @@ def initialize(data, document, options={}, &block)
block.arity < 1 ? instance_eval(&block) : block[self]
end

set_column_widths
set_row_heights
position_cells
if !@multipage
set_column_widths
set_row_heights
position_cells
end
end

# Number of rows in the table.
Expand All @@ -171,6 +174,9 @@ def initialize(data, document, options={}, &block)
#
attr_reader :cells

# Allow multiple pages for table or not
attr_writer :multipage

# Specify a callback to be called before each page of cells is rendered.
# The block is passed a Cells object containing all cells to be rendered on
# that page. You can change styling of the cells in this block, but keep in
Expand Down Expand Up @@ -290,28 +296,110 @@ def draw
# page is finished.
cells_this_page = []

@cells.each do |cell|
if start_new_page?(cell, offset, ref_bounds)
# draw cells on the current page and then start a new one
# this will also add a header to the new page if a header is set
# reset array of cells for the new page
cells_this_page, offset = ink_and_draw_cells_and_start_new_page(cells_this_page, cell)
# for multipage, track cells for all pages and then draw all
pages = []

if @multipage
# track last row and page
last_row = nil
last_page = nil

@cells.each do |cell|
if cell.column == 0
if cell.row == 0
# new page for new table
page = Page.new(0, @header)
pages << page
else
# select odd page for new row
n = pages.length
page = n.even? ? pages[n - 2] : pages[n - 1]

# increase height because is first column and new row
last_cell = page.cells.last
page.height += last_cell[0].height
end

# remember the current row for background coloring
started_new_page_at_row = cell.row
# how is new row clear page's width
pages.each do |pag|
pag.width = 0 unless pag.closed
end
else
# select page for not first columns
if last_page.width < self.width && !last_page.closed
page = last_page
else
n = pages.length

((last_page.index + 1)...n).map do |i|
page = pages[i] if !pages[i].closed
break if !page.nil?
end
end
end

if multipage_not_fits_height?(cell, page, ref_bounds, last_page,
last_row)
# check if row fits in page height
page.closed = true
i = page.index
last_page_header = page.get_header

(i...(pages.length + 1)).map do |n|
# check is even or odd as page
if i.even? == n.even? && n != i
page = select_page(pages, n)
page.add_header(last_page_header)
break
end
end
elsif multipage_not_fits_width?(cell, page, self.width)
# check if row fits in page width
i = page.index + 1
page = select_page(pages, i)
end

page.height += cell.height unless not_increase_height?(last_row, last_page, cell, page)

# set x and y position for cell
cell.x = page.width
cell.y = -page.height

page.width += cell.width
page.cells << [cell, [cell.relative_x, cell.relative_y(offset)]]

last_row, last_page = cell.row, page
end

# Set background color, if any.
cell = set_background_color(cell, started_new_page_at_row)
pages.each do |pag|
# draw cells
@pdf.start_new_page unless pag.index == 0
ink_and_draw_cells(pag.cells)
end
else
@cells.each do |cell|
if start_new_page?(cell, offset, ref_bounds)
# draw cells on the current page and then start a new one
# this will also add a header to the new page if a header is set
# reset array of cells for the new page
cells_this_page, offset = ink_and_draw_cells_and_start_new_page(cells_this_page, cell)

# remember the current row for background coloring
started_new_page_at_row = cell.row
end

# add the current cell to the cells array for the current page
cells_this_page << [cell, [cell.relative_x, cell.relative_y(offset)]]
end
# Set background color, if any.
cell = set_background_color(cell, started_new_page_at_row)

# Draw the last page of cells
ink_and_draw_cells(cells_this_page)
# add the current cell to the cells array for the current page
cells_this_page << [cell, [cell.relative_x, cell.relative_y(offset)]]
end

# Draw the last page of cells
ink_and_draw_cells(cells_this_page)

@pdf.move_cursor_to(@cells.last.relative_y(offset) - @cells.last.height)
@pdf.move_cursor_to(@cells.last.relative_y(offset) - @cells.last.height)
end
end
end

Expand Down Expand Up @@ -410,6 +498,38 @@ def number_of_header_rows
0
end

def multipage_not_fits_height?(cell, page, ref_bounds, last_page, last_row)
(page.height + cell.height > ref_bounds.height) &&
last_page != page && last_row != cell.row
end

def multipage_not_fits_width?(cell, page, table_width)
(page.width + cell.width) > table_width
end

def select_page pages, i
p = nil

pages.each do |page|
if page.index == i
p = page
end
end

if p.nil?
p = Page.new(i, @header)
pages << p
end

p
end

def not_increase_height? last_row, last_page, cell, page
last_row.nil? || last_row == cell.row && last_page == page ||
cell.column == 0 || page.cells.length == 0 ||
page.cells.last[0].row == cell.row
end

# should we start a new page? (does the current row fail to fit on this page)
def start_new_page?(cell, offset, ref_bounds)
# we only need to run this test on the first cell in a row
Expand Down
46 changes: 46 additions & 0 deletions lib/prawn/table/page.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Prawn
class Table
# Page class are thinked to keep data and methods necesary to then render
# table in multiple pages with header
class Page
attr_accessor :index, :width, :height, :cells, :closed, :header,
:header_rows

def initialize i, header
@index = i
@cells = []
@width = 0
@height = 0
@closed = false
@header = header
@header_rows = []
end

# I only handle header like boolean for now, since that is my case use,
# but should be better handle another values that can be get header
def cells_in_first_row
# get cells in first row
first_row = []
cells.each do |cell|
if cell[0].row == 0
first_row << cell
end
end
first_row
end

def get_header
if @header
cells_in_first_row
end
end

def add_header cells
if @header
@cells = cells
@height += cells.last[0].height
end
end
end
end
end
61 changes: 61 additions & 0 deletions spec/table_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,67 @@
end
end
end

describe 'multipage option' do
describe 'when define width for all columns' do
it 'should render table without problems' do
pdf = Prawn::Document.new
pdf.table([["foo", "bar", "baz"]], column_widths: [300, 200, 500], multipage: true)
expect(pdf.page_count).to eq(2)

pdf = Prawn::Document.new
data = [["foo", "bar", "baz"]] * 5
pdf.table(data, column_widths: [300, 200, 500], multipage: true)
expect(pdf.page_count).to eq(2)

pdf = Prawn::Document.new
data = [["foo", "bar", "baz"]] * 20
pdf.table(data, column_widths: [300, 200, 500], multipage: true)
expect(pdf.page_count).to eq(2)

pdf = Prawn::Document.new
data = [["foo", "bar", "baz"]] * 50
pdf.table(data, column_widths: [300, 200, 500], multipage: true)
expect(pdf.page_count).to eq(4)

pdf = Prawn::Document.new
data = [["foo", "bar", "baz"]] * 150
pdf.table(data, column_widths: [300, 200, 500], multipage: true)
expect(pdf.page_count).to eq(10)
end
end

describe 'when not define width for columns' do
it 'should render table' do
pdf = Prawn::Document.new
data = [["foo", "bar", "baz"] * 10 ]
pdf.table(data, multipage: true)
expect(pdf.page_count).to eq(2)

pdf = Prawn::Document.new
data = [["foo", "bar", "baz"] * 15 ]
pdf.table(data, multipage: true)
expect(pdf.page_count).to eq(3)

pdf = Prawn::Document.new
data = [["foo", "bar", "baz"] * 25 ]
pdf.table(data, multipage: true)
expect(pdf.page_count).to eq(4)
end
end

it "allow header with multipage" do
pdf = Prawn::Document.new
header = ["a", "b", "c"] * 10
data = [["foo", "bar", "baz"] * 10 ] * 35
data.insert(0, header)
pdf.table(data, multipage: true, header: true)
expect(pdf.page_count).to eq(4)

output = PDF::Inspector::Page.analyze(pdf.render)
output.pages[0][:strings][0..4].should == output.pages[2][:strings][0..4]
end
end
end

describe "#style" do
Expand Down