Describe the bug
When rendering a text layer via LottieAnimationView#setFontMap / LottieDrawable#setFontMap (the path taken whenever the layer isn't exported as glyph shapes), text is drawn one character at a time: TextLayer#drawFontTextLine calls fillPaint.measureText(charString) on a single character, draws it, then manually canvas.translate()s by that character's width before drawing the next one.
Because each character is measured and drawn independently, none of the mapped font's OpenType pair kerning is ever applied — kerning is inherently a property of adjacent-character pairs, and it can't be produced by measuring/drawing one character in isolation. This holds regardless of tracking (tr), which is a separate, uniform per-character offset applied on top — it can't reproduce specific pair kerning either.
This makes any custom font mapped in via fontMap render measurably looser than the same font rendered normally by the OS (e.g. in a TextView/Compose Text).
Minimal repro
Using stock Roboto Bold (Apache-2.0, ships on every Android device / available at https://github.com/googlefonts/roboto) — no custom font needed to reproduce this.
repro.json:
{
"v": "5.9.6",
"fr": 24,
"ip": 0,
"op": 48,
"w": 500,
"h": 200,
"nm": "fontMap-kerning-repro",
"ddd": 0,
"assets": [],
"fonts": {
"list": [
{ "origin": 0, "fPath": "", "fClass": "", "fFamily": "Roboto", "fWeight": "", "fStyle": "Bold", "fName": "Roboto-Bold", "ascent": 75 }
]
},
"layers": [
{
"ddd": 0, "ind": 1, "ty": 5, "nm": "Total", "sr": 1,
"ks": {
"o": { "a": 0, "k": 100 }, "r": { "a": 0, "k": 0 },
"p": { "a": 0, "k": [40, 120, 0] }, "a": { "a": 0, "k": [0, 0, 0] },
"s": { "a": 0, "k": [100, 100, 100] }
},
"ao": 0,
"t": {
"d": { "k": [ { "s": { "s": 60, "f": "Roboto-Bold", "t": "Total", "j": 0, "tr": 0, "lh": 72, "ls": 0, "fc": [0, 0, 0] }, "t": 0 } ] },
"p": {}, "m": { "g": 1, "a": { "a": 0, "k": [0, 0] } }, "a": []
},
"ip": 0, "op": 48, "st": 0, "bm": 0
}
]
}
val fontMap = mapOf("Roboto-Bold" to Typeface.create("sans-serif", Typeface.BOLD))
// Load repro.json via LottieCompositionFactory, set this fontMap on the LottieDrawable/LottieAnimationView.
- The font's own
To kern pair is −208/2048 em (Roboto Bold, verified via fontTools) — at 60px that's −6.09px that a correctly-kerned renderer would subtract between T and o.
- Rendered through
fontMap, the full unkerned advance is used instead — the word comes out ~6px wider/looser than the same text drawn with canvas.drawText("Total", x, y, paint) (or a plain TextView/Compose Text using the same font/size), which does apply the kerning.
Expected behavior
For a text layer resolved via fontMap/getTypeface (i.e. rendered with a real Android Typeface, not baked glyph shapes), the whole line should be measured and drawn as one run — e.g. fillPaint.measureText(fullLine) / canvas.drawText(fullLine, x, y, paint) — so Android's normal text shaping (which does apply the font's kerning) can take effect, with per-character tracking/coloring/animation applied on top only where actually needed (e.g. TextRangeUnits/character-level color or position keyframes).
Environment
- lottie-android: 6.7.1
- Affected: any text layer using
fontMap/FontAssetDelegate-resolved Typeface (the non-glyph path); glyph-shape text layers (drawTextWithGlyphs/drawCharacterAsGlyph) are unaffected since they don't rely on font kerning at all.
Describe the bug
When rendering a text layer via
LottieAnimationView#setFontMap/LottieDrawable#setFontMap(the path taken whenever the layer isn't exported as glyph shapes), text is drawn one character at a time:TextLayer#drawFontTextLinecallsfillPaint.measureText(charString)on a single character, draws it, then manuallycanvas.translate()s by that character's width before drawing the next one.Because each character is measured and drawn independently, none of the mapped font's OpenType pair kerning is ever applied — kerning is inherently a property of adjacent-character pairs, and it can't be produced by measuring/drawing one character in isolation. This holds regardless of
tracking(tr), which is a separate, uniform per-character offset applied on top — it can't reproduce specific pair kerning either.This makes any custom font mapped in via
fontMaprender measurably looser than the same font rendered normally by the OS (e.g. in aTextView/ComposeText).Minimal repro
Using stock Roboto Bold (Apache-2.0, ships on every Android device / available at https://github.com/googlefonts/roboto) — no custom font needed to reproduce this.
repro.json:{ "v": "5.9.6", "fr": 24, "ip": 0, "op": 48, "w": 500, "h": 200, "nm": "fontMap-kerning-repro", "ddd": 0, "assets": [], "fonts": { "list": [ { "origin": 0, "fPath": "", "fClass": "", "fFamily": "Roboto", "fWeight": "", "fStyle": "Bold", "fName": "Roboto-Bold", "ascent": 75 } ] }, "layers": [ { "ddd": 0, "ind": 1, "ty": 5, "nm": "Total", "sr": 1, "ks": { "o": { "a": 0, "k": 100 }, "r": { "a": 0, "k": 0 }, "p": { "a": 0, "k": [40, 120, 0] }, "a": { "a": 0, "k": [0, 0, 0] }, "s": { "a": 0, "k": [100, 100, 100] } }, "ao": 0, "t": { "d": { "k": [ { "s": { "s": 60, "f": "Roboto-Bold", "t": "Total", "j": 0, "tr": 0, "lh": 72, "ls": 0, "fc": [0, 0, 0] }, "t": 0 } ] }, "p": {}, "m": { "g": 1, "a": { "a": 0, "k": [0, 0] } }, "a": [] }, "ip": 0, "op": 48, "st": 0, "bm": 0 } ] }Tokern pair is −208/2048 em (Roboto Bold, verified viafontTools) — at 60px that's −6.09px that a correctly-kerned renderer would subtract betweenTando.fontMap, the full unkerned advance is used instead — the word comes out ~6px wider/looser than the same text drawn withcanvas.drawText("Total", x, y, paint)(or a plainTextView/ComposeTextusing the same font/size), which does apply the kerning.Expected behavior
For a text layer resolved via
fontMap/getTypeface(i.e. rendered with a real AndroidTypeface, not baked glyph shapes), the whole line should be measured and drawn as one run — e.g.fillPaint.measureText(fullLine)/canvas.drawText(fullLine, x, y, paint)— so Android's normal text shaping (which does apply the font's kerning) can take effect, with per-character tracking/coloring/animation applied on top only where actually needed (e.g.TextRangeUnits/character-level color or position keyframes).Environment
fontMap/FontAssetDelegate-resolvedTypeface(the non-glyph path); glyph-shape text layers (drawTextWithGlyphs/drawCharacterAsGlyph) are unaffected since they don't rely on font kerning at all.