diff --git a/examples/accented.cr b/examples/accented.cr index d2a7b19..07ad48f 100644 --- a/examples/accented.cr +++ b/examples/accented.cr @@ -1,11 +1,11 @@ require "../src/hpdf" -k = "Können wir mit Umlauten umgehen? Ja von Kopf bis Fuß.".encode("ISO8859-2") - pdf = Hpdf::Doc.build do page do - text Hpdf::Base14::Helvetica, 20, encoding: "ISO8859-2" do - text_out :center, height - 50, k + use_encoding Hpdf::Encodings::ISO8859_2 do + text Hpdf::Base14::Helvetica, 20 do + text_out :center, height - 50, "Können wir mit Umlauten umgehen? Ja von Kopf bis Fuß." + end end end end diff --git a/montage.png b/montage.png index bd06ecd..f9d7acc 100644 Binary files a/montage.png and b/montage.png differ diff --git a/pdfs/examples-accented.pdf b/pdfs/examples-accented.pdf index 69c78f5..968505c 100644 Binary files a/pdfs/examples-accented.pdf and b/pdfs/examples-accented.pdf differ diff --git a/pdfs/spec-doc-attributes.pdf b/pdfs/spec-doc-attributes.pdf index 61636b7..3e65404 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 fe4c476..68b74e7 100644 Binary files a/pdfs/spec-doc-password.pdf and b/pdfs/spec-doc-password.pdf differ diff --git a/pdfs/spec-invoice.pdf b/pdfs/spec-invoice.pdf index 400a1c1..cef09f6 100644 Binary files a/pdfs/spec-invoice.pdf and b/pdfs/spec-invoice.pdf differ diff --git a/pdfs/spec-table.pdf b/pdfs/spec-table.pdf index 516c8d6..08de730 100644 Binary files a/pdfs/spec-table.pdf and b/pdfs/spec-table.pdf differ diff --git a/spec/hpdf/invoice_spec.cr b/spec/hpdf/invoice_spec.cr index 515e328..b5f8ce5 100644 --- a/spec/hpdf/invoice_spec.cr +++ b/spec/hpdf/invoice_spec.cr @@ -6,24 +6,26 @@ describe Hpdf::Letter do # debug_draw_boxes # Header - draw_address company: "Evil Corp", - salutation: "Mr.", - name: "Robot", - street: "Street 1", - place: "New York", - country: "USA" + use_encoding Hpdf::Encodings::ISO8859_2 do + draw_address company: "Evil Cörp", + salutation: "Mr.", + name: "Robot", + street: "Street 1", + place: "New York", + country: "USA" - draw_remark_area first: "Good Corp | Awesomestr. 20 | 12345 Place" + draw_remark_area first: "Good Corp | Awesomestr. 20 | 12345 Place" - draw_infobox Hpdf::Base14::Helvetica, 12 do - row "Your contact:", "Max Mustermann" - row "Department:", "Customer Service" - row - row "Phone:", "09161 620-9800" - row "Fax:", "09161 8989-2000" - row "E-Mail:", "info@goodcorp.com" - row - row "Date:", "2022-02-01" + draw_infobox Hpdf::Base14::Helvetica, 12 do + row "Your contact:", "Max Müstermann" + row "Department:", "Customer Service" + row + row "Phone:", "09161 620-9800" + row "Fax:", "09161 8989-2000" + row "E-Mail:", "info@goodcorp.com" + row + row "Date:", "2022-02-01" + end end # Content @@ -39,80 +41,82 @@ describe Hpdf::Letter do "\n\nKind regards your service team" end - table(content_rect.padding(top: 150), fixed_row_height: 22) do - row do - text_cell "Position", - align: Hpdf::TextAlignment::Center, - font: Hpdf::Base14::HelveticaBold, - bg_gray: 0.9 - text_cell "Description", - align: Hpdf::TextAlignment::Center, - font: Hpdf::Base14::HelveticaBold, - span: 4, - bg_gray: 0.9 - text_cell "Quantity", - align: Hpdf::TextAlignment::Center, - font: Hpdf::Base14::HelveticaBold, - bg_gray: 0.9 - text_cell "Price", - align: Hpdf::TextAlignment::Center, - font: Hpdf::Base14::HelveticaBold, - bg_gray: 0.9 - text_cell "Total", - align: Hpdf::TextAlignment::Center, - font: Hpdf::Base14::HelveticaBold, - bg_gray: 0.9 - end - row do - text_cell "1" - text_cell "iPhone 12", - span: 4 - text_cell "2", - align: Hpdf::TextAlignment::Right - text_cell "899.00", - align: Hpdf::TextAlignment::Right - text_cell "1798.00", - align: Hpdf::TextAlignment::Right - end - row do - text_cell "2" - text_cell "MacBookPro 14\"", - span: 4 - text_cell "1", - align: Hpdf::TextAlignment::Right - text_cell "2499.00", - align: Hpdf::TextAlignment::Right - text_cell "2499.00", - align: Hpdf::TextAlignment::Right - end - row do - text_cell "3" - text_cell "iPad", - span: 4 - text_cell "1", - align: Hpdf::TextAlignment::Right - text_cell "499.00", - align: Hpdf::TextAlignment::Right - text_cell "499.00", - align: Hpdf::TextAlignment::Right - end - row { } - row do - text_cell "Total EUR", - span: 7, - align: Hpdf::TextAlignment::Right - text_cell "4796.00", - font: Hpdf::Base14::HelveticaBold, - align: Hpdf::TextAlignment::Right, - bg_gray: 0.9 - end - row do - text_cell "incl. VAT 19%", - span: 7, - align: Hpdf::TextAlignment::Right - text_cell "765.75", - align: Hpdf::TextAlignment::Right, - bg_gray: 0.9 + use_encoding Hpdf::Encodings::ISO8859_2 do + table(content_rect.padding(top: 150), fixed_row_height: 22) do + row do + text_cell "Position", + align: Hpdf::TextAlignment::Center, + font: Hpdf::Base14::HelveticaBold, + bg_gray: 0.9 + text_cell "Description", + align: Hpdf::TextAlignment::Center, + font: Hpdf::Base14::HelveticaBold, + span: 4, + bg_gray: 0.9 + text_cell "Quantity", + align: Hpdf::TextAlignment::Center, + font: Hpdf::Base14::HelveticaBold, + bg_gray: 0.9 + text_cell "Price", + align: Hpdf::TextAlignment::Center, + font: Hpdf::Base14::HelveticaBold, + bg_gray: 0.9 + text_cell "Total", + align: Hpdf::TextAlignment::Center, + font: Hpdf::Base14::HelveticaBold, + bg_gray: 0.9 + end + row do + text_cell "1" + text_cell "iPhone 12", + span: 4 + text_cell "2", + align: Hpdf::TextAlignment::Right + text_cell "899.00", + align: Hpdf::TextAlignment::Right + text_cell "1798.00", + align: Hpdf::TextAlignment::Right + end + row allow_grow: true do + text_cell "2" + text_cell "MacBookPro 14\"\nBehold an entirely new class of GPU.\nAnd the biggest breakthrough in silicön", + span: 4 + text_cell "1", + align: Hpdf::TextAlignment::Right + text_cell "2499.00", + align: Hpdf::TextAlignment::Right + text_cell "2499.00", + align: Hpdf::TextAlignment::Right + end + row do + text_cell "3" + text_cell "iPad", + span: 4 + text_cell "1", + align: Hpdf::TextAlignment::Right + text_cell "499.00", + align: Hpdf::TextAlignment::Right + text_cell "499.00", + align: Hpdf::TextAlignment::Right + end + row { } + row do + text_cell "Total EUR", + span: 7, + align: Hpdf::TextAlignment::Right + text_cell "4796.00", + font: Hpdf::Base14::HelveticaBold, + align: Hpdf::TextAlignment::Right, + bg_gray: 0.9 + end + row do + text_cell "incl. VAT 19%", + span: 7, + align: Hpdf::TextAlignment::Right + text_cell "765.75", + align: Hpdf::TextAlignment::Right, + bg_gray: 0.9 + end end end diff --git a/src/hpdf/doc.cr b/src/hpdf/doc.cr index 31a2954..773c827 100644 --- a/src/hpdf/doc.cr +++ b/src/hpdf/doc.cr @@ -130,8 +130,12 @@ module Hpdf end # gets the handle of a corresponding font object by specified name and encoding. - def font(name : String, encoding : String? = nil) - Font.new(LibHaru.get_font(@doc, name, encoding), self) + def font(name : String, encoding enc : String? = nil) + if enc + Font.new(LibHaru.get_font(@doc, name, enc), self) + else + Font.new(LibHaru.get_font(@doc, name, nil), self) + end end # loads a type1 font from an external file and register it to a document object. diff --git a/src/hpdf/page.cr b/src/hpdf/page.cr index d752904..2a9e699 100644 --- a/src/hpdf/page.cr +++ b/src/hpdf/page.cr @@ -10,6 +10,7 @@ module Hpdf @font : Font? = nil @font_size : Float32 = 0 + @encoding : String? = nil # get the current font if any getter font @@ -73,7 +74,7 @@ module Hpdf # * *text* the text to be displayed. # * *encoder* an encoder handle which is used to encode the text. # If it is null, PDFDocEncoding is used. - def create_text_annotation(rect : Rectangle, text : (String | Bytes), encoder : Encoder? = nil) : TextAnnotation + def create_text_annotation(rect : Rectangle, text : String, encoder : Encoder? = nil) : TextAnnotation TextAnnotation.new(LibHaru.page_create_text_annotation(self, rect, text, encoder), @doc, self) end @@ -94,8 +95,8 @@ module Hpdf end # gets the width of the text in current fontsize, character spacing and word spacing. - def text_width(text : (String | Bytes)) : Float32 - LibHaru.page_text_width(self, text).to_f32 + def text_width(text : String) : Float32 + LibHaru.page_text_width(self, encoded_text(text)).to_f32 end # calculates the byte length which can be included within the specified width. @@ -106,8 +107,8 @@ module Hpdf # until `"J"` can be included within the width, if *word_wrap* parameter is `false` # it returns `12`, and if word_wrap parameter is `false` *word_wrap* parameter is # `false` it returns `10` (the end of the previous word). - def measure_text(text : (String | Bytes), *, width : Number, word_wrap : Bool = true) : MeasuredText - size = LibHaru.page_measure_text(self, text, + def measure_text(text : String, *, width : Number, word_wrap : Bool = true) : MeasuredText + size = LibHaru.page_measure_text(self, encoded_text(text), real(width), bool(word_wrap), out real_width) MeasuredText.new(size, real_width) end @@ -827,9 +828,9 @@ module Hpdf # page is in `GMode::PageDescription` or `GMode::TextObject`. # # * *text* the text to print. - def show_text(text : (String | Bytes)) + def show_text(text : String) requires_mode GMode::PageDescription, GMode::TextObject - LibHaru.page_show_text(self, text) + LibHaru.page_show_text(self, encoded_text(text)) end # moves the current text position to the start of the next line, @@ -837,9 +838,9 @@ module Hpdf # An application can invoke `show_text_next_line` when the # graphics mode of the page is in `GMode::PageDescription` or # `GMode::TextObject`. - def show_text_next_line(text : (String | Bytes)) + def show_text_next_line(text : String) requires_mode GMode::PageDescription, GMode::TextObject - LibHaru.page_show_text_next_line(self, text) + LibHaru.page_show_text_next_line(self, encoded_text(text)) end # moves the current text position to the start of the next line, @@ -847,9 +848,9 @@ module Hpdf # text at the current position on the page. # An application can invoke `show_text_next_line_ex` when the # graphics mode of the page is in `GMode::TextObject`. - def show_text_next_line_ex(text : (String | Bytes), word_space : Number, char_space : Number) + def show_text_next_line_ex(text : String, word_space : Number, char_space : Number) requires_mode GMode::TextObject - LibHaru.page_show_text_next_line_ex(self, real(word_space), real(char_space), text) + LibHaru.page_show_text_next_line_ex(self, real(word_space), real(char_space), encoded_text(text)) end # sets the filling color. @@ -964,7 +965,7 @@ module Hpdf # # * *x*, *y* the point position where the text is displayed. # * *text* the text to show. - def text_out(x : Number | Symbol, y : Number | Symbol, text : (String | Bytes)) + def text_out(x : Number | Symbol, y : Number | Symbol, text : String) requires_mode GMode::TextObject if x == :center x = (width - text_width(text)) / 2 @@ -977,7 +978,7 @@ module Hpdf y = height / 2 - @font_size / 2 end end - LibHaru.page_text_out(self, real(x.as(Number)), real(y.as(Number)), text) + LibHaru.page_text_out(self, real(x.as(Number)), real(y.as(Number)), encoded_text(text)) end # print the text inside the specified region. Some chars may not @@ -991,16 +992,16 @@ module Hpdf # * *text* the text to show. # * *align* the alignment of the text. def text_rect(left : Number, top : Number, right : Number, bottom : Number, - text : (String | Bytes), *, align : TextAlignment = TextAlignment::Left) : Number + text : String, *, align : TextAlignment = TextAlignment::Left) : Number requires_mode GMode::TextObject LibHaru.page_text_rect(self, real(left), real(top), real(right), real(bottom), - text, align.to_i, out len) + encoded_text(text), align.to_i, out len) len end # see `text_rect`. - def text_rect(rect : Rectangle, text : (String | Bytes), *, + def text_rect(rect : Rectangle, text : String, *, align : TextAlignment = TextAlignment::Left) : Number text_rect rect.left, rect.top, rect.right, rect.bottom, text, align: align end @@ -1011,8 +1012,8 @@ module Hpdf set_dash([] of Int32, phase: 0) end - def use_font(name, size, *, encoding : String? = nil) - @font = @doc.font(name, encoding: encoding) + def use_font(name, size, *, encoding enc : String? = @encoding) + @font = @doc.font(name, encoding: enc) @font_size = size.to_f32 if f = @font set_font_and_size(f, @font_size) @@ -1024,9 +1025,9 @@ module Hpdf # measures the passed *text* width using the current font and font size. # An application can invoke `measure_text_width` when the `graphics_mode` # of the page is in `GMode::TextObject`. - def measure_text_width(text : (String | Bytes)) : MeasuredText + def measure_text_width(text : String) : MeasuredText if f = @font - f.measure_text text, font_size: @font_size, + f.measure_text encoded_text(text), font_size: @font_size, width: width, word_space: word_space, char_space: char_space @@ -1053,6 +1054,32 @@ module Hpdf end end + # set encoding to be used for strings + def use_encoding(encoding : String, &) + tmp = @encoding + @encoding = encoding + v = with self yield self + @encoding = tmp + v + end + + private def encoded_text(text : String) : (String | Bytes) + return text if ascii_only?(text) + + if enc = @encoding + text.encode(enc) + 0x00 + else + text + end + end + + private def ascii_only?(str : String) : Bool + str.each_char do |char| + return false if char.ord > 127 + end + true + end + # ## DSL ### # build enables DSL style access to building a page @@ -1060,7 +1087,7 @@ module Hpdf with self yield self end - def text(name = nil, size = nil, *, encoding enc : String? = nil, &) + def text(name = nil, size = nil, *, encoding enc : String = @encoding, &) if name && size use_font(name, size, encoding: enc) end diff --git a/src/hpdf/table.cr b/src/hpdf/table.cr index c7c0af6..b08a25e 100644 --- a/src/hpdf/table.cr +++ b/src/hpdf/table.cr @@ -8,7 +8,6 @@ module Hpdf # creates a new table using the given rect # # * *spacing* spacing between the cells, by default there is no spacing - # * *fixed_row_height* allows to specify the row height instead of calculation def initialize(@rect : Rectangle, *, spacing : Number = 0, @fixed_row_height : Number = 0) @@ -30,8 +29,8 @@ module Hpdf # creates a rows and yields the block with the newly created row # and adds it to the end (bottom) of the table - def row(&) - row = Row.new + def row(*, height = 0, allow_grow = false, &) + row = Row.new(height: height, allow_grow: allow_grow) with row yield row add_row(row) end @@ -65,11 +64,19 @@ module Hpdf end # returns the height of each row - def row_height - if @fixed_row_height > 0 - @fixed_row_height + def row_height(row) + if row.height > 0 + row.height else - (@rect.height + @spacing) / row_count + if @fixed_row_height > 0 + if row.allow_grow? + @fixed_row_height * row.grow_height + else + @fixed_row_height + end + else + (@rect.height + @spacing) / row_count + end end end @@ -81,12 +88,15 @@ module Hpdf # draw rows top down, this means an inverted order as tables, # are top down, while the pdf is bottom-top oriented - rows.each_with_index do |row, i| + top = 0 + rows.each do |row| column_offset = @rect.x - half_spacing + top += row_height(row) - half_spacing + row_rect = Rectangle.new(@rect.x, - @rect.top - (row_height * (i + 1) - half_spacing), + @rect.top - top, @rect.width + @spacing, - row_height) + row_height(row)) row.rect = row_rect column_count = row.cells.reduce(0) { |sum, col| sum + col.span } @@ -96,7 +106,7 @@ module Hpdf column_rect = Rectangle.new(column_offset + half_spacing, row_rect.y + half_spacing, column_width - @spacing, - row_height - @spacing) + row_height(row) - @spacing) cell.rect = column_rect column_offset += column_width end @@ -109,20 +119,24 @@ module Hpdf class Row property cells property rect : Rectangle? + property height + property? allow_grow : Bool # Create a new row - def initialize + def initialize(*, height = 0, allow_grow = false) @cells = [] of BaseCell + @height = height + @allow_grow = allow_grow 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) + add_cell Cell.new(span: span, allow_grow: @allow_grow, &block) end # creates a new text cell using the passed parameters - def text_cell(text : (String | Bytes), + def text_cell(text : String, *, span : Number = 1, font : String = Hpdf::Base14::Helvetica, @@ -131,6 +145,7 @@ module Hpdf bg_gray : Float32 = 0) add_cell TextCell.new(text, span: span, + allow_grow: @allow_grow, font: font, font_size: font_size, align: align, @@ -141,6 +156,14 @@ module Hpdf def add_cell(cell : BaseCell) @cells << cell end + + def grow_height : Float64 + if !allow_grow? + 1.to_f + else + @cells.max_of(&.grow_height) + end + end end # The cell is the smallest part of the `Table`. It is rendered before @@ -148,16 +171,20 @@ module Hpdf abstract class BaseCell property rect : Rectangle? property span + property? allow_grow : Bool # Creates a cell with the provided block and stores the cell span. # # * *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) + def initialize(*, span : Number = 1, @allow_grow = false) @span = span.to_f32 end abstract def render(page : Page) + + # returns the factor by which the cell wants to grow in height + abstract def grow_height : Float64 end # The cell uses a simple captured block to render the cell content. @@ -169,28 +196,33 @@ module Hpdf # # * *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) ->) - super(span: span) + def initialize(*, span : Number = 1, @allow_grow = false, &@block : (Page, Rectangle) ->) + super(span: span, allow_grow: @allow_grow) end # renders the captured block def render(page : Page) @block.call(page, rect.as(Rectangle)) end + + def grow_height : Float64 + 1.to_f + end end # The text cell renders the given text class TextCell < BaseCell property rect : Rectangle? - def initialize(@text : (String | Bytes), + def initialize(@text : String, *, span : Number = 1, + @allow_grow = false, @font : String = Hpdf::Base14::Helvetica, @font_size : Number = 11, @align = Hpdf::TextAlignment::Center, @bg_gray : Float32 = 0) - super(span: span) + super(span: span, allow_grow: @allow_grow) end # renders the captured block @@ -206,10 +238,18 @@ module Hpdf page.text @font, @font_size do # add left and right padding r = r.padding left: 2, right: 2 - # center text - r.y -= (r.height.to_f32 / 2 - @font_size.to_f32 / 2) - 1 + if allow_grow? + r.y -= @font_size.to_f32 / 2 - 1 + else + # center text + r.y -= (r.height.to_f32 / 2 - @font_size.to_f32 / 2) - 1 + end text_rect r, @text, align: @align end end + + def grow_height : Float64 + @text.count("\n").to_f + 1 + end end end