fix(verify_pdf): fold LaTeX's typographic substitutions before --contains; guard T1 fontenc for pdflatex (#385, #384) (#458)

`normalize_text()` folded whitespace only, so `--contains` compared what a
user types against what LaTeX renders. The stock CV compiled with the
documented lualatex command turns `'` into U+2019 and `--` into U+2013, so
`--contains "Master's degree"` and `--contains "2016-2024"` both reported
the keyword missing from a document that plainly contains it, through both
extractors. The documented remedy for a missing keyword is to add it, which
is the one thing the ATS section forbids.

Fold both sides at comparison time: NFC, then curly apostrophes and quotes
to ASCII, en/em dashes to `-`, no-break space to space. `--dump-text` still
writes the raw layer - that is what an ATS parses, and the date-range rule
in 05-cv-templates.md needs the raw en-dash visible there.

Separately, pdflatex without T1 font encoding stores accents decomposed
(`e` + U+0300). NFC repairs the pdftotext side of that, but pypdf reads the
same layer as `Z¨ urich` with a spacing accent, which no fold recovers.
moderncv 2.5 loads T1 itself under pdflatex; the apt-packaged 2.3.1 does
not - reproduced by compiling the template against moderncv v2.3.1 with
pdflatex (before: U+0308/U+0300 in pdftotext, `Z¨ urich` in pypdf; after:
U+00FC/U+00E8 in both). The template and the guide's preamble gain
`\ifpdftex\usepackage[T1]{fontenc}\fi`; the lualatex text layer is
byte-identical before and after.

Tests: ten new cases in test_verify_pdf.py (the fold-through and
normalize_text ones fail on the whitespace-only code) and a
test_latex_guidance.py guard that the fontenc line exists and stays inside
the pdflatex branch. framework_version 1.4.3 -> 1.4.4 on 05-cv-templates.md.

Reported and diagnosed by 9scorp4 in Discussions #385 and #384.
This commit is contained in:
Ayobami Adegoke
2026-09-14 18:30:50 +02:00
committed by GitHub
parent c2cd71ddee
commit 73d52e0991
6 changed files with 189 additions and 3 deletions
+40
View File
@@ -136,5 +136,45 @@ class TestAtsExtractionEncoding(unittest.TestCase):
self.assert_pdftotext_commands_pin_utf8(CV_TEMPLATES)
class TestPdflatexFontEncodingGuard(unittest.TestCase):
"""#384: the pdflatex fallback must load T1 fontenc, and only under pdflatex.
Without T1, pdflatex stores accented letters decomposed in the text layer
(`e` + U+0300), so an ATS keyword match on `Genève` fails while the PDF
looks right. moderncv 2.5 loads T1 itself; the apt-packaged 2.3.1 does not.
The line must be guarded so the documented lualatex path is untouched.
"""
GUARDED_FONTENC = re.compile(r"\\ifpdftex\s*\\usepackage\[T1\]\{fontenc\}\s*\\fi")
def assert_has_guarded_fontenc(self, path):
text = path.read_text(encoding="utf-8")
self.assertRegex(
text,
self.GUARDED_FONTENC,
f"{path.name} must carry `\\ifpdftex\\usepackage[T1]{{fontenc}}\\fi` so a "
"pdflatex fallback keeps accents precomposed in the text layer",
)
unguarded = [
f"{path.name}:{lineno}: {line.strip()}"
for lineno, line in enumerate(text.splitlines(), 1)
if "fontenc" in line
and not line.lstrip().startswith("%")
and not self.GUARDED_FONTENC.search(line)
]
self.assertEqual(
unguarded,
[],
"fontenc must stay inside the \\ifpdftex guard - lualatex output "
"must not change:\n" + "\n".join(unguarded),
)
def test_example_cv_guards_fontenc_for_pdflatex(self):
self.assert_has_guarded_fontenc(EXAMPLE_CV)
def test_cv_guide_preamble_guards_fontenc_for_pdflatex(self):
self.assert_has_guarded_fontenc(CV_TEMPLATES)
if __name__ == "__main__":
unittest.main()