🦐

railsでpdfを描画する

2023/12/20に公開

はじめに

請求書や発注書をweb上で作成して
ユーザーが閲覧・保存できるようにする際に、何かいい方法がないかなと調べたら
何やらrailsにprawnと呼ばれるgemがあるらしく試してみました。
Reactなどでもreact-pdfを使えばpdfの描画はできますが
今回はrailsを使用して、URLを叩けばpdfが見れるようになるというのを一旦ゴールとします。


prawnを使う準備

今回pdfを表示・作成するのに使うgemはprawnというgemです。
tableの描画もすることがあると思うのでprawn-tableも合わせてインストールしましょう。

gem 'prawn'
gem 'prawn-table'

公式のマニュアル (量が多いです。
https://prawnpdf.org/manual.pdf

ちなみに、少し大きめのエビのことをprawnというらしいです。


コントローラーの準備

コントローラを作成します。

pdf_controller.rb

class PdfController < ApplicationController
  def index
  end
end

ルーティングの準備

  resources :pdf, only: :index

日本語フォントの準備

日本語フォントを使用しないと日本語の描画でエラーが出ます
好みのfontをダウンロードして下記に置いておきましょう
今回はIPAexフォントというのを使ってみます

app/assets/font

PDFの作成

PDFを作成するクラスをlib直下に作成します。

ディレクトリ構成

lib
  - contract
    - pdf.rb

lib/contract/pdf.rb

module Contract
  class Pdf < Prawn::Document
    def initialize
      super(
        page_size: 'A4',
        top_margin: 20,
        bottom_margin: 20,
        left_margin: 20,
        right_margin: 20
      )
      stroke_axis # 座標を表示
      font "#{Rails.root}/app/assets/fonts/ipaexm.ttf"

      # 日本語のテキストを描画
      text_box '契約書(けいやくしょ)', at: [50, 750], size: 20

      # テーブルの描画
      bounding_box([50, 700], width: 500, height: 300) do
        table_data = [
          ["項目", "説明", "価格"],
          ["商品A", "商品Aの説明", "¥1000"],
          ["商品B", "商品Bの説明", "¥2000"],
          ["合計", "", "¥3000"]
        ]
        table(table_data, header: true) do
          columns(1..2).align = :right
          self.row_colors = ['DDDDDD', 'FFFFFF']
          self.header = true
        end
      end
    end
  end
end

サンプルなのでこんな感じでおk


コントローラの編集

class PdfController < ApplicationController
  def index
    respond_to do |format|
      format.pdf do
        pdf  =  Contract::Pdf.new().rende
        send_data pdf,
          filename: 'pdf.pdf',
          type: 'application/pdf',
          disposition: 'inline' # 外すとダウンロードになる
      end
    end
  end
end

動作確認

/pdf.pdf

を叩いてpdfが描画されれば動作確認完了。

おそらく下記のような物が出てくるはず、試しに描画しただけなのでしょぼいです。


レイアウトについて

prawnを用いいたpdf描画のレイアウトに関してですが

基本的に座標上に要素を置いていくことになります。
なので開発中はstroke_axisを追加して、座標が常に表示されるようにしましょう。

また、1ページに内に収まらないという場合も
オプションで次ページに送るものがあるので それも用いれば何ページでも作れます。

試しに先ほどのpdfを下記のように修正してみましょう

module Contract
  class Pdf < Prawn::Document
    def initialize
      super(page_size: 'A4', margin: [40, 40, 40, 40])
      font_path = "#{Rails.root}/app/assets/fonts/ipaexm.ttf"
      font font_path
      stroke_axis
      header
      contract_info
      parties
      terms
      move_cursor_to signature_position
      signature
    end

    private

    def header
      text '契約書', size: 24, align: :center
      move_down 10
    end

    def contract_info
      text "契約日: YYYY年mm月dd日", size: 12, align: :right
      move_down 10
    end

    def parties
      text '当事者:', size: 16
      indent(20) do
        text "甲方: 株式会社A"
        text "乙方: 株式会社B"
      end
      move_down 10
    end

    def terms
      text '契約の条項:', size: 16
      indent(20) do
        text "1. 契約の目的"
        text "2. 契約の条件"
        text "3. 支払い条件"
        text "4. 終了条件"
      end
      move_down 10
    end

    def signature_position
      bounds.bottom + 30
    end

    def signature
      signature_start = 300
      indent(signature_start - bounds.left) do
        text '署名:', size: 14
        move_down 5
        stroke_line [0, cursor], [200, cursor]
      end
    end
  end
end

見た目しょぼいけどさっきよりはマシでしょう。

各メソッドの説明

先ほどのアップデートしたpdfのメソッドの説明です。

  • header
    文書のヘッダー(タイトル)を生成
    text メソッドは指定されたテキストを描画し、size と align オプションでフォントサイズと配置を設定します。

  • contract_info
    契約の日付などの情報を表示
    テキストとして描画します。

  • partiesメソッド:
    契約の当事者に関する情報を表示
    indent メソッドでテキストのインデント(字下げ)を設定します。

  • termsメソッド
    契約の条項を表示します。
    ここでも indent メソッドを使用します。

  • signature_positionメソッド:
    署名欄の位置を計算します。
    bounds.bottom はページの底部の位置を返し、それに30を加えて署名欄の位置を下にずらします。

  • signatureメソッド
    署名欄を生成します。
    indent メソッドで署名のテキストと署名欄の位置を調整し、stroke_line で署名線を描画します。

こんな感じでindentmove_downでの調整もできるし
textもsizealign: :rightなどでいろいろ調整できそう。

最後に

今回railsでpdfを描画するgemのprawnを使用して見ました!
慣れるまで自分の思い描いたレイアウトにするのは難しそうですが
簡単にpdfの描画やDLができるのはとても良いと思いました。
バックエンドでの実装なので、DBのデータなども簡単に渡して描画できたりと、出来ることは沢山ありそうでした!
興味がある方は是非試してみてください!

ゲームエイトテックブログ

Discussion