cairosvg converts SVG to PNG, PDF, and PS using Cairo. pip install cairosvg. PNG bytes: import cairosvg; png = cairosvg.svg2png(url="file.svg"). From string: cairosvg.svg2png(bytestring=svg_str.encode()). To file: cairosvg.svg2png(url="in.svg", write_to="out.png"). Scale: cairosvg.svg2png(url="in.svg", scale=2.0) — 2× resolution. DPI: cairosvg.svg2png(url="in.svg", dpi=150). Size override: cairosvg.svg2png(url="in.svg", output_width=400, output_height=300). Background: cairosvg.svg2png(bytestring=svg, background_color="white"). PDF: cairosvg.svg2pdf(url="in.svg", write_to="out.pdf"). PDF bytes: pdf = cairosvg.svg2pdf(bytestring=svg). PS: cairosvg.svg2ps(url="in.svg", write_to="out.ps"). Unsafe: cairosvg.svg2png(url="in.svg", unsafe=True) — allow external resources. Pillow: from PIL import Image; import io; img = Image.open(io.BytesIO(cairosvg.svg2png(bytestring=svg))). DPI tuple: images at 300 DPI for print. Claude Code generates cairosvg SVG thumbnail pipelines, badge rasterizers, and chart-to-PNG exporters.
CLAUDE.md for cairosvg
## cairosvg Stack
- Version: cairosvg >= 2.7 | pip install cairosvg
- PNG: cairosvg.svg2png(bytestring=svg_bytes, scale=2.0) → bytes
- PDF: cairosvg.svg2pdf(bytestring=svg_bytes) → bytes
- File: cairosvg.svg2png(url="in.svg", write_to="out.png")
- Size: output_width=400, output_height=300 override viewBox
- Background: background_color="white" fills transparent SVG
cairosvg SVG Conversion Pipeline
# app/svg_convert.py — cairosvg SVG→PNG/PDF, scale, thumbnail, Pillow, batch
from __future__ import annotations
import io
from pathlib import Path
from typing import Literal
import cairosvg
# ─────────────────────────────────────────────────────────────────────────────
# 1. Core conversion helpers
# ─────────────────────────────────────────────────────────────────────────────
Format = Literal["png", "pdf", "ps"]
_CONVERTERS = {
"png": cairosvg.svg2png,
"pdf": cairosvg.svg2pdf,
"ps": cairosvg.svg2ps,
}
def svg_to_bytes(
svg: str | bytes,
fmt: Format = "png",
scale: float = 1.0,
dpi: int | None = None,
output_width: int | None = None,
output_height: int | None = None,
background_color: str | None = None,
unsafe: bool = False,
) -> bytes:
"""
Convert an SVG string or bytes to PNG/PDF/PS bytes.
svg: SVG source as str or bytes.
fmt: "png", "pdf", or "ps".
scale: multiplier on the SVG's natural size (2.0 = 2×).
dpi: dots per inch (overrides scale for raster output).
output_width/height: force final pixel dimensions.
background_color: fill transparent areas (e.g. "white", "#fff").
unsafe: allow loading external resources (file:// / http://).
Example:
png = svg_to_bytes("<svg .../>", fmt="png", scale=2.0)
"""
bytestring = svg.encode() if isinstance(svg, str) else svg
converter = _CONVERTERS[fmt]
kwargs: dict = {"bytestring": bytestring, "unsafe": unsafe}
if scale != 1.0: kwargs["scale"] = scale
if dpi is not None: kwargs["dpi"] = dpi
if output_width is not None: kwargs["output_width"] = output_width
if output_height is not None: kwargs["output_height"] = output_height
if background_color is not None: kwargs["background_color"] = background_color
result = converter(**kwargs)
return result if isinstance(result, bytes) else b""
def svg_file_to_bytes(
input_path: str | Path,
fmt: Format = "png",
scale: float = 1.0,
dpi: int | None = None,
output_width: int | None = None,
output_height: int | None = None,
background_color: str | None = None,
) -> bytes:
"""Convert an SVG file to PNG/PDF/PS bytes."""
return svg_to_bytes(
Path(input_path).read_bytes(),
fmt=fmt,
scale=scale,
dpi=dpi,
output_width=output_width,
output_height=output_height,
background_color=background_color,
)
def convert_file(
input_path: str | Path,
output_path: str | Path,
fmt: Format | None = None,
scale: float = 1.0,
dpi: int | None = None,
background_color: str | None = None,
) -> int:
"""
Convert input SVG file to output file.
fmt: inferred from output_path extension if None.
Returns byte size of output file.
"""
outp = Path(output_path)
resolved_fmt: Format = fmt or outp.suffix.lstrip(".").lower() # type: ignore[assignment]
data = svg_file_to_bytes(
input_path,
fmt=resolved_fmt,
scale=scale,
dpi=dpi,
background_color=background_color,
)
outp.write_bytes(data)
return len(data)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Thumbnail generator
# ─────────────────────────────────────────────────────────────────────────────
def svg_thumbnail(
svg: str | bytes,
width: int = 128,
height: int = 128,
background_color: str = "white",
) -> bytes:
"""
Rasterize an SVG to a fixed-size PNG thumbnail.
Adjusts output dimensions while preserving aspect ratio if only width given.
"""
return svg_to_bytes(
svg,
fmt="png",
output_width=width,
output_height=height,
background_color=background_color,
)
def svg_to_pil(
svg: str | bytes,
scale: float = 1.0,
background_color: str | None = None,
):
"""
Convert SVG to a PIL Image object.
Requires: pip install pillow
Example:
img = svg_to_pil(svg_str, scale=2.0)
img.save("out.webp")
"""
try:
from PIL import Image
except ImportError as e:
raise ImportError("pip install pillow") from e
png = svg_to_bytes(svg, fmt="png", scale=scale, background_color=background_color)
return Image.open(io.BytesIO(png))
# ─────────────────────────────────────────────────────────────────────────────
# 3. Batch conversion
# ─────────────────────────────────────────────────────────────────────────────
def batch_convert(
input_dir: str | Path,
output_dir: str | Path,
fmt: Format = "png",
scale: float = 1.0,
dpi: int | None = None,
background_color: str | None = "white",
glob: str = "*.svg",
) -> list[tuple[Path, int]]:
"""
Convert all SVG files in input_dir to fmt in output_dir.
Returns [(output_path, byte_size), ...].
Example:
results = batch_convert("icons/", "icons_png/", scale=2.0)
"""
inp = Path(input_dir)
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
results = []
for svg_path in sorted(inp.glob(glob)):
dest = out / svg_path.with_suffix(f".{fmt}").name
size = convert_file(svg_path, dest, fmt=fmt, scale=scale,
dpi=dpi, background_color=background_color)
results.append((dest, size))
return results
# ─────────────────────────────────────────────────────────────────────────────
# 4. Print-quality export (300 DPI)
# ─────────────────────────────────────────────────────────────────────────────
def svg_to_print_png(
svg: str | bytes,
dpi: int = 300,
background_color: str = "white",
) -> bytes:
"""
Export SVG as a 300 DPI PNG suitable for print.
At 300 DPI, a 3.5"×2" SVG renders at 1050×600 px.
"""
return svg_to_bytes(svg, fmt="png", dpi=dpi, background_color=background_color)
def svg_to_print_pdf(svg: str | bytes) -> bytes:
"""Export SVG as vector PDF (lossless, suitable for print)."""
return svg_to_bytes(svg, fmt="pdf")
# ─────────────────────────────────────────────────────────────────────────────
# 5. FastAPI endpoint helper
# ─────────────────────────────────────────────────────────────────────────────
FASTAPI_EXAMPLE = '''
from fastapi import FastAPI, Query
from fastapi.responses import Response
from app.svg_convert import svg_to_bytes
app = FastAPI()
SVG_TEMPLATE = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80">
<rect width="200" height="80" rx="8" fill="#4a90d9"/>
<text x="100" y="50" text-anchor="middle" fill="#fff"
font-family="Arial" font-size="18" font-weight="bold">{label}</text>
</svg>"""
@app.get("/badge/{label}.png")
def badge_png(label: str, scale: float = Query(2.0, ge=0.5, le=4.0)):
svg = SVG_TEMPLATE.format(label=label).encode()
png = svg_to_bytes(svg, fmt="png", scale=scale, background_color="white")
return Response(png, media_type="image/png")
@app.get("/badge/{label}.svg")
def badge_svg(label: str):
return Response(SVG_TEMPLATE.format(label=label), media_type="image/svg+xml")
'''
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
SAMPLE_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<circle cx="100" cy="100" r="90" fill="#4a90d9" stroke="#2c5f9a" stroke-width="4"/>
<text x="100" y="110" text-anchor="middle" fill="white"
font-family="Arial" font-size="28" font-weight="bold">SVG</text>
</svg>"""
print("=== PNG at 1× ===")
png1 = svg_to_bytes(SAMPLE_SVG, fmt="png")
Path("/tmp/circle_1x.png").write_bytes(png1)
print(f" 1×: {len(png1):,} bytes")
print("=== PNG at 3× ===")
png3 = svg_to_bytes(SAMPLE_SVG, fmt="png", scale=3.0, background_color="white")
Path("/tmp/circle_3x.png").write_bytes(png3)
print(f" 3×: {len(png3):,} bytes")
print("=== PDF ===")
pdf = svg_to_bytes(SAMPLE_SVG, fmt="pdf")
Path("/tmp/circle.pdf").write_bytes(pdf)
print(f" PDF: {len(pdf):,} bytes")
print("=== Thumbnail 64×64 ===")
thumb = svg_thumbnail(SAMPLE_SVG, width=64, height=64)
Path("/tmp/thumb.png").write_bytes(thumb)
print(f" thumb: {len(thumb):,} bytes")
print("=== Print quality 300 DPI ===")
hi_res = svg_to_print_png(SAMPLE_SVG, dpi=300)
Path("/tmp/circle_300dpi.png").write_bytes(hi_res)
print(f" 300 DPI: {len(hi_res):,} bytes")
For the Inkscape CLI alternative — Inkscape’s --export-png command-line can convert SVG at any DPI but requires Inkscape installed as an OS binary and spawns a subprocess; cairosvg is a pure-Python library (using the system libcairo) with a clean API and no subprocess overhead, making it straightforward to embed in web services and Lambda functions. For the svglib + reportlab alternative — svglib parses SVG to ReportLab RLG objects and renders to PDF/PNG; it has broader SVG feature support for complex filters but is slower and requires the ReportLab dependency chain; cairosvg is faster for straightforward SVG rendering and produces smaller output files with accurate CSS styling including text elements and linearGradient. The Claude Skills 360 bundle includes cairosvg skill sets covering svg_to_bytes() with scale/dpi/output_width/background_color, svg_file_to_bytes()/convert_file() file-based pipeline, svg_thumbnail() fixed-size rasterizer, svg_to_pil() Pillow integration, batch_convert() directory processing, svg_to_print_png() 300 DPI export, svg_to_print_pdf() vector PDF, and FastAPI badge-on-demand endpoint. Start with the free tier to try SVG conversion code generation.