Swap PDF reports to DejaVu Sans (fix Unicode black squares) - #36
Merged
Conversation
Both the transit report (report.py) and the microlensing report
(microlensing_report.py) were rendering Greek letters, superscripts,
and astronomy symbols (θ, Δ, μ, ², °, ⊕, ±, ≈, ≥, …) as black
squares because Helvetica's built-in WinAnsi subset doesn't cover them.
This landed hardest on the microlensing PDF, whose observables +
planet-predictions tables use θ_E, μ_rel, M⊕/M♃, ΔBIC, and
per-parameter ± error notation on almost every row.
Fix: register DejaVu Sans (regular + bold) as reportlab TrueType fonts
on module import, and swap the hardcoded "Helvetica" / "Helvetica-Bold"
strings for the resolved font names. DejaVu ships bundled with
matplotlib (already a Vetstar dep) so no new install is needed and no
system-font path assumption creeps in — production containers on Render
and Fly.io will have it automatically.
Changes
- New backend/app/pdf_fonts.py: `ensure_dejavu_registered()` +
`font_normal()` / `font_bold()`. Idempotent. Looks up
matplotlib.get_data_path()/fonts/ttf/DejaVuSans{,-Bold}.ttf,
registers via reportlab.pdfbase.pdfmetrics, and binds a font family
so Paragraph inline <b>...</b> markup uses the bold face. Silent
no-op if matplotlib/DejaVu aren't available (falls back to Helvetica
with a warning — same behaviour as before, no regression).
- backend/app/report.py: import + apply FONT_NORMAL / FONT_BOLD across
every TableStyle "FONTNAME" cmd, canvas.setFont, and _build_styles()
ParagraphStyle (which previously inherited Helvetica from
getSampleStyleSheet's Heading1/BodyText — that inheritance is now
explicitly overridden per style).
- backend/app/microlensing_report.py: same swap, plus ml_small
ParagraphStyle now sets fontName explicitly (was inheriting from
Normal → Helvetica).
Verified via pypdf text extraction: Greek + symbol glyphs that appear
in the microlensing PDF (θ, Δ, σ, °, ⊕, ±, ≈, μ, ≥) now round-trip
correctly through the DejaVuSans font stream — no black squares.
PDF font resource dictionary shows /AAAAAA+DejaVuSans and
/AAAAAA+DejaVuSans-Bold (subsetted embeddings) alongside the
inescapable base14 /Helvetica reportlab always writes.
Tests: 45/45 microlensing tests still pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both the transit report (report.py) and the microlensing report (microlensing_report.py) were rendering Greek letters, superscripts, and astronomy symbols (θ, Δ, μ, ², °, ⊕, ±, ≈, ≥, …) as black squares because Helvetica's built-in WinAnsi subset doesn't cover them. This landed hardest on the microlensing PDF, whose observables + planet-predictions tables use θ_E, μ_rel, M⊕/M♃, ΔBIC, and per-parameter ± error notation on almost every row.
Fix: register DejaVu Sans (regular + bold) as reportlab TrueType fonts on module import, and swap the hardcoded "Helvetica" / "Helvetica-Bold" strings for the resolved font names. DejaVu ships bundled with matplotlib (already a Vetstar dep) so no new install is needed and no system-font path assumption creeps in — production containers on Render and Fly.io will have it automatically.
Changes
ensure_dejavu_registered()+font_normal()/font_bold(). Idempotent. Looks up matplotlib.get_data_path()/fonts/ttf/DejaVuSans{,-Bold}.ttf, registers via reportlab.pdfbase.pdfmetrics, and binds a font family so Paragraph inline ... markup uses the bold face. Silent no-op if matplotlib/DejaVu aren't available (falls back to Helvetica with a warning — same behaviour as before, no regression).Verified via pypdf text extraction: Greek + symbol glyphs that appear in the microlensing PDF (θ, Δ, σ, °, ⊕, ±, ≈, μ, ≥) now round-trip correctly through the DejaVuSans font stream — no black squares. PDF font resource dictionary shows /AAAAAA+DejaVuSans and /AAAAAA+DejaVuSans-Bold (subsetted embeddings) alongside the inescapable base14 /Helvetica reportlab always writes.
Tests: 45/45 microlensing tests still pass.