Added in [API level 31](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels) Summary: [Nested Classes](https://developer.android.com/reference/android/text/TextShaper#nestedclasses) \| [Methods](https://developer.android.com/reference/android/text/TextShaper#pubmethods) \| [Inherited Methods](https://developer.android.com/reference/android/text/TextShaper#inhmethods)

# TextShaper

*** ** * ** ***

` public class TextShaper `   
` extends https://developer.android.com/reference/java/lang/Object ` ` `

|---|---|
| [java.lang.Object](https://developer.android.com/reference/java/lang/Object) ||
| ↳ | android.text.TextShaper |

<br />

*** ** * ** ***

Provides text shaping for multi-styled text. Here is an example of animating text size and letter spacing for simple text.


    // In this example, shape the text once for start and end state, then animate between two shape
    // result without re-shaping in each frame.
    class SimpleAnimationView @JvmOverloads constructor(
            context: Context,
            attrs: AttributeSet? = null,
            defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
        private val textDir = TextDirectionHeuristics.LOCALE
        private val text = "Hello, World."  // The text to be displayed

        // Class for keeping drawing parameters.
        data class DrawStyle(val textSize: Float, val alpha: Int)

        // The start and end text shaping result. This class will animate between these two.
        private val start = mutableListOf<Pair<PositionedGlyphs, DrawStyle>>()
        private val end = mutableListOf<Pair<PositionedGlyphs, DrawStyle>>()

        init {
            val startPaint = TextPaint().apply {
                alpha = 0 // Alpha only affect text drawing but not text shaping
                textSize = 36f // TextSize affect both text shaping and drawing.
                letterSpacing = 0f // Letter spacing only affect text shaping but not drawing.
            }

            val endPaint = TextPaint().apply {
                alpha = 255
                textSize =128f
                letterSpacing = 0.1f
            }

            TextShaper.shapeText(text, 0, text.length, textDir, startPaint) { _, _, glyphs, paint ->
                start.add(Pair(glyphs, DrawStyle(paint.textSize, paint.alpha)))
            }
            TextShaper.shapeText(text, 0, text.length, textDir, endPaint) { _, _, glyphs, paint ->
                end.add(Pair(glyphs, DrawStyle(paint.textSize, paint.alpha)))
            }
        }

        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)

            // Set the baseline to the vertical center of the view.
            canvas.translate(0f, height / 2f)

            // Assume the number of PositionedGlyphs are the same. If different, you may want to
            // animate in a different way, e.g. cross fading.
            start.zip(end) { (startGlyphs, startDrawStyle), (endGlyphs, endDrawStyle) ->
                // Tween the style and set to paint.
                paint.textSize = lerp(startDrawStyle.textSize, endDrawStyle.textSize, progress)
                paint.alpha = lerp(startDrawStyle.alpha, endDrawStyle.alpha, progress)

                // Assume the number of glyphs are the same. If different, you may want to animate in
                // a different way, e.g. cross fading.
                require(startGlyphs.glyphCount() == endGlyphs.glyphCount())

                if (startGlyphs.glyphCount() == 0) return@zip

                var curFont = startGlyphs.getFont(0)
                var drawStart = 0
                for (i in 1 until startGlyphs.glyphCount()) {
                    // Assume the pair of Glyph ID and font is the same. If different, you may want
                    // to animate in a different way, e.g. cross fading.
                    require(startGlyphs.getGlyphId(i) == endGlyphs.getGlyphId(i))
                    require(startGlyphs.getFont(i) === endGlyphs.getFont(i))

                    val font = startGlyphs.getFont(i)
                    if (curFont != font) {
                        drawGlyphs(canvas, startGlyphs, endGlyphs, drawStart, i, curFont, paint)
                        curFont = font
                        drawStart = i
                    }
                }
                if (drawStart != startGlyphs.glyphCount() - 1) {
                    drawGlyphs(canvas, startGlyphs, endGlyphs, drawStart, startGlyphs.glyphCount(),
                            curFont, paint)
                }
            }
        }

        // Draws Glyphs for the same font run.
        private fun drawGlyphs(canvas: Canvas, startGlyph: PositionedGlyphs,
                               endGlyph: PositionedGlyphs, start: Int, end: Int, font: Font,
                               paint: Paint) {
            var cacheIndex = 0
            for (i in start until end) {
                intArrayCache[cacheIndex] = startGlyph.getGlyphId(i)
                // The glyph positions are different from start to end since they are shaped
                // with different letter spacing. Use linear interpolation for positions
                // during animation.
                floatArrayCache[cacheIndex * 2] =
                        lerp(startGlyph.getGlyphX(i), endGlyph.getGlyphX(i), progress)
                floatArrayCache[cacheIndex * 2 + 1] =
                        lerp(startGlyph.getGlyphY(i), endGlyph.getGlyphY(i), progress)
                if (cacheIndex == CACHE_SIZE) {  // Cached int array is full. Flashing.
                    canvas.drawGlyphs(
                            intArrayCache, 0, // glyphID array and its starting offset
                            floatArrayCache, 0, // position array and its starting offset
                            cacheIndex, // glyph count
                            font,
                            paint
                    )
                    cacheIndex = 0
                }
                cacheIndex++
            }
            if (cacheIndex != 0) {
                canvas.drawGlyphs(
                        intArrayCache, 0, // glyphID array and its starting offset
                        floatArrayCache, 0, // position array and its starting offset
                        cacheIndex, // glyph count
                        font,
                        paint
                )
            }
        }

        // Linear Interpolator
        private fun lerp(start: Float, end: Float, t: Float) = start * (1f - t) + end * t
        private fun lerp(start: Int, end: Int, t: Float) = (start * (1f - t) + end * t).toInt()

        // The animation progress.
        var progress: Float = 0f
            set(value) {
                field = value
                invalidate()
            }

        // working copy of paint.
        private val paint = Paint()

        // Array cache for reducing allocation during drawing.
        private var intArrayCache = IntArray(CACHE_SIZE)
        private var floatArrayCache = FloatArray(CACHE_SIZE * 2)
    }

<br />

**See also:**

- `https://developer.android.com/reference/android/graphics/text/TextRunShaper#shapeTextRun(char[],%20int,%20int,%20int,%20int,%20float,%20float,%20boolean,%20android.graphics.Paint)`
- `https://developer.android.com/reference/android/graphics/text/TextRunShaper#shapeTextRun(java.lang.CharSequence,%20int,%20int,%20int,%20int,%20float,%20float,%20boolean,%20android.graphics.Paint)`
- `https://developer.android.com/reference/android/text/TextShaper#shapeText(java.lang.CharSequence,%20int,%20int,%20android.text.TextDirectionHeuristic,%20android.text.TextPaint,%20android.text.TextShaper.GlyphsConsumer)`

## Summary

| ### Nested classes ||
|---|---|
| ` interface` | `https://developer.android.com/reference/android/text/TextShaper.GlyphsConsumer` A consumer interface for accepting text shape result. |

| ### Public methods ||
|---|---|
| ` static void` | ` https://developer.android.com/reference/android/text/TextShaper#shapeText(java.lang.CharSequence,%20int,%20int,%20android.text.TextDirectionHeuristic,%20android.text.TextPaint,%20android.text.TextShaper.GlyphsConsumer)(https://developer.android.com/reference/java/lang/CharSequence text, int start, int count, https://developer.android.com/reference/android/text/TextDirectionHeuristic dir, https://developer.android.com/reference/android/text/TextPaint paint, https://developer.android.com/reference/android/text/TextShaper.GlyphsConsumer consumer) ` Shape multi-styled text. |

### Inherited methods

From class ` https://developer.android.com/reference/java/lang/Object ` ` https://developer.android.com/reference/java/lang/Object` ` https://developer.android.com/reference/java/lang/Object#clone()() `

Creates and returns a copy of this object.
` boolean` ` https://developer.android.com/reference/java/lang/Object#equals(java.lang.Object)(https://developer.android.com/reference/java/lang/Object obj) `

Indicates whether some other object is "equal to" this one.
` void` ` https://developer.android.com/reference/java/lang/Object#finalize()() `

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
` final https://developer.android.com/reference/java/lang/Class<?>` ` https://developer.android.com/reference/java/lang/Object#getClass()() `

Returns the runtime class of this `Object`.
` int` ` https://developer.android.com/reference/java/lang/Object#hashCode()() `

Returns a hash code value for the object.
` final void` ` https://developer.android.com/reference/java/lang/Object#notify()() `

Wakes up a single thread that is waiting on this object's monitor.
` final void` ` https://developer.android.com/reference/java/lang/Object#notifyAll()() `

Wakes up all threads that are waiting on this object's monitor.
` https://developer.android.com/reference/java/lang/String` ` https://developer.android.com/reference/java/lang/Object#toString()() `

Returns a string representation of the object.
` final void` ` https://developer.android.com/reference/java/lang/Object#wait(long,%20int)(long timeoutMillis, int nanos) `

Causes the current thread to wait until it is awakened, typically by being *notified* or *interrupted*, or until a certain amount of real time has elapsed.
` final void` ` https://developer.android.com/reference/java/lang/Object#wait(long)(long timeoutMillis) `

Causes the current thread to wait until it is awakened, typically by being *notified* or *interrupted*, or until a certain amount of real time has elapsed.
` final void` ` https://developer.android.com/reference/java/lang/Object#wait()() `

Causes the current thread to wait until it is awakened, typically by being *notified* or *interrupted*.

## Public methods

### shapeText

Added in [API level 31](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels)

```
public static void shapeText (CharSequence text, 
                int start, 
                int count, 
                TextDirectionHeuristic dir, 
                TextPaint paint, 
                TextShaper.GlyphsConsumer consumer)
```

Shape multi-styled text. In the LTR context, the shape result will go from left to right, thus you may want to draw glyphs from left most position of the canvas. In the RTL context, the shape result will go from right to left, thus you may want to draw glyphs from right most position of the canvas.

<br />

| Parameters ||
|---|---|
| `text` | `CharSequence`: a styled text. This value cannot be `null`. <br /> |
| `start` | `int`: a start index of shaping target in the text. Value is 0 or greater <br /> |
| `count` | `int`: a length of shaping target in the text. Value is 0 or greater <br /> |
| `dir` | `TextDirectionHeuristic`: a text direction. This value cannot be `null`. <br /> |
| `paint` | `TextPaint`: a paint. This value cannot be `null`. <br /> |
| `consumer` | `TextShaper.GlyphsConsumer`: a consumer of the shape result. This value cannot be `null`. <br /> |