Proofing in CI

Hellbox’s headless commands run anywhere macOS runs, including GitHub Actions runners. A font repo can render a fresh proof PDF on every commit, attach it to the pull request, and fail the build when character coverage or layout regresses.

Want to see it working? Fork the example repo: jakefleming/hellbox-ci-example proofs Space Grotesk on every push with the exact workflow below.

The idea

  1. Author a proof document (.fontproof) in Hellbox and commit it to your font repo, with your font files next to it (or in a fonts/ folder beside it). Documents are portable: when Hellbox renders headlessly on another machine, it resolves fonts relative to the document. Requires Hellbox 1.20.2 or later.
Authoring the example proof in Hellbox: sections for character set, spacing strings, reading sizes, and a tracking test The example repo's proof document in Hellbox: spacing strings, reading sizes, and a tracking test, committed beside the fonts.
  1. In CI, after your fonts build, copy the fresh binaries over the committed ones, then run --render-check for a machine-readable audit and --export for the PDF artifact.
  2. Fail the build if the audit reports coverage or overflow issues.

--export and --render-check don’t require a license on the machine running them; the document is authored in the app.

What —render-check reports

{
  "ok": true,
  "pages": 24,
  "pageWidthPts": 612,
  "pageHeightPts": 792,
  "coverageIssues": [
    { "section": "Diacritics", "font": "MyFont-Regular", "missingCount": 3, "missing": ["ẽ", "ő", "ų"] }
  ],
  "overflowIssues": [
    { "section": "Spacing", "shownPages": 1, "neededPages": 2 }
  ]
}

coverageIssues lists characters a proofed font has no glyph for, per section and font. overflowIssues flags sections whose content no longer fits their page budget. Both keys are absent when clean.

Example GitHub Actions workflow

name: Proof
on: [push, pull_request]

jobs:
  proof:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      # Build your fonts however you already do, then place the
      # binaries where the committed .fontproof expects them
      # (next to the document, or in fonts/ beside it).
      - name: Build fonts
        run: |
          # e.g. fontmake, or copy prebuilt binaries:
          # cp build/*.otf proofs/fonts/

      - name: Install Hellbox
        run: |
          curl -sL -o Hellbox.dmg \
            https://jakefleming.github.io/font-proof-swift/downloads/Hellbox-latest.dmg
          hdiutil attach Hellbox.dmg -nobrowse -quiet
          cp -R /Volumes/Hellbox/Hellbox.app /Applications/
          hdiutil detach /Volumes/Hellbox -quiet

      - name: Render check
        run: |
          HELLBOX=/Applications/Hellbox.app/Contents/MacOS/Hellbox
          "$HELLBOX" --render-check proofs/family.fontproof > check.json
          cat check.json
          # Fail on any coverage or overflow regression:
          jq -e '(.coverageIssues // []) + (.overflowIssues // []) | length == 0' check.json

      - name: Export proof PDF
        run: |
          /Applications/Hellbox.app/Contents/MacOS/Hellbox \
            --export proofs/family.fontproof proof.pdf

      - uses: actions/upload-artifact@v4
        with:
          name: proof
          path: proof.pdf

Pin a specific Hellbox version by replacing Hellbox-latest.dmg with a versioned file such as Hellbox-1.20.1.dmg; every release’s DMG URL is permanent.

Tips

  • Keep the committed .fontproof small and targeted: a coverage-heavy document (Diacritics, Every glyph) makes the coverage gate meaningful.
  • --render-pngs proofs/family.fontproof out/ 2 renders per-page PNGs if you’d rather diff images than read PDFs.
  • Since proofs render with no font fallback, characters your font lacks appear as its own .notdef in the PDF, and in coverageIssues, so a shrinking character set is visible in both places.