PerfectVector
著者: Irene Kim2 分で読める

CairoSVG:SVGを適切なサイズのPDF・PNGに書き出す

CairoSVGでSVGを変換し、出力幅や倍率を選びます。DPIと親要素の寸法を理解し、納品前にPNGのピクセル数とPDFのページ寸法を検証します。

目次

ラスター出力にはcairosvg.svg2png()、PDFにはcairosvg.svg2pdf()を使います。PNGの幅を指定するにはoutput_width、比率を保って拡大するにはscaleを使います。parent_widthは、パーセント指定の寸法を算出するコンテナーを提供します。dpiはピクセルと物理単位の関係を制御するため、変更するとPNGとPDFのサイズに異なる影響を与える場合があります。

以下の例では、小さなSVGのエンブレムを使い、各設定で生成されるファイルを確認します。ロゴの書き出しを自動化するデザイナーや、文書用の画像を準備する開発者に適しています。CairoSVGは既存のSVG内容を描画します。PNGのロゴを編集可能なパスへトレースする機能ではありません。

自己完結したSVGから始める

この例はPython 3.12.11とCairoSVG 2.9.1で実行しました。プロジェクトの環境に、バージョンを固定した変換ツールをインストールします。

python -m pip install "CairoSVG==2.9.1"

CairoSVGには、ネイティブのCairoライブラリも必要です。インストールやインポートが失敗する場合は、インストールのドキュメントにあるOS別の説明に従ってください。

3本のパスで作った次の独自エンブレムをemblem.svgとして保存します。四角い開口部は塗りの規則の欠落を見つけやすくし、細い棒は細部の確認点になります。手書きのデモ画像であり、PerfectVectorで変換したものではありません。

<svg xmlns="http://www.w3.org/2000/svg"
     width="240" height="120" viewBox="0 0 240 120">
  <path fill="#176b63" fill-rule="evenodd"
        d="M20 100 V20 H100 V100 Z M40 40 V80 H80 V40 Z"/>
  <path fill="#e5a43a" d="M110 100 L160 20 L210 100 Z"/>
  <path fill="#24435d" d="M220 20 H230 V100 H220 Z"/>
</svg>

ルートの寸法は出力ビューポートを表し、viewBoxは画像内部の座標矩形を表します。ここでは両方とも縦横比が2:1です。自分のロゴに例を合わせる場合も、この関係を意図して設定してください。

1つのマスターを必要なサイズで書き出す

SVGの横にexport_emblem.pyとして保存し、python export_emblem.pyを実行します。

import cairosvg
 
cairosvg.svg2png(
    url="emblem.svg",
    write_to="emblem-480.png",
    output_width=480,
)
cairosvg.svg2pdf(
    url="emblem.svg",
    write_to="emblem.pdf",
)

今回のPNGは480 × 240ピクセルでした。PDFのページは180 × 90ポイントで、2.5 × 1.25インチに相当しました。これは用意した素材の測定結果であり、どのSVGでも同じ寸法になるわけではありません。

コマンドラインでの同等の操作は次のとおりです。

cairosvg emblem.svg --output-width 480 -o emblem-480.png
cairosvg emblem.svg -o emblem.pdf

CairoSVGのAPIとコマンドラインのリファレンスでは、urlによる入力ファイル名、bytestringによるSVGのバイト列、write_toによるファイル出力を説明しています。CLIでは出力ファイル名の拡張子が形式を選びます。

1つの幾何学的なエンブレムから、異なるサイズのキャンバス上に同じ比率の2つの出力を作る図
書き出し時の判断の図解です。ベクターのマスターを保持し、納品先ごとに必要な寸法を選びます。CairoSVGで測定した結果は以下に示します。

問題に合った設定を選ぶ

似た名前のオプションは、それぞれ異なる問題に対応します。CairoSVGのサイズ指定リファレンスでは、親コンテナーと要求する出力寸法を区別しています。

要件PythonのオプションCLIのオプション確認点
PNGの幅を正確に480ピクセルにするoutput_width=480--output-width 480高さと画像の比率
算出された出力寸法を2倍にするscale=2--scale 2両方の寸法が2倍か
パーセントで指定したルートの幅や高さを算出するparent_width, parent_height--width, --height指定したコンテナーに対する割合
インチなどの物理単位を解釈するdpi--dpiPNGのピクセル寸法とPDFの実寸を別々に確認

ピクセル寸法の元データで、各オプションを独立して実行しました。

設定PNGの寸法PDFのページ寸法
既定値240 × 120 px180 × 90 pt
output_width=480480 × 240 px360 × 180 pt
scale=2480 × 240 px360 × 180 pt
dpi=192240 × 120 px90 × 45 pt

最後の行が注意点です。DPIを2倍にしても、このピクセル寸法のSVGではPNGのピクセル数は増えませんでした。PDFでは、同じピクセル寸法がより小さな実寸になりました。納品要件がピクセル数の場合は、出力寸法を使ってください。

両方の出力寸法を指定するとキャンバスの矩形が決まりますが、ロゴが隙間なく詰まるとは限りません。SVGの縦横比の規則やviewBox内の空白も影響します。元の比率を保ちたい場合は、1つの出力寸法から始め、結果を確認してください。

パーセント寸法には親の寸法が必要

ルートにwidth="50%" height="50%"を使った素材のコピーでは、次の呼び出しで960 × 480ピクセルの親を指定します。

cairosvg.svg2png(
    url="emblem-percent.svg",
    parent_width=960,
    parent_height=480,
    write_to="emblem-percent.png",
)

出力は480 × 240ピクセルとなり、親の幅と高さの半分でした。これが、parent_width=480とoutput_width=480を置き換えられない理由です。

SVGのサイズが未定義だとCairoSVGが報告する場合は、まずルートの寸法を確認します。用途に合う正の寸法を文書に与えるか、パーセント指定が親に依存する場合は親の寸法を指定します。ブラウザーでたまたま大きく表示されたことから、意図したページサイズを推測しないでください。

物理寸法では挙動が異なる

元のルートをwidth="2.5in" height="1.25in"に変更し、viewBoxを保持します。テストでは、dpi=96で240 × 120のPNG、dpi=192で480 × 240のPNGが生成されました。PDFはどちらも180 × 90ポイントのままでした。

ここでは元データが実寸を定義しているため、1インチあたりのピクセル数を増やすと、PDFの物理ページサイズを保持しながらラスターの解像度が上がります。同じ原理で、意図して作成したページにミリメートルやインチを使えます。ピクセル数が大きいからA4やレターサイズになると想定せず、書き出したPDFのページボックスを確認します。

文字、余白、複数の要素があるレポート内へロゴを配置する場合は、文書レイアウトの工程を使います。ReportLabのSVG配置ガイドではPDFを直接構築する方法を説明し、WeasyPrintのリソースとサイズの確認ではHTML文書を扱っています。

ベクターのまま残った部分も含めて書き出しを検証する

PillowでPNGのヘッダーを読み、pypdf 6.10.0でPDFのページボックスとコンテンツストリームを読みました。ピクセル寸法の元データから作った4つのPDFは、すべて描画パスの操作を含み、ページ画像はゼロでした。これは、画像を含まないこの単純な素材でベクター形状が保持されたことを確認するものです。任意のSVGの完全な再現性を証明するものではありません。

自分の出力寸法を確認するには、同じ環境へpillowとpypdfをインストールし、次を実行します。

from PIL import Image
from pypdf import PdfReader
 
with Image.open("emblem-480.png") as image:
    print("PNG pixels:", image.size)
 
page = PdfReader("emblem.pdf").pages[0]
print("PDF points:", float(page.mediabox.width),
      float(page.mediabox.height))
print("Page images:", len(page.images))

PageObjectリファレンスではmediaboxとimagesを説明しています。画像数は役立つ診断情報ですが、すべての形状、フォント、効果が正しく残ったことは証明できません。

納品前に、次を確認します。

  1. PNGとPDFを開き、SVGとエンブレムを比較します。開口部、三角形、棒、余白を確認します。
  2. PNGのピクセル寸法とPDFの実寸を独立して検証します。
  3. 細い隙間や小さな細部も含め、実際に使うサイズで画像を確認します。
  4. 元のSVGを保持し、画像の変更や変換ツールの更新後に、これらの確認をやり直します。

CairoSVGのSVG対応ページでは、可能な場合にベクター出力を保持すると説明しています。埋め込まれたラスター画像は、ラスターのままです。SVGの入れ物にPNGが入っている場合は、埋め込み画像の診断で書き出し前に確認できます。

CairoSVGは静的なレンダラーです。アニメーション、スクリプト、高度なテキストレイアウト、多くのフィルターは対応する手順の範囲外です。ドキュメントにある対応フィルターはfeOffset、feBlend、feFloodだけです。他の効果に依存する画像は、結果を慎重に比較するか、それらの機能に対応したアプリから書き出します。変換呼び出しが成功しただけでは十分ではありません。

元データに必要な場合だけベクターマスターを復元する

ロゴがPNGやJPGでしか残っていない場合は、CairoSVGで書き出す前段階として、PerfectVectorでラスターをSVGへ変換できます。エディターで復元した形状、小さな開口部、ブランドの文字を確認し、自己完結したSVGのマスターを保存します。ロゴのベクター化手順では、その確認を説明しています。

編集可能なSVGがすでにあれば、直接書き出しに進めます。PNGに変換して再びトレースすると、有用な元の形状が失われます。写真は、意図する内容がラスターなら、そのままラスター画像として保持できます。

ラスターしかないロゴでは、PerfectVectorで自分の画像を試し、SVGの輪郭と開口部を確認してから、CairoSVGの出力のピクセル寸法と物理ページサイズを両方検証してください。

よくある質問

DPIを変えてもPNGのサイズが同じなのはなぜですか? SVGのルートがピクセル寸法なら、DPIを変えてもPNGのピクセル数が変わるとは限りません。この例では、240 × 120ピクセルのSVGは192 DPIでも同じサイズでした。出力ピクセル数を増やすにはoutput_widthまたはscaleを使います。

parent widthはoutput widthと同じですか? いいえ。parent_widthはパーセント寸法を算出するコンテナーを提供し、output_widthは最終出力幅を要求します。この例では、ルートの幅が50%でparent_widthが960の場合、480ピクセルと算出されました。

CairoSVGは必ず完全なベクターPDFを作りますか? いいえ。CairoSVGは可能な場合にベクター出力を保持しますが、SVGにラスター画像や非対応の機能が含まれる場合があります。拡張子を証拠とせず、PDFの内容と見た目を確認してください。

参考資料

  1. CairoSVGドキュメント — インストール、変換API、サイズ制御、描画の制限を説明しています。
  2. CairoSVGのSVG 1.1対応 — ベクター保持、埋め込み画像、非対応の機能を説明しています。
  3. pypdf PageObjectリファレンス — PDF確認に使うページボックスと画像のインターフェースを説明しています。

ブログのその他の記事

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