Skip to content

Fix PDF compilation for packages needing 3+ LaTeX passes#2827

Open
saustinp wants to merge 2 commits intoPreTeXtBook:masterfrom
saustinp:fix/pdf-rerun-loop
Open

Fix PDF compilation for packages needing 3+ LaTeX passes#2827
saustinp wants to merge 2 commits intoPreTeXtBook:masterfrom
saustinp:fix/pdf-rerun-loop

Conversation

@saustinp
Copy link
Copy Markdown

@saustinp saustinp commented Apr 9, 2026

Summary

  • Replace hardcoded 2-pass LaTeX compilation in pdf() with a log-checking rerun loop
  • Fixes nicematrix \cellcolor overlay nodes expanding to fill the entire page in the sample article PDF (page 60, Figure 10.12)
  • Documents needing only 2 passes are unaffected — the loop checks the log after pass 2 and breaks immediately if no rerun is requested

Root cause

The pdf() function in pretext/lib/pretext.py ran exactly 2 LaTeX passes (subprocess.run called twice). The nicematrix package's code-before directive uses TikZ remember picture, overlay nodes internally to position colored cell backgrounds. These nodes require 3 passes to resolve their final page coordinates. With only 2 passes, the overlay rectangles are mis-positioned and can cover the entire page.

The standalone latex-image extraction pipeline (pretext/lib/pretext.py:696-707) already had a proper rerun loop that checks for "Rerun" in the .log file, which is why the generated image files (latex-three-pass.pdf/.svg/.png) rendered correctly. The main document compilation was missing this same logic.

Change

pretext/lib/pretext.pypdf() function (line ~5250)

Before:

subprocess.run(latex_cmd)
subprocess.run(latex_cmd)

After:

subprocess.run(latex_cmd)
for pass_num in range(2, MAX_PASSES + 1):
    subprocess.run(latex_cmd)
    if os.path.isfile(logname):
        with open(logname) as f:
            log_contents = f.read()
        if "Rerun" not in log_contents and "rerun" not in log_contents:
            break
    else:
        break

The loop always runs at least 2 passes (matching previous behavior), then checks the .log for rerun requests before each additional pass, up to a maximum of 10. This matches the existing strategy used for standalone latex-image compilation.

Test plan

  • Rebuild examples/sample-article PDF — page 60 (Figure 10.12, "A matrix with colored entries") now renders correctly with properly bounded colored cells
  • Verified the standalone image gen/latex-image/latex-three-pass.pdf was already correct (confirming the issue was isolated to main document compilation)
  • Documents without multi-pass packages still compile with exactly 2 passes (no performance regression)

Fixes #2825

@saustinp saustinp force-pushed the fix/pdf-rerun-loop branch from a8e4ac7 to bc81d16 Compare April 9, 2026 21:14
The pdf() function hardcoded exactly 2 LaTeX passes. Packages like
nicematrix (which uses TikZ "remember picture" for cell coloring)
require 3+ passes to correctly position overlay nodes. With only 2
passes, colored cell backgrounds could expand to fill the entire page.

Replace the hardcoded 2-pass approach with a rerun loop that checks
the .log file for "Rerun" requests after each pass, matching the
strategy already used for standalone latex-image compilation. Documents
not needing extra passes still get exactly 2 passes (the loop checks
the log after pass 2 and breaks immediately).

Fixes PreTeXtBook#2825
@saustinp saustinp force-pushed the fix/pdf-rerun-loop branch from bc81d16 to fe9fc59 Compare April 9, 2026 21:16
@rbeezer
Copy link
Copy Markdown
Collaborator

rbeezer commented Apr 9, 2026

I sense a family effort here. ;-) Thanks very much, @saustinp. Will Claude be joining the family? Back in a few.

@saustinp
Copy link
Copy Markdown
Author

saustinp commented Apr 9, 2026

Yes, Claude has certainly come in handy :) Thanks for taking a look!

Copy link
Copy Markdown
Collaborator

@rbeezer rbeezer left a comment

Choose a reason for hiding this comment

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

Good catch — the two-pass hardcoding has been a known limitation, and this is the right fix. The PR description is thorough and the change is well-scoped.

A few observations:

  1. Log search string is broader than the existing pattern. The latex-image loop at line 703 checks for "Rerun to get", which is the standard LaTeX message. This PR checks for both "Rerun" and "rerun" (case-sensitive substring matches). That's more permissive — it will catch more package-specific messages, which is probably fine, but it also risks false positives on strings like "Do not rerun" or informational messages that mention rerun without requesting one. Consider matching "Rerun to get" (matching the existing latex-image loop) or at least "Rerun" alone (capital R, which is the convention for LaTeX rerun requests).

  2. No returncode check. The existing latex-image loop checks result.returncode == 0 before rerunning — if LaTeX errors out, it stops. This loop doesn't capture the return code and will keep running even after a fatal error, potentially repeating a failing compilation up to 10 times. Should check subprocess.run(...).returncode and break on failure.

  3. No max-loop warning. The latex-image loop logs an error and sets a nonzero return code when MAX_LOOPS is hit (lines 709-711). This loop silently exits after 10 passes with no indication that something may be wrong.

  4. Style: MAX_PASSES as a local constant. The existing code uses MAX_LOOPS at module scope or function scope similarly, so this is consistent, though both could arguably be a module-level constant. Not worth changing here.

  5. pass_num is unused. Minor — the loop variable exists only to drive the count. Could use _ to signal intent, but not important.

Overall this is a straightforward and correct improvement. Items 2 and 3 are the substantive ones — matching the existing error handling from the latex-image loop would make this robust.

Claude Opus 4.6, acting as a review assistant for Rob Beezer

@rbeezer
Copy link
Copy Markdown
Collaborator

rbeezer commented Apr 9, 2026

Let me know what you would like to address and when you feel this is ready.

- Narrow log search to "Rerun to get" (matching latex-image loop at
  line 703) instead of broad "Rerun"/"rerun" substring match, avoiding
  false positives on informational messages
- Check subprocess returncode and break on failure, preventing repeated
  compilation of a fatally errored document
- Log a warning when MAX_PASSES is exhausted without convergence
- Rename unused loop variable to _
@saustinp
Copy link
Copy Markdown
Author

saustinp commented Apr 9, 2026

Thanks for the thorough review. All four points addressed in the latest push:
1. Log search narrowed to "Rerun to get" — matches the existing latex-image loop at line 703 exactly. No more risk of false positives from informational messages.
2. Returncode check added — result.returncode != 0 breaks the loop immediately, preventing repeated compilation of a fatally errored document.
3. Max-pass warning added — if the loop exhausts all 10 passes without convergence, log.warning() reports the filename and pass count.
4. Unused pass_num renamed to _.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sample article PDF improperly formatted on one page

2 participants