PyLaTeX generates LaTeX and PDF documents from Python. pip install pylatex (requires LaTeX: apt install texlive-full or MacTeX). Document: from pylatex import Document; doc = Document(). Add section: doc.append(Section("Introduction")). Text: doc.append("Hello world"). Bold: from pylatex.utils import bold; doc.append(bold("important")). italic: from pylatex.utils import italic. NoEscape: from pylatex.utils import NoEscape; doc.append(NoEscape(r"\LaTeX")). Math: from pylatex import Math; doc.append(Math(data=[r"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}"])). Inline math: Math(data=[r"E=mc^2"], inline=True). Equation: from pylatex import Equation. Tabular: from pylatex import Tabular; t = Tabular("lrr"); t.add_hline(); t.add_row(["Name","Score","Grade"]). Multicolumn: from pylatex import MultiColumn. MultiRow: from pylatex import MultiRow. Figure: from pylatex import Figure; with doc.create(Figure(position="htbp")) as fig: fig.add_image("plot.png", width=NoEscape(r"0.8\linewidth")). Package: doc.packages.append(Package("geometry", options=["margin=2cm"])). Geometry: from pylatex import Package; doc.packages.append(Package("geometry", options=["a4paper","margin=2.5cm"])). Enumerate: from pylatex import Enumerate; with doc.create(Enumerate()) as enum: enum.add_item("First item"). Itemize: from pylatex import Itemize. Description: from pylatex import Description. NewPage: from pylatex import NewPage; doc.append(NewPage()). HRef: from pylatex import HRef. Color: from pylatex import TextColor. Verbatim: from pylatex import Verbatim. generate_pdf: doc.generate_pdf("output", clean_tex=True, compiler="pdflatex"). generate_tex: doc.generate_tex("output") — writes .tex file. Claude Code generates PyLaTeX scientific reports, invoices, data tables, and automated PDF pipelines.
CLAUDE.md for PyLaTeX
## PyLaTeX Stack
- Version: pylatex >= 1.4 | pip install pylatex | requires pdflatex or xelatex
- Document: doc = Document(geometry_options={"margin": "2cm"})
- Add: doc.append(text) | doc.append(Math(data=["expr"])) | doc.append(Section("title"))
- Table: t = Tabular("lcr") | t.add_row([...]) | t.add_hline()
- Figure: with doc.create(Figure()) as fig: fig.add_image("path", width=...)
- Generate: doc.generate_pdf("output", clean_tex=True)
PyLaTeX PDF Generation Pipeline
# app/latex_gen.py — PyLaTeX document, sections, math, tables, figures, report pipeline
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from pylatex import (
Command,
Document,
Enumerate,
Figure,
Foot,
HFill,
Head,
HorizontalSpace,
Itemize,
LargeText,
LineBreak,
Math,
MediumText,
MiniPage,
MultiColumn,
NewLine,
NewPage,
Package,
PageStyle,
Section,
SmallText,
Subsection,
Subsubsection,
Tabular,
TextColor,
VerticalSpace,
)
from pylatex.utils import NoEscape, bold, italic, verbatim
# ─────────────────────────────────────────────────────────────────────────────
# 1. Document factory
# ─────────────────────────────────────────────────────────────────────────────
def make_document(
title: str | None = None,
author: str | None = None,
date: str | None = None,
font_size: str = "11pt",
paper: str = "a4paper",
margin: str = "2.5cm",
packages: list[str] | None = None,
) -> Document:
"""
Create a Document with standard packages and optional title page.
Example:
doc = make_document("Annual Report", author="Alice", date="2024-01")
"""
geo = {"margin": margin, paper: True}
doc = Document(
geometry_options=geo,
document_options=[font_size],
font_size=font_size,
)
# Standard packages
doc.packages.append(Package("fontenc", options=["T1"]))
doc.packages.append(Package("inputenc", options=["utf8"]))
doc.packages.append(Package("amsmath"))
doc.packages.append(Package("amssymb"))
doc.packages.append(Package("booktabs"))
doc.packages.append(Package("xcolor", options=["table"]))
doc.packages.append(Package("hyperref", options=["hidelinks"]))
doc.packages.append(Package("graphicx"))
doc.packages.append(Package("microtype"))
doc.packages.append(Package("lmodern"))
for pkg in packages or []:
doc.packages.append(Package(pkg))
if title:
doc.preamble.append(Command("title", title))
if author:
doc.preamble.append(Command("author", author))
if date is not None:
doc.preamble.append(Command("date", date or NoEscape(r"\today")))
if title:
doc.append(NoEscape(r"\maketitle"))
return doc
# ─────────────────────────────────────────────────────────────────────────────
# 2. Structural helpers
# ─────────────────────────────────────────────────────────────────────────────
def add_section(doc: Document | Any, title: str, numbered: bool = True) -> Section:
"""
Append a Section and return it for use as context manager.
Example:
sec = add_section(doc, "Introduction")
with sec:
sec.append("This paper...")
"""
sec = Section(title, numbering=numbered)
doc.append(sec)
return sec
def add_subsection(parent: Any, title: str, numbered: bool = True) -> Subsection:
sub = Subsection(title, numbering=numbered)
parent.append(sub)
return sub
def add_paragraph(parent: Any, text: str) -> None:
parent.append(text)
parent.append(NewLine())
def add_itemize(parent: Any, items: list[str]) -> None:
"""
Add a bulleted list.
Example:
add_itemize(section, ["Item one", "Item two", "Item three"])
"""
with parent.create(Itemize()) as lst:
for item in items:
lst.add_item(item)
def add_enumerate(parent: Any, items: list[str]) -> None:
"""
Add a numbered list.
"""
with parent.create(Enumerate()) as lst:
for item in items:
lst.add_item(item)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Math helpers
# ─────────────────────────────────────────────────────────────────────────────
def inline_math(expr: str) -> Math:
"""
Inline math: $expr$
Example:
section.append(inline_math(r"E = mc^2"))
section.append(" is Einstein's famous formula.")
"""
return Math(data=[NoEscape(expr)], inline=True)
def display_math(expr: str) -> Math:
"""
Display (block) math.
Example:
section.append(display_math(r"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}"))
"""
return Math(data=[NoEscape(expr)])
def add_equation(parent: Any, expr: str, label: str | None = None) -> None:
"""
Add a numbered equation with optional label.
Example:
add_equation(section, r"F = ma", label="eq:newton2")
"""
if label:
parent.append(NoEscape(
r"\begin{equation}" + f"\n{expr}\n" +
r"\label{" + label + r"}" + "\n" +
r"\end{equation}"
))
else:
parent.append(NoEscape(r"\begin{equation}" + f"\n{expr}\n" + r"\end{equation}"))
# ─────────────────────────────────────────────────────────────────────────────
# 4. Table helpers
# ─────────────────────────────────────────────────────────────────────────────
def add_table(
parent: Any,
headers: list[str],
rows: list[list],
caption: str | None = None,
col_spec: str | None = None,
booktabs: bool = True,
) -> None:
"""
Add a LaTeX table.
Example:
add_table(section,
headers=["Name", "Score", "Grade"],
rows=[["Alice", 95, "A"], ["Bob", 82, "B"]],
caption="Student Results",
)
"""
n_cols = len(headers)
spec = col_spec or ("l" + "r" * (n_cols - 1))
table = Tabular(spec)
if booktabs:
table.add_hline()
table.add_row([bold(h) for h in headers])
if booktabs:
table.add_hline()
for row in rows:
table.add_row([str(v) for v in row])
if booktabs:
table.add_hline()
if caption:
parent.append(NoEscape(r"\begin{table}[htbp]\centering"))
parent.append(table)
parent.append(NoEscape(r"\caption{" + caption + r"}\end{table}"))
else:
parent.append(table)
def add_data_table(
parent: Any,
data: list[dict],
columns: list[str] | None = None,
caption: str | None = None,
col_spec: str | None = None,
) -> None:
"""
Add a table from a list of dicts.
Example:
add_data_table(section,
data=[{"name":"Alice","age":30},{"name":"Bob","age":25}],
caption="Users",
)
"""
if not data:
return
columns = columns or list(data[0].keys())
rows = [[str(row.get(c, "")) for c in columns] for row in data]
add_table(parent, headers=columns, rows=rows, caption=caption, col_spec=col_spec)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Figure helpers
# ─────────────────────────────────────────────────────────────────────────────
def add_figure(
parent: Any,
image_path: str | Path,
caption: str = "",
width: str = r"0.8\linewidth",
label: str | None = None,
position: str = "htbp",
) -> None:
"""
Add a centered figure.
Example:
add_figure(section, "charts/revenue.pdf", caption="Monthly Revenue", width=r"0.9\linewidth")
"""
with parent.create(Figure(position=position)) as fig:
fig.add_image(str(image_path), width=NoEscape(width))
if caption:
fig.add_caption(caption)
if label:
fig.append(NoEscape(r"\label{" + label + r"}"))
# ─────────────────────────────────────────────────────────────────────────────
# 6. Report generator
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class ReportSection:
title: str
body: str = ""
items: list[str] | None = None
table_headers: list[str] | None = None
table_rows: list[list] | None = None
table_caption: str = ""
math_exprs: list[str] | None = None
def generate_report(
title: str,
author: str,
date: str,
abstract: str,
sections: list[ReportSection],
output_path: str | Path = "report",
compile_pdf: bool = True,
clean_tex: bool = True,
) -> Path:
"""
Generate a structured PDF report.
Example:
generate_report(
title="Q1 Analysis",
author="Data Team",
date="April 2024",
abstract="This report summarizes Q1 performance.",
sections=[
ReportSection("Overview", body="Revenue grew 15% YoY."),
ReportSection("Data",
table_headers=["Month","Revenue","Growth"],
table_rows=[["Jan","$1.2M","5%"],["Feb","$1.4M","8%"]],
table_caption="Monthly Revenue",
),
],
)
"""
doc = make_document(title=title, author=author, date=date)
# Abstract
doc.append(NoEscape(r"\begin{abstract}"))
doc.append(abstract)
doc.append(NoEscape(r"\end{abstract}"))
for sec_data in sections:
with doc.create(Section(sec_data.title)) as sec:
if sec_data.body:
sec.append(sec_data.body)
if sec_data.items:
add_itemize(sec, sec_data.items)
if sec_data.math_exprs:
for expr in sec_data.math_exprs:
add_equation(sec, expr)
if sec_data.table_headers and sec_data.table_rows:
add_table(
sec,
headers=sec_data.table_headers,
rows=sec_data.table_rows,
caption=sec_data.table_caption,
)
output = Path(str(output_path))
if compile_pdf:
doc.generate_pdf(str(output), clean_tex=clean_tex, compiler="pdflatex")
return output.with_suffix(".pdf")
else:
doc.generate_tex(str(output))
return output.with_suffix(".tex")
# ─────────────────────────────────────────────────────────────────────────────
# Demo (generates .tex only — PDF requires pdflatex)
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== Generating LaTeX document ===")
doc = make_document(
title="PyLaTeX Demo Report",
author="Claude Code",
date=NoEscape(r"\today"),
)
with doc.create(Section("Introduction")) as intro:
intro.append("This document demonstrates PyLaTeX capabilities.")
intro.append(NewLine())
intro.append("We can include ")
intro.append(inline_math(r"E = mc^2"))
intro.append(" inline, or display formulas:")
add_equation(intro, r"\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2}", label="eq:gauss")
with doc.create(Section("Data Table")) as data_sec:
add_table(
data_sec,
headers=["Library", "Stars", "Lang"],
rows=[
["PyLaTeX", "2.1k", "Python"],
["Jinja2", "10k", "Python"],
["Pandoc", "32k", "Haskell"],
],
caption="Popular Document Generation Tools",
)
with doc.create(Section("Lists")) as list_sec:
list_sec.append("Key features:")
add_itemize(list_sec, [
"Declarative Python API",
"Automatic escaping of special characters",
"Math equations via amsmath",
"Tables, figures, and custom LaTeX",
])
with tempfile.TemporaryDirectory() as td:
out = Path(td) / "demo"
doc.generate_tex(str(out))
tex_path = out.with_suffix(".tex")
tex_content = tex_path.read_text()
print(f" Generated .tex: {tex_path} ({len(tex_content):,} chars)")
print(f" First 200 chars: {tex_content[:200]!r}")
print("\nTo compile PDF:")
print(" doc.generate_pdf('output', clean_tex=True, compiler='pdflatex')")
print(" Requires: sudo apt install texlive-full # or MacTeX")
For the WeasyPrint alternative — WeasyPrint renders HTML/CSS to PDF, making it ideal when you want design control via CSS and can author content as HTML; PyLaTeX produces typeset documents with professional mathematical quality, precise typography, and TeX’s sophisticated hyphenation — use WeasyPrint for web-report PDFs where HTML/CSS skills are more natural, PyLaTeX for academic papers, scientific reports, and documents with complex mathematics. For the python-docx / reportlab alternative — python-docx generates editable .docx files for Microsoft Word; reportlab provides low-level PDF drawing primitives (flowables, platypus, canvas); PyLaTeX produces publication-quality PDFs via the TeX typesetting engine — use python-docx when users need editable documents, reportlab for programmatic PDF layout control, PyLaTeX when mathematical typesetting or TeX’s typography quality is required. The Claude Skills 360 bundle includes PyLaTeX skill sets covering make_document() with standard packages, add_section()/add_subsection()/add_paragraph()/add_itemize()/add_enumerate() structure, inline_math()/display_math()/add_equation() math, add_table()/add_data_table() with booktabs style, add_figure() with caption/label, and generate_report() full pipeline. Start with the free tier to try LaTeX and PDF document generation code generation.