Skip to content

An all-zero FontBBox produces text chunks of zero height, and the text is then silently discarded #1624

Description

@SangWJDev

Summary

TextChunksHelper.calculateTextBoundingBoxH() falls back to /FontBBox when the font descriptor has no /Ascent and no /Descent. When that FontBBox is [0 0 0 0], both ascent and descent become 0 and every text chunk of that font gets a height of exactly zero.

A font bounding box whose four elements are all zero carries no size information — it does not mean the glyphs have no size. Propagating it as a literal 0 produces a chunk that no consumer can distinguish from a glyph that genuinely has no extent.

Affected code

wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextChunksHelper.java:

double[] fontBoundingBox = font.getBoundingBox();   // [0, 0, 0, 0]
Double descent = font.getDescent();                 // null - no /Descent
if (descent == null) {
    descent = fontBoundingBox[1];                   // -> 0.0
}
Double ascent = font.getAscent();                   // null - no /Ascent
if (ascent == null) {
    ascent = fontBoundingBox[3];                    // -> 0.0
}

Reproducing

repro-issueB-zero-fontbbox.pdf — 1.8 KB, hand-written, readable in a text editor. The generator script and both repro files are here; there are no third-party dependencies.

A single Type 3 font with the conventional /FontMatrix [0.001 0 0 0.001 0 0] and /Widths [500 500 500], so nothing unusual is going on with the font matrix. The only anomaly is:

/FontBBox        [ 0 0 0 0 ]
/FontDescriptor  << /Type /FontDescriptor /FontName /ReproType3B /Flags 32
                    /FontBBox [ 0 0 0 0 ] /ItalicAngle 0 /StemV 80 >>

— all-zero in both places, and no /Ascent, no /Descent. The content stream is a plain Tj with no kerning at all, so issue A cannot interfere.

Result, running the file through a veraPDF-based text consumer (opendataloader-pdf 2.x, which bundles veraPDF 1.31.99):

poppler (pdftotext) : ABC
veraPDF-based       : (empty file - 0 bytes, no exception, no warning, no log entry)

Impact

The chunk itself is produced; it just has height == 0. Consumers that filter by height then drop it. In opendataloader-pdf, TextProcessor.filterTinyText discards any chunk with height <= 1.0:

if (chunk.getBoundingBox().getHeight() <= 1.0) {
    list.set(i, null);
}

The document that led me here is a 156-page PowerPoint export in which all 156 embedded Type 3 fonts have /FontBBox [0 0 0 0] and no /Ascent or /Descent. It extracted 0 characters, and the run was reported as a success — no exception, no warning, nothing in the log. Everything needed to lay the text out is present in the file; it is read and then thrown away. (That document separately also hits #1623; the attached repro file does not, so it isolates this problem.)

I would argue the zero height is wrong regardless of what any particular downstream consumer does with it: a text chunk that reports zero height is stating something about the document that is not true.

Possible fix

Treat "no usable vertical metrics" as unknown rather than as zero, and fall back to a common em split:

if (ascent == 0.0 && descent == 0.0) {
    ascent = 0.8 / verticalScalingFactor;
    descent = -0.2 / verticalScalingFactor;
}

Dividing by the vertical scaling factor expresses the values in glyph space, so this holds for a Type 3 font with any FontMatrix as well as for ordinary fonts.

The exact constants are a proposal, not a claim. 0.8/−0.2 is a widely used default, but any reasonable pair works; the point is not to propagate a meaningless 0. If you would rather derive the extent from the d1 operands in the Type 3 CharProcs, or from /FontMatrix alone, or expose it as a setting, I am happy to rework the patch — I have no attachment to these numbers.

Fix

Proposed in veraPDF/veraPDF-validation#750

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions