2026/05/17

PDF透かし(ウォーターマーク)をAPIで実装する完全ガイド

pdfwatermarkapihtmlcss

PDFに「社外秘」「DRAFT」「SAMPLE」などの透かし(ウォーターマーク)を入れたい場面は多くあります。既存のPDFを加工するアプローチもありますが、HTML/CSSで透かしを含むテンプレートを作り、最初からPDFとして生成する方法が最もシンプルで柔軟です。

この記事では、FUNBREW PDF APIを使ってHTML/CSSベースの透かしを実装する方法を、curl・JavaScript・Pythonの実装例とともに解説します。テキスト透かし・画像透かし・全ページ繰り返しの3パターンをカバーします。

HTML/CSSによるPDF生成の基礎はHTML→PDF完全ガイドを、CSSのページ設定は@pageルールガイドを参照してください。

HTML/CSSで透かしを実装する仕組み

FUNBREW PDF APIはChromiumエンジンでHTMLをレンダリングしてPDFを生成します。つまり、HTMLとCSSに透かしの定義を含めれば、API呼び出しだけで透かし入りPDFが完成します。

  • 外部ツールや後処理が不要
  • 透かしのデザイン・位置・透明度をCSSで自由に制御
  • 全ページに一括適用できる
  • テキストでも画像でも対応可能

パターン1: テキスト透かし(基本)

最もシンプルなアプローチです。position: fixed で透かしを固定位置に配置し、opacity で半透明にします。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    /* ===== 透かしの定義 ===== */
    .watermark {
      position: fixed;        /* スクロールに追従しない固定位置 */
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(-45deg); /* 中央配置 + 斜め45度 */
      font-size: 80px;
      font-weight: bold;
      color: rgba(200, 0, 0, 0.15); /* 赤色・15%不透明度 */
      white-space: nowrap;
      pointer-events: none;   /* クリックを透過 */
      z-index: 9999;          /* 最前面に表示 */
      user-select: none;
    }

    /* ===== 本文のスタイル ===== */
    body {
      font-family: 'Noto Sans JP', sans-serif;
      padding: 40px;
      color: #1a1a1a;
    }
  </style>
</head>
<body>
  <!-- 透かしレイヤー(bodyの直下に配置) -->
  <div class="watermark">社外秘</div>

  <!-- 本文コンテンツ -->
  <h1>月次売上レポート</h1>
  <p>2026年5月度の売上データをまとめました。</p>
  <!-- ... 本文 ... -->
</body>
</html>

position: fixed はビューポート(印刷領域)に対して固定されるため、複数ページにまたがるドキュメントでも全ページに同じ透かしが自動的に表示されます。

パターン2: 繰り返しテキスト透かし(タイル状)

ページ全体にタイル状に透かしを敷き詰めるパターンです。重要文書によく使われます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark-layer {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 9999;
      pointer-events: none;

      /* グリッドで繰り返し配置 */
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(5, 1fr);
      align-items: center;
      justify-items: center;
    }

    .watermark-item {
      font-size: 28px;
      font-weight: bold;
      color: rgba(150, 150, 150, 0.12);
      transform: rotate(-30deg);
      white-space: nowrap;
      user-select: none;
    }

    body {
      font-family: 'Noto Sans JP', sans-serif;
      padding: 40px;
    }
  </style>
</head>
<body>
  <!-- タイル状透かし: 3×5 = 15個配置 -->
  <div class="watermark-layer">
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
  </div>

  <h1>機密文書</h1>
  <p>本書は社外秘です。無断複製・転載を禁じます。</p>
</body>
</html>

パターン3: 画像透かし

ロゴや印鑑画像を透かしとして使うパターンです。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark-image {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 300px;
      height: auto;
      opacity: 0.08;           /* 8%不透明度で薄く表示 */
      z-index: 9999;
      pointer-events: none;
      user-select: none;
    }

    body {
      font-family: 'Noto Sans JP', sans-serif;
      padding: 40px;
    }
  </style>
</head>
<body>
  <!-- Base64エンコードした画像またはURL指定 -->
  <img
    class="watermark-image"
    src="https://example.com/logo-watermark.png"
    alt=""
  >

  <h1>契約書</h1>
  <p>本契約書の内容に同意の上、署名してください。</p>
</body>
</html>

画像をBase64エンコードしてインライン埋め込みにすると、外部URLへの依存がなくなり確実に表示されます。

APIでの実装例

curl

# テキスト透かし入りPDFを生成
curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/from-html \
  -H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<!DOCTYPE html><html><head><style>.watermark{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-45deg);font-size:80px;font-weight:bold;color:rgba(200,0,0,0.15);z-index:9999;pointer-events:none;}</style></head><body><div class=\"watermark\">社外秘</div><h1>レポート</h1><p>内容</p></body></html>",
    "format": "A4",
    "engine": "quality"
  }' \
  --output report-watermark.pdf

JavaScript (Node.js / fetch)

const fs = require('fs');

const watermarkHTML = `
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(-45deg);
      font-size: 80px;
      font-weight: bold;
      color: rgba(200, 0, 0, 0.15);
      white-space: nowrap;
      pointer-events: none;
      z-index: 9999;
      user-select: none;
    }
    body {
      font-family: sans-serif;
      padding: 40px;
    }
  </style>
</head>
<body>
  <div class="watermark">DRAFT</div>
  <h1>提案書</h1>
  <p>このドキュメントはドラフト版です。</p>
</body>
</html>
`;

async function generateWatermarkedPDF(html, outputPath) {
  const response = await fetch('https://pdf.funbrew.cloud/api/v1/pdf/from-html', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.FUNBREW_PDF_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html,
      format: 'A4',
      engine: 'quality',
    }),
  });

  if (!response.ok) {
    throw new Error(`PDF generation failed: ${response.status}`);
  }

  const buffer = await response.arrayBuffer();
  fs.writeFileSync(outputPath, Buffer.from(buffer));
  console.log(`Generated: ${outputPath}`);
}

generateWatermarkedPDF(watermarkHTML, 'draft-proposal.pdf');

Python (requests)

import os
import requests

FUNBREW_API_KEY = os.environ["FUNBREW_PDF_API_KEY"]
API_URL = "https://pdf.funbrew.cloud/api/v1/pdf/from-html"

def build_watermark_html(content_html: str, watermark_text: str = "社外秘") -> str:
    """コンテンツHTMLに透かしを追加したHTMLを生成する"""
    return f"""<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark {{
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(-45deg);
      font-size: 80px;
      font-weight: bold;
      color: rgba(200, 0, 0, 0.15);
      white-space: nowrap;
      pointer-events: none;
      z-index: 9999;
      user-select: none;
    }}
    body {{
      font-family: 'Noto Sans JP', sans-serif;
      padding: 40px;
      color: #1a1a1a;
    }}
  </style>
</head>
<body>
  <div class="watermark">{watermark_text}</div>
  {content_html}
</body>
</html>"""


def generate_watermarked_pdf(content_html: str, watermark_text: str = "社外秘") -> bytes:
    """透かし入りPDFを生成してバイナリを返す"""
    html = build_watermark_html(content_html, watermark_text)

    response = requests.post(
        API_URL,
        json={"html": html, "format": "A4", "engine": "quality"},
        headers={
            "Authorization": f"Bearer {FUNBREW_API_KEY}",
            "Content-Type": "application/json",
        },
        timeout=60,
    )
    response.raise_for_status()
    return response.content


# 使用例
content = """
<h1>月次売上レポート</h1>
<p>2026年5月度の売上データをまとめました。</p>
<table>
  <thead><tr><th>商品</th><th>売上</th></tr></thead>
  <tbody>
    <tr><td>プランA</td><td>¥1,200,000</td></tr>
    <tr><td>プランB</td><td>¥800,000</td></tr>
  </tbody>
</table>
"""

pdf_bytes = generate_watermarked_pdf(content, "社外秘")
with open("report-confidential.pdf", "wb") as f:
    f.write(pdf_bytes)

動的な透かし文字列

ユーザー名や日時を透かしに含めると、不正コピーの抑止力が上がります。

from datetime import datetime

def generate_personalized_pdf(content_html: str, user_name: str) -> bytes:
    """ユーザー名と日時入りの透かしPDFを生成する"""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
    watermark_text = f"{user_name} | {timestamp}"

    return generate_watermarked_pdf(content_html, watermark_text)
// Node.js版
function buildPersonalizedWatermark(userName) {
  const timestamp = new Date().toLocaleString('ja-JP', {
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit',
  });
  return `${userName} | ${timestamp}`;
}

@pageルールと組み合わせた全ページ対応

複数ページのPDFで特定ページだけ透かしを入れる場合は、@page ルールと position: fixed を組み合わせます。

/* 全ページに透かしを表示(position: fixed を使う) */
.watermark {
  position: fixed;    /* 印刷時は全ページに繰り返される */
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-45deg);
  font-size: 80px;
  color: rgba(200, 0, 0, 0.12);
  z-index: 9999;
}

/* ページ余白の設定 */
@page {
  size: A4;
  margin: 20mm 15mm;
}

position: fixed は印刷時に各ページの同じ位置に繰り返し描画されます。これが全ページ透かしの核心です。

@page ルールの詳細な使い方は@pageルールガイドを参照してください。

Playground で試す

透かしのデザインを実際に確認してから実装したい場合は、PlaygroundでHTMLを貼り付けてリアルタイムにプレビューできます。opacity・font-size・rotate の値を変えながら見た目を調整してから、APIに組み込むとスムーズです。

透かし付きPDFのユースケースとして多い請求書自動生成は請求書PDF自動化ガイドも参考にしてください。

よくある問題と対処法

透かしが薄すぎる・濃すぎる

colorrgba の第4引数(アルファ値)で調整します。

  • rgba(200, 0, 0, 0.08) — 薄め(背景感覚)
  • rgba(200, 0, 0, 0.15) — 標準
  • rgba(200, 0, 0, 0.25) — 濃め(判読を妨げたい場合)

透かしが特定のページにしか表示されない

position: absolute ではなく position: fixed を使ってください。absolute はドキュメントの絶対位置で、最初のページにしか表示されません。

本文テキストが透かしで隠れる

透かしを半透明(opacity < 0.3)にすることで可読性を保ちつつ存在感を出せます。また、透かしは z-index: 9999 で最前面ですが、テキスト自体の可読性はフォントサイズと行間で確保します。

日本語フォントが表示されない

日本語の透かしテキストを使う場合は、HTMLの <head> でGoogle Fontsなどを読み込んでください。

<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@700&display=swap" rel="stylesheet">

日本語フォントの詳細は日本語フォントガイドを参照してください。

まとめ

FUNBREW PDF APIとHTML/CSSを組み合わせることで、専用の透かしライブラリなしに、柔軟な透かしPDFを生成できます。

  • position: fixed + opacity + transform: rotate() で全ページ透かしを実現
  • テキスト・画像・タイル状など用途に応じてデザインを選択
  • ユーザー名・日時を動的に埋め込んで追跡可能な透かしにすることも可能

APIの全パラメーターはドキュメントで確認できます。PlaygroundでHTMLを試してから実装に移るとスムーズです。

Powered by FUNBREW PDF