1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでPDFに透かし(Watermark)を追加する

Last updated at Posted at 2025-07-11

はじめに

PythonでPDFに透かしを追加しようとしたら、かなりはまったが最終的にうまくいったのでメモ。
特に以下の点を実現しようとするとなかなか思い通りにできなかった。

  • なるべく一般的な(と思われる)Pythonのライブラリを使いたい。
  • 透かしの文字は透明にして、斜めに配置したい。

処理の流れ

  • ReportLabを使って透かしのPDFを作成する。
    • pypdfでこの部分もできれば良かったが、文字を透明にしたり斜めに配置する方法が見つけられなかった。
  • pypdfで元のPDFを読み込み、透かしPDFをマージして新たなPDFを作成する。

ReportLabはReportLab社が開発したライブラリだが、有償のReportLab PlusとオープンソースのReportLabがある。ここではオープンソース版だけを利用する。

pypdfはPDFを操作するための、オープンソースのライブラリ。
pypdf2, pypdf3, pypdf4などのバリエーションがあるが、現在はオリジナルのpypdfに取り込まれた様子。https://pypdf.readthedocs.io/en/latest/meta/history.html

手順

ReportLabとpypdfのインストール

pip install reportlab
pip install pypdf

ReportLabで透かしPDFの作成

from reportlab.pdfgen import canvas
import io
buf = io.BytesIO()
can = canvas.Canvas(buf, (width, height))
# can = canvas.Canvas('hoge.pdf', (width, height))

canvasを生成する。

  • ReportLabでは白紙ページの「canvas」を用意して、文字や図を入力してPDFのページを作成していく。
  • 透かしPDFは一時的にしか使わないので、canvasの保存先には io.BytesIO のバッファ(buf)を指定しておく。
  • canvasのサイズは幅と高さを「pt=1/72インチ」で指定する。
    • 透かしを追加したいPDFをAcrobat readerなどで開いて、ドキュメントのプロパティーで「13.33 x 7.50 in」のような値を確認して72倍した値(960, 540)。
    • ドキュメントのプロパティーの値がmmの単位であれば72/25.4倍。
can.rotate(30) # 30度傾ける
can.setFillColorRGB(1, 0, 0) # 真っ赤
can.setFillAlpha(0.3) # 0~1の範囲、0だと完全に透明
can.setFont('Helvetica-Bold', 48) # フォント名とサイズ
can.drawCentredString(width/2, height/2, 'Confidential document NDA only') # 透かし文字の位置
can.showPage() # ページ入力の終了
can.save() # canvasの保存

次に透かしのテキストを作成する。

buf.seek(0)
from pypdf import PdfWriter
writer = PdfWriter(buf)
writer.write('hoge.pdf')

ここで念のため透かしPDFが正しく作られているか確認する。

  • bufをシークしてページ0に戻す。
  • pypdfからPdfWriterをインポートし、作成したwriterオブジェクトにbufを渡す。
  • writerオブジェクトのwriteメソッドでbufをPDFファイルとして出力できる。

結果。。。中心にテキストを置きたかったが、canvasを30度傾けたせいでずれてしまったらしい。
hoge1.png

-30度分テキストの座標を移動させればよいので。

from reportlab.pdfgen import canvas
import io
import math

width, height = (960, 540)
rotate = 30
cos = math.cos(math.radians(-1 * rotate))
sin = math.sin(math.radians(-1 * rotate))
x, y = (width * 0.5, height * 0.5)
x, y = (x * cos - y * sin, x * sin + y * cos) # -30度の座標変換

buf = io.BytesIO()
can = canvas.Canvas(buf, (width, height))
can.rotate(rotate) # 30度傾ける
can.setFillColorRGB(1, 0, 0) # 真っ赤
can.setFillAlpha(0.3) # 0~1の範囲、0だと完全に透明
can.setFont('Helvetica-Bold', 48) # フォント名とサイズ
can.drawCentredString(x, y, 'Confidential document NDA only') # 透かし文字の位置
can.showPage() # ページ入力の終了
can.save() # canvasの保存
buf.seek(0)

from pypdf import PdfWriter
writer = PdfWriter(buf)
writer.write('hoge.pdf')

できた。。
hoge2.png

透かしPDFを元のPDFにマージ

from pypdf import PdfReader
from pypdf import PdfWriter
reader = PdfReader(buf)
wm_page = reader.pages[0]
writer = PdfWriter(clone_from='original.pdf')
for page in writer.pages:
    page.merge_page(wm_page, over=True)
writer.write('watermarked.pdf')
  • pypdfからPdfReaderとPdfWriterをインポート
  • PdfReaderのオブジェクト(reader)にbufを渡し、1ページ目を透かしとして使う。
  • PdfWriterのオブジェクト(writer)を元のPDFファイルのクローンで作成する。
  • writerの各ページに、readerの1ページ目(透かし)をマージしていく。
    • over=Trueは透かしを元のPDFの上に重ねる設定。Falseにすると下に重ねられる。
    • 本当は上に重ねるのはStamp、下に重ねるのをWatermarkと呼ぶらしい。
  • watermarked.pdfに出力する。

完成!
watermarked-1.png
watermarked-2.png

最終的なコード

from pypdf import PdfReader
from pypdf import PdfWriter

def watermark_page(text, color, alpha, font, width, height, rotate):
    from reportlab.pdfgen import canvas
    import io
    import math
    font_size = width / len(text) * 1.7
    cos = math.cos(math.radians(-1 * rotate))
    sin = math.sin(math.radians(-1 * rotate))
    x, y = (width * 0.5, height * 0.5)
    x, y = (x * cos - y * sin, x * sin + y * cos)
    buf = io.BytesIO()
    can = canvas.Canvas(buf, (width, height))
    can.rotate(rotate)
    can.setFillColorRGB(*color)
    can.setFillAlpha(alpha)
    can.setFont(font, font_size)
    can.drawCentredString(x, y, text)
    can.showPage()
    can.save()
    buf.seek(0)
    return PdfReader(buf).pages[0]

if __name__ == '__main__':
    file    = 'original.pdf'
    wm_file = file.replace('.pdf', '_wm.pdf')
    writer = PdfWriter(clone_from=file)
    text   = 'Confidential document NDA only'
    color  = (1, 0, 0)
    alpha  = 0.3
    font   = 'Helvetica-Bold'
    width  = writer.pages[0].mediabox.width
    height = writer.pages[0].mediabox.height
    rotate = 30
    wm_page = watermark_page(text, color, alpha, font, width, height, rotate)
    for page in writer.pages:
        page.merge_page(wm_page, over=True)
    writer.write(wm_file)
  • 元のPDFのサイズはmediabox.width、mediabox.heightで取得できる。
  • フォントサイズは透かしの文字数に合わせて適当に調整。使うフォントによっては大きすぎるかも。
  • 透かしの文字数が多い場合は2行にしたかったが今後の課題。

書き終わってみると、これだけのコードだが素人プログラマにとっては大変でした。
Streamlitからファイルや設定の入力ができるようにしたので、いずれ公開します。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?