fix(math): align inline equations to the text baseline

This commit is contained in:
davotoula
2026-06-09 19:51:31 +02:00
parent 8097e6c52d
commit c8de54d1c2
@@ -36,6 +36,7 @@ import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.sp
@@ -91,12 +92,33 @@ fun LatexEquation(
return
}
// Baseline alignment. The parent FlowRow centers each word vertically, so a
// tight equation bitmap floats above the line — most of a formula's mass sits
// above the baseline, so centering its box lifts the baseline above the text.
// We pad the box so that, once centered, the equation's baseline coincides
// with a surrounding text word's baseline. With text ascent/descent A/Dsc and
// icon total height H and depth-below-baseline Dp, the padded box satisfies
// `baseline-from-center = (A - Dsc) / 2` — the same value a Text box has — when
// pad = (A - Dsc) - H + 2*Dp (on top if positive, on bottom if negative).
val resolvedStyle = LocalTextStyle.current.let { if (fontSize.isUsable()) it else it.copy(fontSize = 16.sp) }
val measurer = rememberTextMeasurer()
val (drawTopPx, boxHeightPx) =
remember(drawable, resolvedStyle) {
val text = measurer.measure("Mg", resolvedStyle)
val ascent = text.firstBaseline
val descent = text.size.height - ascent
val iconHeight = drawable.intrinsicHeight.toFloat()
val iconDepth = drawable.icon().iconDepth.toFloat()
val pad = (ascent - descent) - iconHeight + 2f * iconDepth
maxOf(pad, 0f) to (iconHeight + maxOf(pad, 0f) + maxOf(-pad, 0f))
}
val widthDp = with(density) { drawable.intrinsicWidth.toDp() }
val heightDp = with(density) { drawable.intrinsicHeight.toDp() }
val boxHeightDp = with(density) { boxHeightPx.toDp() }
// Wide display equations can overflow the column; allow them to scroll
// horizontally instead of being clipped.
val equationSize = Modifier.size(widthDp, heightDp)
val equationSize = Modifier.size(widthDp, boxHeightDp)
val equationModifier = if (displayMode) Modifier.horizontalScroll(rememberScrollState()).then(equationSize) else equationSize
// Row keeps leading/trailing punctuation (the `(` and `)` in `($x$)`) hugging
@@ -107,8 +129,13 @@ fun LatexEquation(
}
Canvas(modifier = equationModifier) {
drawIntoCanvas { canvas ->
val native = canvas.nativeCanvas
val checkpoint = native.save()
// Position the icon's baseline on the text baseline within the padded box.
native.translate(0f, drawTopPx)
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.draw(canvas.nativeCanvas)
drawable.draw(native)
native.restoreToCount(checkpoint)
}
}
if (trailing.isNotEmpty()) {