mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 08:27:04 +00:00
fix(math): collapse over-escaped backslashes in inline equations
Some sources (e.g. the math-academy posts) over-escape their LaTeX, so the content carries `\\ldots` / `\\cdots` instead of `\ldots` / `\cdots`. JLaTeXMath reads the `\\` as a forced TeX line break — splitting every inline equation across two lines — and renders the trailing command name as the literal letters "ldots"/"cdots", so the ellipsis symbol is lost. Collapse doubled backslashes (`\\cmd` -> `\cmd`) before rendering inline math; display math is left untouched since `\\` can be a genuine line break there. No-op when the content isn't over-escaped.
This commit is contained in:
@@ -39,6 +39,7 @@ import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MathParser
|
||||
import ru.noties.jlatexmath.JLatexMathDrawable
|
||||
|
||||
/**
|
||||
@@ -58,6 +59,11 @@ fun LatexEquation(
|
||||
val density = LocalDensity.current
|
||||
val fontSize = LocalTextStyle.current.fontSize
|
||||
|
||||
// Inline math has no legitimate `\\` line break, so a doubled backslash is an
|
||||
// over-escaped command (`\\ldots` → `\ldots`). Without this JLaTeXMath would
|
||||
// read `\\` as a line break and render `ldots`/`cdots` as literal letters.
|
||||
val formula = if (displayMode) latex else MathParser.collapseDoubledBackslashes(latex)
|
||||
|
||||
// Display math renders a touch larger than the surrounding prose, matching
|
||||
// the visual weight KaTeX gives block equations.
|
||||
val textSizePx =
|
||||
@@ -67,10 +73,10 @@ fun LatexEquation(
|
||||
}
|
||||
|
||||
val drawable =
|
||||
remember(latex, color, textSizePx) {
|
||||
remember(formula, color, textSizePx) {
|
||||
runCatching {
|
||||
JLatexMathDrawable
|
||||
.builder(latex)
|
||||
.builder(formula)
|
||||
.textSize(textSizePx)
|
||||
.color(color)
|
||||
.align(JLatexMathDrawable.ALIGN_LEFT)
|
||||
|
||||
+14
@@ -69,6 +69,20 @@ object MathParser {
|
||||
return first >= 0 && line.indexOf('$', first + 1) >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses doubled backslashes (`\\cmd` → `\cmd`).
|
||||
*
|
||||
* Some sources over-escape their LaTeX (every `\` written as `\\`). In inline
|
||||
* math a `\\` is a TeX forced line break that is never intended, so `\\ldots`
|
||||
* really means `\ldots`. Left as-is, a renderer like JLaTeXMath reads `\\` as
|
||||
* a line break — splitting the equation across two lines — and the trailing
|
||||
* command name (`ldots`, `cdots`) as literal letters, so the symbol is lost.
|
||||
*
|
||||
* No-op when the input isn't over-escaped. Only meant for inline math; display
|
||||
* math (`$$…$$`) may use `\\` as a genuine line break in aligned/matrix forms.
|
||||
*/
|
||||
fun collapseDoubledBackslashes(latex: String): String = if (latex.contains("\\\\")) latex.replace("\\\\", "\\") else latex
|
||||
|
||||
/**
|
||||
* Splits [line] on spaces into [Token.Word]s, with any whitespace-delimited
|
||||
* math span surfaced as a [Token.Math].
|
||||
|
||||
+13
@@ -122,6 +122,19 @@ class MathParserTest {
|
||||
assertTrue(math(MathParser.split("empty $d$d here")).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun collapsesOverEscapedBackslashes() {
|
||||
// `\\ldots` (over-escaped) -> `\ldots`; otherwise JLaTeXMath sees a line
|
||||
// break plus the literal letters "ldots".
|
||||
assertEquals("A_1, ${bs}ldots, A_n", MathParser.collapseDoubledBackslashes("A_1, $bs$bs" + "ldots, A_n"))
|
||||
assertEquals("x_1 = ${bs}cdots = x_n", MathParser.collapseDoubledBackslashes("x_1 = $bs$bs" + "cdots = x_n"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleBackslashLatexIsLeftAlone() {
|
||||
assertEquals("${bs}frac{1}{2} + ${bs}sqrt{x}", MathParser.collapseDoubledBackslashes("${bs}frac{1}{2} + ${bs}sqrt{x}"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gluedMathStaysPlainWord() {
|
||||
// Math without a separating space is not whitespace-delimited, so it
|
||||
|
||||
Reference in New Issue
Block a user