2.6 KiB
libgdx-0004: Kerning.readSubtable2 — O(N²) IntArray.contains() in GPOS coverage loop
Severity: MEDIUM File: extensions/gdx-tools/src/com/badlogic/gdx/tools/hiero/Kerning.java Line: 236–244 Status: PATCHED
Description
In Kerning.java, the GPOS lookup type 2 (pair adjustment / class-based kerning) handler
at lines 236–244 iterates over every covered glyph and, for each glyph, performs a linear
scan through all class-1 glyph arrays to find which class the glyph belongs to.
IntArray.contains(int) is an O(K) linear scan. With C coverage glyphs and N class-1
definitions each containing an average of G glyphs, total cost is O(C × N × G) —
cubic in glyph/class count.
This runs at font load time inside the Hiero bitmap font tool, but also inside any
Kerning.load() call at runtime. Fonts with large kern class tables (e.g. professional
typefaces with 200+ class-1 groups and 5000+ coverage glyphs) will experience
multi-second hangs on a path that should be sub-millisecond.
Root Cause
// Kerning.java:236-244
for (int i = 0; i < coverage.length; i++) {
int glyph = coverage[i];
boolean found = false;
for (int j = 1; j < class1Count && !found; j++) {
found = glyphsByClass1[j].contains(glyph); // O(K) linear scan per class
}
if (!found) {
glyphsByClass1[0].add(glyph);
}
}
IntArray.contains(int) iterates the entire backing int[] array. No IntSet (libGDX's
O(1) integer hash set) is used.
Fix
Build a single int[] glyphToClass1 array indexed by glyph code (or an IntIntMap)
once during readClassDefinition, then use O(1) lookup to check/assign class membership.
// After readClassDefinition, build reverse map:
IntIntMap glyphToClass1 = new IntIntMap();
for (int c = 0; c < class1Count; c++) {
IntArray glyphs = glyphsByClass1[c];
for (int k = 0; k < glyphs.size; k++)
glyphToClass1.put(glyphs.items[k], c);
}
// Replace O(C × N × G) loop with O(C):
for (int i = 0; i < coverage.length; i++) {
int glyph = coverage[i];
if (!glyphToClass1.containsKey(glyph)) { // O(1)
glyphsByClass1[0].add(glyph);
glyphToClass1.put(glyph, 0);
}
}
Speedup
| Coverage glyphs | Class-1 groups | Avg glyphs/class | Before | After |
|---|---|---|---|---|
| 500 | 50 | 20 | 500 000 ops | ~500 ops |
| 2 000 | 200 | 50 | 20 000 000 ops | ~2 000 ops |
Estimated 1000x speedup for professional typefaces with large kerning class tables.