読者です 読者をやめる 読者になる 読者になる

オープンソースこねこね

Webプログラミングなどについてあれこれ。

Go+LuaでHTMLからPDFを生成するCLIツールを作った

github.com

以前、Goでプロビジョニングツールを作った でも書いたように、GoとGopherLuaCLIツールをいろいろ実装していて、今回もその成果物の一つです。

概要

Goで書かれたワンバイナリで動くPDF生成ツールです。現時点で動作環境は64bitのMacLinuxのみをサポートしてます(windowsはまだ)。全体的にコードの実装はcofuからコピペして雑に作ったので、使いかたもほぼ同じです。

html2pdfというコマンドをreleaseページからダウンロードしてPATHの通ったディレクトリに配置し、

local html2pdf = require "html2pdf"

local example = html2pdf.pdf "example.pdf"
example.options = {
    page_size = "A4",
}
example.pages = {
    input_content = "hello world!"
}

のようなLuaのコードをexample.luaに保存。html2pdf example.luaを実行する。

$ html2pdf example.lua
==> Starting html2pdf...
==> Loaded 1 pdf config.
==> Evaluating example.pdf
    output_file: example.pdf
==> Complete!

これでexample.pdfが出力されます。上記の例だとコンテンツを設定するinput_contentに単純な文字列(hello world!)を指定しているだけですが、実際にはHTMLを書いて使います。

使い方

URLを指定

input_contentの代わりにinputを使えばURLを指定できます。

local html2pdf = require "html2pdf"

local example = html2pdf.pdf "example.pdf"
example.pages = {
    input = "https://github.com/kohkimakimoto/html2pdf"
}

複数ページ

pagesには複数のページを設定できます。

example.pages = {
    { input = "https://github.com/kohkimakimoto/html2pdf" },
    { input = "https://github.com/kohkimakimoto/cof" },
}

表紙

coverで表紙を設定できます。ページの指定と同じくinput_contentinputでコンテンツを指定できます。

example.cover = {
    input_content = "<b>title</b>"
    -- or
    -- input = "https://..."
}

その他オプション

html2pdfは内部でwkhtmltopdfを使っています。というより、実装的にはwkhtmltopdfにLuaスクリプティング機能を載せたラッパーコマンドになっています。なのでwkhtmltopdfのオプションが使えます。optionsで以下のようにして指定します。

example.options = {
    page_size = "A4",
    margin_left = 5,
    margin_top = 5,
    margin_bottom = 5,
    margin_right = 5,
    orientation = "Landscape",
    -- etc...
}

詳しくはwkhtmltopdfのドキュメントを参考にしてください。wkhtmltopdf docs

変数

-var-var-fileオプションで実行時にコマンドラインから変数を渡せます。変数はJSONで以下のように指定します。

$ html2pdf example.lua -var='{"output_file": "foo.pdf"}'

スクリプト内ではvar変数として格納されています。

local html2pdf = require "html2pdf"

local example = html2pdf.pdf "example.pdf"
example.output_file = var.output_file
example.pages = {
    input = "https://github.com/kohkimakimoto/html2pdf"
}

Shebang

#!/usr/bin/env html2pdf

スクリプトファイルの先頭に記述して実行権限をつければ、直接実行してPDFを吐くスクリプトになります。

Markdown

MarkdownからHTMLを生成するLuaモジュールも組み込んだので、すこしだけLuaでロジックを書けばmarkdownからPDFドキュメントを作ることもできます。以下にサンプルを作ったので参考にしてください。

https://github.com/kohkimakimoto/html2pdf/tree/master/_example

表紙付きドキュメントを生成するサンプルには結果のPDFもアップロードしてあります。

https://github.com/kohkimakimoto/html2pdf/blob/master/_example/doc/doc.pdf

DSLな記法

設定はLuaDSLスタイルでも記述することもできます。次の2つの例は同じ設定です。もりもりコードを書くのでなければ、DSLのほうが見やすいかもしれません。

DSLLua:

pdf "hello.pdf" {
    pages = {
        input_content = "hello world!"
    },
}

PlainなLua

local html2pdf = require "html2pdf"

local hello = html2pdf.pdf "hello.pdf"
hello.pages = {
    input_content = "hello world!"
}

DSLで使っているpdf関数はhtml2pdf.pdfメソッドのエイリアスになっています。

実装の話

当初、PDF変換部分のwkhtmltopdfを調べていたら、これはCで書かれたプロダクトだったので、Goからcgoをつかって連携しようかと考えて調査したのですが、どうも共有ライブラリをロードするGoのプログラムはスタンドアロンバイナリにならないようだと判明。あとクロスコンパイル周りで罠があったりとか、Macではcgoをつかってスタティックなビルドができないとか。。。(結構いろいろ調べたのですが、もう詳細忘れた。。。とにかくできそうになかった。自分のスキルが足りない可能性ももちろんありますが)。cgoツライ。

html2pdfを使うマシンにwkhtmltopdfをインストールしておけばいいだけなのですが、これでは当然、スタンドアロンなシングルバイナリとはいえないわけです。これはイヤだなと。

で、wkhtmltopdfコマンド自体がスタンドアロンバイナリなので、もうこのバイナリ自体をまるごとgo-bindataでGoのコードに埋め込んでおき、実行時に/tmpディレクトリ以下にwkhtmltopdfのコマンドファイルを生成して、それをhtml2pdfから叩くという荒業を使うことにしました。これで見た目上はhtml2pdfのみで動いているように見えるし、別途wkhtmltopdfをインストールする必要もない。

自分は何に使っているのか

個人事業主やってるので、クライアントに提出する請求書をPDFで生成するのに使っています。