fix: clamp ratex blocks size
This commit is contained in:
parent
54a2b2750c
commit
c445ba209e
|
|
@ -76,7 +76,6 @@ import com.mikepenz.markdown.model.State
|
||||||
import com.mikepenz.markdown.model.markdownAnnotator
|
import com.mikepenz.markdown.model.markdownAnnotator
|
||||||
import com.mikepenz.markdown.model.markdownInlineContent
|
import com.mikepenz.markdown.model.markdownInlineContent
|
||||||
import com.mikepenz.markdown.model.rememberMarkdownState
|
import com.mikepenz.markdown.model.rememberMarkdownState
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import dev.snipme.highlights.Highlights
|
import dev.snipme.highlights.Highlights
|
||||||
import dev.snipme.highlights.model.SyntaxThemes
|
import dev.snipme.highlights.model.SyntaxThemes
|
||||||
import io.ratex.RaTeXEngine
|
import io.ratex.RaTeXEngine
|
||||||
|
|
@ -90,11 +89,19 @@ import org.intellij.markdown.ast.ASTNode
|
||||||
import org.intellij.markdown.ast.getTextInNode
|
import org.intellij.markdown.ast.getTextInNode
|
||||||
import org.intellij.markdown.flavours.gfm.GFMElementTypes
|
import org.intellij.markdown.flavours.gfm.GFMElementTypes
|
||||||
import org.intellij.markdown.parser.MarkdownParser
|
import org.intellij.markdown.parser.MarkdownParser
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
private data class MathEntry(val key: String, val latex: String, val displayMode: Boolean)
|
private data class MathEntry(val key: String, val latex: String, val displayMode: Boolean)
|
||||||
|
|
||||||
private val mathSizeCache = ConcurrentHashMap<Triple<String, Boolean, Float>, Size>()
|
private val mathSizeCache = ConcurrentHashMap<Triple<String, Boolean, Float>, Size>()
|
||||||
|
|
||||||
|
// Hard-cap ratex blocks size so adversarial expressions can't crash the layout engine
|
||||||
|
private const val MAX_MATH_DIMENSION_PX = 4096f
|
||||||
|
private fun Size.clampForLayout(): Size = Size(
|
||||||
|
width.coerceIn(0f, MAX_MATH_DIMENSION_PX),
|
||||||
|
height.coerceIn(0f, MAX_MATH_DIMENSION_PX),
|
||||||
|
)
|
||||||
|
|
||||||
private fun collectMathEntries(node: ASTNode, content: String): List<MathEntry> {
|
private fun collectMathEntries(node: ASTNode, content: String): List<MathEntry> {
|
||||||
val entries = mutableListOf<MathEntry>()
|
val entries = mutableListOf<MathEntry>()
|
||||||
node.children.forEach { child ->
|
node.children.forEach { child ->
|
||||||
|
|
@ -103,10 +110,12 @@ private fun collectMathEntries(node: ASTNode, content: String): List<MathEntry>
|
||||||
val latex = child.getTextInNode(content).toString().removeSurrounding("$")
|
val latex = child.getTextInNode(content).toString().removeSurrounding("$")
|
||||||
entries += MathEntry("math:i:$latex", latex, false)
|
entries += MathEntry("math:i:$latex", latex, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
GFMElementTypes.BLOCK_MATH -> {
|
GFMElementTypes.BLOCK_MATH -> {
|
||||||
val latex = child.getTextInNode(content).toString().removeSurrounding("$$").trim()
|
val latex = child.getTextInNode(content).toString().removeSurrounding("$$").trim()
|
||||||
entries += MathEntry("math:b:$latex", latex, true)
|
entries += MathEntry("math:b:$latex", latex, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> entries += collectMathEntries(child, content)
|
else -> entries += collectMathEntries(child, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +126,9 @@ private fun collectEmoteUlids(node: ASTNode, content: String): List<String> {
|
||||||
val ulids = mutableListOf<String>()
|
val ulids = mutableListOf<String>()
|
||||||
node.children.forEach { child ->
|
node.children.forEach { child ->
|
||||||
when (child.type) {
|
when (child.type) {
|
||||||
CUSTOM_EMOTE_ELEMENT_TYPE -> ulids += child.getTextInNode(content).toString().removeSurrounding(":")
|
CUSTOM_EMOTE_ELEMENT_TYPE -> ulids += child.getTextInNode(content).toString()
|
||||||
|
.removeSurrounding(":")
|
||||||
|
|
||||||
else -> ulids += collectEmoteUlids(child, content)
|
else -> ulids += collectEmoteUlids(child, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -212,11 +223,16 @@ fun ChatMarkdown(
|
||||||
var mathSizes by remember(mathEntries, fontSizePx) {
|
var mathSizes by remember(mathEntries, fontSizePx) {
|
||||||
mutableStateOf(buildMap {
|
mutableStateOf(buildMap {
|
||||||
mathEntries.forEach { entry ->
|
mathEntries.forEach { entry ->
|
||||||
mathSizeCache[Triple(entry.latex, entry.displayMode, fontSizePx)]?.let { put(entry.key, it) }
|
mathSizeCache[Triple(
|
||||||
|
entry.latex,
|
||||||
|
entry.displayMode,
|
||||||
|
fontSizePx
|
||||||
|
)]?.let { put(entry.key, it) }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
val uncachedEntries = mathEntries.filter { mathSizeCache[Triple(it.latex, it.displayMode, fontSizePx)] == null }
|
val uncachedEntries =
|
||||||
|
mathEntries.filter { mathSizeCache[Triple(it.latex, it.displayMode, fontSizePx)] == null }
|
||||||
LaunchedEffect(mathEntries, fontSizePx) {
|
LaunchedEffect(mathEntries, fontSizePx) {
|
||||||
if (uncachedEntries.isEmpty()) return@LaunchedEffect
|
if (uncachedEntries.isEmpty()) return@LaunchedEffect
|
||||||
withContext(Dispatchers.IO) { RaTeXFontLoader.ensureLoaded(context) }
|
withContext(Dispatchers.IO) { RaTeXFontLoader.ensureLoaded(context) }
|
||||||
|
|
@ -224,12 +240,14 @@ fun ChatMarkdown(
|
||||||
buildMap {
|
buildMap {
|
||||||
uncachedEntries.forEach { entry ->
|
uncachedEntries.forEach { entry ->
|
||||||
try {
|
try {
|
||||||
val dl = RaTeXEngine.parseBlocking(entry.latex, entry.displayMode, onSurfaceArgb)
|
val dl =
|
||||||
|
RaTeXEngine.parseBlocking(entry.latex, entry.displayMode, onSurfaceArgb)
|
||||||
val r = RaTeXRenderer(dl, fontSizePx) { RaTeXFontLoader.getTypeface(it) }
|
val r = RaTeXRenderer(dl, fontSizePx) { RaTeXFontLoader.getTypeface(it) }
|
||||||
val size = Size(r.widthPx, r.totalHeightPx)
|
val size = Size(r.widthPx, r.totalHeightPx).clampForLayout()
|
||||||
mathSizeCache[Triple(entry.latex, entry.displayMode, fontSizePx)] = size
|
mathSizeCache[Triple(entry.latex, entry.displayMode, fontSizePx)] = size
|
||||||
put(entry.key, size)
|
put(entry.key, size)
|
||||||
} catch (_: Exception) { }
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +278,8 @@ fun ChatMarkdown(
|
||||||
}
|
}
|
||||||
|
|
||||||
GFMElementTypes.BLOCK_MATH -> {
|
GFMElementTypes.BLOCK_MATH -> {
|
||||||
val latex = child.getTextInNode(content).toString().removeSurrounding("$$").trim()
|
val latex =
|
||||||
|
child.getTextInNode(content).toString().removeSurrounding("$$").trim()
|
||||||
if (latex.isNotEmpty()) appendInlineContent("math:b:$latex", latex)
|
if (latex.isNotEmpty()) appendInlineContent("math:b:$latex", latex)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -457,61 +476,70 @@ fun ChatMarkdown(
|
||||||
} else {
|
} else {
|
||||||
fontSize * 4f to fontSize * 1.5f
|
fontSize * 4f to fontSize * 1.5f
|
||||||
}
|
}
|
||||||
put(entry.key, InlineTextContent(
|
put(
|
||||||
Placeholder(
|
entry.key, InlineTextContent(
|
||||||
width = widthSp,
|
Placeholder(
|
||||||
height = heightSp,
|
width = widthSp,
|
||||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
height = heightSp,
|
||||||
)
|
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
||||||
) { latex ->
|
)
|
||||||
val foreground = LocalContentColor.current
|
) { latex ->
|
||||||
AndroidView(
|
val foreground = LocalContentColor.current
|
||||||
factory = { ctx -> RaTeXView(ctx) },
|
AndroidView(
|
||||||
update = { view ->
|
factory = { ctx -> RaTeXView(ctx) },
|
||||||
view.displayMode = entry.displayMode
|
update = { view ->
|
||||||
view.latex = latex
|
view.displayMode = entry.displayMode
|
||||||
view.fontSize = with(density) { fontSize.toPx().toDp().value }
|
view.latex = latex
|
||||||
view.color = foreground.toArgb()
|
view.fontSize =
|
||||||
},
|
with(density) { fontSize.toPx().toDp().value }
|
||||||
modifier = Modifier.fillMaxSize(),
|
view.color = foreground.toArgb()
|
||||||
)
|
},
|
||||||
})
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
emoteUlids.forEach { ulid ->
|
emoteUlids.forEach { ulid ->
|
||||||
put("emote:$ulid", InlineTextContent(
|
put(
|
||||||
Placeholder(
|
"emote:$ulid", InlineTextContent(
|
||||||
width = fontSize * 1.5f,
|
Placeholder(
|
||||||
height = fontSize * 1.5f,
|
width = fontSize * 1.5f,
|
||||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
height = fontSize * 1.5f,
|
||||||
)
|
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
||||||
) { _ ->
|
)
|
||||||
val emote = StoatAPI.emojiCache[ulid]
|
) { _ ->
|
||||||
if (emote == null) {
|
val emote = StoatAPI.emojiCache[ulid]
|
||||||
LaunchedEffect(ulid) {
|
if (emote == null) {
|
||||||
try {
|
LaunchedEffect(ulid) {
|
||||||
StoatAPI.emojiCache[ulid] = fetchEmoji(ulid)
|
try {
|
||||||
} catch (_: Exception) {
|
StoatAPI.emojiCache[ulid] = fetchEmoji(ulid)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
with(LocalDensity.current) {
|
||||||
|
RemoteImage(
|
||||||
|
url = "$STOAT_FILES/emojis/$ulid",
|
||||||
|
description = emote.name,
|
||||||
|
contentScale = ContentScale.Fit,
|
||||||
|
modifier = Modifier
|
||||||
|
.width((fontSize * 1.5f).toDp())
|
||||||
|
.height((fontSize * 1.5f).toDp())
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
) {
|
||||||
|
scope.launch {
|
||||||
|
ActionChannel.send(
|
||||||
|
Action.EmoteInfo(
|
||||||
|
ulid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
with(LocalDensity.current) {
|
|
||||||
RemoteImage(
|
|
||||||
url = "$STOAT_FILES/emojis/$ulid",
|
|
||||||
description = emote.name,
|
|
||||||
contentScale = ContentScale.Fit,
|
|
||||||
modifier = Modifier
|
|
||||||
.width((fontSize * 1.5f).toDp())
|
|
||||||
.height((fontSize * 1.5f).toDp())
|
|
||||||
.clickable(
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = null,
|
|
||||||
) {
|
|
||||||
scope.launch { ActionChannel.send(Action.EmoteInfo(ulid)) }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue