Jason Harrop created FOP-3339:
---------------------------------
Summary: Subsetting a font whose last glyph is empty throws
EOFException, and the font is then not embedded in the PDF
Key: FOP-3339
URL: https://issues.apache.org/jira/browse/FOP-3339
Project: FOP
Issue Type: Bug
Components: font/opentype
Affects Versions: 2.11
Reporter: Jason Harrop
{{GlyfTable.isComposite(int)}} reads a 2-byte {{numberOfContours}} at
{{tableOffset + mtxTab\[i].getOffset()}} without checking the glyph's length. A
glyph whose {{loca}} entry gives it a length of zero has no glyph description
at all, so those two bytes belong to the next glyph — and when the empty glyph
is the *last* in the font and {{glyf}} is the *last* table in the file, they
are past the end of the file:
{noformat}
ERROR org.apache.fop.pdf.PDFFactory - Failed to embed font [...{Carlito Regular}
{metrics-url=null,embed-url=.../Carlito-Regular.ttf,kerning=true,advanced=true}]
Carlito-Regular
java.io.EOFException: Reached EOF, file size=593908
at
org.apache.fop.fonts.truetype.FontFileReader.read(FontFileReader.java:100)
at
org.apache.fop.fonts.truetype.FontFileReader.readTTFUByte(FontFileReader.java:124)
at
org.apache.fop.fonts.truetype.FontFileReader.readTTFShort(FontFileReader.java:140)
at
org.apache.fop.fonts.truetype.FontFileReader.readTTFShort(FontFileReader.java:184)
at org.apache.fop.fonts.truetype.GlyfTable.isComposite(GlyfTable.java:206)
at
org.apache.fop.fonts.truetype.GlyfTable.scanGlyphsRecursively(GlyfTable.java:156)
at
org.apache.fop.fonts.truetype.GlyfTable.populateGlyphsWithComposites(GlyfTable.java:126)
at
org.apache.fop.fonts.truetype.TTFSubSetFile.scanGlyphs(TTFSubSetFile.java:572)
at
org.apache.fop.fonts.truetype.TTFSubSetFile.readFont(TTFSubSetFile.java:481)
at org.apache.fop.pdf.PDFFactory.getFontSubsetBytes(PDFFactory.java:1482)
at org.apache.fop.pdf.PDFFactory.makeFontFile(PDFFactory.java:1406)
at org.apache.fop.pdf.PDFFactory.makeFontDescriptor(PDFFactory.java:1330)
at org.apache.fop.pdf.PDFFactory.makeFont(PDFFactory.java:970)
at org.apache.fop.pdf.PDFResources.addFonts(PDFResources.java:137)
at
org.apache.fop.render.pdf.PDFDocumentHandler.endDocument(PDFDocumentHandler.java:208)
{noformat}
{{PDFFactory.makeFontFile}} catches the exception, logs it and returns null, so
the font descriptor is written with no {{/FontFile2}}. The PDF then *names a
font it does not embed*: it fails PDF/A, and a reader or printer without that
font substitutes another, so the document does not look as it was written.
Nothing else fails, and the text is still drawn, so the defect is easy to miss.
Fonts of this shape are not exotic. The last glyph of *Arimo* and of *Carlito*
— the metric-compatible Arial and Calibri substitutes in Google's croscore and
crosextra families, which most Linux distributions package — is U+00A0 NO-BREAK
SPACE, it is empty, and {{glyf}} is the last table in the file. Whether the
read runs off the end then depends only on how many pad bytes follow, and for
five of the eight faces there are fewer than two:
||file||bytes after the last glyph's offset||{{TTFSubSetFile}} with that glyph||
|Arimo-Regular.ttf|2|ok|
|Arimo-Bold.ttf|0|*EOFException*|
|Arimo-Italic.ttf|1|*EOFException*|
|Arimo-BoldItalic.ttf|1|*EOFException*|
|Carlito-Regular.ttf|0|*EOFException*|
|Carlito-Bold.ttf|2|ok|
|Carlito-Italic.ttf|0|*EOFException*|
|Carlito-BoldItalic.ttf|3|ok|
So any document with a non-breaking space in an Arial or Calibri run loses that
font from the PDF. Measured over a 449-document real-document corpus, 4
documents were affected; in one of them the font that went missing was the body
font of the whole document.
*Reproducer* — subset any font whose last glyph is empty and whose {{glyf}}
table ends the file, with that glyph in the subset:
{code:java}
FontFileReader in = new FontFileReader(new
FileInputStream("Carlito-Regular.ttf"));
Map<Integer, Integer> glyphs = new HashMap<>();
glyphs.put(0, 0);
glyphs.put(2531, 1); // the last glyph, U+00A0, empty
new TTFSubSetFile().readFont(in, "probe", null, glyphs); // EOFException
{code}
(Carlito 1.104: 593,908 bytes, numGlyphs 2532, {{glyf}} at 121,352 of length
472,556 — so the last glyph begins at exactly the end of the file. With glyph
2530 instead of 2531 the same call returns a 4,908-byte subset.)
*Fix* — an empty glyph is never composite, so return false without reading. The
glyph's length is the next {{mtxTab}} entry's offset, or the {{glyf}} table's
own length for the last glyph; {{GlyfTable}}'s constructor is already handed
the {{OFDirTabEntry}} that carries it, so no signature changes and the PCL
callers are unaffected. {{TTFFile.readGlyf}} already guards its own read of
these bytes the same way ({{if (lastLoca != 0 && lastLoca ==
mtxTab\[i].getOffset()) break;}}).
The same guard also fixes an empty glyph in the *middle* of the table, which
was read as the next glyph's {{numberOfContours}} — so an empty glyph
immediately before a composite one was reported composite and then walked as
one.
Patch and two unit tests in the PR.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)