How the PDF Compilation Loop Verifies Page Counts and Prevents Orphaned Entries

The repository implements a repeat-until-valid workflow where tools/verify_pdf.py enforces exact page counts using pdfinfo while LaTeX macros like \needspace prevent section titles from separating from their content.

The MadsLorentzen/ai-job-search repository automates professional document generation through a robust PDF compilation loop that guarantees strict layout constraints. This system combines Python-based verification with LaTeX spacing controls to ensure every CV and cover letter meets exact page requirements while eliminating orphaned headings.

The Repeat-Until-Valid Compilation Workflow

The architecture follows a compile-verify-adjust cycle that repeats until all validation criteria pass. The process begins with LaTeX compilation engines and concludes with automated verification scripts.

LaTeX Compilation Engines

The CV compiles with lualatex while the cover letter uses xelatex, both running in non-stop mode to enable automated iteration. As noted in the repository documentation at line 221 of README.md, these engines handle the modern font requirements and Unicode characters common in international CVs.

The Verification Layer

After each compilation, the script tools/verify_pdf.py performs three critical checks to validate the generated PDF. This Python module serves as the gatekeeper for document quality, ensuring that layout adjustments continue until the output meets specifications.

How Page Count Verification Works

The verification script extracts metadata and content from the compiled PDF to enforce strict pagination requirements.

Extracting Page Counts with pdfinfo

The script calls pdfinfo to retrieve the actual page count and compares it against the expected_pages parameter. In tools/verify_pdf.py, lines 49-55 implement this check:

if expected_pages is not None:
    actual_pages = parse_page_count(run_tool(["pdfinfo", str(pdf_path)]))
    if actual_pages != expected_pages:
        raise VerificationError(
            f"expected {expected_pages} page(s), found {actual_pages}"
        )

This guarantees that CVs are exactly 2 pages and cover letters are exactly 1 page, matching the requirements supplied by the CI job or Claude assistant.

Text Layer Validation

Beyond page counts, the script validates content presence using pdftotext to ensure the PDF contains extractable text. Lines 59-62 check that the document meets a minimum character threshold, preventing compilation errors that might produce blank or corrupted pages. Optional required string validation (lines 64-66) further ensures critical content sections exist.

Preventing Orphaned Entries with LaTeX Macros

While Python handles pagination verification, the LaTeX templates prevent visual orphans through spacing macros documented in .claude/skills/job-application-assistant/05-cv-templates.md (lines 70-80).

The needspace Macro

The \needspace{5\baselineskip} command appears before every \cventry in the LaTeX source. This macro forces LaTeX to check for sufficient remaining vertical space before starting a new entry. If insufficient space exists, LaTeX automatically inserts a page break, preventing section headings from appearing at the bottom of a page while their bullet lists flow to the next.

Dynamic Page Adjustment with enlargethispage

When content nearly fits but spills slightly onto an additional page, the \enlargethispage{2-3\baselineskip} command pulls extra lines onto the current page. This fine-tuning capability allows the compilation loop to compress sections slightly rather than consuming an entire new page for one or two lines of content.

Implementation in CI and Automation

The verification loop integrates directly into the continuous integration pipeline defined in .github/workflows/ci.yml.

CI Pipeline Integration

The workflow compiles the example CV using lualatex, then immediately invokes the verification script to enforce the two-page requirement. If verify_pdf raises a VerificationError, the Claude assistant or CI system adjusts the LaTeX source—adding \needspace or tuning \enlargethispage values—before recompiling. This cycle continues until the PDF satisfies both page count and content requirements.

You can run the verifier manually to check existing PDFs:


# Verify that a CV is exactly 2 pages and contains at least 100 characters

python -m tools.verify_pdf cv/main_example.pdf --pages 2 --min-chars 100

Python Implementation Example

A typical automation script implements the loop as follows:

import subprocess
from pathlib import Path
from tools.verify_pdf import verify_pdf, VerificationError

pdf_path = Path("cv/main_example.pdf")
while True:
    # Compile with lualatex (non-stop mode)

    subprocess.run(
        ["lualatex", "-interaction=nonstopmode", "-halt-on-error", "cv/main_example.tex"],
        check=True,
    )
    try:
        verify_pdf(pdf_path, expected_pages=2, min_chars=100)
        print("✅ PDF is valid")
        break
    except VerificationError as e:
        print(f"⚠️ Verification failed: {e}")
        # Assistant would edit the .tex (e.g., insert \needspace) before next iteration

Summary

  • tools/verify_pdf.py enforces exact page counts using pdfinfo and validates content presence via pdftotext
  • The repeat-until-valid workflow automatically recompiles documents when verification fails, adjusting LaTeX spacing macros between iterations
  • \needspace{5\baselineskip} prevents orphaned headings by ensuring sufficient space exists before starting new entries
  • \enlargethispage allows minor page compression to avoid single-line overflows onto new pages
  • The system guarantees CVs are exactly 2 pages and cover letters are exactly 1 page through automated CI integration

Frequently Asked Questions

What happens if the PDF page count doesn't match the expected value?

The verify_pdf function raises a VerificationError immediately when actual_pages differs from expected_pages. This exception triggers the compilation loop to adjust the LaTeX source—typically by modifying spacing macros—and recompile until the page count matches the requirement exactly.

How does \needspace prevent orphaned entries in LaTeX?

The \needspace{5\baselineskip} command checks that at least five lines of vertical space remain on the current page before rendering a \cventry section. If insufficient space exists, LaTeX inserts a page break first, ensuring that section titles never appear isolated at the bottom of a page while their content flows to the next.

Can I run the verification script manually outside of the CI pipeline?

Yes, the tools/verify_pdf.py module supports command-line execution. You can verify any PDF manually by specifying the expected page count and minimum character requirements: python -m tools.verify_pdf path/to/file.pdf --pages 2 --min-chars 100.

Why does the repository use different LaTeX engines for CVs versus cover letters?

The CV uses lualatex to handle complex Unicode characters and modern font features common in international applications, while the cover letter employs xelatex for its robust font handling capabilities. Both engines support the spacing macros required for orphan prevention and integrate seamlessly with the Python verification layer.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →