はじめに
この記事は「mofmof Advent Calendar 2023」22日目の記事です。
wicked_pdf
の代替案としてFerrum
というgemを使ってみました。
元々はPDF生成機能を実装するためにwicked_pdf
を使おうとしていましたが、
依存しているwkhtmltopdf
が今年に入ってアーカイブされていたことを知りました。
内部的に利用しているQT Webkit
レンダリングエンジンのメンテナンスが止まったことで、wkhtmltopdf
もメンテナンスが継続できなくなってしまったようです。
wicked_pdfの現状
wicked_pdf
はwkhtmltopdf-binary
と併せて利用しますが、wkhtmltopdf-binary
のメンテナンスが滞っているようです。
willnetさんがM1 Mac(arm64)対応やdebian12(bookworm)対応をしてくださったPRが出されていますが、リポジトリは管理されている様子がなく、マージされる気配も感じられません。
M1 Mac(arm64)やdebian12が使われているRubyイメージを使用していてwicked_pdf
を使いたい場合は、このPRを元に公開されたwkhtmltopdf-binary-ng
という別のgemを使うのが現状良さそうです。
gem 'wkhtmltopdf-binary-ng'
PDF生成どうするか
選択肢としては、ヘッドレスブラウザ等でHTMLをPDFに変換できるGrover
やFerrum
といったgemがあります。
Ferrum
はRubyでChromeブラウザを操作できるAPIを提供してくれて、Grover
のようにPuppeteerを経由する必要がありません。
今回はFerrum
でPDF生成してみます。
前提
- Rails 7.1.2
- Rails 7.1から
$ rails new
でDockerfileが生成されるようになっています -
$ rails g scaffold post title:string body:text published:boolean
実行済み
- Rails 7.1から
必要なのはChrome or Chromiumだけ
Dockerfileでインストールしておく
# Install packages needed for deployment
RUN apt-get update -qq && \
- apt-get install --no-install-recommends -y curl libvips postgresql-client && \
+ apt-get install --no-install-recommends -y curl libvips postgresql-client chromium && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
FerrumでPDF生成
class PostsController < ApplicationController
before_action :set_post, only: %i[ show edit update destroy download_pdf ]
# 中略
def download_pdf
html = render_to_string(template: 'posts/_post', layout: 'pdf', locals: { post: @post })
pdf = html2pdf(html)
send_data pdf, filename: 'post.pdf', type: 'application/pdf'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Ferrumを使ってHTMLからPDFを生成
def html2pdf(html)
# chromiumへのパスとno-sandbox browserオプションを渡す
browser = Ferrum::Browser.new(browser_path: '/usr/bin/chromium', browser_options: { 'no-sandbox': nil })
header_html = render_to_string('pdf/header', layout: false)
footer_html = render_to_string('pdf/footer', layout: false)
browser.goto("data:text/html,#{html}")
pdf = browser.pdf(
format: :A4,
encoding: :binary,
display_header_footer: true,
header_template: header_html,
footer_template: footer_html,
)
browser.quit
pdf
end
end
その他のコード
- download_pdfアクションのルート追加
Rails.application.routes.draw do
resources :posts do
member do
get :download_pdf
end
end
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Defines the root path route ("/")
# root "posts#index"
end
- PDFダウンロードボタンを追加
<p style="color: green"><%= notice %></p>
<%= render @post %>
<div>
<%= link_to "Edit this post", edit_post_path(@post) %> |
<%= link_to "Back to posts", posts_path %>
<%= button_to "Destroy this post", @post, method: :delete %>
+ <%= link_to 'Download PDF', download_pdf_post_path(@post) %>
</div>
- PDF用レイアウト
<!DOCTYPE html>
<html>
<head>
<title>PDF generated by Ferrum</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<%= yield %>
</body>
</html>
<div style="margin-left: 0.5cm; font-size: 9px; width: 100%;">
<span class="date"></span>
</div>
<div style="position: relative; border-top: 1px solid black; margin: 0.5cm; font-size: 9px; width: 100%;">
<div style="position: absolute; width: 100%; top: 0.2cm; text-align: center;">
<span class='pageNumber'></span> / <span class='totalPages'></span>
</div>
<div style="position: absolute; right: 0; top: 0.2cm;">generated by Ferrum</div>
</div>
リポジトリはこちら
フォーマット指定
標準的な用紙サイズがオプションとして用意されています。
format
オプションで用紙サイズを指定するか、paper_width
とpaper_height
を指定します。
standard paper sizes :letter, :legal, :tabloid, :ledger, :A0, :A1, :A2, :A3, :A4, :A5, :A6
Dockerで利用する場合
Dockerコンテナ内で、rootユーザーとして実行する場合は以下のオプションを渡す必要があります。
Ferrum::Browser.new(browser_options: { 'no-sandbox': nil })
また、M1 Mac上のDockerコンテナ内でFerrumを実行する際にChromeプロセスがクラッシュするという報告があるみたいです。今のところ自分の環境では発生していませんが不穏ですね。
ヘッダーとフッターはカスタマイズできる
ヘッダーとフッターはHTML文字列を渡してカスタマイズできます。
display_header_footer: true
にしても表示されないと思っていましたが、極小文字サイズで見切れてしまっていただけでした...。
font-sizeだけは少なくともstyle設定する必要がありますね。
display_header_footer: true
にするとヘッダーとフッター両方表示してしまうので、どちらかだけ表示したい場合は空文字を渡します。
最後に
ここまで読んでくださり、ありがとうございました!
帳票や請求書などのPDF作成機能実装時はwkhtmltopdf
に大変お世話になったので悲しいです。
お世話になっているライブラリに対して、個人で少しでも貢献できるようになりたいと強く感じますね。
参考