Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

wicked_pdfからの移行ガイド

wicked_pdf(+ wkhtmltopdf)を使っているRailsアプリを、sghtmltopdfのgemへ移すための対応表と注意点。

wkhtmltopdfのオプションの対応状況はwkhtmltopdfオプション対応表とwkhtmltopdfからの移行を参照してください。 ここでは「Rails/Rubyから見た違い」だけを扱います。

最小の置き換え

# Gemfile
- gem "wicked_pdf"
- gem "wkhtmltopdf-binary"
+ gem "sghtmltopdf"

コントローラはそのまま動くはずです。

def show
  respond_to do |format|
    format.pdf { render pdf: "invoice", template: "invoices/show", layout: "pdf" }
  end
end

外部プロセスの起動が無くなるので、wicked_pdfのexe_path(wkhtmltopdfのバイナリの場所)の設定は不要になる。

設定の置き場所

# config/initializers/sghtmltopdf.rb
Sghtmltopdf.configure do |c|
  c.page_size   = "A4"
  c.margin_top  = "20mm"
  c.gothic_font = Rails.root.join("vendor/fonts/NotoSansJP-Regular.ttf")
end

wicked_pdfのWickedPdf.config = {...}に相当する。 マージ順は グローバル設定 → renderの引数で、後者が勝ちます。

オプション名の対応

wicked_pdfはネストしたHash(margin: {top: 10})を使うが、sghtmltopdfは CLIのフラグ名をそのままキーにした平坦なHashを使う(_が-に対応する。page_size: → --page-size)。 オプションの定義はRust側の1箇所に集約されていて、Ruby側はホワイトリストを持ちません。

wicked_pdfsghtmltopdf備考
pdf: "name"同じファイル名(.pdfは自動で付く)
template: / layout: / locals: / formats:同じRailsのビュー描画へそのまま渡る
disposition: / filename: / status:同じ既定のdispositionはinline
show_as_html: true同じPDFにせずHTMLを返すデバッグ用
page_size: "A4"page_size: "A4"
page_height: / page_width:同じ単位付きの文字列("210mm")で渡す
orientation: "Landscape"同じ
margin: {top: 10, bottom: 10}margin_top: "10mm", margin_bottom: "10mm"wicked_pdfの数値はmm。単位を明示する
dpi: / zoom:同じ
grayscale: true同じ
background: falseno_background: true
encoding: "UTF-8"同じ
title:同じPDFのメタデータ
user_style_sheet:同じパスの配列も可
no_pdf_compression: true同じ
cover: "shared/cover"cover: <ファイルパス>テンプレート名ではなくHTMLファイルのパス(後述)
toc: {}toc: true見た目はtoc_header_text:などで調整
header: {left:, center:, right:}header_left: / header_center: / header_right:
header: {html: {template: "..."}}header_html_content: <描画済みHTML>render_to_stringで描画して渡す。footerも同様(後述)
header: {line: true, spacing: 5, font_name:, font_size:}header_line: true, header_spacing: 5, header_font_name:, header_font_size:footerも同様
outline: {}—PDFアウトラインは非対応
disable_javascript / javascript_delay / window_status—JSは実行しない(設計上の非目標)
print_media_type—常にprintメディア扱い
lowquality / viewport_size / disable_smart_shrinking—WebKit固有
exe_path / wkhtmltopdf—外部プロセスを使わない
extra—生のコマンドライン文字列は受けない。個別のキーで指定する

対応していないキーを渡すと、レンダリング時にSghtmltopdf::UsageErrorが理由付きで上がる(黙って無視はしない)。

表紙・ヘッダー・フッターのHTML

wicked_pdfはRailsのテンプレート名を受け取って内部で描画するが、sghtmltopdfではrender_to_stringで描画したHTMLをheader_html_content / footer_html_contentへ直接渡せます。一時ファイルは不要です。

def show
  header = render_to_string(template: "invoices/header", layout: false)

  render pdf: "invoice", template: "invoices/show", header_html_content: header
end

既存のHTMLファイルを使う場合は、header_html / footer_htmlへファイルパスを渡せます。従来どおり、Railsのテンプレートを一時ファイルへ書き出す方法も利用できます。

require "tempfile"

def show
  header = Tempfile.new(["header", ".html"])
  header.write(render_to_string(template: "invoices/header", layout: false))
  header.flush

  render pdf: "invoice", template: "invoices/show", header_html: header.path
ensure
  header&.close!
end

footer_htmlでも同じ方法を使えます。同じ側のパスとHTML文字列を同時に指定するとSghtmltopdf::UsageErrorになります。

表紙のcoverは引き続きファイルパスを取ります。Railsのテンプレートを表紙に使う場合は、render_to_stringで描画したHTMLを一時ファイルへ書き出し、そのパスを渡してください。

ビューヘルパ

wicked_pdfsghtmltopdf
wicked_pdf_stylesheet_link_tagsghtmltopdf_stylesheet_link_tag
wicked_pdf_image_tagsghtmltopdf_image_tag
wicked_pdf_asset_pathsghtmltopdf_asset_path(見つからなければnil)
wicked_pdf_javascript_include_tag— (JSを実行しないので不要)
wicked_pdf_asset_base64— (sghtmltopdf_image_tagにinline: true)

素のimage_tagも、アセットがpublic/配下へprecompileされていればそのまま動きます。 PDFのレンダリングはHTTPサーバを介さないので、/assets/…のようなURLは--base-url(Railsでの既定はRails.root/public)を基準にローカルファイルとして解決される。

開発環境のようにアセットがまだpublic/に無い場合は、パイプラインのロードパスから実ファイルを引くヘルパを使う。 sghtmltopdf_stylesheet_link_tagはCSSを<style>へ展開し、sghtmltopdf_image_tagは画像をパスで指す(読めない場所にある場合はdata:URIで埋め込む)。 wicked_pdf_image_tagがfile://のURLを出していたのに対し、こちらはエンジンがローカルファイルとして読む形になる。

CSSについては素のstylesheet_link_tagでは足りません。 パイプラインはprecompile時にCSS中のurl()も書き換えるため、asset_hostを設定していると@font-faceの参照がHTTPSの絶対URLになり、取得できずにフォントが既定へ落ちます。 sghtmltopdf_stylesheet_link_tagは展開時にこれをディスク上のファイルへ指し直します。

<%= sghtmltopdf_stylesheet_link_tag "pdf" %>
<%= sghtmltopdf_image_tag "logo.png" %>

既定値の違い

  • マージン: wkhtmltopdfは四辺10mm。sghtmltopdfは四辺1in(96px)。 同じ見た目にしたければmargin_*を明示する
  • CLIオプションとCSSの@page: wkhtmltopdfはCLIが勝つが、sghtmltopdfは @page が勝つ(オプションは初期値)
  • ローカルファイルの参照範囲: Railsでは--allow-path(旧--allow。別名として受ける)の既定がpublic/とアセットパイプラインのロードパスになる。そこから外れるファイル(例: /usr/share/fonts、Rails.root.join("tmp"))を<img>や@font-faceのurl()で参照している場合は、Sghtmltopdf.configure { |c| c.allow_path += ["/usr/share/fonts"] }のように明示する。--font系(gothic_fontなど)で渡すフォントはこの制限を受けない
  • リモート取得: http(s)のアセット取得は既定で無効。必要ならallow_remote_assets: true
  • disable_local_file_accessとの併用: --allow-pathは読み取り範囲を狭めるだけで、許可を与えるものではない。wicked_pdfでdisable_local_file_access: trueとallow: [dir]を併用して「dirだけ読める」状態にしていた場合、そのまま持ち込むとローカル読み取りが全て止まる。allow_pathだけを残すこと

フォント

wkhtmltopdfはシステムのフォント設定に依存するが、sghtmltopdfはgothic_font/serif_font/mono_fontで指定できる。 日本語を出す場合は、コンテナのフォント事情に左右されないよう明示するのが安全。

Sghtmltopdf.configure do |c|
  c.gothic_font = Rails.root.join("vendor/fonts/NotoSansJP-Regular.ttf")
end

別プロセスへ逃がす(wicked_pdfには無い選択肢)

wicked_pdfはリクエストごとにwkhtmltopdfのプロセスを起動するが、sghtmltopdfのgemはアプリのプロセス内で変換する(重い処理の間はGVLを解放するのでPumaの他スレッドは止まらない)。 それでもアプリのCPUを使いたくない場合は、server_urlで別プロセス(sghtmltopdf server)へ委譲できる。

Sghtmltopdf.configure { |c| c.server_url = "http://pdf.internal:8080" }

負荷分散はLB(nginx・k8s Service)を前段に置く前提で、URLは1つだけ受ける。 到達できないときはSghtmltopdf::ServerErrorになり、ローカル変換へはフォールバックしない。

サーバモードではbase_url・allow_path・フォント指定などローカルパスを取るオプションはリクエストから指定できない(サーバ起動時にだけ設定できる)。 Railtieが入れる既定値は自動的に外れるが、configureで明示的に設定している場合は400(UsageError)になるので、サーバ側の起動オプションへ移す。

まだ無いもの

  • PDFの結合・アウトライン: 非対応

なお逐次出力(ストリーミング)はwicked_pdfには無い機能で、ブロック付きのrenderで使える。 ActionController::Liveと組み合わせれば、確定したページから順にレスポンスへ流せます → Ruby / Rails