diff --git a/montage.png b/montage.png index d9d3524..528ec5c 100644 Binary files a/montage.png and b/montage.png differ diff --git a/pdfs/examples-font.pdf b/pdfs/examples-font.pdf index 3c1483c..c3f6bcc 100644 Binary files a/pdfs/examples-font.pdf and b/pdfs/examples-font.pdf differ diff --git a/pdfs/examples-hello.pdf b/pdfs/examples-hello.pdf index 9f843b1..be3a70d 100644 Binary files a/pdfs/examples-hello.pdf and b/pdfs/examples-hello.pdf differ diff --git a/pdfs/spec-doc-attributes.pdf b/pdfs/spec-doc-attributes.pdf index 7933074..5b8e9eb 100644 Binary files a/pdfs/spec-doc-attributes.pdf and b/pdfs/spec-doc-attributes.pdf differ diff --git a/pdfs/spec-doc-password.pdf b/pdfs/spec-doc-password.pdf index 0992229..21260fe 100644 Binary files a/pdfs/spec-doc-password.pdf and b/pdfs/spec-doc-password.pdf differ diff --git a/pdfs/spec-table.pdf b/pdfs/spec-table.pdf new file mode 100644 index 0000000..e068f36 Binary files /dev/null and b/pdfs/spec-table.pdf differ diff --git a/spec/hpdf/table_spec.cr b/spec/hpdf/table_spec.cr new file mode 100644 index 0000000..a714cde --- /dev/null +++ b/spec/hpdf/table_spec.cr @@ -0,0 +1,80 @@ +describe Hpdf::Table do + it "can create a table" do + testdoc "table" do + page do + sudoku = [ + [9, 0, 4, 0, 8, 0, 0, 0, 0], + [0, 0, 0, 7, 0, 0, 0, 9, 0], + [2, 8, 0, 0, 0, 0, 4, 0, 1], + [0, 0, 0, 5, 1, 0, 0, 4, 6], + [0, 2, 0, 0, 4, 0, 0, 0, 0], + [0, 0, 5, 8, 0, 0, 9, 0, 7], + [4, 0, 6, 0, 5, 8, 7, 1, 3], + [5, 7, 2, 0, 3, 9, 0, 8, 4], + [0, 1, 0, 0, 7, 6, 0, 0, 0], + ] + + text Hpdf::Base14::Helvetica, 20 do + move_text_pos 100, 450 + show_text "Sudoku" + end + + table(x: 100, y: 100, width: 300, height: 300) do + sudoku.each do |row_data| + row do + row_data.each do |val| + cell do |p, rect| + if val != 0 + # gray background for prefilled numbers + p.context do + p.gray_fill = 0.9 + rectangle rect + fill + end + + p.text Hpdf::Base14::Helvetica, 16 do + # center text + rect.x += 12 + rect.y -= 7 + + # draw text + text_rect rect, val.to_s + end + end + end + end + end + end + end + + text Hpdf::Base14::Helvetica, 20 do + move_text_pos 100, 650 + show_text "Cell span" + end + + table(x: 100, y: 500, width: 300, height: 120, line_width: 3) do + row do + cell span: 2 { } + cell span: 4 { } + end + row do + cell span: 2 { } + cell span: 2 { } + cell span: 2 { } + end + row do + cell { } + cell { } + cell { } + cell { } + cell { } + cell { } + end + row do + cell span: 6 { } + end + end + end + end + end +end diff --git a/src/hpdf.cr b/src/hpdf.cr index 34adfc9..7e350ce 100644 --- a/src/hpdf.cr +++ b/src/hpdf.cr @@ -30,5 +30,5 @@ require "./hpdf/*" # # Start building a document using `Doc#build` module Hpdf - VERSION = "0.9.2" + VERSION = "0.9.3" end diff --git a/src/hpdf/page.cr b/src/hpdf/page.cr index 4c8b9b8..17449ce 100644 --- a/src/hpdf/page.cr +++ b/src/hpdf/page.cr @@ -1028,9 +1028,15 @@ module Hpdf char_space: char_space end - def draw_rectangle(x : Number, y : Number, w : Number, h : Number, *, line_width lw = 1) - @line_width = lw - rectangle(x, y, w, h) + # draws a rectangle with the given coordinates and linw width + def draw_rectangle(x : Number, y : Number, w : Number, h : Number, *, line_width lw : Number = 1) + draw_rectangle(Rectangle.new(x, y, w, h), line_width: lw) + end + + # draws a rectangle with the given rectangle and linw width + def draw_rectangle(rect : Rectangle, *, line_width lw = 1) + self.line_width = lw + rectangle(rect) stroke end @@ -1066,11 +1072,30 @@ module Hpdf v end + # create path at given coordinates and yields the block + # closes the path at the end and returns the result of + # the block def path(x : Number, y : Number) move_to x, y v = with self yield self close_path v end + + # table creates a table at the given coordinates and yields + # the block in the context of the created table. + # + # * *line_width* changes the line with of the drawn cells + def table(*, x : Number, y : Number, + width : Number, height : Number, + line_width lw : Number = 1, &block) + table = Table.new(x, y, width, height) + with table yield table + table.render(self) do |object, rect| + if object.is_a? Cell + draw_rectangle rect, line_width: lw + end + end + end end end diff --git a/src/hpdf/table.cr b/src/hpdf/table.cr new file mode 100644 index 0000000..8be2baf --- /dev/null +++ b/src/hpdf/table.cr @@ -0,0 +1,114 @@ +module Hpdf + # The table is the container for `Row`, which contain `Cell`. + class Table + property rows + property rect + + # creates a new table using the given rect + def initialize(@rect : Rectangle) + @rows = [] of Row + end + + # creates a new table with the given coordinates and dimensions + def initialize(x : Number, y : Number, width : Number, height : Number) + initialize(Rectangle.new(x, y, width, height)) + end + + # creates a rows and yields the block with the newly created row + # and adds it to the end (bottom) of the table + def row(&block) + row = Row.new + with row yield row + add_row(row) + end + + # adds the row to the end (bottom) of the table + def add_row(row : Row) + @rows << row + end + + # render will call cell rendering for all cells. Then it will render + # the rectangles for the cell, row and table using the passed function + def render(page : Page, &block : (Table | Row | Cell, Rectangle) ->) + row_index = 0 + + # draw rows top down, this means an inverted order as tables, + # are top down, while the pdf is bottom-top oriented + (row_count - 1).to(0) do |i| + column_offset = @rect.x + row_rect = Rectangle.new(@rect.x, + @rect.y + row_height * i, + @rect.width, + row_height) + + row = @rows[row_index] + column_count = row.cells.reduce(0) { |sum, col| sum + col.span } + + row.cells.each do |cell| + column_width = (@rect.width / column_count) * cell.span + column_rect = Rectangle.new(column_offset, + row_rect.y, + column_width, + row_height) + cell.block.call(page, column_rect) + block.call(cell, column_rect) + + column_offset += column_width + end + + block.call(row, row_rect) + row_index += 1 + end + + block.call(self, @rect) + end + + # returns the number of rows + def row_count + rows.size + end + + # returns the height of each row + def row_height + @rect.height / row_count + end + end + + # The row is part of a `Table` and hold multiple + # `Cell` in the same vertical position. + class Row + property cells + + # Create a new row + def initialize + @cells = [] of Cell + end + + # creates a cell by capturing the block for the cell and + # adding the cell to the end of the row + def cell(*, span : Number = 1, &block : (Page, Rectangle) ->) + add_cell Cell.new(span: span, &block) + end + + # add the passed cell to the row (at the end) + def add_cell(cell : Cell) + @cells << cell + end + end + + # The cell is the smallest part of the `Table`. It is rendered before + # all grids in the order it was inserted into the `Row` and `Table`. + class Cell + property block + property span + + # Creates a cell with the provided block to render. The block provides + # a reference to the page and a rectange of the cell. + # + # * *span* a cell can expand more then one cell (in the right direction) + # the default value `1` means no extend + def initialize(*, span : Number = 1, &@block : (Page, Rectangle) ->) + @span = span.to_f32 + end + end +end