prawn icon indicating copy to clipboard operation
prawn copied to clipboard

Print out via lpr does not work with embedded Open Sans font

Open tekkla opened this issue 7 years ago • 10 comments

I'm using Open Sans as embedded font in my documents. When I send them to a printer by lpr the text gets corrupted. I think it's an encoding problem. A document using Open Sans created with TCPDF under PHP prints out via lpr without problems.

I can open the prawn created document in and print out from Evince without any problems. But not with lpr. The result is garbage. When I remove the Open Sans fonts and use the default font, the document gets printed out correct through lpr.

Any ideas what I can do to change this behavior?

tekkla avatar Apr 14 '18 22:04 tekkla

Hi Michael,

Does TCPDF perform subsetting as well?

Could you please generate a simple PDF that demonstrates the issue? Also could you please generate the same document with TCPDF for comparison?

pointlessone avatar Apr 15 '18 07:04 pointlessone

I have to change the document creation due to private data that I can not publish here on github. This takes some time. In advance I can give you this

Prawn prawn

TCPDF tcpdf

tekkla avatar Apr 15 '18 10:04 tekkla

Here a quick and dirty TCPDF document Rechnung_10927.pdf

And the one created with Prawn Invoice_10002.pdf

tekkla avatar Apr 15 '18 12:04 tekkla

Anything new on this? Do you need more informations?

tekkla avatar May 14 '18 06:05 tekkla

@tekkla How did you manage to get prawn to use OpenSans. I am doing following and unable to use font.

class DocumentPdf < Prawn::Document
  def initialize(document, view)
    font_families.update(
        "OpenSans" => { normal: "#{Rails.root.join("app/assets/fonts/OpenSans-Regular.ttf")}" })
    font 'OpenSans'
  end
end

I keep getting the following error -

NoMethodError : undefined method `pages' for nil:NilClass

C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/prawn-2.2.2/lib/prawn/font.rb:53:in `font'

Can you please help me to know what am I doing wrong here?

shah-disha avatar Jun 15 '18 11:06 shah-disha

@shah-disha I have no idea. Sorry.

tekkla avatar Jun 18 '18 06:06 tekkla

Okay, as far as I can say for now it's not a problem with embedded OpenSans. It's a general problem with each embedded font. I tried different fonts and all documents can't be printed directly via CUPS. And I think I know the reason why.

As far as I understood converts CUPS all Files (incl. PDF) to Postscript and then back to PDF before sending it to the printer. This conversion process seems to have problems with the PDF created from prawn. With pdf2ps (Ghostscript and default converter in CUPS) you can reproduce this, while pdftops converts the PDF correctly into a ps file.

tekkla avatar Jun 18 '18 10:06 tekkla

OK, at this point it looks more like an issue with pdf2ps rather than Prawn. However, I'm curious what part of a generated document may result in an incompatibility. Could you please provide a sample code, PDF and PS files produced with each tool in case someone would like to take a stab at this issue?

pointlessone avatar Jun 18 '18 10:06 pointlessone

Here you have a fresh prawn generate pdf file and the postscript files in a zip archive.

Invoice.pdf Postscript.zip

This is my code

class InvoicePdf

  include Prawn::View

  def initialize(invoice, view)

    @invoice = invoice

    # for debug and development always on
    @invoice.heading = 1
    @invoice.net_invoice = 1

    @view = view
    @number_and_date_size = 14
    @border_width = 0.1
    @price_key = @invoice.net_invoice ? 'net' : 'gross'
    @cols = {
      'position' => {
        'width' => 10,
        'align' => :left
      },
      'title' => {
        'align' => :left
      },
      'amount_with_unit' => {
        'width' => 40,
        'align' => :center
      },
      "#{@price_key}" => {
        'width' => 60,
        'align' => :right,
        'currency' => true
      },
      "#{@price_key}_sum" => {
        'width' => 60,
        'align' => :right,
        'currency' => true
      }
    }
    @font_small = 8
    @font_normal = 10

    font_name = 'OpenSans'
    font_path = Rails.root.join("app/assets/fonts/#{font_name}")
    font_families.update(
      font_name.to_s => {
        normal: "#{font_path}-Regular.ttf",
        bold: "#{font_path}-Bold.ttf",
        extra_bold: "#{font_path}-ExtraBold.ttf"
      }
    )

    font(font_name) do
      repeat :all do
        header
        footer
        fold_marks 570
      end
      content
      page_counter
    end

  end

  def document
    @my_prawn_doc ||= Prawn::Document.new(
      page_size: 'A4',
      info: {
        Title: "#{I18n.t('invoices.title.one')} #{@invoice.number}",
        CreationDate: Time.now
      },
      left_margin: 50,
      right_margin: 25,
      bottom_margin: 5,
      compress: true
    )
  end

  private

  def header
    company
    address_label
    number_and_date
  end

  def footer

    bounding_box [bounds.left, bounds.bottom + 45], width: bounds.width do

      col_width = bounds.width / 5

      font_size @font_small do
        bounding_box([0, 0], width: col_width) do
          text 'Somewhere'
          text 'Uhlenkamp 33'
          text '24119 Kolsten'
        end

        bounding_box([col_width, bounds.top], width: col_width / 5) do
          text 'Tel'
          text 'Fax'
          text 'Mail'
          text 'Web'
        end

        bounding_box([col_width + col_width / 5, bounds.top], width: col_width * 1.5) do
          text '0431 521578'
          text '0431 521579'
          text '[email protected]'
          text 'www.somewhere.de'
        end

        bounding_box([col_width * 2.4, bounds.top], width: col_width * 1.25) do
          text 'Bankverbindung (IBAN)'
          text 'DE27 21 15 11 79 412 5190 24'
          text 'Förde Sparkasse'
          text 'BIC NOLADE21 KIE'
        end

        bounding_box([col_width * 3.75, bounds.top], width: col_width) do
          text 'USt-Id. Nr. DE174563218'
          text 'Handelsregister HR A 4567'
        end
      end
    end
  end

  def company
    # COMPANY AND IMAGE
    bounding_box [bounds.left, bounds.top], width: bounds.width do
      image "#{Rails.root}/app/assets/images/logo.png",
            width: 170,
            position: :right
      move_up 80
      text 'SOMEWHERE', size: 33.5, style: :bold
      move_up 3
      text 'And Everywhere', size: 16
    end
  end

  def address_label
    bounding_box [bounds.left + 20, 710], width: 195 do
      text 'Somewhere - Uhlenkamp 33 - 24119 Kolsten',
           size: @font_small,
           align: :center
      my_horizontal_rule
      bounding_box [5, cursor - 5], width: 185 do
        text @invoice.addresslabel, size: @font_normal
      end
    end
  end

  def number_and_date
    bounding_box [bounds.left, 555], width: bounds.width do
      text "#{I18n.t('invoices.title.one')}: #{@invoice.number}",
           size: @number_and_date_size,
           style: :bold
      move_up @number_and_date_size
      text @invoice.date_of.to_s,
           size: @number_and_date_size,
           align: :right,
           style: :bold
    end
  end

  def content
    bounding_box([bounds.left, 520], width: bounds.width, height: 460) do
      title_and_description
      line_items
      terms
    end
  end

  def title_and_description

    return if @invoice.heading.blank?

    font_size @font_normal do
      if @invoice.title.present?
        text @invoice.title, style: :bold
        move_down 5
      end

      if @invoice.description.present?
        text @invoice.description
        move_down 10
      end
    end
  end

  def line_items

    pad 5 do
      text I18n.t('invoices.pdf.line_items').upcase, style: :bold
    end

    table line_item_rows,
          width: bounds.width,
          cell_style: {
            inline_format: true,
            borders: [:top],
            border_widths: @border_width,
            size: @font_small
          } do
      row(0).font_style = :bold
      columns(3..4).align = :right
      self.header = true
    end
    send("summary_#{@price_key}")
  end

  def summary_gross

    string = "#{I18n.t('invoices.pdf.taxes_included')}: "

    @invoice.taxes.each do |taxes|
      string += "#{percent(taxes.tax_rate)}= #{price(taxes.tax_sum)}"
    end

    string += "\n#{I18n.t('invoices.pdf.work_and_drivecost_sum', value: price(@invoice.work_and_drivecost_sum))}"

    summary = [
      [
        {
          content: string,
          size: @font_small
        },
        {
          content: I18n.t("invoices.pdf.total"),
          align: :right,
          width: 80,
          font_style: :bold
        },
        {
          content: price(@invoice.send(@price_key)),
          width: 140,
          align: :right,
          font_style: :bold
        }
      ]
    ]

    table summary,
          width: bounds.width,
          cell_style: {
            inline_format: true,
            borders: [:top],
            border_widths: @border_width,
            padding_top: 0,
            valign: :center
          } do
    end

    # double line under total
    my_horizontal_rule
    move_down 1
    my_horizontal_rule

  end

  def summary_net

    my_horizontal_rule

    # start summary with net amount
    summary = [
      [
        I18n.t('invoices.pdf.net_amount'),
        price(@invoice.net)
      ]
    ]

    # add taxes and values
    @invoice.taxes.each do |taxes|
      summary << [
        "+ #{I18n.t('invoices.pdf.tax')} #{percent(taxes.tax_rate)}",
        price(taxes.tax_sum)
      ]
    end

    ## and finally the total value
    summary << [
      I18n.t('invoices.pdf.total'),
      price(@invoice.gross)
    ]

    table summary,
          width: bounds.width,
          cell_style: {
            size: @font_small,
            align: :right,
            borders: [:bottom],
            border_widths: @border_width,
            font_style: :bold
          } do
      columns(1).width = 110
      rows(-1).size = @font_normal
    end

    # double line under total
    move_down 1
    my_horizontal_rule

  end

  def terms
    unless @invoice.terms.blank?
      move_down 5
      text I18n.t('invoices.pdf.col.terms')
      text I18n.t(@invoice.terms, days: @invoice.days), style: :bold
    end
  end

  def line_item_rows

    table = []
    th = []

    @cols.each do |key, col|
      th << { content: I18n.t("invoices.pdf.col.#{key}"), width: col['width'] }
    end

    p th

    table << th

    @invoice.items.each do |item|

      tr = []

      @cols.each do |key, col|

        value = item.send(key).to_s
        value = price(value) unless col['currency'].blank?
        value = "<b>#{value}</b>"

        if key == 'title'
          value += "\n#{item.description}" unless item.description.blank?
        end

        tr << value

      end

      p tr

      table << tr
    end

    table
  end

  def page_counter
    number_pages I18n.t('invoices.pdf.pages'),
                 at: [bounds.left, 540],
                 width: bounds.width - 1,
                 start_count_at: 1,
                 style: :bold,
                 align: :right,
                 size: 8
  end

  def price(num)
    @view.number_to_currency num,
                             unit: '€',
                             separator: ',',
                             delimiter: '.',
                             format: '%n %u'
  end

  def percent(num)
    @view.number_to_percentage num,
                               precision: 2,
                               separator: ',',
                               delimiter: '.'
  end

  def my_horizontal_rule(line_width = 0.1)
    stroke do
      line_width line_width
      horizontal_rule
    end
  end

  def fold_marks(y_position, line_width = 0.1)
    stroke do
      line_width line_width
      stroke_color "aaaaaa"
      horizontal_line bounds.left - 20, bounds.left - 15, at: y_position
      horizontal_line bounds.right - 5, bounds.right, at: y_position
    end
  end

end

tekkla avatar Jun 18 '18 14:06 tekkla

@tekkla It's been a while but still… Could you please check if you're having this issue with Prawn/ttfunk/pdf-core from master?

pointlessone avatar Jan 22 '24 10:01 pointlessone