Skip to content

Commit

Permalink
adds table rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
threez committed Apr 1, 2023
1 parent ee98ae0 commit df68dc1
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 4 deletions.
Binary file modified montage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified pdfs/examples-font.pdf
Binary file not shown.
Binary file modified pdfs/examples-hello.pdf
Binary file not shown.
Binary file modified pdfs/spec-doc-attributes.pdf
Binary file not shown.
Binary file modified pdfs/spec-doc-password.pdf
Binary file not shown.
Binary file added pdfs/spec-table.pdf
Binary file not shown.
80 changes: 80 additions & 0 deletions spec/hpdf/table_spec.cr
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/hpdf.cr
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ require "./hpdf/*"
#
# Start building a document using `Doc#build`
module Hpdf
VERSION = "0.9.2"
VERSION = "0.9.3"
end
31 changes: 28 additions & 3 deletions src/hpdf/page.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
114 changes: 114 additions & 0 deletions src/hpdf/table.cr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit df68dc1

Please sign in to comment.