hyphenate text fix for desktop

This commit is contained in:
Sergey Chernov 2026-05-17 02:48:53 +03:00
parent 3fd6606077
commit 14c9863a83
2 changed files with 62 additions and 4 deletions

View File

@ -26,15 +26,22 @@ import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
@ -48,6 +55,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import net.sergeych.toread.fb2.Fb2Block import net.sergeych.toread.fb2.Fb2Block
import net.sergeych.toread.fb2.Fb2Book import net.sergeych.toread.fb2.Fb2Book
@ -57,6 +65,9 @@ import net.sergeych.toread.fb2.Fb2Text
import net.sergeych.toread.fb2.Fb2TextSpan import net.sergeych.toread.fb2.Fb2TextSpan
import net.sergeych.toread.fb2.Fb2TextStyle import net.sergeych.toread.fb2.Fb2TextStyle
import net.sergeych.toread.text.HyphenationRegistry import net.sergeych.toread.text.HyphenationRegistry
import net.sergeych.toread.text.SoftHyphen
import kotlin.math.max
import kotlin.math.min
@Composable @Composable
internal fun ContinuousBookReader( internal fun ContinuousBookReader(
@ -343,11 +354,50 @@ private fun ReaderText(
textAlign: TextAlign, textAlign: TextAlign,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val annotatedText = text.toAnnotatedString(language, hyphenation)
val needsSoftHyphenPaintWorkaround = isDesktopPlatform()
var textLayout by remember(annotatedText) { mutableStateOf<TextLayoutResult?>(null) }
val desktopHyphenColor = MaterialTheme.colorScheme.onSurface
val desktopHyphenGutter = 8.dp
val textModifier = modifier
.fillMaxWidth()
.then(
if (needsSoftHyphenPaintWorkaround) {
Modifier.drawWithContent {
drawContent()
val layout = textLayout ?: return@drawWithContent
val layoutText = layout.layoutInput.text.text
val fontSizePx = if (style.fontSize.isSpecified) style.fontSize.toPx() else 18.sp.toPx()
val hyphenLength = fontSizePx * 0.36f
val strokeWidth = max(1f, fontSizePx * 0.055f)
for (line in 0 until layout.lineCount) {
if (!layout.endsAtSoftHyphen(layoutText, line)) continue
val lineRight = layout.getLineRight(line)
val x = min(lineRight + hyphenLength * 0.12f, size.width - hyphenLength)
val y = layout.getLineBaseline(line) - fontSizePx * 0.32f
drawLine(
color = desktopHyphenColor,
start = Offset(x, y),
end = Offset(x + hyphenLength, y),
strokeWidth = strokeWidth,
cap = StrokeCap.Square,
)
}
}.padding(end = desktopHyphenGutter)
} else {
Modifier
},
)
Text( Text(
text = text.toAnnotatedString(language, hyphenation), text = annotatedText,
style = style, style = style,
textAlign = textAlign, textAlign = textAlign,
modifier = modifier.fillMaxWidth(), modifier = textModifier,
onTextLayout = { textLayout = it },
) )
} }
@ -364,6 +414,14 @@ private fun readerParagraphTextStyle(language: String?): TextStyle =
private fun isAndroidPlatform(): Boolean = private fun isAndroidPlatform(): Boolean =
getPlatform().name.startsWith("Android") getPlatform().name.startsWith("Android")
private fun isDesktopPlatform(): Boolean =
getPlatform().name.startsWith("Java")
private fun TextLayoutResult.endsAtSoftHyphen(text: String, line: Int): Boolean {
val end = getLineEnd(line, visibleEnd = false)
return text.getOrNull(end - 1) == SoftHyphen || text.getOrNull(end) == SoftHyphen
}
@Composable @Composable
private fun BookImage( private fun BookImage(
book: Fb2Book, book: Fb2Book,

View File

@ -11,13 +11,13 @@ androidx-lifecycle = "2.10.0"
androidx-testExt = "1.3.0" androidx-testExt = "1.3.0"
composeHotReload = "1.1.0" composeHotReload = "1.1.0"
composeMaterialIcons = "1.7.3" composeMaterialIcons = "1.7.3"
composeMultiplatform = "1.10.3" composeMultiplatform = "1.11.0"
junit = "4.13.2" junit = "4.13.2"
kotlin = "2.3.21" kotlin = "2.3.21"
kotlinx-coroutines = "1.10.2" kotlinx-coroutines = "1.10.2"
ktor = "3.4.3" ktor = "3.4.3"
logback = "1.5.32" logback = "1.5.32"
material3 = "1.10.0-alpha05" material3 = "1.11.0-alpha07"
h2 = "2.4.240" h2 = "2.4.240"
[libraries] [libraries]