From 1fba966518c78b5489d87818561dd34d8613d5b8 Mon Sep 17 00:00:00 2001 From: Patrick Michalik <120058021+patrickmichalik@users.noreply.github.com> Date: Sat, 29 Jul 2023 11:44:02 +0000 Subject: [PATCH] `TextComponent`: Fix handling of RTL text Co-authored-by: Patryk Goworowski --- .../vico/compose/axis/AxisComponents.kt | 84 ++++++++++++++----- .../vico/compose/component/Components.kt | 56 ++++++++++--- .../vico/compose/style/ChartStyle.kt | 4 + .../vico/core/component/text/TextComponent.kt | 60 +++++++++++-- .../theme/TextComponentStyleExtensions.kt | 21 +++-- vico/views/src/main/res/values/attrs.xml | 6 ++ 6 files changed, 179 insertions(+), 52 deletions(-) diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/axis/AxisComponents.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/axis/AxisComponents.kt index 67ae84459..0f32541ac 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/axis/AxisComponents.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/axis/AxisComponents.kt @@ -18,6 +18,7 @@ package com.patrykandpatrick.vico.compose.axis import android.graphics.Paint import android.graphics.Typeface +import android.text.Layout import android.text.TextUtils import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Brush @@ -43,19 +44,19 @@ import com.patrykandpatrick.vico.core.dimensions.emptyDimensions public typealias ChartShape = com.patrykandpatrick.vico.core.component.shape.Shape /** - * Creates a label to be displayed on chart axes. + * Creates a [TextComponent] to be used for axis labels. * * @param color the text color. * @param textSize the text size. * @param background a [ShapeComponent] to be displayed behind the text. * @param ellipsize the text truncation behavior. * @param lineCount the line count. - * @param verticalPadding the vertical padding between the text and the background. - * @param horizontalPadding the horizontal padding between the text and the background. - * @param verticalMargin the vertical margin around the background. - * @param horizontalMargin the horizontal margin around the background. + * @param verticalPadding the amount of top and bottom padding between the text and the background. + * @param horizontalPadding the amount of start and end padding between the text and the background. + * @param verticalMargin the size of the top and bottom margins around the background. + * @param horizontalMargin the size of the start and end margins around the background. * @param typeface the typeface used for the label. - * @param textAlign the text alignment. + * @param textAlignment the text alignment. */ @Composable public fun axisLabelComponent( @@ -69,25 +70,64 @@ public fun axisLabelComponent( verticalMargin: Dp = currentChartStyle.axis.axisLabelVerticalMargin, horizontalMargin: Dp = currentChartStyle.axis.axisLabelHorizontalMargin, typeface: Typeface = currentChartStyle.axis.axisLabelTypeface, - textAlign: Paint.Align = currentChartStyle.axis.axisLabelTextAlign, + textAlignment: Layout.Alignment = currentChartStyle.axis.axisLabelTextAlignment, ): TextComponent = textComponent( - color = color, - textSize = textSize, - background = background, - ellipsize = ellipsize, - lineCount = lineCount, - padding = dimensionsOf( - vertical = verticalPadding, - horizontal = horizontalPadding, - ), - margins = dimensionsOf( - vertical = verticalMargin, - horizontal = horizontalMargin, - ), - typeface = typeface, - textAlign = textAlign, + color, + textSize, + background, + ellipsize, + lineCount, + dimensionsOf(verticalPadding, horizontalPadding), + dimensionsOf(verticalMargin, horizontalMargin), + typeface, + textAlignment, ) +/** + * Creates a [TextComponent] to be used for axis labels. + * + * @param color the text color. + * @param textSize the text size. + * @param background a [ShapeComponent] to be displayed behind the text. + * @param ellipsize the text truncation behavior. + * @param lineCount the line count. + * @param verticalPadding the amount of top and bottom padding between the text and the background. + * @param horizontalPadding the amount of start and end padding between the text and the background. + * @param verticalMargin the size of the top and bottom margins around the background. + * @param horizontalMargin the size of the start and end margins around the background. + * @param verticalMargin the size of the vertical margins around the background. + * @param horizontalMargin the size of the horizontal margins around the background. + * @param typeface the typeface used for the label. + * @param textAlign the text alignment. + */ +@Composable +@Deprecated("Instead of `textAlign`, use `textAlignment`.") +public fun axisLabelComponent( + color: Color = currentChartStyle.axis.axisLabelColor, + textSize: TextUnit = currentChartStyle.axis.axisLabelTextSize, + background: ShapeComponent? = currentChartStyle.axis.axisLabelBackground, + ellipsize: TextUtils.TruncateAt = TextUtils.TruncateAt.END, + lineCount: Int = currentChartStyle.axis.axisLabelLineCount, + verticalPadding: Dp = currentChartStyle.axis.axisLabelVerticalPadding, + horizontalPadding: Dp = currentChartStyle.axis.axisLabelHorizontalPadding, + verticalMargin: Dp = currentChartStyle.axis.axisLabelVerticalMargin, + horizontalMargin: Dp = currentChartStyle.axis.axisLabelHorizontalMargin, + typeface: Typeface = currentChartStyle.axis.axisLabelTypeface, + textAlign: Paint.Align, +): TextComponent = + @Suppress("DEPRECATION") + textComponent( + color, + textSize, + background, + ellipsize, + lineCount, + dimensionsOf(verticalPadding, horizontalPadding), + dimensionsOf(verticalMargin, horizontalMargin), + typeface, + textAlign, + ) + /** * Creates a [LineComponent] styled as an axis line. * diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/component/Components.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/component/Components.kt index 66875679c..51dea44a1 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/component/Components.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/component/Components.kt @@ -18,6 +18,7 @@ package com.patrykandpatrick.vico.compose.component import android.graphics.Paint import android.graphics.Typeface +import android.text.Layout import android.text.TextUtils import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -229,6 +230,45 @@ public fun overlayingComponent( innerPaddingEnd = innerPaddingAll, ) +/** + * Creates a [TextComponent]. + * + * @param color the text color. + * @param textSize the text size. + * @param background an optional [ShapeComponent] to display behind the text. + * @param ellipsize the text truncation behavior. + * @param lineCount the line count. + * @param padding the padding between the text and the background. + * @param margins the margins around the background. + * @param typeface the [Typeface] for the text. + * @param textAlignment the text alignment. + */ +@Composable +public fun textComponent( + color: Color = Color.Black, + textSize: TextUnit = DefaultDimens.TEXT_COMPONENT_TEXT_SIZE.sp, + background: ShapeComponent? = null, + ellipsize: TextUtils.TruncateAt = TextUtils.TruncateAt.END, + lineCount: Int = DEF_LABEL_LINE_COUNT, + padding: MutableDimensions = emptyDimensions(), + margins: MutableDimensions = emptyDimensions(), + typeface: Typeface? = null, + textAlignment: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL, +): TextComponent = + remember(color, textSize, background, ellipsize, lineCount, padding, margins, typeface, textAlignment) { + textComponent { + this.color = color.toArgb() + textSizeSp = textSize.pixelSize() + this.ellipsize = ellipsize + this.lineCount = lineCount + this.background = background + this.padding = padding + this.margins = margins + this.typeface = typeface + this.textAlignment = textAlignment + } + } + /** * Creates a [TextComponent]. * @@ -243,6 +283,7 @@ public fun overlayingComponent( * @param textAlign the text alignment. */ @Composable +@Deprecated("Instead of `textAlign`, use `textAlignment`.") public fun textComponent( color: Color = Color.Black, textSize: TextUnit = DefaultDimens.TEXT_COMPONENT_TEXT_SIZE.sp, @@ -252,18 +293,8 @@ public fun textComponent( padding: MutableDimensions = emptyDimensions(), margins: MutableDimensions = emptyDimensions(), typeface: Typeface? = null, - textAlign: Paint.Align = Paint.Align.LEFT, -): TextComponent = remember( - color, - textSize, - background, - ellipsize, - lineCount, - padding, - margins, - typeface, - textAlign, -) { + textAlign: Paint.Align, +): TextComponent = remember(color, textSize, background, ellipsize, lineCount, padding, margins, typeface, textAlign) { textComponent { this.color = color.toArgb() textSizeSp = textSize.pixelSize() @@ -273,6 +304,7 @@ public fun textComponent( this.padding = padding this.margins = margins this.typeface = typeface + @Suppress("DEPRECATION") this.textAlign = textAlign } } diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/style/ChartStyle.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/style/ChartStyle.kt index 87888cdae..704fbcfbe 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/style/ChartStyle.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/style/ChartStyle.kt @@ -18,6 +18,7 @@ package com.patrykandpatrick.vico.compose.style import android.graphics.Paint import android.graphics.Typeface +import android.text.Layout import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -83,6 +84,7 @@ public data class ChartStyle( * @property axisLabelRotationDegrees the number of degrees by which axis labels are rotated. * @property axisLabelTypeface the typeface used for axis labels. * @property axisLabelTextAlign the text alignment for axis labels. + * @property axisLabelTextAlignment the text alignment for axis labels. * @property axisGuidelineColor the color of axis guidelines. * @property axisGuidelineWidth the width of axis guidelines. * @property axisGuidelineShape the [Shape] used for axis guidelines. @@ -106,7 +108,9 @@ public data class ChartStyle( val axisLabelHorizontalMargin: Dp = DefaultDimens.AXIS_LABEL_HORIZONTAL_MARGIN.dp, val axisLabelRotationDegrees: Float = DefaultDimens.AXIS_LABEL_ROTATION_DEGREES, val axisLabelTypeface: Typeface = Typeface.MONOSPACE, + @Deprecated("Use `axisLabelTextAlignment` instead.") val axisLabelTextAlign: Paint.Align = Paint.Align.LEFT, + val axisLabelTextAlignment: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL, val axisGuidelineColor: Color, val axisGuidelineWidth: Dp = DefaultDimens.AXIS_GUIDELINE_WIDTH.dp, val axisGuidelineShape: Shape = Shapes.dashedShape( diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/component/text/TextComponent.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/component/text/TextComponent.kt index 8f6ede345..a6d26996f 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/component/text/TextComponent.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/component/text/TextComponent.kt @@ -108,7 +108,15 @@ public open class TextComponent protected constructor() : Padding, Margins { /** * The text alignment. */ - public var textAlign: Paint.Align by textPaint::textAlign + @Deprecated("Use `textAlignment` instead.") + public var textAlign: Paint.Align + get() = textAlignment.equivLtrPaintAlign + set(value) { textAlignment = value.equivLtrLayoutAlignment } + + /** + * The text alignment. + */ + public var textAlignment: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL /** * The padding between the text and the background. This is applied even if [background] is null. @@ -167,7 +175,7 @@ public open class TextComponent protected constructor() : Padding, Margins { save() val bounds = layout.getBounds(tempMeasureBounds) - val textAlignCorrection = textAlign.getXCorrection(width = bounds.width()) + val textAlignmentCorrection = getTextAlignmentCorrection(bounds.width()) with(receiver = bounds) { left -= padding.getLeftDp(isLtr).pixels @@ -215,7 +223,7 @@ public open class TextComponent protected constructor() : Padding, Margins { ) translate( - bounds.left + padding.getLeftDp(isLtr).pixels + textAlignCorrection, + bounds.left + padding.getLeftDp(isLtr).pixels + textAlignmentCorrection, bounds.top + padding.topDp.pixels, ) @@ -247,10 +255,21 @@ public open class TextComponent protected constructor() : Padding, Margins { width: Float, ): Float = baseXPosition - padding.getRightDp(isLtr).pixels - margins.getRightDp(isLtr).pixels - width - private fun Paint.Align.getXCorrection(width: Float): Float = when (this) { - Paint.Align.LEFT -> 0f - Paint.Align.CENTER -> width.half - Paint.Align.RIGHT -> width + private fun getTextAlignmentCorrection(width: Float): Float { + val ltrAlignment = if (layout.getParagraphDirection(0) == Layout.DIR_LEFT_TO_RIGHT) { + textAlignment + } else { + when (textAlignment) { + Layout.Alignment.ALIGN_NORMAL -> Layout.Alignment.ALIGN_OPPOSITE + Layout.Alignment.ALIGN_OPPOSITE -> Layout.Alignment.ALIGN_NORMAL + Layout.Alignment.ALIGN_CENTER -> Layout.Alignment.ALIGN_CENTER + } + } + return when (ltrAlignment) { + Layout.Alignment.ALIGN_NORMAL -> 0f + Layout.Alignment.ALIGN_OPPOSITE -> width - layout.width + Layout.Alignment.ALIGN_CENTER -> (width - layout.width).half + } } @JvmName("getTextTopPositionExt") @@ -366,6 +385,7 @@ public open class TextComponent protected constructor() : Padding, Margins { width = correctedWidth, maxLines = lineCount, ellipsize = ellipsize, + align = textAlignment, ) } } @@ -409,7 +429,15 @@ public open class TextComponent protected constructor() : Padding, Margins { /** * @see [TextComponent.textAlign] */ - public var textAlign: Paint.Align = Paint.Align.LEFT + @Deprecated("Use `textAlignment` instead.") + public var textAlign: Paint.Align + get() = textAlignment.equivLtrPaintAlign + set(value) { textAlignment = value.equivLtrLayoutAlignment } + + /** + * The text alignment. + */ + public var textAlignment: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL /** * @see [TextComponent.padding] @@ -431,7 +459,7 @@ public open class TextComponent protected constructor() : Padding, Margins { ellipsize = this@Builder.ellipsize lineCount = this@Builder.lineCount background = this@Builder.background - textAlign = this@Builder.textAlign + textAlignment = this@Builder.textAlignment padding.set(this@Builder.padding) margins.set(this@Builder.margins) } @@ -452,3 +480,17 @@ public open class TextComponent protected constructor() : Padding, Margins { */ public inline fun textComponent(block: TextComponent.Builder.() -> Unit = {}): TextComponent = TextComponent.Builder().apply(block).build() + +private val Paint.Align.equivLtrLayoutAlignment + get() = when (this) { + Paint.Align.LEFT -> Layout.Alignment.ALIGN_NORMAL + Paint.Align.CENTER -> Layout.Alignment.ALIGN_CENTER + Paint.Align.RIGHT -> Layout.Alignment.ALIGN_OPPOSITE + } + +private val Layout.Alignment.equivLtrPaintAlign + get() = when (this) { + Layout.Alignment.ALIGN_NORMAL -> Paint.Align.LEFT + Layout.Alignment.ALIGN_OPPOSITE -> Paint.Align.RIGHT + Layout.Alignment.ALIGN_CENTER -> Paint.Align.CENTER + } diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/theme/TextComponentStyleExtensions.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/theme/TextComponentStyleExtensions.kt index b1b72a5d0..201c3b64f 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/theme/TextComponentStyleExtensions.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/theme/TextComponentStyleExtensions.kt @@ -21,6 +21,7 @@ import android.content.res.TypedArray import android.graphics.Paint import android.graphics.Typeface import android.os.Build +import android.text.Layout import android.text.TextUtils import androidx.annotation.StyleableRes import androidx.core.content.res.ResourcesCompat @@ -61,7 +62,9 @@ internal fun TypedArray.getTextComponent( this.lineCount = getInteger(R.styleable.TextComponentStyle_android_maxLines, DEF_LABEL_LINE_COUNT) this.ellipsize = getTruncateAt() getTypeface(context)?.let { this.typeface = it } - this.textAlign = getTextAlign() + @Suppress("DEPRECATION") + getTextAlign()?.let { this.textAlign = it } + this.textAlignment = getTextAlignment() } } @@ -150,11 +153,11 @@ private fun TypedArray.getMargins(context: Context): MutableDimensions { ) } -private fun TypedArray.getTextAlign(): Paint.Align { - val values = Paint.Align.values() - val index = getInt( - R.styleable.TextComponentStyle_textAlign, - Paint.Align.LEFT.ordinal, - ).coerceAtMost(maximumValue = Paint.Align.RIGHT.ordinal) - return values[index] -} +private fun TypedArray.getTextAlign(): Paint.Align? = + Paint.Align.values().getOrNull(getInt(R.styleable.TextComponentStyle_textAlign, -1)) + +private fun TypedArray.getTextAlignment(): Layout.Alignment = + Layout.Alignment + .values() + .getOrNull(getInt(R.styleable.TextComponentStyle_textAlignment, Layout.Alignment.ALIGN_NORMAL.ordinal)) + ?: Layout.Alignment.ALIGN_NORMAL diff --git a/vico/views/src/main/res/values/attrs.xml b/vico/views/src/main/res/values/attrs.xml index 9225167c3..95de33fad 100644 --- a/vico/views/src/main/res/values/attrs.xml +++ b/vico/views/src/main/res/values/attrs.xml @@ -139,11 +139,17 @@ + + + + + +