Skip to content

fix(render-pdf): draw Latin text as letters rather than ligatures - #601

Merged
DemchaAV merged 1 commit into
developfrom
fix/pdf-latin-text-layer-rebased
Aug 25, 2026
Merged

DemchaAV merged 1 commit into
developfrom
fix/pdf-latin-text-layer-rebased

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Rebase of #576 onto develop. Same commit, same content; the only change is the CHANGELOG entry, which now sits inside the existing ### Fixed section of v2.2.1 instead of opening a second one. #576 conflicted only on that file after #596 / #598 / #599 / #600 landed. Opened as a new branch rather than force-pushing fix/pdf-latin-text-layer, because #577 is stacked on it — #576 can be closed as superseded, and #577 retargeted to develop.

Why (from #576, re-verified on today's develop)

The defect is live. Rendering "Platform notification fifty software" and reading the text layer back:

Lato       BROKEN  "Pla orm no fica on fi y so ware"
Carlito    BROKEN  "Pla orm no fica on fi y so ware"
PT Sans / PT Serif / Fira Sans / Ubuntu / Poppins / IBM Plex Serif / Helvetica  — OK

It is not a corner case. Typography(headlineFont, bodyFont, …) puts Lato in the bodyFont slot of 10 of the 17 typography presets, so it is the body text of most shipped templates. Counting the pairs in three committed CV previews:

preview ti tf ft
cv-centered-headline-v2.pdf 0 0 0
cv-engineering-resume-v2.pdf 0 0 0
cv-executive-v2.pdf 0 0 0

Zero across three résumés of English prose — every one of those pairs was dropped. That is what an ATS parser reads.

Verification of this rebase

Full reactor gate on the rebased branch: clean verify over the eight modules — BUILD SUCCESS, qa 785 tests, 0 failures. That matters more than usual here: the 26 visual baselines this PR re-recorded were blessed before #596 (SVG opacity), #598, #599 and #600 landed, and they still match — nothing that landed in between moved those pages.

After installing this branch, the same probe reads back clean on every family, Lato and Carlito included.

Unchanged from #576

64 lines of production in PdfFontLoader.java; PdfLatinLigatureTextLayerTest (render-pdf) and CvPresetTextLayerTest (qa) as the guards; 26 visual baselines and 25 committed example previews re-recorded by the ligature change alone.

PDFBox applies a font's GSUB substitutions itself: a PDType0Font carrying
them rewrites every shown string from characters into glyph identifiers,
and in most of the bundled families `ti`, `tf` and `ft` each became one
glyph. The ToUnicode map is built by reading the font's character map
backwards, where a ligature is reachable from no character at all, so the
entry was absent and both letters were lost on extraction — `Platform`
came back as `Pla orm`. The page looked right, which is why nothing showed
it, and the text layer is what search, copy-and-paste, screen readers and
applicant tracking systems read.

A Latin face is now handed to PDFBox with nothing to substitute. That is
what the engine already assumed: layout measures a string ligature-blind,
so a line drawn with ligatures was narrower than the box measured for it,
and the DOCX and PPTX backends never substituted. Faces whose active
script is one PDFBox shapes — Devanagari, Bengali, Gujarati — keep their
substitutions, because there they are how the script renders.

PDFBox applies ccmp, liga and clig together with no way to keep one
without the others; in the bundled families the Latin ccmp changes
nothing, so only the ligature pairs move.

Tests: PdfLatinLigatureTextLayerTest sweeps all 35 bundled families and
CvPresetTextLayerTest all 16 CV presets, both asserting the probe words
come back whole; without the fix 10 of the 16 presets go red. Full
reactor gate green (762 qa tests); the layered CV and cover-letter visual
baselines and the 25 drifted committed previews were re-recorded.
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.

1 participant