PerfectVector
著者: Irene Kim2 分で読める

ReportLabとSVG:生成PDFでロゴのパスを鮮明に保つ

svglibでSVGロゴをReportLabのPDFへ配置し、比率を保持します。完成ファイルのベクターパス、サイズ、細部の欠落を確認します。

目次

svglib.svg2rlg()でSVGをReportLabのDrawingへ読み込み、renderPDF.draw()で配置します。ページ上に寸法を決めたボックスを用意し、両軸に1つの倍率を使います。適したロゴ形状を、ビットマップのスクリーンショットではなく、PDFの描画指示として保持できます。

文書レイアウトのない単独の書き出しには、CairoSVGのサイズ指定手順が、PNGのピクセル数、PDFページ寸法、DPIを比較しています。

役立つのは、完成したPDFのテストです。文書に必要なサイズで、エンブレムの比率、色、小さな穴が保たれたでしょうか。Pythonの呼び出し成功だけでは答えになりません。以下の例では、同じ独自エンブレムを2回配置し、生成ファイルを確認します。

編集可能なベクターロゴがあれば、それから始めます。PNGやJPGだけが残っているなら、文書を作る前にベクターマスターを復元します。写真や数値のグラフでは、元データについて別の判断が必要です。

画像をDrawingとして読み込む

svglibの文書化された手順は、SVGをReportLabの描画オブジェクトへ変換します。ReportLabのグラフィックスガイドは、それらをcanvasへ描画したり、Platypusのflowableとして使ったりできると説明しています。この例では、canvasで各配置に明示的な位置とサイズを与えます。

Python 3.12、svglib 2.2.0、ReportLab 4.4.9で実行しました。プロジェクト環境へ、固定した2つのパッケージをインストールします。

python -m pip install "svglib==2.2.0" "reportlab==4.4.9"

小さなエンブレムをlogo.svgとして保存します。単色の塗りの3本のパスがあり、リンクした画像、フォント、効果はありません。手書きのデモ画像で、PerfectVectorの変換結果ではありません。

<svg xmlns="http://www.w3.org/2000/svg"
     width="240" height="120" viewBox="0 0 240 120">
  <path fill="#1c625e" d="M0 120 L60 0 L120 120 Z"/>
  <path fill="#e5a340" d="M96 120 L156 24 L216 120 Z"/>
  <path fill="#163451" d="M228 12 L240 12 L240 108 L228 108 Z"/>
</svg>

SVGのviewBoxは内部座標の矩形を表します。読み込んだDrawingのwidthとheightが、PDF配置に使う寸法です。SVGの1ピクセルがPDFの1ポイントだと想定せず、値を読んでください。

このファイルでは、svglib 2.2.0は180 × 90ポイントを返しました。現在の単位のドキュメントは、1 CSSピクセルを0.75 PDFポイントに対応させます。古い例では前提が異なる場合があるので、既存の処理を更新するときは寸法を確認します。

ロゴを伸ばさずに収める

SVGの横にplace_logo.pyとして保存し、python place_logo.pyを実行します。すべてのページ位置とボックス寸法はPDFポイントです。1インチは72ポイントで、既定のcanvas原点は左下です。ReportLabのcanvasガイドが説明しています。

from reportlab.graphics import renderPDF
from reportlab.pdfgen import canvas
from svglib.svglib import svg2rlg
 
 
def draw_svg_in_box(pdf, drawing, x, y, box_w, box_h):
    if drawing.width <= 0 or drawing.height <= 0:
        raise ValueError("The SVG needs positive dimensions")
    factor = min(box_w / drawing.width, box_h / drawing.height)
    pdf.saveState()
    pdf.translate(x, y)
    pdf.scale(factor, factor)
    renderPDF.draw(drawing, pdf, 0, 0)
    pdf.restoreState()
 
 
drawing = svg2rlg("logo.svg")
if drawing is None:
    raise ValueError("SVG could not be loaded")
 
pdf = canvas.Canvas("logo.pdf", pagesize=(640, 360))
draw_svg_in_box(pdf, drawing, 40, 154, 144, 72)
draw_svg_in_box(pdf, drawing, 302, 126, 288, 144)
pdf.save()

min()が制限となる寸法を選ぶので、Drawingはボックス内に収まります。XとYに同じ倍率を使うと比率を保てます。この関数は、Drawingをボックスの左下に固定します。未使用の空間は上か右に残ります。レイアウトに必要なら、中央配置を別途行います。

canvasの変換は一時的です。restoreState()により、ロゴの倍率が後の文字やページ要素へ影響するのを防ぎ、元のDrawingは2回目の配置にも使えます。

テストでの倍率は0.8と1.6で、画像は144 × 72 ptと288 × 144 ptになりました。プレビューには、同じ2つの配置のサイズを比較しやすくするラベルと背景を加えています。

同じ緑と金色のエンブレムを144 × 72ポイントと288 × 144ポイントで配置したPDF描画
テストPDFを描画した画像です。3本の独自パスからなるロゴを2つのサイズで描いています。Web画像はラスタープレビューで、PDFの検査では描画パスがあり、埋め込み画像はありませんでした。

Platypusの文書では、Drawingをflowableとしてstoryへ加えられます。drawing.scale(factor, factor)で内容を拡大縮小した後は、レイアウトが新しい範囲を使うよう、drawing.widthとdrawing.heightも同じ倍率で更新します。異なるサイズを適用するときは新しいDrawingから作業します。上のcanvas例はDrawing自体を変更しません。

形状と見た目を別々に確認する

pypdf 6.10.0でテストPDFのコンテンツストリームを検査しました。3本のパスを2回描いたことに対応するパス開始操作が6つあり、ページ画像はゼロでした。また、Popplerでページ全体を描画し、2つのエンブレムを確認しました。形状、重なり、垂直の棒、比率は意図した配置に一致しました。

これらはこの単純な素材の観察結果です。パス数だけでは、別のSVGのすべての機能が正しく描画されたと証明できません。同様に、画面でPDFを拡大しても、ベクター指示か大きなビットマップかを確定できません。

自分の文書では、次を行います。

  1. ブラウザーやエディターのSVGと完成PDFを比較します。細部の欠落、塗りの変化、切れ、予期しない白い領域を探します。
  2. 細い隙間やストロークを読める必要がある、実際の文書サイズでエンブレムを確認します。
  3. ベクター画像が要件なら、PDFの画像オブジェクトを調べます。ページの他の場所にある意図した写真も考慮します。
  4. 文書生成コードとともにSVGマスターを保持し、画像や描画依存関係を変えた後に再確認します。

SVG自体にラスター画像がある場合があります。その入れ物を読み込んでもピクセルからパスは復元されません。埋め込み画像の診断は、ベクターマスターとビットマップを含むSVGを区別するのに役立ちます。

インポーターが再現できない機能を扱う

現在のsvglibの制限には、非対応のマスクやforeignObject、無視されるスタイルシートの@import、CSSやクリッピングの制限があります。そのため、ブラウザープレビューはPDFインポーターのテストの代わりにはなりません。

簡略化の前にマスターのコピーを保持します。ロゴが非対応の効果に依存する場合は、その部分を通常の形状で作り直すか、検証できる別の書き出し方法を検討します。写真の見た目がパス編集より重要なら、元のラスターを保持します。平坦化した画像は見た目を保持できますが、ベクターの要件は変わります。

文字では、文書のテキストとアウトライン画像を意図して選びます。文字をテキストにすると編集と検索ができますが、文書の処理で意図したフォントが使える必要があります。承認済みロゴの文字をアウトライン化すると、形状を保持する一方で通常の文字編集を失います。今回のパスだけのエンブレムはフォント処理をテストしていないので、同じ処理に頼る前に実際の文字のサンプルを使います。

Pythonではなく組版システムで文書を組むなら、LaTeXでSVGを使う手順が、別のグラフィックスとラベルの方法を説明しています。

PDF配置の前にラスターだけのロゴを復元する

請求書テンプレートの古いPNGは、元ロゴがない場合にベクター復元の適した候補になります。PerfectVectorのロゴベクター化ツールでラスター画像をSVGとしてプレビューし、輪郭と穴を確認して、文書処理用の候補をダウンロードします。

読み込み前に小さな文字と縁を確認します。正確な文字組みが必要な部分は描き直すか組み直し、エディターで不要な背景形状を取り除きます。ロゴのベクター化ガイドは、元データからマスターへの判断を詳しく扱っています。

その後、実際のReportLabの読み込みと上のPDF確認を行います。良いトレースでも、インポーターで正しく扱え、文書に収まる必要があります。PerfectVectorは画像のパスを提供しますが、ReportLabの座標を選んだり、文書フォントを登録したり、結果のPDFを検証したりはしません。グラフはラベルや値をトレースせず、データと描画元から再生成します。

よくある質問

SVGを通常のビットマップ画像としてReportLabへ渡せますか? このベクターの手順では、svglibでDrawingを作り、renderPDFで描画します。先にSVGをPNGへ変換すると、ラスターとして挿入されます。

読み込んだSVGが古い例より小さいのはなぜですか? テストしたsvglib 2.2.0は、CSSピクセルを0.75倍でPDFポイントへ対応させます。古いピクセル対ポイントの前提に頼らず、返されたDrawing寸法を読み、明示したボックスへ収めます。

すべてのSVG効果が保持されますか? いいえ。マスクやforeignObjectなど、文書化された機能制限があります。スクリプトがエラーなく終わっても、書き出したPDFを意図した画像と比較します。

グラフのスクリーンショットをベクター化するべきですか? 可能ならデータと描画元から再生成します。ロゴのトレースは、編集可能な元データがない、適した装飾画像に使います。

参考資料

  1. svglib — パッケージと単位のドキュメント — SVGからDrawingへの変換、インストール、PDFポイントのサイズ。
  2. ReportLab — グラフィックス — Drawingオブジェクト、PDF描画、Platypusのflowableとしての利用。
  3. ReportLab — pdfgenによるグラフィックスと文字 — Canvas座標、ポイント、変換。
  4. svglib — 既知の制限 — 完成PDFの確認が必要な読み込み制限。

編集可能な元データがあれば保持します。ラスターのロゴしか残っていないなら、SVGロゴの候補を準備し、パスを確認してから必要なサイズで配置し、完成PDFを確認してください。

ブログのその他の記事

編集しやすい
よりきれいなSVGから始めましょう