Pythonでborbを使ってPDF文書を作成する

PDF(Portable Document Format)は、WYSIWYG(What You See is What You Get)形式ではありません。

プラットフォームに依存せず、基盤となるオペレーティングシステムやレンダリングエンジンに依存しないように開発されました。

これを実現するために、PDFはプログラミング言語のようなもので操作できるように構築されており、結果を得るためには一連の命令と操作に依存することになります。

実際、PDFはスクリプト言語であるPostScriptをベースにしています。

PostScriptは、デバイスに依存しない最初のページ記述言語でした。

この言語には、グラフィックの状態を変更する演算子があり、大まかには次のようなものです。

  • フォントを “Helvetica “に設定する。
  • 黒にストロークカラーを設定する
  • (60,700)に移動
  • グリフ “H “を描画する

これで、いくつかのことがわかります。

  • PDFからテキストを曖昧さなく抽出することが困難な理由
  • PDF文書を編集することが困難な理由
  • ほとんどのPDFライブラリは、コンテンツ作成に非常に低レベルなアプローチを強制する理由(プログラマーは、テキストをレンダリングする座標、余白などを指定しなければならない)。

このガイドでは、PDF文書の読み取り、操作、生成に特化したPythonライブラリであるborbを使用して、PDF文書を作成することにします。

borbは低レベルモデル(正確な座標やレイアウトにアクセスできる)と高レベルモデル(余白や位置などの正確な計算をレイアウトマネージャーに委ねることができる)の両方を提供します。

ここでは、Pythonでborbを使ってPDFドキュメントを作成し、検査する方法と、LayoutElementsのいくつかを使ってバーコードやテーブルを追加する方法について見ていきます。

borbのインストール

borbはGitHubのソースからダウンロードするか、pip経由でインストールすることができます。

$ pip install borb


Pythonでborbを使ったPDFドキュメントの作成方法

borbには直感的なキークラスである DocumentPage があり、これらはドキュメントとその中のページを表します。

これらは、PDFドキュメントを作成するための主要なフレームワークです。

さらに、 PDF クラスは作成した Document を読み込んだり保存したりするための API を表しています。

このことを念頭に置いて、空のPDFファイルを作成してみましょう。

from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF


# Create an empty Document
document = Document()


# Create an empty page
page = Page()


# Add the Page to the Document
document.append_page(page)


# Write the Document to a file
with open("output.pdf", "wb") as pdf_file_handle:
    PDF.dumps(pdf_file_handle, document)


コードの大部分はここで説明されています。

まず、空の Document を作成し、次に append() 関数で空の PageDocument に追加して、最後に PDF.dumps() でファイルを保存しています。

注目すべきは、Pythonにこのテキストをエンコードさせたくないので、バイナリモードで書き込むために "wb" フラグを使用したことです。

この結果、ローカルファイルシステム上に output.pdf という名前の空のPDFファイルが生成されます。

borbで「Hello World」ドキュメントを作成する

もちろん、空の PDF ドキュメントでは多くの情報を伝えることはできません。

そこで、 Document インスタンスに追加する前に、 Page に何らかのコンテンツを追加してみましょう。

先ほどの2つのインテグラルクラスと同じように、 Page にコンテンツを追加するために、表示したいレイアウトのタイプを指定する PageLayout を追加して、そのレイアウトに1つまたは複数の Paragraph を追加します。

そのために、 Document はオブジェクトの階層の最下層に位置し、 Paragraph は最上層のインスタンスで PageLayout の上に積み重なり、結果として Page になります。

それでは、ParagraphPage に追加してみましょう。

from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF
from borb.pdf.canvas.layout.paragraph import Paragraph
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.io.read.types import Decimal


document = Document()
page = Page()


# Setting a layout manager on the Page
layout = SingleColumnLayout(page)


# Adding a Paragraph to the Page
layout.add(Paragraph("Hello World", font_size=Decimal(20), font="Helvetica"))


document.append_page(page)


with open("output.pdf", "wb") as pdf_file_handle:
    PDF.dumps(pdf_file_handle, document)


2つのオブジェクトが追加されていることに気がつくでしょう。

  • このクラスは、Pageのどこにコンテンツが追加されているか、どの領域が今後のコンテンツに使用できるか、Pageのマージンはどうなっているか、そしてリーディング(Paragraphオブジェクト間のスペース)はどうなっているか、ということを記録しています。

ここでは1つのカラムだけを扱うので、SingleColumnLayoutを使用しています。

別の方法として、 MultiColumnLayout を使用することもできます。

  • Paragraph インスタンス: このクラスはテキストのブロックを表します。フォント、font_size、font_colorなどのプロパティを設定することができます。より多くの例については、ドキュメントを参照してください。

これで、私たちの Paragraph を含む output.pdf ファイルが生成されました。

borbによる生成されたPDFの検査

注意: PDFドキュメントの内部構造に興味がなければ、このセクションは完全に省略可能です。

しかし、この形式について少し知っておくと非常に便利です(古典的な「なぜ私のコンテンツが今このページに表示されるのか」という問題をデバッグするときなど)。

一般的に、PDFリーダーは文書を最後のバイトから読み始めます。

xref
0 11
0000000000 00000 f
0000000015 00000 n
0000002169 00000 n
0000000048 00000 n
0000000105 00000 n
0000000258 00000 n
0000000413 00000 n
0000000445 00000 n
0000000475 00000 n
0000000653 00000 n
0000001938 00000 n
trailer
<<61e6d144af4b84e0e0aa52deab87cfe9>]>>
startxref
2274
%%EOF


ここでは、ファイル終了マーカー(%%EOF)と相互参照表(通常、xrefと省略されます)が表示されています。

xref“startxref”“xref”` というトークンで区切られます。

xref`は、PDFリーダーのルックアップテーブルとして機能します。

これは、PDF内の各オブジェクトのバイトオフセット(ファイルの先頭から始まる)を含んでいます。

xrefの最初の行 (0 11) は、このxref` には11個のオブジェクトがあり、最初のオブジェクトは 0 番から始まるということを表しています。

それ以降の各行は、バイトオフセットと、いわゆる世代番号、そして f または n という文字で構成されています。

  • f` でマークされたオブジェクトはフリーオブジェクトで、レンダリングされる予定はありません。
  • n` でマークされたオブジェクトは “使用中” です。

xrefの下部には、トレイラー辞書があります。

PDFの構文では、辞書は<<` で区切られます。

この辞書は次のようなペアを持っています。

  • /Root 1 0 R
  • /Info 2 0 R
  • /Size 11
  • /ID [&lt;61e6d144af4b84e0e0aa52deab87cfe9&gt; &lt;61e6d144af4b84e0e0aa52deab87cfe9&gt;]

トレイラー辞書は、PDFリーダーの出発点であり、他のすべてのデータへの参照を含んでいます。

この場合

  • /Root : これは、ドキュメントの実際のコンテンツにリンクしている別の辞書です。
  • /Info : これは、ドキュメントのメタ情報(著者、タイトルなど)を含むディクショナリです。

1 0 Rのような文字列は、PDF の構文では「参照」と呼ばれます。

そして、ここでxref` テーブルが役に立ちます。

1 0 R` に関連するオブジェクトを見つけるには、オブジェクト1(世代番号0)を調べます。

xref` ルックアップテーブルは、このオブジェクトがドキュメントのバイト15にあることを教えてくれます。

それをチェックすると、次のようになります。

1 0 obj
&lt;&gt;
endobj


このオブジェクトは 1 0 obj で始まり、 endobj で終わっていることに注意してください。

これは、私たちが実際にオブジェクト1を扱っていることを示すもう一つの確認です。

この辞書は、ドキュメントのページがオブジェクト 3 にあることを教えてくれます。

3 0 obj
&lt;&gt;
endobj


これは /Pages 辞書で、このドキュメントには1ページがあることを示しています (/Count のエントリ)。

Kids` のエントリは典型的な配列で、1ページにつき1つのオブジェクト参照があります。

最初のページはオブジェクト 4 にあると考えられます。

4 0 obj
&lt;&gt;
endobj


この辞書には、いくつかの興味深いエントリが含まれています。

  • /MediaBox: ページの物理的な大きさ(この場合はA4サイズのページ)。
  • /Contents: PDF コンテンツ オペレータの (通常圧縮された) ストリームへの参照。
  • /Resources: このページをレンダリングするために使用されるすべてのリソース(フォント、画像など)を含む辞書への参照。

オブジェクト 5 をチェックして、このページで実際に何がレンダリングされているのかを見てみましょう。

5 0 obj
&lt;&gt;
stream
xÚãR@
È&lt;§ž`a¥£šÔw3T0„É
€!K¡š3B˜„žœenl7'§999ù
åùE9)š
!Y(’!8õÂyšT*î
endstream
endobj


先に述べたように、この(コンテンツの)ストリームは圧縮されています。

どの圧縮方法が使われたかは、/Filterの項目で知ることができます。

オブジェクト 5 に解凍 (unzip) を適用すると、実際のコンテンツオペレータを取得できるはずです。

5 0 obj
&lt;&gt;
stream
            q
            BT
            0.000000 0.000000 0.000000 rg
            /F1 1.000000 Tf            
            20.000000 0 0 20.000000 60.000000 738.000000 Tm            
            (Hello world) Tj
            ET            
            Q
endstream
endobj


最後に、コンテンツをデコードできるレベルまで来ました。

それぞれの行は、引数とその演算子で構成されています。

早速、演算子について見ていきましょう。

  • q`: 現在のグラフィックの状態を保持します (スタックにプッシュします)。
  • BT: テキストを開始します。
  • 0 0 0 rg: 現在の描画色を (0,0,0) rgb に設定します。これは黒です。
  • /F1 1 Tf: 現在のフォントを /F1 に設定し (これは先ほどのリソース辞書のエントリです)、フォントサイズを 1 に設定します。
  • 20.000000 0 0 20.000000 60.000000 738.000000 Tm : テキストマトリクスを設定します。テキストマトリックスについては、独自のガイドが必要です。このマトリックスは、フォントのサイズとテキストの位置を調整するものであると言えば、十分でしょう。ここでは、フォントを font-size 20 に拡大縮小し、テキストを描画するカーソルを 60,738 に設定しています。PDFの座標系はページの左下から始まっています。つまり、60,738はページの左上付近ということになります(ページの高さが842単位であることを考慮すると)。
  • (Hello world) Tj : PDFの構文における文字列は、()で区切られます。このコマンドは、先ほどテキストマトリクスで指定した位置に、文字列 “Hello world” を、その前のコマンドで指定したフォント、サイズ、色で表示するようにPDFリーダーに指示します。
  • ET: テキストを終了します。
  • Q: スタックからグラフィックスの状態をポップアップします(したがって、グラフィックスの状態を復元します)。

他のborb LayoutElementsをページに追加する

borbには様々なLayoutElementオブジェクトが付属しています。

前の例では、Paragraphについて簡単に説明しました。

しかし、他にもUnorderedList,OrderedList,Image,Shape,Barcode,Table` といった要素も用意されています。

少し難しい例ですが、 TableBarcode を使って作ってみましょう。

テーブルTableCellから構成され、Table` のインスタンスに追加します。

バーコードは BarcodeType と呼ばれるもので、ここでは QR を使用します。

from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF
from borb.pdf.canvas.layout.paragraph import Paragraph
from borb.pdf.canvas.layout.page_layout import SingleColumnLayout
from borb.io.read.types import Decimal
from borb.pdf.canvas.layout.table import Table, TableCell
from borb.pdf.canvas.layout.barcode import Barcode, BarcodeType
from borb.pdf.canvas.color.color import X11Color


document = Document()
page = Page()


# Layout
layout = SingleColumnLayout(page)


# Create and add heading
layout.add(Paragraph("DefaultCorp Invoice", font="Helvetica", font_size=Decimal(20)))


# Create and add barcode
layout.add(Barcode(data="0123456789", type=BarcodeType.QR, width=Decimal(64), height=Decimal(64)))


# Create and add table
table = Table(number_of_rows=5, number_of_columns=4)


# Header row
table.add(TableCell(Paragraph("Item", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Unit Price", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Amount", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Price", font_color=X11Color("White")), background_color=X11Color("SlateGray")))


# Data rows
for n in [("Lorem", 4.99, 1), ("Ipsum", 9.99, 2), ("Dolor", 1.99, 3), ("Sit", 1.99, 1)]:
    table.add(Paragraph(n[0]))
    table.add(Paragraph(str(n[1])))
    table.add(Paragraph(str(n[2])))
    table.add(Paragraph(str(n[1] * n[2])))


# Set padding
table.set_padding_on_all_cells(Decimal(5), Decimal(5), Decimal(5), Decimal(5))
layout.add(table)


# Append page
document.append_page(page)


# Persist PDF to file
with open("output4.pdf", "wb") as pdf_file_handle:
    PDF.dumps(pdf_file_handle, document)


いくつかの実装の詳細です。

  • BARCODEは様々なカラーモデルをサポートしています。また、RGBColor,HexColor,X11Color,HSVColor` などの様々なカラーモデルをサポートしています。
  • テーブルオブジェクトに直接レイアウト要素を追加することもできますが、それらを TableCell オブジェクトでラップすることもできます。
  • もし、 fontfont_sizefont_color を指定しなかった場合、 Paragraph はデフォルトで Helveticasize 12black を使用します。

この結果は

結論

このガイドでは、PDFファイルを読み書きしたり操作したりするためのライブラリであるborbについて見てきました。

DocumentPageといった主要なクラスや、ParagraphBarcodePageLayout` といった要素について見てきました。

最後に、様々な内容のPDFファイルをいくつか作成し、PDFがどのようにデータを保存しているかを調べました。

タイトルとURLをコピーしました