-
-
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, | |
| ) | |
| } | |
| } |
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!
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