Skip to content

Commit

Permalink
TextComponent: Fix handling of RTL text
Browse files Browse the repository at this point in the history
Co-authored-by: Patryk Goworowski <patrykgoworowski@gmail.com>
  • Loading branch information
patrickmichalik and Gowsky committed Jul 31, 2023
1 parent 3a309da commit 1fba966
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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].
*
Expand All @@ -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,
Expand All @@ -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()
Expand All @@ -273,6 +304,7 @@ public fun textComponent(
this.padding = padding
this.margins = margins
this.typeface = typeface
@Suppress("DEPRECATION")
this.textAlign = textAlign
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
)

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -366,6 +385,7 @@ public open class TextComponent protected constructor() : Padding, Margins {
width = correctedWidth,
maxLines = lineCount,
ellipsize = ellipsize,
align = textAlignment,
)
}
}
Expand Down Expand Up @@ -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]
Expand All @@ -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)
}
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -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
Loading

0 comments on commit 1fba966

Please sign in to comment.