Skip to content

Instantly share code, notes, and snippets.

@CanYumusak
Last active July 29, 2024 07:13
Show Gist options
  • Save CanYumusak/34e6620f444d5ba0c8f7419362d5d394 to your computer and use it in GitHub Desktop.
Save CanYumusak/34e6620f444d5ba0c8f7419362d5d394 to your computer and use it in GitHub Desktop.
LabelLayoutModifier which adjusts Android text placement to match Figma text placement
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,
)
}
}
@CanYumusak
Copy link
Author

Sorry for not getting back to you: Yes, that's aboslutely what i meant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment