← Writing

Ruby on Rails: 呼叫 `.pdf` 轉換術

before_action :why I need .pdf? 現階段接了一項新任務,為專案做出 pdf 檔案 專案在如火如荼地進行著,實作將 html 轉成 pdf 格式! 原本運用的是 wicked_pdf, 後來又使用了 jspdf + html2canvas 來製作這項功能, 就想著順道來記...

ruby on railswicked_pdfjspdfhtml2canvas

before_action :why I need .pdf?

現階段接了一項新任務,為專案做出 pdf 檔案 專案在如火如荼地進行著,實作將 html 轉成 pdf 格式! 原本運用的是 wicked_pdf, 後來又使用了 jspdf + html2canvas 來製作這項功能, 就想著順道來記錄一下,每一階段在運行的狀況!

# Step by step to generate .pdf - version 1

安裝 wicked_pdfwkhtmltopdf-binary

依照 wicked_pdf README.md 說明, 需要先將 wicked_pdfwkhtmltopdf-binary 載入:

gem 'wicked_pdf'
gem 'wkhtmltopdf-binary'

實際上 wicked_pdf 是使用 wkhtmltopdf 這個 command line tools 來把html轉成pdf!

為 controller 建立轉檔方法

以自己想要哪一個頁面轉為 pdf 來做方法設定,這裡是以 ResumesController 的 show 來做:

class ResumesController < ApplicationController
  def show
    respond_to do |format|
      format.html
      format.pdf do
        render pdf: "file_name", template: "resumes/show", formats: [:html]
      end
    end
  end
end

要注意的是,如果是用 Rails 7 的話,必須將 format.pdf 的寫法轉換成:

format.pdf do
  render pdf: "file_name", template: "profiles/show", formats: [:html]
end

如果是照著 README.md 的方法寫會一直噴出 Template is missing 錯誤!

在 show.html.erb 設定連結到 .pdf 頁面上:

<%= link_to 'PDF this profile', resume_path(@resume, format: :pdf) %>

除此之外,讓 pdf 可以讀取樣式,因為 wicked_pdf 是以 wkhtmltopdf-binary 二進制在 Rails 應用程序之外運行,所以在 layout 上必須設定以下 wicked_pdf 相關連結:

<!doctype html>
<html>
  <head>
    <meta charset='utf-8' />
    <%= wicked_pdf_stylesheet_link_tag "pdf" -%>
    <%= wicked_pdf_javascript_include_tag "number_pages" %>
  </head>
  <body onload='number_pages'>
    <div id="header">
      <%= wicked_pdf_image_tag 'mysite.jpg' %>
    </div>
    <div id="content">
      <%= yield %>
    </div>
  </body>
</html>

以上是 wicked_pdf 需要注意的主要重點, 剩下的可以在 GitHub 的 README.md 上做更仔細的設定! 接著要來介紹最終決定的套件!jspdf

# Step by step to generate .pdf - version 2

因為專案上是以 Rails 7 進行,在引入 javascript 套件時,我使用 importmap 的方式引入! 當你在使用 jspdf 時,本身套件就涵蓋 html2canvas,可以依照 README 文件去做設定!

我主要分享在運用這個套件所遇到的問題!

Sovling Problems

  • 實作將履歷在網頁上運用截圖的方式,但在使用 html2canvas 時, 一直出現 Uncaught (in promise) DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported. 錯誤,因為我在專案上傳圖片時,是儲存在 AWS S3 bucket 裡,在訪問 AWS S3 上的圖片上會有 CORS 的設置問題,已確定我在 AWS S3 上的 CORS 設置,並且還依照一下連結的方式設置,還是有同樣的錯誤,當時決定冷靜一天...

Handling CORS with html2Canvas and AWS S3 Images: A Comprehensive Guide

終於,在隔天!我找到了最終解法:將圖片轉換為 Base64 編碼!!! (困擾我整整兩天的 pdf 終於有結果了!)

在 Rails 中,我透過 Helper 來優化 view 上面的程式碼,其中最重要的部分是將圖片轉為 Base64,一開始的圖片路徑都是寫絕對路徑的方式導向 AWS S3 儲存庫,就是因為這點有 CORS 的問題,因此,當下先從 stackoverflow 去尋找與我有著相同錯誤的討論,當時一直卡在設置 CORS 的規格上,後續發現那裡不是主要的問題,而是我應該要改成相對路徑或者 Base64 的編碼,這樣在取用上才不會有問題。

module ResumesHelper
  def display_avatar_base64(profile)
    if profile.avatar.attached? #如果有頭貼
      image = profile.avatar.variant(:thumb).processed #透過 `.processed` 獲得這張頭貼
      base64_data = Base64.strict_encode64(image.download) #將下載的頭像數據編碼轉為 Base64 格式
      image_tag("data:image/jpeg;base64,#{base64_data}", class: "mx-auto") #將 image_tag 創建一個帶有 Base64 編碼數據的圖像
    else
      image_tag("defaultuser.png", class: "rounded-full w-32 h-32 mx-auto")
    end
  end
end

參考資料

[Rails]如何使用 wicked pdf 生成 pdf jspdf+html2canvas 跨來源資源共用(CORS) CORS 是什麼? 為什麼要有 CORS?