From edbddead8f2b026c28147fa696fad66ca9e8af2c Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Mon, 16 Sep 2024 19:46:06 +0200 Subject: [PATCH 1/3] Update mpp demo --- .../compose/mpp/demo/FontRasterization.kt | 76 ------- .../androidx/compose/mpp/demo/MainScreen.kt | 3 - .../compose/mpp/demo/components/Components.kt | 2 + .../{ => components/text}/FontFamilies.kt | 51 +++-- .../demo/components/text/FontRasterization.kt | 110 ++++++++++ .../components/text/LineHeightStyleDemo.kt | 101 +++++++++ .../demo/components/text/TextDemoMetrics.kt | 195 ++++++++++++++++++ .../mpp/demo/components/text/TextDemos.kt | 27 +++ .../{ => components/text}/TextDirection.kt | 2 +- 9 files changed, 460 insertions(+), 107 deletions(-) delete mode 100644 compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/FontRasterization.kt rename compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/{ => components/text}/FontFamilies.kt (64%) create mode 100644 compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/FontRasterization.kt create mode 100644 compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/LineHeightStyleDemo.kt create mode 100644 compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemoMetrics.kt create mode 100644 compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemos.kt rename compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/{ => components/text}/TextDirection.kt (99%) diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/FontRasterization.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/FontRasterization.kt deleted file mode 100644 index 67f872caceb10..0000000000000 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/FontRasterization.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.mpp.demo - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.BasicText -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.ExperimentalTextApi -import androidx.compose.ui.text.FontHinting -import androidx.compose.ui.text.FontRasterizationSettings -import androidx.compose.ui.text.FontSmoothing -import androidx.compose.ui.text.PlatformParagraphStyle -import androidx.compose.ui.text.PlatformTextStyle -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.dp - -@OptIn(ExperimentalTextApi::class) -@Composable -fun FontRasterization() { - MaterialTheme { - val state = rememberScrollState() - Column(Modifier.fillMaxSize().padding(10.dp).verticalScroll(state)) { - val hintingOptions = listOf(FontHinting.None, FontHinting.Slight, FontHinting.Normal, FontHinting.Full) - val isAutoHintingForcedOptions = listOf(false, true) - val subpixelOptions = listOf(false, true) - val smoothingOptions = listOf(FontSmoothing.None, FontSmoothing.AntiAlias, FontSmoothing.SubpixelAntiAlias) - val text = "Lorem ipsum" - - for (subpixel in subpixelOptions) { - for (smoothing in smoothingOptions) { - for (hinting in hintingOptions) { - for (autoHintingForced in isAutoHintingForcedOptions) { - val fontRasterizationSettings = FontRasterizationSettings( - smoothing = smoothing, - hinting = hinting, - subpixelPositioning = subpixel, - autoHintingForced = autoHintingForced - ) - BasicText( - text = "$text [$fontRasterizationSettings]", - style = TextStyle( - platformStyle = PlatformTextStyle( - null, PlatformParagraphStyle(fontRasterizationSettings) - ) - ) - ) - Spacer(Modifier.height(8.dp)) - } - } - } - } - } - } -} \ No newline at end of file diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/MainScreen.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/MainScreen.kt index 0a7e710478d90..ffe7cd7b89e68 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/MainScreen.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/MainScreen.kt @@ -27,13 +27,10 @@ val MainScreen = Screen.Selection( BugReproducers, Screen.Example("Example1") { Example1() }, Screen.Example("ImageViewer") { ImageViewer() }, - Screen.Example("TextDirection") { TextDirection() }, - Screen.Example("FontFamilies") { FontFamilies() }, Screen.Example("LottieAnimation") { LottieAnimation() }, Screen.Fullscreen("ApplicationLayouts") { ApplicationLayouts(it) }, Screen.Example("GraphicsLayerSettings") { GraphicsLayerSettings() }, Screen.Example("Blending") { Blending() }, - Screen.Example("FontRasterization") { FontRasterization() }, Screen.Example("InteropOrder") { InteropOrder() }, AndroidTextFieldSamples, Screen.Example("Android TextBrushDemo") { TextBrushDemo() }, diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt index fdfd13805e7c1..619ae8408ba9e 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt @@ -30,6 +30,7 @@ import androidx.compose.mpp.demo.components.material3.ModalNavigationDrawerExamp import androidx.compose.mpp.demo.components.material3.SearchBarExample import androidx.compose.mpp.demo.components.material3.WindowSizeClassExample import androidx.compose.mpp.demo.components.popup.Popups +import androidx.compose.mpp.demo.components.text.TextDemos import androidx.compose.mpp.demo.textfield.TextFields import androidx.compose.runtime.Composable @@ -56,6 +57,7 @@ val Components = Screen.Selection( "Components", Popups, Dialogs, + TextDemos, TextFields, LazyLayouts, MaterialComponents, diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/FontFamilies.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/FontFamilies.kt similarity index 64% rename from compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/FontFamilies.kt rename to compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/FontFamilies.kt index f22f161f54cea..5ee46a5223b93 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/FontFamilies.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/FontFamilies.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.compose.mpp.demo +package androidx.compose.mpp.demo.components.text import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -29,27 +29,23 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp - @Composable fun FontFamilies() { - val state = rememberScrollState() - MaterialTheme { - Column( - modifier = Modifier - .fillMaxSize() - .padding(10.dp) - .verticalScroll(state), - verticalArrangement = Arrangement - .spacedBy(10.dp) - ) { - for (fontFamily in listOf( - FontFamily.SansSerif, - FontFamily.Serif, - FontFamily.Monospace, - FontFamily.Cursive - )) { - FontFamilyShowcase(fontFamily) - } + Column( + modifier = Modifier + .fillMaxSize() + .padding(10.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement + .spacedBy(10.dp) + ) { + for (fontFamily in listOf( + FontFamily.SansSerif, + FontFamily.Serif, + FontFamily.Monospace, + FontFamily.Cursive + )) { + FontFamilyShowcase(fontFamily) } } } @@ -60,15 +56,16 @@ private fun FontFamilyShowcase(fontFamily: FontFamily) { Text( text = "$fontFamily" ) - Text( - text = "The quick brown fox jumps over the lazy dog.", + val textStyle = MaterialTheme.typography.h3.copy( fontFamily = fontFamily, - style = MaterialTheme.typography.h3 ) - Text( + TextWithMetrics( + text = "The quick brown fox jumps over the lazy dog.", + style = textStyle + ) + TextWithMetrics( text = "1234567890", - fontFamily = fontFamily, - style = MaterialTheme.typography.h3 + style = textStyle ) } } diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/FontRasterization.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/FontRasterization.kt new file mode 100644 index 0000000000000..f3a00d1fda6d5 --- /dev/null +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/FontRasterization.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.mpp.demo.components.text + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.FontHinting +import androidx.compose.ui.text.FontRasterizationSettings +import androidx.compose.ui.text.FontSmoothing +import androidx.compose.ui.text.PlatformParagraphStyle +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalTextApi::class) +@Composable +fun FontRasterization() { + val state = rememberScrollState() + Column( + modifier = Modifier.fillMaxSize().padding(10.dp).verticalScroll(state), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + val hintingOptions = listOf(FontHinting.None, FontHinting.Slight, FontHinting.Normal, FontHinting.Full) + val isAutoHintingForcedOptions = listOf(false, true) + val subpixelOptions = listOf(false, true) + val smoothingOptions = listOf(FontSmoothing.None, FontSmoothing.AntiAlias, FontSmoothing.SubpixelAntiAlias) + val text = "Lorem ipsum" + + for (hinting in hintingOptions) { + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + for (smoothing in smoothingOptions) { + Column( + modifier = Modifier.weight(1f).border(1.dp, Color.Black).padding(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + for (subpixel in subpixelOptions) { + for (autoHintingForced in isAutoHintingForcedOptions) { + FontRasterizationSample( + text = text, + modifier = Modifier.offset(x = 0.25.dp, y = 0.25.dp), + hinting = hinting, + smoothing = smoothing, + subpixelPositioning = subpixel, + autoHintingForced = autoHintingForced + ) + } + } + } + } + } + } + } +} + +@OptIn(ExperimentalTextApi::class) +@Composable +private fun FontRasterizationSample( + text: String, + modifier: Modifier, + hinting: FontHinting, + smoothing: FontSmoothing, + subpixelPositioning: Boolean, + autoHintingForced: Boolean +) { + BasicText( + text = text, + modifier = modifier, + style = TextStyle( + platformStyle = PlatformTextStyle( + spanStyle = null, + paragraphStyle = PlatformParagraphStyle( + fontRasterizationSettings = FontRasterizationSettings( + smoothing = smoothing, + hinting = hinting, + subpixelPositioning = subpixelPositioning, + autoHintingForced = autoHintingForced + ) + ) + ) + ) + ) +} diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/LineHeightStyleDemo.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/LineHeightStyleDemo.kt new file mode 100644 index 0000000000000..c56ddd7936bce --- /dev/null +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/LineHeightStyleDemo.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.mpp.demo.components.text + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.text.style.LineHeightStyle.Alignment +import androidx.compose.ui.text.style.LineHeightStyle.Trim +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun LineHeightStyleDemo() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(5.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement + .spacedBy(5.dp) + ) { + for (trim in sequenceOf( + Trim.FirstLineTop, + Trim.LastLineBottom, + Trim.Both, + Trim.None, + )) { + Row( + horizontalArrangement = Arrangement.spacedBy(5.dp), + ) { + for (alignment in sequenceOf( + Alignment.Top, + Alignment.Center, + Alignment.Proportional, + Alignment.Bottom, + )) { + LineHeightStyleShowcase( + modifier = Modifier + .weight(1f) + .border(1.dp, Color.Black) + .padding(5.dp), + lineHeightStyle = LineHeightStyle( + trim = trim, + alignment = alignment + ) + ) + } + } + } + } +} + +@Composable +private fun LineHeightStyleShowcase(modifier: Modifier, lineHeightStyle: LineHeightStyle) { + Column(modifier) { + val labelStyle = TextStyle.Default.copy( + fontSize = 8.sp, + fontFamily = FontFamily.Monospace, + ) + BasicText(lineHeightStyle.alignment.toString(), style = labelStyle) + BasicText(lineHeightStyle.trim.toString(), style = labelStyle) + Spacer(modifier = Modifier.height(5.dp)) + + val demoTextStyle = TextStyle.Default.copy( + fontSize = 36.sp, + lineHeight = 96.sp, + lineHeightStyle = lineHeightStyle, + ) + TextWithMetrics("Lorem Ipsum", style = demoTextStyle ) + } +} + diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemoMetrics.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemoMetrics.kt new file mode 100644 index 0000000000000..71b4828e57f3a --- /dev/null +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemoMetrics.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.mpp.demo.components.text + +import androidx.compose.foundation.background +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.toSize + +@Composable +internal fun TextWithMetrics( + text: String, + modifier: Modifier = Modifier, + style: TextStyle = TextStyle.Default, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + minLines: Int = 1, + colors: TextMetricColors? = null +) { + val textLayout = remember { mutableStateOf(null) } + BasicText( + text = text, + modifier = modifier.drawTextMetrics(textLayout.value, colors), + style = style, + onTextLayout = { textLayout.value = it }, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + minLines = minLines, + ) +} + +@Composable +internal fun TextFieldWithMetrics( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + style: TextStyle, + maxLines: Int, + softWrap: Boolean = true, + colors: TextMetricColors? = null +) { + var textLayout by remember { mutableStateOf(null) } + + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = Modifier.drawTextMetrics(textLayout, colors).background(Color.White), + textStyle = style, + singleLine = !softWrap, + maxLines = maxLines, + onTextLayout = { + textLayout = it + } + ) +} + +internal class TextMetricColors( + val background: Color = WinterDoldrums, + val text: Color = BlackInk, + val top: Color = MadMagenta, + val bottom: Color = MandarinOrange, + val ascent: Color = BlueBlue, + val descent: Color = YellowYellow, + val baseline: Color = RedRed, + val border: Color = Silver, + val leftRight: Color = CherryTomato +) { + companion object { + private val WinterDoldrums = Color(0xfff5f2eb) + private val BlackInk = Color(0xff44413c) + private val MadMagenta = Color(0xffce5ec9) + private val CherryTomato = Color(0xffba2710) + private val MandarinOrange = Color(0xffff7800) + private val Silver = Color(0xffbdbdbd) + private val RedRed = Color(0xffff1744) + private val YellowYellow = Color(0xffffeb3b) + private val BlueBlue = Color(0xff2962ff) + + val Default = TextMetricColors() + } +} + +internal fun Modifier.drawTextMetrics( + textLayoutResult: TextLayoutResult?, + colors: TextMetricColors? +) = composed { + val thickness = with(LocalDensity.current) { 1.dp.toPx() } + val textSize = with(LocalDensity.current) { 12.sp.toPx() } + val localColors = colors ?: TextMetricColors.Default + drawWithContent { + drawContent() + TextMetricHelper(thickness, textSize, localColors, this).drawTextLayout(textLayoutResult) + } +} + +private class TextMetricHelper( + val thickness: Float, + val labelSize: Float, + val colors: TextMetricColors = TextMetricColors.Default, + drawScope: DrawScope +) : DrawScope by drawScope { + + private enum class Alignment { Left, Right, Center } + + private val pathEffect = PathEffect.dashPathEffect(floatArrayOf(5f, 5f)) + private val overflow = 3 * thickness + + fun drawTextLayout(textLayout: TextLayoutResult?) { + if (textLayout == null) return + val size = textLayout.size.toSize() + val layoutStart = 0f + val layoutEnd = size.width + val x1 = layoutStart + val x2 = layoutEnd + val textOffset = labelSize + drawRect(colors.border, topLeft = Offset.Zero, size = size, style = Stroke(thickness)) + for (lineIndex in 0 until textLayout.lineCount) { + val lineTop = textLayout.getLineTop(lineIndex) + val lineBottom = textLayout.getLineBottom(lineIndex) + val lineBaseline = textLayout.getLineBaseline(lineIndex) + horizontal(colors.top, x1, x2, lineTop, Alignment.Center, -textOffset) + horizontal(colors.bottom, x1, x2, lineBottom, Alignment.Center, textOffset) + horizontal(colors.baseline, x1, x2, lineBaseline, Alignment.Center, textOffset) + vertical(colors.leftRight, textLayout.getLineLeft(lineIndex), lineTop, lineBottom) + vertical(colors.leftRight, textLayout.getLineRight(lineIndex), lineTop, lineBottom) + } + } + + private fun horizontal( + color: Color, + startX: Float, + endX: Float, + y: Float, + alignment: Alignment = Alignment.Left, + textOffset: Float = 0f + ) { + drawLine( + color = color, + start = Offset(startX - overflow, y), + end = Offset(endX + overflow, y), + strokeWidth = thickness, + pathEffect = pathEffect + ) + val x = when (alignment) { + Alignment.Left -> startX + textOffset + Alignment.Right -> endX - labelSize - textOffset + Alignment.Center -> startX + (endX - startX) / 2f + textOffset + } + } + + private fun vertical(color: Color, x: Float, startY: Float, endY: Float) { + drawLine( + color = color, + start = Offset(x, startY - overflow), + end = Offset(x, endY + overflow), + strokeWidth = thickness, + pathEffect = pathEffect + ) + } +} diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemos.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemos.kt new file mode 100644 index 0000000000000..9a473a616afc7 --- /dev/null +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDemos.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.mpp.demo.components.text + +import androidx.compose.mpp.demo.Screen + +val TextDemos = Screen.Selection( + "Text", + Screen.Example("FontFamilies") { FontFamilies() }, + Screen.Example("FontRasterization") { FontRasterization() }, + Screen.Example("LineHeightStyle") { LineHeightStyleDemo() }, + Screen.Example("TextDirection") { TextDirection() }, +) diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/TextDirection.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDirection.kt similarity index 99% rename from compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/TextDirection.kt rename to compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDirection.kt index ff921f8201985..6ee1186101aba 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/TextDirection.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/text/TextDirection.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.compose.mpp.demo +package androidx.compose.mpp.demo.components.text import androidx.compose.foundation.Canvas import androidx.compose.foundation.border From 3144fda61355be103dd290c8ff04e522fdd5052f Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Mon, 16 Sep 2024 20:05:28 +0200 Subject: [PATCH 2/3] Support lineHeightStyle alignment/topRatio --- .../text/platform/ParagraphBuilder.skiko.kt | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/ParagraphBuilder.skiko.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/ParagraphBuilder.skiko.kt index 31faf9b1ecbc1..dbe2df098efc6 100644 --- a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/ParagraphBuilder.skiko.kt +++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/ParagraphBuilder.skiko.kt @@ -103,6 +103,7 @@ private data class ComputedStyle( var drawStyle: DrawStyle? = null, var blendMode: BlendMode = DrawScope.DefaultBlendMode, var lineHeight: Float? = null, + var topRatio: Float = -1f, ) { constructor( density: Density, @@ -110,8 +111,9 @@ private data class ComputedStyle( brushSize: Size = Size.Unspecified, blendMode: BlendMode = DrawScope.DefaultBlendMode, lineHeight: TextUnit, + lineHeightStyle: LineHeightStyle?, ) : this() { - set(density, spanStyle, brushSize, blendMode, lineHeight) + set(density, spanStyle, brushSize, blendMode, lineHeight, lineHeightStyle) } fun set( @@ -120,6 +122,7 @@ private data class ComputedStyle( brushSize: Size = Size.Unspecified, blendMode: BlendMode = DrawScope.DefaultBlendMode, lineHeight: TextUnit, + lineHeightStyle: LineHeightStyle?, ) { this.textForegroundStyle = spanStyle.textForegroundStyle this.brushSize = brushSize @@ -144,6 +147,8 @@ private data class ComputedStyle( this.lineHeight = if (lineHeight.isSpecified) { lineHeight.toPx(density, spanStyle.fontSize) } else null + val alignment = lineHeightStyle?.alignment ?: LineHeightStyle.Alignment.Proportional + this.topRatio = alignment.topRatio } private val _foregroundPaint = SkiaTextPaint() @@ -214,6 +219,7 @@ private data class ComputedStyle( lineHeight?.let { res.height = it / fontSize } + res.topRatio = topRatio return res } @@ -274,7 +280,14 @@ internal class ParagraphBuilder( initialStyle = textStyle.toSpanStyle().copyWithDefaultFontSize( drawStyle = drawStyle ) - defaultStyle.set(density, initialStyle, brushSize, blendMode, textStyle.lineHeight) + defaultStyle.set( + density = density, + spanStyle = initialStyle, + brushSize = brushSize, + blendMode = blendMode, + lineHeight = textStyle.lineHeight, + lineHeightStyle = textStyle.lineHeightStyle, + ) } fun updateForegroundPaint(paragraph: SkParagraph?) { @@ -469,7 +482,14 @@ internal class ParagraphBuilder( private fun mergeStyles(activeStyles: List): ComputedStyle { // there is always at least one active style - val style = ComputedStyle(density, activeStyles[0], brushSize, blendMode, textStyle.lineHeight) + val style = ComputedStyle( + density = density, + spanStyle = activeStyles[0], + brushSize = brushSize, + blendMode = blendMode, + lineHeight = textStyle.lineHeight, + lineHeightStyle = textStyle.lineHeightStyle + ) for (i in 1 until activeStyles.size) { style.merge(density, activeStyles[i]) } @@ -519,8 +539,6 @@ internal class ParagraphBuilder( pStyle.heightMode = HeightMode.DISABLE_ALL } - // TODO: Support lineHeightStyle.alignment. Currently it's not exposed in skia - pStyle.direction = textDirection.toSkDirection() textStyle.textIndent?.run { with(density) { @@ -544,12 +562,22 @@ internal class ParagraphBuilder( // workaround for https://bugs.chromium.org/p/skia/issues/detail?id=11321 :( internal fun emptyLineMetrics(paragraph: SkParagraph): Array { val metrics = defaultFont.metrics - val heightMultiplier = defaultStyle.lineHeight?.let { - it / defaultStyle.fontSize.toDouble() - } ?: 1.0 - val ascent = metrics.ascent * heightMultiplier // TODO: Support non-proportional alignment - val descent = metrics.descent * heightMultiplier // TODO: Support non-proportional alignment + var ascent = metrics.ascent.toDouble() + var descent = metrics.descent.toDouble() val baseline = paragraph.alphabeticBaseline.toDouble() + val lineHeight = defaultStyle.lineHeight + if (lineHeight != null) { + val topRatio = defaultStyle.topRatio + if (topRatio in 0.0f..1.0f) { + val extraLeading = lineHeight - defaultStyle.fontSize + ascent -= extraLeading * topRatio + descent += extraLeading * (1.0f - topRatio) + } else { + val multiplier = lineHeight / defaultStyle.fontSize + ascent *= multiplier + descent *= multiplier + } + } val height = descent - ascent return arrayOf( LineMetrics( From 32c04b7f46a76e15f26047c2703cbb8c9a0a5a6e Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Wed, 18 Sep 2024 21:21:33 +0200 Subject: [PATCH 3/3] Update skiko to 0.8.14 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf9a09aa6a86a..effb7d5ff0fe7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,7 +54,7 @@ moshi = "1.13.0" protobuf = "3.21.8" paparazzi = "1.0.0" paparazziNative = "2022.1.1-canary-f5f9f71" -skiko = "0.8.13" +skiko = "0.8.14" sqldelight = "1.3.0" retrofit = "2.7.2" wire = "4.5.1"