Skip to content

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

Open
DemchaAV wants to merge 1 commit into
developfrom
fix/pdf-latin-text-layer
Open

fix(render-pdf): draw Latin text as letters rather than ligatures#576
DemchaAV wants to merge 1 commit into
developfrom
fix/pdf-latin-text-layer

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

Text set in a bundled TrueType family lost letters from its text layer. Platform
extracted as Pla orm, certification as cer fica on, retired as re red.
Nothing on the page showed it, and the text layer is what search, copy-and-paste,
screen readers and applicant tracking systems read — so a CV rendered through one of
these presets did not contain the words printed on it.

PDFBox applies a font's GSUB substitutions itself: PDAbstractContentStream.setFont
registers a GsubWorker for any PDType0Font whose active script is latn, and from
then on every string shown through it is rewritten from characters into glyph
identifiers. In most of the bundled families ti, tf and ft each become one glyph.
The ToUnicode map a subset font carries is built by reading the font's character map
backwards, and a ligature is reachable from no character at all, so the entry is simply
absent and both letters are lost. The families whose ligatures happen to have code
points of their own (fi, fl) survived, which is why the damage looked arbitrary.

What changed

PdfFontLoader hands a Latin face to PDFBox with nothing to substitute
(TrueTypeFont.setEnableGsub(false)), at the single seam both the render and the
measurement pipelines resolve their faces through.

That is also what the engine already assumed. Layout measures a string with
PDFont.getStringWidth, which knows nothing of ligatures, so a line drawn with them was
narrower than the box measured for it — Lato's tf ligature is 64 font units short of
t + f. The DOCX and PPTX backends never substituted either.

Only Latin is silenced. PDFBox also shapes Devanagari, Bengali and Gujarati through the
same mechanism, and there the substitutions are how the script renders rather than a
flourish on top of it, so a face whose active script is one of those keeps them
(Poppins carries Devanagari and is pinned as the negative case).

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: decomposed combining
sequences (e + U+0301) and precomposed letters are drawn byte-identically with GSUB
off — only the ligature pairs move.

Visible consequence: text in a bundled family no longer forms ligatures, so fi and
fl are drawn as two letters.

Verification

./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -am — BUILD SUCCESS, 762 qa tests.
./mvnw -B -ntp javadoc:javadoc -pl :graph-compose-core — BUILD SUCCESS.

  • PdfLatinLigatureTextLayerTest (render-pdf, 4 tests) renders a probe sentence through
    all 35 bundled families and asserts the whole sentence comes back from
    PDFTextStripper; it also pins that a Latin face reports no substitutions and that a
    Devanagari-carrying face keeps its own.
  • CvPresetTextLayerTest (qa, 16 tests) does the same through every layered CV preset.
    With the fix reverted, 10 of the 16 go red — the same count as the presets reported
    broken.
  • The layered CV and cover-letter visual baselines (37 PNGs) and the 25 committed
    previews the drift gate named were re-recorded; every other baseline and preview is
    byte-unchanged.

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