-
-
Save CanYumusak/34e6620f444d5ba0c8f7419362d5d394 to your computer and use it in GitHub Desktop.
public class LabelLayoutModifier( | |
val context: Context, | |
val lineHeight: TextUnit, | |
val style: MyTextStyle, | |
) : LayoutModifier { | |
override fun MeasureScope.measure( | |
measurable: Measurable, | |
constraints: Constraints | |
): MeasureResult { | |
val placeable = measurable.measure(constraints) | |
val lineCount = lineCount(placeable) | |
val fullHeight = (lineHeight.toPx() * lineCount).roundToInt() | |
val fontMetrics = fontMetrics(context, style) | |
val centerOffset = floor((lineHeight.toPx().toDp() - fontMetrics.descent.toDp() + fontMetrics.ascent.toDp()).value / 2f).dp.toPx().toInt() | |
val figmaOffset = fontMetrics.ascent - fontMetrics.top | |
return layout(width = placeable.width, height = fullHeight) { | |
// Alignment lines are recorded with the parents automatically. | |
placeable.placeRelative( | |
x = 0, | |
y = (centerOffset - figmaOffset).toInt() | |
) | |
} | |
} | |
override fun IntrinsicMeasureScope.maxIntrinsicHeight( | |
measurable: IntrinsicMeasurable, | |
width: Int | |
): Int { | |
return ceilToLineHeight(measurable.maxIntrinsicHeight(width)) | |
} | |
override fun IntrinsicMeasureScope.minIntrinsicHeight( | |
measurable: IntrinsicMeasurable, | |
width: Int | |
): Int { | |
return ceilToLineHeight(measurable.minIntrinsicHeight(width)) | |
} | |
override fun IntrinsicMeasureScope.minIntrinsicWidth( | |
measurable: IntrinsicMeasurable, | |
height: Int | |
): Int { | |
return measurable.minIntrinsicWidth(height) | |
} | |
override fun IntrinsicMeasureScope.maxIntrinsicWidth( | |
measurable: IntrinsicMeasurable, | |
height: Int | |
): Int { | |
return measurable.maxIntrinsicWidth(height) | |
} | |
private fun Density.lineCount(placeable: Placeable): Int { | |
val firstToLast = (placeable[LastBaseline] - placeable[FirstBaseline]).toFloat() | |
return (firstToLast / lineHeight.toPx()).roundToInt() + 1 | |
} | |
private fun Density.ceilToLineHeight(value: Int): Int { | |
val lineHeightPx = lineHeight.toPx() | |
return (ceil(value.toFloat() / lineHeightPx) * lineHeightPx).roundToInt() | |
} | |
} | |
private fun Density.fontMetrics(context: Context, textStyle: MyTextStyle): Paint.FontMetrics { | |
val fontResourceId = textStyle.fonts[textStyle.fontWeight]!! | |
val font = ResourcesCompat.getFont(context, fontResourceId) | |
val paint = Paint().also { | |
it.typeface = font | |
it.textSize = textStyle.fontSize.toPx() | |
} | |
return paint.fontMetrics | |
} |
public data class MyTextStyle internal constructor( | |
val fontSize: TextUnit, | |
val fontWeight: FontWeight, | |
val letterSpacing: TextUnit, | |
val lineHeight: TextUnit, | |
) { | |
public val fonts: Map<FontWeight, Int> = mapOf( | |
WaveFontWeight.Demi.fontWeight to R.font.nationale_demi_bold, | |
WaveFontWeight.Bold.fontWeight to R.font.nationale_bold, | |
) | |
private val fontFamily: FontFamily = FontFamily( | |
fonts.map { Font(it.value, it.key) } | |
) | |
internal fun asTextStyle(): TextStyle { | |
return TextStyle( | |
fontSize = fontSize, | |
fontWeight = fontWeight, | |
fontFamily = fontFamily, | |
letterSpacing = letterSpacing, | |
lineHeight = lineHeight, | |
) | |
} | |
} |
Our font has a default line height which is 1.2 times the font size. For example, if the font size is 24px, the default line height would be 28.8px. According to the Figma design, the designer requires a consistent line height of 24px matching the font size of 24px.
In Compose/Android, it's feasible to increase the line height from the default font specification but not reduce it.
Any idea how to achieve this?
Reducing should be just as possible as reducing it. I would highly recommend to not use lineheight equal to the font size though, this is a common design mistake that does not take into account ascending and descending glyphs in the font.
You can read up on the space that fonts take up here: https://dev.to/canyudev/android-and-figma-typography-and-how-to-achieve-100-fidelity-l40
(ignore the calculations, these are pre-Compose-change)
Thanks. You mean, Reducing should be just as possible as increasing it?
Sorry for not getting back to you: Yes, that's aboslutely what i meant.
@CanYumusak Hello, is this available in android view system as well? since i have the same issue, i think i manually have to calculate lineHeight like you did here but for view system.
A lot has changed in recent compose versions, thus this is now available for free without custom layouts. I didn't dig as deep on the view system but parts may be applicable, although I assume it should be tedious
so in compose if i just specify platformStyle = PlatformTextStyle(
includeFontPadding = false,
),
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
), it will match exactly like in figma? without any other calculations?
That's correct! Just make sure to set the line height, if you don't set it it will not be correct
i have also searched up and have seen that such small font mismatches can be due to the Renderer of figma and Android and this is normal, i do not know though if i am being wrong :)
Id recommend reading my article about the topic for details
https://dev.to/canyudev/android-and-figma-typography-and-how-to-achieve-100-fidelity-l40
However you are right, given that these are 2 different renderings it is possible that the result is not sub-pixel similar. I assume that this is good enough for most practical reasons though
yeah, i have already read that article, really interesting and challenging actually. thank you for your answers!
Heyhey,
an important detail with that change is to set the line-height to the correct value specified in Figma. The intrinsic calculation of the LineHeight does not render the same value that Figma gets at.
So please make sure to set the same height.
PS: It also seems like your letterspacing is off