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)

Reply via email to