From 3e698af4cce3aab2c6b4923890c988b491aa10aa Mon Sep 17 00:00:00 2001 From: ccarruitero Date: Sat, 1 Aug 2015 23:24:39 -0500 Subject: [PATCH 1/7] first try to get multipage option --- lib/prawn/table.rb | 135 ++++++++++++++++++++++++++++++++++++++------- spec/table_spec.rb | 45 +++++++++++++++ 2 files changed, 161 insertions(+), 19 deletions(-) diff --git a/lib/prawn/table.rb b/lib/prawn/table.rb index 264bf7a..d7bddec 100644 --- a/lib/prawn/table.rb +++ b/lib/prawn/table.rb @@ -144,9 +144,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 +173,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 +295,98 @@ 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 = pages[0] = set_new_page(0) + else + # select odd page for new row + n = pages.size + page = n.even? ? pages[n - 2] : pages[n - 1] + end + + # how is new row clear page's width + pages.each do |k,v| + pages[k]["width"] = 0 unless pages[k]["closed"] + end + else + # select page for not first columns + if last_page["width"] < self.width && !last_page["closed"] + page = last_page + else + n = pages.size + + ((last_page["index"] + 1)...n).map do |i| + page = pages[i] if !pages[i]["closed"] + break if !page.nil? + end + end + end - # remember the current row for background coloring - started_new_page_at_row = cell.row + 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"] + + (i...(pages.size + 1)).map do |n| + # check is even or odd as page + if i.even? == n.even? && n != i + page = select_page(pages, n) + 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["width"] += cell.width + page["cells"] << [cell, [cell.relative_x, cell.relative_y(offset)]] + + page["height"] += cell.height unless last_row == cell.row && last_page == page + 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 |k, v| + # draw cells + @pdf.start_new_page unless k == 0 + ink_and_draw_cells(pages[k]["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) + + # 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) + # 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 +485,28 @@ 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 set_new_page i + page = {} + page["index"] = i + page["cells"] = [] + page["width"] = 0 + page["height"] = 0 + page["closed"] = false + page + end + + def select_page obj, i + obj[i].nil? ? (obj[i] = set_new_page(i)) : obj[i] + 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/spec/table_spec.rb b/spec/table_spec.rb index d33c245..236640a 100644 --- a/spec/table_spec.rb +++ b/spec/table_spec.rb @@ -944,6 +944,51 @@ end end end + + describe "multipage option" do + it "allow render table in multiple pages based in cells width" 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 + + it "allow multipage table when not define widths" 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 end describe "#style" do From 8fd14c0fcd0a3723f67afe5c67b8ac16f598d15a Mon Sep 17 00:00:00 2001 From: ccarruitero Date: Mon, 31 Aug 2015 10:04:33 -0500 Subject: [PATCH 2/7] set cells position for be drawn --- lib/prawn/table.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/prawn/table.rb b/lib/prawn/table.rb index d7bddec..08bc957 100644 --- a/lib/prawn/table.rb +++ b/lib/prawn/table.rb @@ -312,6 +312,10 @@ def draw # select odd page for new row n = pages.size 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 # how is new row clear page's width @@ -351,10 +355,14 @@ def draw page = select_page(pages, i) end + # 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)]] + page["height"] += cell.height unless not_increase_height?(last_row, last_page, cell, page) - page["height"] += cell.height unless last_row == cell.row && last_page == page last_row, last_page = cell.row, page end @@ -507,6 +515,10 @@ def set_new_page i def select_page obj, i obj[i].nil? ? (obj[i] = set_new_page(i)) : obj[i] end + + def not_increase_height? last_row, last_page, cell, page + last_row.nil? || last_row == cell.row && last_page == page || cell.column == 0 + 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 From 4ea5c38add4b31742e864a15f7728dd7b7dc28d9 Mon Sep 17 00:00:00 2001 From: ccarruitero Date: Mon, 31 Aug 2015 18:57:10 -0500 Subject: [PATCH 3/7] fix cells y-position --- lib/prawn/table.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/prawn/table.rb b/lib/prawn/table.rb index 08bc957..78cc0de 100644 --- a/lib/prawn/table.rb +++ b/lib/prawn/table.rb @@ -355,13 +355,14 @@ def draw 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)]] - page["height"] += cell.height unless not_increase_height?(last_row, last_page, cell, page) last_row, last_page = cell.row, page end @@ -517,8 +518,11 @@ def select_page obj, i end def not_increase_height? last_row, last_page, cell, page - last_row.nil? || last_row == cell.row && last_page == page || cell.column == 0 + last_row.nil? || last_row == cell.row && last_page == page || + cell.column == 0 || page["cells"].size == 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 From 06ed27d6e6358974b38a46577126d0efe9a5d944 Mon Sep 17 00:00:00 2001 From: ccarruitero Date: Fri, 25 Sep 2015 22:16:21 -0500 Subject: [PATCH 4/7] add spec for header with multipage --- spec/table_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/table_spec.rb b/spec/table_spec.rb index 236640a..3427516 100644 --- a/spec/table_spec.rb +++ b/spec/table_spec.rb @@ -988,6 +988,18 @@ pdf.table(data, multipage: true) expect(pdf.page_count).to eq(4) 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 From 048cbf1b362028eb9d3e66aff272b90a813eccf3 Mon Sep 17 00:00:00 2001 From: ccarruitero Date: Sun, 27 Sep 2015 22:34:01 -0500 Subject: [PATCH 5/7] add page class --- lib/prawn/table.rb | 81 ++++++++++++++++++++++------------------- lib/prawn/table/page.rb | 18 +++++++++ 2 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 lib/prawn/table/page.rb diff --git a/lib/prawn/table.rb b/lib/prawn/table.rb index 78cc0de..39c162d 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 @@ -296,7 +297,7 @@ def draw cells_this_page = [] # for multipage, track cells for all pages and then draw all - pages = {} + pages = [] if @multipage # track last row and page @@ -307,30 +308,31 @@ def draw if cell.column == 0 if cell.row == 0 # new page for new table - page = pages[0] = set_new_page(0) + page = Page.new(0) + pages << page else # select odd page for new row - n = pages.size + 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 + last_cell = page.cells.last + page.height += last_cell[0].height end # how is new row clear page's width - pages.each do |k,v| - pages[k]["width"] = 0 unless pages[k]["closed"] + 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"] + if last_page.width < self.width && !last_page.closed page = last_page else - n = pages.size + n = pages.length - ((last_page["index"] + 1)...n).map do |i| - page = pages[i] if !pages[i]["closed"] + ((last_page.index + 1)...n).map do |i| + page = pages[i] if !pages[i].closed break if !page.nil? end end @@ -339,10 +341,10 @@ def draw 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"] + page.closed = true + i = page.index - (i...(pages.size + 1)).map do |n| + (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) @@ -351,26 +353,26 @@ def draw end elsif multipage_not_fits_width?(cell, page, self.width) # check if row fits in page width - i = page["index"] + 1 + i = page.index + 1 page = select_page(pages, i) end - page["height"] += cell.height unless not_increase_height?(last_row, last_page, cell, page) + 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"] + cell.x = page.width + cell.y = -page.height - page["width"] += cell.width - page["cells"] << [cell, [cell.relative_x, cell.relative_y(offset)]] + page.width += cell.width + page.cells << [cell, [cell.relative_x, cell.relative_y(offset)]] last_row, last_page = cell.row, page end - pages.each do |k, v| + pages.each do |pag| # draw cells - @pdf.start_new_page unless k == 0 - ink_and_draw_cells(pages[k]["cells"]) + @pdf.start_new_page unless pag.index == 0 + ink_and_draw_cells(pag.cells) end else @cells.each do |cell| @@ -495,32 +497,35 @@ def number_of_header_rows end def multipage_not_fits_height?(cell, page, ref_bounds, last_page, last_row) - (page["height"] + cell.height > ref_bounds.height) && + (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 + (page.width + cell.width) > table_width end - def set_new_page i - page = {} - page["index"] = i - page["cells"] = [] - page["width"] = 0 - page["height"] = 0 - page["closed"] = false - page - 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) + pages << p + end - def select_page obj, i - obj[i].nil? ? (obj[i] = set_new_page(i)) : obj[i] + 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"].size == 0 || - page["cells"].last[0].row == cell.row + 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) diff --git a/lib/prawn/table/page.rb b/lib/prawn/table/page.rb new file mode 100644 index 0000000..5b9d5ef --- /dev/null +++ b/lib/prawn/table/page.rb @@ -0,0 +1,18 @@ +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_rows + + def initialize i + @index = i + @cells = [] + @width = 0 + @height = 0 + @closed = false + @header_rows = [] + end + end + end +end From d05718a01a67a5d3235a56261c3fd66d29911653 Mon Sep 17 00:00:00 2001 From: ccarruitero Date: Sun, 27 Sep 2015 22:55:30 -0500 Subject: [PATCH 6/7] add more description in multipage specs --- spec/table_spec.rb | 80 ++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/spec/table_spec.rb b/spec/table_spec.rb index 3427516..4b0f34f 100644 --- a/spec/table_spec.rb +++ b/spec/table_spec.rb @@ -945,48 +945,52 @@ end end - describe "multipage option" do - it "allow render table in multiple pages based in cells width" 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) + 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 - it "allow multipage table when not define widths" do - pdf = Prawn::Document.new - data = [["foo", "bar", "baz"] * 10 ] - pdf.table(data, multipage: true) - expect(pdf.page_count).to eq(2) + 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"] * 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) + 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 From 111e044a782653889fb5b3d994c60c15496169fb Mon Sep 17 00:00:00 2001 From: ccarruitero Date: Mon, 5 Oct 2015 22:14:27 -0500 Subject: [PATCH 7/7] allow header with multipage --- lib/prawn/table.rb | 6 ++++-- lib/prawn/table/page.rb | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/prawn/table.rb b/lib/prawn/table.rb index 39c162d..0a97295 100644 --- a/lib/prawn/table.rb +++ b/lib/prawn/table.rb @@ -308,7 +308,7 @@ def draw if cell.column == 0 if cell.row == 0 # new page for new table - page = Page.new(0) + page = Page.new(0, @header) pages << page else # select odd page for new row @@ -343,11 +343,13 @@ def draw # 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 @@ -515,7 +517,7 @@ def select_page pages, i end if p.nil? - p = Page.new(i) + p = Page.new(i, @header) pages << p end diff --git a/lib/prawn/table/page.rb b/lib/prawn/table/page.rb index 5b9d5ef..13c6696 100644 --- a/lib/prawn/table/page.rb +++ b/lib/prawn/table/page.rb @@ -3,16 +3,44 @@ 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_rows + attr_accessor :index, :width, :height, :cells, :closed, :header, + :header_rows - def initialize i + 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