August 10, 2023

Viiisit [Ruby on Rails] - 呼叫 `.pdf` 轉換術!

#ruby on rails#wicked_pdf#jspdf#html2canvas

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 載入:

1
2
gem 'wicked_pdf'
gem 'wkhtmltopdf-binary'

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

為 controller 建立轉檔方法

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

1
2
3
4
5
6
7
8
9
10
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 的寫法轉換成:

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
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?