https://qiita.com/godan09/items/13866970972bf3a1c243
PDFで见积书を出力するにあたって関连ライブラリのメリデメを洗い出してみました。
そのライブラリの中で実装方法とアウトプットを提示し、比较できる状态にしすることが本稿の目的です。
この记事は2つの手法のPDF出力ライブラリをリスト化して比较しました。その手法と、各种ライブラリは次のとおりです。
- HTMLをPDFを化して出力する
- django-wkhtmltopdf
- django_xhtml2pdf
- WeasyPrint
- コードからPDFを生成するもの
- reportlab
また比较に関しては次の点に注目しています。
- ライブラリの使いやすさ
- 使うのは容易か
- コードは复雑にならないか
- 动作は重くないか
- 动作させるために别途ソフトウェアのインストールは必要か
-
自由度
- A4一枚の见积书を作成するのは容易か
- レイアウトに対して细かい设定が可能か
-
保守性
- メンテナンスはしやすいか
- ライブラリの更新频度
- 出力されたPDFのクオリティは问题なさそうか
- ネット上の情报は多いか
TL;DR.
个人的な主観的な比较を置いておきます。
ライブラリ名 | 使いやすさ | 自由度 | 保守性 | 备考 |
---|---|---|---|---|
django-wkhtmltopdf | ○ | ○ | △ | |
django_xhtml2pdf | ○ | × | △ | |
WeasyPrint | ○ | △ | △ | |
reportlab | × | ◎ | ○ |
HTML to PDF
django-wkhtmltopdf
ドキュメント django-wkhtmltopdf 3.2.0 documentation
グーグル検索でPython PDF
で検索したときに検索上位に出るwkhtmltopdfをdjango向けにラップされたライブラリです。
Djangoのクラスベースビューに対応しており非常に安易に导入できます。
生成されるPDFもデフォルトできれいに出力されています。ただ、别途ソフトウェアのインストールが必要なのでAppEngineでの导入の手间は大変そうです(要検证)。
インストール方法
$ pipenv install django-wkhtmltopdf
别途wkhtmltopdfからソフトウェアインストールが必要。
SampleCode
from wkhtmltopdf.views import PDFTemplateView
class PdfSampleView(PDFTemplateView):
filename = 'my_psdf.pdf'
template_name = "pdf_sample/sample.html"
生成されたPDF
django_xhtml2pdf
PythonでHTMLをPDFに変换するライブラリ django_xhtml2pdf
をDjango向けにラップしたライブラリです。
クラスベースビュー向けのmixinを提供されていて简単に使用できます。またデコレーターが标准でサポートされています。
ただしドキュメントが少なく、オプションはほとんど无い。またCSSの解釈が独特なためか通常のHTMLとは违う构成で出力されます。
インストール方法
$ pipenv install django_xhtml2pdf
SampleCode
from django_xhtml2pdf.views import PdfMixin
class Xhtml2pdfSampleView_(PdfMixin, TemplateView):
template_name = "pdf_sample/sample.html"
生成されたPDF
WeasyPrint
wkhtmltopdfに近いPDF生成ツールとライブラリ。ドキュメントが充実しています。
wkhtmltopdfほどではないがxhtmlよりかは高品质なPDFが出力されます。xhtmlとくらべ相対的にHTMLとの出力に差分がすくない。ただ、インストールドキュメントを见るとパッケージとは别にインストールが必要とのためAppEngineではむずかしそうです(要调查)。
WeasyPrint — WeasyPrint 51 documentation
SampleCode
from weasyprint import HTML, CS
from django.http import HttpResponse
from django.template.loader import get_templat
class WeasyPrintView(TemplateView):
template_name = 'pdf_sample/sample.html'
def get(self, request, *args, **kwargs):
html_template = get_template('pdf_sample/sample.html')
context = super().get_context_data(**kwargs)
html_str = html_template.render(context)
pdf_file = HTML(string=html_str, base_url=request.build_absolute_uri()).write_pdf(
)
response = HttpResponse(pdf_file, content_type='application/pdf')
response['Content-Disposition'] = 'filename="fuga.pdf"'
return response
生成されたPDF
HardCodePDF
reportlab
Pythonのコード上で実际にレイアウトを指定して生成するライブラリです。
スタイルを含めコード上で起こすため必然的に长くなる。PDFの生成は问题なく実行できます。
またすべてのデータをコードで挿入できるため実装の自由度は非常に高いです。
オプションとドキュメントも充実しているため一通りの帐簿などの作成は可能です。
ReportLab - Content to PDF Solutions
インストール方法
$ pipenv install reportlab
SampleCode
from django.views.generic import TemplateView
from django.http import HttpResponse
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib.pagesizes import A4, portrait
from reportlab.platypus import Table, TableStyle
from reportlab.lib.units import mm
from reportlab.lib import colors
class ReportlabView(TemplateView):
template_name = 'pdf_sample/sample.html'
def get(self, request, *args, **kwargs):
response = HttpResponse(status=200, content_type='application/pdf')
response['Content-Disposition'] = 'filename="example.pdf"'
# response['Content-Disposition'] = 'attachment; filename="example.pdf"'
self._create_pdf(response)
return response
def _create_pdf(self, response):
# 日本语が使えるゴシック体のフォントを设定する
font_name = 'HeiseiKakuGo-W5'
pdfmetrics.registerFont(UnicodeCIDFont(font_name))
# A4縦向きのpdfを作る
size = portrait(A4)
# pdfを描く场所を作成:pdfの原点は左上にする(bottomup=False)
pdf_canvas = canvas.Canvas(response)
# ヘッダー
font_size = 24 # フォントサイズ
pdf_canvas.setFont("HeiseiKakuGo-W5", font_size)
pdf_canvas.drawString(93 * mm, 770, "见积书")
font_size = 10
pdf_canvas.setFont("HeiseiKakuGo-W5", font_size)
pdf_canvas.drawString(
150 * mm, 813, f"见积発行日: "
)
pdf_canvas.drawString(
150 * mm,
800,
"xxxxxxxxxxx-xxxxxxxxxx",
)
# (4) 社名
data = [
[f"ほげほげ会社御中", ""],
["案件名", "ほげほげ案件"],
["御见积有効限:発行日より30日", ""],
]
table = Table(data, colWidths=(15 * mm, 80 * mm), rowHeights=(7 * mm))
table.setStyle(
TableStyle(
[
("FONT", (0, 0), (-1, -1), "HeiseiKakuGo-W5", 12),
("LINEABOVE", (0, 1), (-1, -1), 1, colors.black),
("VALIGN", (0, 0), (1, -1), "MIDDLE"),
("VALIGN", (0, 1), (0, -1), "TOP"),
]
)
)
table.wrapOn(pdf_canvas, 20 * mm, 248 * mm)
table.drawOn(pdf_canvas, 20 * mm, 248 * mm)
pdf_canvas.drawString(20 * mm, 238 * mm, "下记の通り御见积申し上げます")
# (4) 社名
data = [
["合计金额(消费税込)", f"1000 円"],
]
table = Table(data, colWidths=(50 * mm, 60 * mm), rowHeights=(7 * mm))
table.setStyle(
TableStyle(
[
("FONT", (0, 0), (1, 2), "HeiseiKakuGo-W5", 10),
("BOX", (0, 0), (2, 3), 1, colors.black),
("INNERGRID", (0, 0), (1, -1), 1, colors.black),
("VALIGN", (0, 0), (1, 2), "MIDDLE"),
("ALIGN", (1, 0), (-1, -1), "RIGHT"),
]
)
)
table.wrapOn(
pdf_canvas,
20 * mm,
218 * mm,
)
table.drawOn(
pdf_canvas,
20 * mm,
218 * mm,
)
# 品目
data = [["内容", "开始月", "终了月", "単価", "数量", "金额"]]
for idx in range(13):
data.append([" ", " ", " ", " ", " ", ""])
data.append([" ", " ", " ", "合计", "", f"{1000:,}"])
data.append([" ", " ", " ", "消费税", "", f"{1000 * 0.10:,.0f}"])
data.append([" ", " ", " ", "税込合计金额", "", f"{1000 * 1.10:,.0f}"])
data.append(
[" ", " ", " ", "", "", ""],
)
table = Table(
data,
colWidths=(70 * mm, 25 * mm, 25 * mm, 20 * mm, 20 * mm, 20 * mm),
rowHeights=6 * mm,
)
table.setStyle(
TableStyle(
[
("FONT", (0, 0), (-1, -1), "HeiseiKakuGo-W5", 8),
("BOX", (0, 0), (-1, 13), 1, colors.black),
("INNERGRID", (0, 0), (-1, 13), 1, colors.black),
("LINEABOVE", (3, 11), (-1, 18), 1, colors.black),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("ALIGN", (1, 0), (-1, -1), "RIGHT"),
]
)
)
table.wrapOn(pdf_canvas, 17 * mm, 100 * mm)
table.drawOn(pdf_canvas, 17 * mm, 100 * mm)
pdf_canvas.drawString(17 * mm, 100 * mm, "<备考>")
table = Table(
[[""]],
colWidths=(180 * mm),
rowHeights=90 * mm,
)
table.setStyle(
TableStyle(
[
("FONT", (0, 0), (-1, -1), "HeiseiKakuGo-W5", 8),
("BOX", (0, 0), (-1, -1), 1, colors.black),
("INNERGRID", (0, 0), (-1, -1), 1, colors.black),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]
)
)
table.wrapOn(pdf_canvas, 17 * mm, 5 * mm)
table.drawOn(pdf_canvas, 17 * mm, 5 * mm)
pdf_canvas.showPage()
# pdfの书き出し
pdf_canvas.save()