Compile a LaTeX paper (ACM/IEEE/arXiv) to PDF and render its pages to high-DPI PNGs for mobile/on-screen review. Use whenever asked to "render / compile / show / screenshot" a .tex paper, or to produce phone-readable page images of a draft (e.g. ACM/IEEE workshop submissions). Encodes the toolchain + the gotchas already hit (font-expansion crash, filecontents bib, bibtex cycle).
7 stars
0 votes
0 copies
1 view
Added September 26, 2026
researchpythonrustgoshellbashnodegit
Works with
terminal
Security analysis
B77/100
highPerforms destructive filesystem operations
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of Render Paper?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/zernie-render-paper)
---
name: render-paper
description: Compile a LaTeX paper (ACM/IEEE/arXiv) to PDF and render its pages to high-DPI PNGs for mobile/on-screen review. Use whenever asked to "render / compile / show / screenshot" a .tex paper, or to produce phone-readable page images of a draft (e.g. ACM/IEEE workshop submissions). Encodes the toolchain + the gotchas already hit (font-expansion crash, filecontents bib, bibtex cycle).
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, SendUserFile]
---
<!-- vigiles:sha256:ff8dca1e1be3977e compiled from skills/render-paper/SKILL.md.spec.ts -->
# render-paper — .tex → PDF → readable page PNGs
Turns a LaTeX source into (a) a compiled PDF and (b) per-page PNG images crisp enough to read
the font on a phone. Encodes the exact toolchain and the failures already debugged, so it works
first try.
## 🔴 This is a COMMAND, not a stage
You run it twenty times a day. It has no position in the pipeline and nothing waits its turn to reach
it: **any `.tex` edit is the trigger** — render, then eyeball pages 1–2 before believing
anything you say about the paper. Scorecard row **`render`** (in the CONTINUOUS section of
`paper-pipeline`) records the last render and its page count; that page count is the hard input the
`tighten-paper` structure gate is blocked on, which is the only sense in which anything "comes after"
this.
*It used to carry a number in a stage list, which made a compile look like a milestone you pass once.
That list was replaced on 2026-08-03; nothing about the toolchain below changed.*
## Toolchain (one command, not a checklist)
- **Compiler:** TeX Live, installed by paperlint with exactly the packages the venue profiles declare:
```
npx paperlint toolchain # into ~/.cache/paperlint/texlive (PAPERLINT_TEXLIVE_DIR overrides); idempotent
npx paperlint toolchain --check # report what is missing, change nothing
```
Measured 2026-09-24: 3 min from an empty directory, 269 MB, TeX Live 2026, 47 packages verified
by `kpsewhich` against the files each profile names. The last line it prints is `bin: <dir>` —
put that directory first on PATH before calling `pdflatex` or `check-render.sh` by hand.
🔴 The package list lives in the venue profiles (`presets/*.jsonc` in the package, the
`tex` block; `tex-base.jsonc` for every paper) and nowhere else. A new venue that needs a new
package gets it there — never in a script, never here. A TeX Live without `libertine` builds an
acmart paper GREEN in Computer Modern; that is why the files are checked, not the exit code.
- **Reading the PDF** needs nothing installed: `paperlint build` and `extract-pdf-facts.mjs` use pdf.js,
which comes with paperlint. Page size, columns and font sizes come from banal (HotCRP's page-geometry
script), which `npx paperlint toolchain` installs and which runs on pdf.js output — it needs `perl`,
and no poppler. Without banal those facts are `null` and the build says so.
- **Renderer:** `pip install --quiet pymupdf` (system rasterizers and ghostscript are often missing or
404 on apt here; pymupdf is reliable). No `playwright install`, no external fetches.
## Compile (full bibtex cycle — needed or citations show as `[?]`)
```
npx paperlint build <paper-dir>
```
It picks a TeX Live that has every package the venue declares (paperlint's own, else one on PATH),
runs pdflatex and bibtex until the references settle, fails on an undefined `\ref`/`\cite`
through `paper-guards.tex`, then measures the PDF into `_build/paper.facts.json` (fonts, Type 3,
the last page's column heights). It judges nothing: a venue that needs a balanced last page turns
on the optional lint rule `pdf/last-page-balance` (see `docs/optional-rules.md` in the package). Without a
terminal and without a qualifying TeX Live it stops with one line naming `npx paperlint toolchain`.
Check the last log anyway: `grep -iE "Fatal|Output written" ` and
`grep -ciE "Undefined control|Citation.*undefined|Reference.*undefined"` (must be 0).
### MECHANICAL RENDER GATE (run this — do not just eyeball)
Eyeballing a page or two misses `Overfull \hbox` defects on later pages (text/tables bleeding
past the column into the margin — the #1 "wtf, formatting is broken" bug). LaTeX reports every one
in the `.log`; **`check-render.sh` parses them so it's a checked fact, not a hope:**
```
bash .claude/skills/render-paper/check-render.sh <paper-dir> [texbasename=paper] [threshold_pt=5]
```
It compiles, then FAILS (exit 1) on: any `Overfull \hbox` ≥ threshold (visible overflow), any
`Overfull \vbox`, or any undefined reference/citation/control-sequence. **A paper is not "renders
clean" until this exits 0.** Common fixes when it fails:
- **Long `\texttt{...}` / literal commands / URLs overflowing** → wrap in `\seqsplit{}`, or use
`\url{}` (breakable), or a `p{width}`/`>{\ttfamily\small}p{width}` table column instead of `l`,
or shrink the block with `\small`/`\footnotesize`.
- **Wide table** → `\small` + fixed-width `p{}` columns so cells wrap; or `\resizebox{\columnwidth}{!}{...}`.
- Re-run the gate until PASS.
## KNOWN GOTCHAS (already hit — apply proactively)
1. **`pdfTeX error (font expansion): auto expansion is only possible with scalable fonts`** — acmart +
microtype want Libertine outlines that `texlive-fonts-recommended` may lack. Fix: add
`\microtypesetup{expansion=false}` in the preamble (after `\documentclass`). This is the #1 crash.
2. **`Undefined control sequence \lesssim` / `\gtrsim`** — need `amssymb`; either `\usepackage{amssymb}`
or replace with `\le`/`\ge`.
3. **Bibliography via embedded `\begin{filecontents*}[overwrite]{refs.bib}...`** before
`\documentclass` works and keeps the paper one-file; still run the full bibtex cycle above.
⚠️ **The block IS the source of truth — it `overwrite`s `refs.bib` on every build.** Editing `refs.bib`
directly does nothing (regenerated away); fix citations INSIDE the filecontents block in the `.tex`.
Consider `.gitignore`-ing the generated `refs.bib` so it can't drift from the block.
4. **Double-blind:** ACM uses `\documentclass[sigconf,review,anonymous]{acmart}`; keep author
`Anonymous Author(s)` for the review PDF.
5. **`.gitignore`** the build junk (`*.aux *.log *.out *.bbl *.blg`); keep `paper.pdf` if you want it
version-controlled, else ignore it too.
6. **Multi-token shell commands in `\texttt{}` break at their internal space** — `\texttt{rm -rf}` can wrap
as `rm` ⏎ `-rf`, which reads as broken typesetting. This is a LEGAL break, so it raises **no** Overfull
warning and the render gate is blind to it. Fix: non-breaking ties — `\texttt{rm~-rf~/}` (or wrap in
`\mbox{}`). The space *before an argument* (`rm~-rf node\_modules`) stays breakable — only tie the
command tokens. `check-render.sh` now greps for a literal space inside `\texttt{…}` and warns.
7. **Page-limit / body-end: trust the RENDERED References-start page, NOT `\newlabel{tmpbodyend}` in the
`.aux`.** The aux label lags one compile (it reflects the *previous* build's pagination), so it triggers
false page-limit alarms — this session it read "p11" while the body genuinely ended on p10. To check the
body length: open the built PDF, find the page where the `\section*{References}`/`REFERENCES` heading
first appears (that page = last body page). `python3 -c "import fitz;d=fitz.open('paper.pdf');[print('refs p',i+1) for i in range(d.page_count) if 'REFERENCES' in d[i].get_text()][:1]"`. Never gate a page-limit decision on the aux value.
## Render pages to PNGs
```
python3 -c "import fitz; d=fitz.open('paper.pdf'); \
[d[i].get_pixmap(dpi=200).save(f'/<scratch>/p{i+1}.png') for i in range(d.page_count)]; \
print('rendered', d.page_count)"
```
- **DPI 200** = phone-readable (zoomable); use 150 for quick previews, 300 for print-check.
- Write PNGs to the session scratchpad, not the repo.
- **The mechanical gate above (`check-render.sh`) is the primary defense** for text/table overflow and
undefined refs — run it first, on the whole document, and get it to PASS.
- **Then eyeball EVERY page** (not just 1–2) for the defects the log CANNOT flag: **figure-internal
label collisions** (TikZ/pgfplots nodes overlapping axis labels or each other — e.g. a legend
landing on an axis tick), color/legibility, misaligned sub-figures. These do not raise an Overfull
warning, so a human scan of each figure is still required. Fix and re-render until clean.
## Deliver
- Send the PNGs with `SendUserFile` (`display: render`) so the user can read on phone; caption what
changed since the last render. Send the PDF too (`display: attach`) if they want the file.
## Compose with
- `paper-adversarial-review` / `pc-panel-review` (incl. its venue-fit mode) — review the rendered draft.