diff --git a/lib/prawn/table.rb b/lib/prawn/table.rb index 264bf7a..0a97295 100644 --- a/lib/prawn/table.rb +++ b/lib/prawn/table.rb @@ -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 @@ -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. @@ -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 @@ -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 @@ -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 diff --git a/lib/prawn/table/page.rb b/lib/prawn/table/page.rb new file mode 100644 index 0000000..13c6696 --- /dev/null +++ b/lib/prawn/table/page.rb @@ -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 diff --git a/spec/table_spec.rb b/spec/table_spec.rb index d33c245..4b0f34f 100644 --- a/spec/table_spec.rb +++ b/spec/table_spec.rb @@ -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