Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@
copyright = "2026, MLC Community"
release = "0.0.1"

# Unicode symbols (≈, ×, ≤, →, etc.) in the text require a Unicode-aware engine;
# pdflatex chokes on them. xelatex handles them natively.
latex_engine = "xelatex"

# Sphinx's xelatex default uses GNU FreeFont (FreeSerif/FreeSans/FreeMono), which
# BasicTeX doesn't ship. Use the tex-gyre fonts (already installed) instead, and
# let xelatex fall back to a system font for any glyphs they lack.
latex_elements = {
"fontpkg": r"""
\setmainfont{texgyretermes-regular.otf}[
BoldFont=texgyretermes-bold.otf,
ItalicFont=texgyretermes-italic.otf,
BoldItalicFont=texgyretermes-bolditalic.otf]
\setsansfont{texgyreheros-regular.otf}[
BoldFont=texgyreheros-bold.otf,
ItalicFont=texgyreheros-italic.otf,
BoldItalicFont=texgyreheros-bolditalic.otf]
\setmonofont{texgyrecursor-regular.otf}[
BoldFont=texgyrecursor-bold.otf,
ItalicFont=texgyrecursor-italic.otf,
BoldItalicFont=texgyrecursor-bolditalic.otf]
""",
}

extensions = ["myst_parser", "sphinx_copybutton"]

# Markdown (MyST) is the primary source format.
Expand Down Expand Up @@ -57,3 +81,5 @@
"use_download_button": False,
"use_fullscreen_button": False,
}

extensions = ["myst_parser", "sphinx_copybutton", "sphinxcontrib.rsvgconverter"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of redefining the entire extensions list (which was already defined on line 34), append the new extension to the existing list to avoid duplication and keep the configuration clean.

Suggested change
extensions = ["myst_parser", "sphinx_copybutton", "sphinxcontrib.rsvgconverter"]
extensions.append("sphinxcontrib.rsvgconverter")

117 changes: 117 additions & 0 deletions docs/BUILDING_PDF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Building the book as a PDF

The book is a Sphinx + MyST-Markdown project that normally builds to **HTML**
(`sphinx-build -b html . _build/html`). This doc explains how to also produce a
**PDF** via the LaTeX builder.

> TL;DR — once prerequisites are installed:
> ```bash
> ./scripts/build-pdf.sh
> open _build/latex/moderngpuprogrammingformlsys.pdf
> ```

## How it works

`Sphinx (LaTeX builder) -> .tex -> xelatex (latexmk) -> PDF`

We use **XeLaTeX**, not the default pdfLaTeX, because the text contains Unicode
symbols (`≈ × ≤ → …`) that pdfLaTeX cannot typeset. SVG figures are converted to
PDF on the fly via `rsvg-convert`.

`conf.py` already contains the needed settings:

```python
latex_engine = "xelatex"
latex_elements = {
"fontpkg": r"""
\setmainfont{texgyretermes-regular.otf}[ ... ]
\setsansfont{texgyreheros-regular.otf}[ ... ]
\setmonofont{texgyrecursor-regular.otf}[ ... ]
""",
}
# extensions list also includes "sphinxcontrib.rsvgconverter"
```

The tex-gyre fonts are referenced **by OTF filename** because a minimal TeX
install (BasicTeX) doesn't ship Sphinx's default GNU FreeFont, and the font
database doesn't always resolve tex-gyre by family name.

## Prerequisites (one-time)

These need admin rights (`brew`, `sudo tlmgr`) and a fair amount of download.

### 1. Python build deps
```bash
pip install sphinx myst-parser sphinx-copybutton sphinxcontrib-svg2pdfconverter
```

### 2. A TeX distribution (provides xelatex + latexmk)
```bash
brew install --cask basictex # ~100 MB minimal TeX (or 'mactex' for the full ~5 GB)
# put TeX on PATH for the current shell:
eval "$(/usr/libexec/path_helper)"
export PATH="/Library/TeX/texbin:$PATH"
```

### 3. LaTeX packages Sphinx's output needs
```bash
sudo tlmgr update --self
sudo tlmgr install latexmk tex-gyre fncychap wrapfig capt-of needspace \
tabulary varwidth titlesec framed upquote
```
If a later build complains `LaTeX Error: File 'xxx.sty' not found`, install it:
`sudo tlmgr install xxx`.

### 4. SVG -> PDF converter (for the figures)
```bash
brew install librsvg # provides rsvg-convert
```

## Build

```bash
./scripts/build-pdf.sh
```

or manually:

```bash
export PATH="/Library/TeX/texbin:$PATH"
sphinx-build -b latex . _build/latex
cd _build/latex && make # latexmk drives xelatex (multiple passes for TOC/refs)
```

**Output:** `_build/latex/moderngpuprogrammingformlsys.pdf`

`_build/` is gitignored, so the PDF is a local artifact — it is not committed.

## Known limitations of the PDF

1. **Interactive demos are missing.** The book embeds self-contained HTML/JS
slide demos via `<iframe>` (the `_extra/` content + `html_extra_path`). These
are live web content and **cannot render in a PDF** — expect blank/absent
regions where they appear in the HTML.
2. **Some box-drawing / special glyphs may be missing.** The build logs
`Missing character` warnings for glyphs like `─` (U+2500) used in some
ASCII-art / code blocks, because the tex-gyre fonts lack them. The PDF still
builds; those glyphs just render as gaps. To recover them, add a Unicode-rich
monospace fallback in `conf.py`'s `fontpkg` (e.g. DejaVu Sans Mono / Symbola).
3. **First-pass reference warnings are normal.** "Latex failed to resolve N
reference(s)" appears mid-build; latexmk re-runs to resolve them. Only a
non-zero final exit / "no output PDF" indicates a real failure — check
`_build/latex/moderngpuprogrammingformlsys.log`.

## Troubleshooting

| Symptom in the `.log` | Fix |
| --- | --- |
| `Unicode character ... not set up for use with LaTeX` | Ensure `latex_engine = "xelatex"` (not pdflatex). |
| `fontspec Error: The font "..." cannot be found` | Use the tex-gyre **OTF filenames** (as in `conf.py`), or `sudo tlmgr install tex-gyre`. |
| `File 'xxx.sty' not found` | `sudo tlmgr install xxx`. |
| SVG figure errors / blank figures | `brew install librsvg` and confirm `sphinxcontrib.rsvgconverter` is in `extensions`. |

## Note on conf.py

`conf.py` currently defines `extensions = [...]` **twice** — the second
assignment (which adds `sphinxcontrib.rsvgconverter`) wins. It works, but is
fragile; consider consolidating into a single `extensions` list.
Comment on lines +113 to +117

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since we consolidated/appended the extension in conf.py to avoid duplicate definitions, this note is no longer necessary and can be removed.

49 changes: 49 additions & 0 deletions scripts/build-pdf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
#
# build-pdf.sh — build the book as a PDF via Sphinx -> XeLaTeX.
#
# Run from the repo root: ./scripts/build-pdf.sh
# Output: _build/latex/moderngpuprogrammingformlsys.pdf
#
# Prerequisites (one-time, need admin/brew — see README "PDF build"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The PDF build instructions are located in docs/BUILDING_PDF.md, not README.md. Update the reference to point to the correct file.

Suggested change
# Prerequisites (one-time, need admin/brew — see README "PDF build"):
# Prerequisites (one-time, need admin/brew — see docs/BUILDING_PDF.md):

# - MacTeX/BasicTeX (xelatex, latexmk) on PATH (/Library/TeX/texbin)
# - librsvg (rsvg-convert) for the SVG figures: brew install librsvg
# - LaTeX packages: sudo tlmgr install latexmk tex-gyre fncychap wrapfig \
# capt-of needspace tabulary varwidth titlesec framed upquote
# - Python deps: pip install sphinx myst-parser sphinx-copybutton \
# sphinxcontrib-svg2pdfconverter
#
# conf.py already sets: latex_engine="xelatex" + tex-gyre OTF fonts (BasicTeX
# lacks Sphinx's default FreeFont), and loads sphinxcontrib.rsvgconverter for SVGs.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

# Make sure the TeX toolchain is reachable even from a fresh shell.
export PATH="/Library/TeX/texbin:$PATH"

PDF="_build/latex/moderngpuprogrammingformlsys.pdf"

echo "==> Checking toolchain"
for bin in sphinx-build xelatex latexmk rsvg-convert; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "ERROR: '$bin' not found on PATH. See README 'PDF build' prerequisites." >&2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the error message to reference docs/BUILDING_PDF.md instead of README.md to guide users to the correct documentation.

Suggested change
echo "ERROR: '$bin' not found on PATH. See README 'PDF build' prerequisites." >&2
echo "ERROR: '$bin' not found on PATH. See docs/BUILDING_PDF.md for prerequisites." >&2

exit 1
fi
done

echo "==> 1/2 Generating LaTeX from Sphinx sources"
sphinx-build -b latex . _build/latex

echo "==> 2/2 Compiling LaTeX -> PDF (xelatex via latexmk)"
( cd _build/latex && latexmk -C >/dev/null 2>&1 || true; make )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a semicolon ; inside the subshell compound command can lead to make running in the repository root if the cd command fails (even with set -e active, depending on shell behavior). Structuring this as a multi-line block inside the subshell is much cleaner, safer, and avoids operator precedence issues.

Suggested change
( cd _build/latex && latexmk -C >/dev/null 2>&1 || true; make )
(
cd _build/latex
latexmk -C >/dev/null 2>&1 || true
make
)


if [[ -f "$PDF" ]]; then
echo
echo "==> Done: $REPO_ROOT/$PDF"
echo " (Open with: open \"$REPO_ROOT/$PDF\")"
else
echo "ERROR: PDF was not produced. Check _build/latex/moderngpuprogrammingformlsys.log" >&2
exit 1
fi