0

Tạo file pdf sử dụng Wicked PDF

Trong quá trình phát triển dự án, chắc hẳn sẽ có nhiều chức năng yêu cầu tạo các file pdf từ dữ liệu của hệ thống theo các mẫu (template). Bài viết này giới thiệu một phương pháp tạo file pdf mình đã thực hiện thành công trong dự án của mình.

Wicked PDF sử dụng các tiện ích wkhtmltopdf để tạo file PDF cho người dùng từ HTML. Bạn chỉ cần viết một trang HTML như bình thường, sau đó để cho Wicked PDF sẽ sử dụng đó làm đầu vào tạo ra file pdf cho bạn.

Hướng dẫn Cài đặt

Thêm gem wicked_pdf vào Gemfile rồi chạy bundle install

gem 'wicked_pdf'

Sau đó chạy lệnh khởi tạo

rails generate wicked_pdf

Thêm dòng sau vào config/initializers/mime_types.rb

Mime::Type.register "application/pdf", :pdf

Do wicked_pdf sử dụng wkhtmltopdf nên chúng ta cần cài đặt thêm wkhtmltopdf. Để cài đặt, thêm dòng sau vào Gemfile rồi chạy bundle install

gem 'wkhtmltopdf-binary'

Nếu wkhtmltopdf không nằm trên cùng webserver, thì cần thêm cấu hình như sau vào file config config/initializers/wicked_pdf.rb

WickedPdf.config = {
  exe_path: '/usr/local/bin/wkhtmltopdf'
}

Cách sử dụng

Trên controller, bạn chỉ cần render pdf như sau:

class ThingsController < ApplicationController
  def show
    respond_to do |format|
      format.html
      format.pdf do
        render pdf: "file_name", layout: "layout_name"   # loại bỏ đuôi trong file_name ".pdf"
      end
    end
  end
end

Điều kiện sử dụng

wkhtmltopdf đang chạy bên ngoài của ứng dụng Rails của bạn, do đó layout thông thường trong dự án sẽ không thể hoạt động. Nếu muốn sử dụng CSS, Javascript hay hình ảnh thì bạn phải thay đổi cách tham chiếu tuyệt đối đến các tập tin. Cách tốt nhất để Rails không sử dụng asset pipeline là sử dụng wicked_pdf_stylesheet_link_tag, wicked_pdf_javascript_include_tag và wicked_pdf_image_tag hoặc trỏ thẳng đến một CDN cho các thư việc của jquery

Ví dụ về layout voucher mình sử dụng:

html
  head
    meta charset="utf-8"
    = wicked_pdf_stylesheet_link_tag "voucher"
    = wicked_pdf_javascript_include_tag "voucher"
  body
    .wrapper
      header#header
        #header_top
      #main
        = yield
      footer#footer_voucher.footer-voucher
        p = t "voucher.copyright_html"

và trong source show, mình sử dụng ảnh như sau:

<%= wicked_pdf_image_tag "logo-voucher" %>

thay thế cho

<%= image_tag "logo-voucher" %>

Các tùy chọn nâng cao khác

def show
      respond_to do |format|
        format.html
        format.pdf do
          render pdf:                            "file_name",
                 disposition:                    "attachment",
                 template:                       "things/show.pdf.erb",
                 file:                           "#{Rails.root}/files/foo.erb"
                 layout:                         "pdf.html",
                 wkhtmltopdf:                    "/usr/local/bin/wkhtmltopdf",
                 show_as_html:                   params.key?("debug"),
                 orientation:                    "Landscape",
                 page_size:                      "A4, Letter, ...",
                 page_height:                    NUMBER,
                 page_width:                     NUMBER,
                 save_to_file:                   Rails.root.join("pdfs", "#{filename}.pdf"),
                 save_only:                      false,
                 proxy:                          "TEXT",
                 basic_auth:                     false
                 username:                       "TEXT",
                 password:                       "TEXT",
                 title:                          "Alternate Title",
                 cover:                          "URL, Pathname, or raw HTML string",
                 dpi:                            "dpi",
                 encoding:                       "TEXT",
                 user_style_sheet:               "URL",
                 cookie:                         ["_session_id SESSION_ID"],
                 post:                           ["query QUERY_PARAM"],
                 redirect_delay:                 NUMBER,
                 javascript_delay:               NUMBER,
                 window_status:                  "TEXT",
                 image_quality:                  NUMBER,
                 no_pdf_compression:             true,
                 zoom:                           FLOAT,
                 page_offset:                    NUMBER,
                 book:                           true,
                 default_header:                 true,
                 disable_javascript:             false,
                 grayscale:                      true,
                 lowquality:                     true,
                 enable_plugins:                 true,
                 disable_internal_links:         true,
                 disable_external_links:         true,
                 print_media_type:               true,
                 disable_smart_shrinking:        true,
                 use_xserver:                    true,
                 background:                     false,
                 no_background:                  true,
                 viewport_size:                  "TEXT",
                 extra:                          "",
                 outline: {   outline:           true,
                              outline_depth:     LEVEL },
                 margin:  {   top:               SIZE,
                              bottom:            SIZE,
                              left:              SIZE,
                              right:             SIZE },
                 header:  {   html: {            template: "users/header.pdf.erb",
                                                 layout:   "pdf_plain.html",
                                                 url:      "www.example.com",
                                                 locals:   { foo: @bar }},
                              center:            "TEXT",
                              font_name:         "NAME",
                              font_size:         SIZE,
                              left:              "TEXT",
                              right:             "TEXT",
                              spacing:           REAL,
                              line:              true,
                              content:           "HTML CONTENT ALREADY RENDERED"},
                 footer:  {   html: {   template:"shared/footer.pdf.erb",
                                        layout:  "pdf_plain.html",
                                        url:     "www.example.com",
                                        locals:  { foo: @bar }},
                              center:            "TEXT",
                              font_name:         "NAME",
                              font_size:         SIZE,
                              left:              "TEXT",
                              right:             "TEXT",
                              spacing:           REAL,
                              line:              true,
                              content:           "HTML CONTENT ALREADY RENDERED"},
                 toc:     {   font_name:         "NAME",
                              depth:             LEVEL,
                              header_text:       "TEXT",
                              header_fs:         SIZE,
                              text_size_shrink:  0.8,
                              l1_font_size:      SIZE,
                              l2_font_size:      SIZE,
                              l3_font_size:      SIZE,
                              l4_font_size:      SIZE,
                              l5_font_size:      SIZE,
                              l6_font_size:      SIZE,
                              l7_font_size:      SIZE,
                              level_indentation: NUM,
                              l1_indentation:    NUM,
                              l2_indentation:    NUM,
                              l3_indentation:    NUM,
                              l4_indentation:    NUM,
                              l5_indentation:    NUM,
                              l6_indentation:    NUM,
                              l7_indentation:    NUM,
                              no_dots:           true,
                              disable_dotted_lines:  true,
                              disable_links:     true,
                              disable_toc_links: true,
                              disable_back_links:true,
                              xsl_style_sheet:   "file.xsl"}
        end
      end
    end

Cách sử dụng nâng cao

Nếu bạn chỉ cần tạo ra một file pdf mà không cần hiển thị thì không cần phải đưa vào controller như trên. Tham khảo các lệnh sau:

# tạo file pdf từ một string
pdf = WickedPdf.new.pdf_from_string('<h1>Hello There!</h1>')

# tạo file pdf từ file html, không cần convert sang string
# Đường dẫn tới file cần là đường dẫn tuyệt đối
pdf = WickedPdf.new.pdf_from_html_file('/your/absolute/path/here')

# tạo file pdf từ một URL
pdf = WickedPdf.new.pdf_from_url('https://github.com/mileszs/wicked_pdf')

# tạo file pdf từ string sử dụng templates, layouts và các tùy chọn nội dung cho header hoặc footer (cách này thường được sử dụng nhiều nhất vì chúng ta có thể dễ dàng tùy chỉnh được nội dung , thiết kế của file pdf)
pdf = WickedPdf.new.pdf_from_string(
  render_to_string('templates/pdf', layout: 'pdfs/layout_pdf'),
  footer: {
    content: render_to_string(layout: 'pdfs/layout_pdf')
  }
)

# render trực tiếp từ controller, sử dụng views, templates và tất cả các tùy chọn của wicked_pdf như bình thường
pdf = render_to_string pdf: "some_file_name", template: "templates/pdf", encoding: "UTF-8"

# sau đó tiến hành lưu file pdf tạo được ra
save_path = Rails.root.join('pdfs','filename.pdf')
File.open(save_path, 'wb') do |file|
  file << pdf
end

Qua trên ta thấy bản chất việc wicked_pdf tạo ra file pdf chính là việc convert kiểu dữ liệu hoàn thiện từ html sang pdf. Vì vậy, sau khi thực hiện xong các cài đặt như trên thì bạn chỉ cần chỉnh sửa mã nguồn html thì sẽ có ngay một file pdf như ý.

Ngoài ra, trong số các tùy chọn phía trên, tùy chọn show_as_html: true sẽ cho phép chúng ta xem file pdf như một file html trên trình duyệt. Nhờ đó kiểm tra được mã nguồn html dễ dàng trên trình duyệt.

Bên cạnh đó, bạn cũng có thể config layout áp dụng cho file pdf tại phần config/initializers/wicked_pdf.rb hoặc có thể setting cụ thể tại mỗi lần chạy lệnh tạo file. Các config đặt trong wicked_pdf.rb sẽ được áp dụng mặc định. Còn việc thiết lập tại mỗ lần tạo file sẽ ghi đè lên nội dung trong config.

Cảm ơn các bạn đã theo dõi bài viết!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí