はじめに
最近、自作のpythonパッケージをPyPIにアップロードして公開しました。
pdfを作る時、「組版」を意識しないといけない案件があり、reportplabを使いたいなと色々調査をする中、
標準の機能(ParagraphStyle)を使い、wordWrap="CJK"で長文を出力してみるとこんな感じ。
...いや、収まってないですけどね(´・ω・`)
結局、reportlabの文字描画の機能だけでは実現できなさそうだなと思ったので、「作るしかないか」と思ったのが始まりでした。
...車輪の再発明をしてないといいなぁ(´・ω・`)
(2025/09/20追記)
...と思って見ていると、ドキュメントに色々使えそうな標準機能がありそうなので確認中です。
紹介
機能
Alignment
drawStringで描画するだけだと、「この範囲の中央に置きたい」と思うと、
- 指定のフォントで文字がどんなサイズで描画されるかを計算
- 原点からのオフセット量を計算
...をしないといけないのがちょっと面倒なので、直感的に指定できるようにしました。
aligned_layout = (
BlockAligner(text_layout, width=block_width, height=block_height)
.alignment(horizontal=HAlign.CENTER, vertical=VAlign.MIDDLE)
.apply()
)
Fontのフォールバック
cssのfont-familyのように、優先的にヒットしたフォントで描画して、対応してなかったら次のフォント...みたいな指定ができるようにしました。
(欧文と日本語でフォントを変えている例)
禁則処理
フォントの幅だけで計算された折り返しだと、いわゆる「禁則処理」に対応できなかったりします...(これが一番ネックだった)
↓は何が面倒なの?って方は開いて見てね
文字数だけで見れば、以下のように改行されてしまいます。
禁則文字の中には様々な表現がありますが、その中の一つは「
括弧」で、閉じ括弧は行頭に置けず、開き括弧は行末に置くこ
とができません。
一行目の末尾の「は」の横には一見、開き括弧が入れそうな余白がありますが、開き括弧は行の最後にくることができないので次の行に移し、一行目の文字数が減った分、文字間の余白で調整することで右端を揃える...みたいなことをしています。
均等割付け・ハイフネーションもしたいと思ったら相当面倒です。
何それ?って方は開いて見てね
均等割付け(Justification)
行の両端をテキストブロックの左右の端に揃えるために、文字と文字の間隔を調整します。
処理は大きく2つのケースに分かれます。
-
スペースを広げる場合(行の長さがブロック幅より短い) 行の長さに足りない分のスペースを、その行に含まれるすべてのグリフ(文字や単語)間の隙間に均等に分配します。これにより、行末がブロックの右端に揃います。
-
スペースを詰める場合(行の長さがブロック幅より長い) これは、禁則処理の「追い込み」(句読点などを行末に含める処理)によって行の長さがブロック幅を超えてしまった場合に発生します。
-
優先処理: まず、「、」や「。」といった句読点の直後のスペースを優先的に詰めます(最大でフォントサイズの半分まで)
-
均等処理: それでもまだ幅が余っている場合、残りの詰めるべき量を、行内のすべてのグリフ間の隙間に均等に分配
-
ハイフネーション (Hyphenation)
-
改行判定: 行末に配置しようとした英単語(例: "typesetting")が、行の幅に収まらない場合にハイフネーション処理が開始されます。
-
分割点の探索: その単語を音節に基づいて分割できるすべての候補(例: "type-set-ting")をリストアップします。(pyphenライブラリを使いました)
-
最適な分割点の選択: リストアップされた候補の中から、現在の行に収まる最も長い部分を探します。例えば、"type-"は収まるが"typeset-"は収まらない場合、"type-"が最適な分割点として選ばれます。
-
改行の実行:
- 現在の行の末尾に、ハイフンを含んだ単語の最初の部分("type-")を配置します。
- 単語の残りの部分("setting")は、次の行の先頭に送られます。
ハイフネーションをしない場合、単語全体が次の行に送られて行末に大きな空白ができてしまいますが、この処理によって行の長さをより均一に保ち、読みやすいレイアウトを実現します。
↓の例のハイフネーションは少し冗長かもしれません。自動化って難しいですね(´・ω・`)
いわゆる、上記のような 「組版」 に対応した帳票を作成したいと思った時に、自分がほしいなと思うパッケージを作成しました。
インストール方法
pythonのパッケージのインストール方法でよく見るコレ
pip install reportlab-typesetting
「このパッケージの作り方って、そもそもどうやるの?」と思ったこともあり、勉強がてら作ってみた時の手順をまとめておこうと思います。
パッケージを作る
必要なツール
- build: pyproject.tomlを元に、配布用のファイル(.whlや.tar.gz)を生成する
- twine: 生成されたファイルをPyPIに安全にアップロードする
python -m pip install --upgrade build twine
フォルダ構成
/
├── dist
│ ├── <package_name>-0.0.1-py3-none-any.whl
│ └── <package_name>-0.0.1.tar.gz
│
├── example
│ └── run.py
│
├── LICENSE
│
├── pyproject.toml
│
├── README.md
│
├── src
│ └── <package_name>
│ ├── __init__.py
│ └── hoge.py
│
└── tests
└── test_hoge.py
| ファイル/フォルダ | 役割 | 必須/推奨 |
|---|---|---|
| pyproject.toml | プロジェクトのメタデータ(名前、バージョン、依存関係など)やビルド設定を定義する中心的なファイル。 | 必須 |
| src/ | パッケージのソースコードを格納するディレクトリ。 | 推奨 |
| src/<package_name>/ | 実際のPythonモジュール(.pyファイル)を配置する場所。 | 必須 |
| README.md | プロジェクトの概要、インストール方法、使い方などを記述するファイル。PyPIのトップページに表示される。 | 必須 |
| LICENSE | ソフトウェアの利用許諾条件を明記するライセンスファイル。 | 必須 |
| tests/ | pytestなどのテストフレームワークで使うユニットテストのコードを格納するディレクトリ。 | 推奨 |
| .gitignore | Gitのバージョン管理から除外するファイルやフォルダ(仮想環境、ビルド成果物など)を指定する。 | 推奨 |
| dist/ | python -m buildコマンドで生成される配布用ファイル(.whl, .tar.gz)が格納されるフォルダ。リポジトリには含めない。 | 自動生成 |
| .venv/ | プロジェクト専用のPython仮想環境。リポジトリには含めない。 | 開発環境 |
パッケージのビルド
プロジェクトのルートディレクトリ(pyproject.tomlがある場所)で、以下のコマンドを実行してパッケージをビルドします。
python -m build
成功すると、プロジェクト内にdistというフォルダが作成され、その中に2つのファイルが生成されます。
- <package_name>-0.0.1-py3-none-any.whl (Wheel形式)
- <package_name>-0.0.1.tar.gz (ソースアーカイブ形式)
PyPIアカウントの作成
PyPIにパッケージをアップロードするためのアカウントが必要です。
練習用にTestPyPI、本番用にPyPIの2つのサイトでアカウントを作成すると良いらしい。
TestPyPIアカウント作成: https://test.pypi.org/account/register/
PyPIアカウント作成: https://pypi.org/account/register/
APIトークンの作成
セキュリティのため、パスワードの代わりにAPIトークンを使うことが強く推奨されています。
- TestPyPIにログインし、「Account settings」に移動
- 「API tokens」のセクションまでスクロールし、「Add API token」をクリック
- トークン名を入力し、「Scope」を「Entire account」にしてトークンを作成
- 表示されたトークン文字列(pypi-...)をコピーして、安全な場所に保存(一度しか表示されないので注意)
アップロードの実行
distフォルダ内のファイルをTestPyPIにアップロードします。
twine upload --repository testpypi dist/*
コマンドを実行すると、APIトークンの入力を求められるので貼り付けて実行します。
TestPyPIからのインストール確認
アップロードが成功したか、別の仮想環境などで以下のコマンドを実行してテストします。
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ <package_name>
このコマンドはpipに以下のように指示しています。
- まずreportlab-typesettingをTestPyPI(--index-url)で探す
- その依存関係(reportlabなど)もまずTestPyPIで探し、見つからなければ本番のPyPI(--extra-index-url)で探してインストールする
これにより、依存関係も自動でインストールされます。
requirement.txt で指定するには
# 1. パッケージをまずTestPyPIで探すように指示
--index-url https://test.pypi.org/simple/
# 2. TestPyPIに見つからない場合は、本番のPyPIを探すように指示
--extra-index-url https://pypi.org/simple/
# 3. TestPyPIからインストールしたいパッケージを記述
reportlab_typesetting==0.0.3
# 4. 他に入れたいパッケージは別に指定(TestPyPIにもある場合があるので注意)
requests
PyPI(本番)へのアップロード
TestPyPIでのテストが成功したら、いよいよ本番のPyPIにアップロードします。
本番のPyPIサイトでも同様にAPIトークンを作成し、コピーしておきます。
以下のコマンドを実行します。
twine upload dist/*
TestPyPIの時と同様に、本番PyPI用のAPIトークンを入力してアップロードします。
公開の確認
これでライブラリは世界中に公開されました。
誰でも以下のコマンドでインストールできるようになります!
pip install <package_name>
バージョンアップ
dist/ フォルダを削除しておく
rm -rf dist/
pyproject.tomlファイルを開き、versionを更新
[project]
name = "<package_name>"
version = "0.0.2" # <- 更新
buildコマンドで新しいバージョンのパッケージファイルを作成
python -m build
TestPyPIにアップロードして、動作確認
twine upload --repository testpypi dist/*
本番PyPIへアップロード
twine upload dist/*
気になったこと(随時更新)
対応バージョンってどう確認するの?
asdfで切り替え、venvで環境をたくさん試した(力技)
メールアドレスって公開される?
Githubから提供されている noreply メールアドレスを設定した
hyphenation は標準にあるのでは?
ParagraphStyle に hyphenationLang の設定はあって、htmlさえ頑張って作れればできそうではあったのですが、日本語の禁則処理の兼ね合いで文字・単語ごとにゴリ押しで描画する形にしています。
registerFontFamily は標準にあるよ?
cssのfont-familyとはちょっと違う考え方だと思ったので、別物として作りました。
太字とかイタリックとかできてないよ?
...がんばります(´・ω・`)
おわりに
パッケージ作成自体はかなり久しぶりで、全世界から見られるコードとなると、さすがに適当には書けないと思って作成したので、かなり良い経験になりました。(まだまだ雑かもしれません...閲覧するときはご注意を)
まだステータスはα版としていますし、日本語の組版なのでそんなに使われないだろうなと思っているので、細々と継続して改良していけたらいいなと思っております。




