Sử dụng Prawn để generrater PDF in Rails

Trong một số dự án, đôi lúc chúng ta cần tạo ra file PDF để hiển thị một CV của ứng viên hay là hóa đơn thanh toán v.v...

Hiện nay, trong Ruby đã có rất nhiều những thư viện để hỗ trợ cho lập trình viên tạo file PDF một cách nhanh chóng và hiệu quả.

Một trong số đó là gem prawn(readme). Trong bài viết này sẽ trình bày các bước để tạo một file PDF trong Rails sử dụng prawn.

Cài đặt#

Để cài đặt praw vào ứng dụng Rails rất đơn giản, ta chỉ cần khai báo dòng gem "prawn" vào trong Gemfile. Tiếp đó, ta cần chạy lệnh trong terminal

bundle install

Sử dụng#

Bước 1: Ta cần khai báo PDF Mime::Type vào file config/initializers/mime_types.rb như sau:

Mime::Type.register “application/pdf”, :pdf

Kể từ bây giờ, mỗi khi có request mà mime là PDF thì ứng dụng sẽ trả về nội dung file PDF.

Bước 2: Ta sẽ thực hiện xử lý trong controller để trả về định dạng file PDF. Ví dụ như đoạn code dưới đây:

class InvoicesController < ApplicationController

  before_filter :authenticate_customer!, :only => [:index, :show]

  def index
    @invoices = Invoice.all_invoices(current_customer)
  end

  def show
    @invoice = Invoice.find(params[:id])
    respond_to do |format|
      format.html
      format.pdf do
        pdf = InvoicePdf.new(@invoice, view_context)
        send_data pdf.render, filename:
        "invoice_#{@invoice.created_at.strftime("%d/%m/%Y")}.pdf",
        type: "application/pdf"
      end
    end
  end
end

Trong đoạn code trên, chúng ta đã có thay đổi format của respond theo hai định dạng là htmlpdf. Và bên trong block format.pdf, ta có gọi phương thức send_data với các tham số là nội dung file, tên file, và kiểu ứng dụng để đọc file. Ngoài ra, hàm này còn có thêm một option nữa là disposition: "inline" có tác dụng sẽ hiển thị file PDF ngay trên trình duyệt thay vì phải download về máy client.

Bước 3: Ta sẽ tiến hành việc định dạng file PDF cần được tạo ra bằng cách sử dụng praw. Việc này được thực hiện trong lớp InvoicePdf kế thừa lớp Prawn::Document trong thư viện praw.

class InvoicePdf < Prawn::Document

  def initialize(invoice, view)
    super()
    text "This is an order invoice", size: 16, font: "Helvetica", color: "0000FF"
  end
end

Đoạn code trên sử dụng phương thức text của praw để hiển thị trong file PDF với kích thước chữ là 16, font là Helvetica và chữ màu xanh. Tương tự, ta đều có thể hiển thị các dòng text khác bằng cách sử dụng lại phương thức này.

Khi tạo một hóa đơn thì chúng ta thường in logo của công ty. Như vậy làm sao ta có thể tạo hình ảnh trong file PDF?. Điều này trở thành vô cùng đơn giản với việc sử dụng phương thức image của thư viện. Ví dụ, ta muộn hiển thị ảnh ở giữa file với kích thước là 50x50 sẽ thực hiện như sau:

def logo
  logopath =  "#{Rails.root}/app/assets/images/logo.png"
  open(logopath), fit: [500, 500], position: :center
end

Moving Around##

Đây là một tính năng giúp chúng ta có thể xác định chính xác vị trí muốn đặt từng phần tử text or image... trong tài liệu bằng cách di chuyển vị trí con trỏ chuột đến tọa độ mong muốn thông qua các hàm move_down(n), move_up(n) hoặc move_cursor_to(n). Lưu ý đó là trục tọa độ của trang tài liệu có gốc [0, 0] là ở vị trí cuối cùng bên trái trang và trục dương hướng lên trên.

Ngoài ra, thư viện praw, còn cho phép chúng ta trình bày tài liệu dưới dạng bảng bằng cách sử dụng phương thức table, rowcolumn.

Sau đây là code của lớp InvoicePdf để hiển thị một Invoice dưới dạng PDF để các bạn tham khảo cho việc định dạng 1 file PDF đơn giản

class InvoicePdf < Prawn::Document

  def initialize(invoice, view)
    super()
    @invoice = invoice
    @view = view
    logo
    thanks_message
    subscription_date
    subscription_details
    subscription_amount
    regards_message
  end

  def logo
    logopath =  "#{Rails.root}/app/assets/images/logo.png"
    image logopath, width: 197, height: 91
    move_down 10
    draw_text "Receipt", at: [220, 575], size: 22
  end

  def thanks_message
    move_down 80
    text "Hello #{@invoice.customer.profile.first_name.capitalize},"
    move_down 15
    text "Thank you for your order.Print this receipt as
    confirmation of your order.",
    indent_paragraphs: 40, size: 13
  end

  def subscription_date
    move_down 40
    text "Subscription start date:
    #{@invoice.start_date.strftime("%d/%m/%Y")} ", size: 13
    move_down 20
    text "Subscription end date :
    #{@invoice.end_date.strftime("%d/%m/%Y")}", size: 13
  end

  def subscription_details
    move_down 40
    table subscription_item_rows, width: 500 do
      row(0).font_style = :bold
      columns(1..3).align = :right
      self.header = true
      self.column_widths = {0 => 200, 1 => 100, 2 => 100, 3 => 100}
    end
  end

  def subscription_amount
    subscription_amount = @invoice.calculate_subscription_amount
    vat = @invoice.calculated_vat
    delivery_charges = @invoice.calculated_delivery_charges
    sales_tax =  @invoice.calculated_sales_tax
    table ([["Vat (12.5% of Amount)", "", "", "#{precision(vat)}"] ,
    ["Sales Tax (10.3% of half the Amount)", "", "",
    "#{precision(sales_tax)}"]   ,
    ["Delivery charges", "", "", "#{precision(delivery_charges)}  "],
    ["", "", "Total Amount", "#{precision(@invoice.total_amount) }  "]]),
    :width => 500 do
      columns(2).align = :left
      columns(3).align = :right
      self.header = true
      self.column_widths = {0 => 200, 1 => 100, 2 => 100, 3 => 100}
      columns(2).font_style = :bold
    end
  end

  def subscription_item_rows
    [["Description", "Quantity", "Rate", "Amount"]] +
    @invoice.subscriptions.map do |subscribe|
      [ "#{subscribe.description} ", subscribe.quantity,
      "#{precision(subscribe.rate)}  ",
      "#{precision(subscribe.quantity  * subscribe.rate)}" ]
    end
  end

  def precision(num)
    @view.number_with_precision(num, :precision => 2)
  end

  def regards_message
    move_down 50
    text "Thank You," ,indent_paragraphs: 400
    move_down 6
    text "XYZ",
    indent_paragraphs: 370, :size: 14, style:  :bold
  end

end

Bước cuối cùng: Ta thêm link của file PDF vào trong views: app/views/invoices/show.html.erb:


<%= link_to "Download invoice", invoice_path(invoice.id, format: "pdf") %>;

Kết luận#

Trên đây là bài giới thiệu cơ bản về cách sử dụng praw trong rails. Ngoài ra, các bạn có thể tham khảo thêm các phương thức khác ở đây

Tham khảo: http://www.sitepoint.com/hackable-pdf-typesetting-in-ruby-with-prawn/