Update TextShaper APIs to address API council feedback
This CL contains followings:
- Rename TextShaper to TextRunShaper, StyledTextShaper to TextShaper
- Renamed getTotalAdvance to getAdvance
- Rename getStyle to getGlyphStyle
- Rename getOriginX/Y to getOffsetX/Y
- Rename getPositionX/Y to getGlyphX/Y
- Fixed some documentation errors.
- Remvoed GlyphStyle. Added GlyphConsumer instead.
Bug: 170255480
Test: atest TextShaperRunTest GlyphStyleTest TextShaperTest
Change-Id: I0ffd7a3374e9cd1e04872240c2d0da26bc530244
diff --git a/api/current.txt b/api/current.txt
index a550bf8..1c20e78 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16525,23 +16525,6 @@
package android.graphics.text {
- public class GlyphStyle {
- ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int);
- ctor public GlyphStyle(@NonNull android.graphics.Paint);
- method public void applyToPaint(@NonNull android.graphics.Paint);
- method @ColorInt public int getColor();
- method public int getFlags();
- method @FloatRange(from=0) public float getFontSize();
- method @FloatRange(from=0) public float getScaleX();
- method @FloatRange(from=0) public float getSkewX();
- method public void setColor(@ColorInt int);
- method public void setFlags(int);
- method public void setFontSize(@FloatRange(from=0) float);
- method public void setFromPaint(@NonNull android.graphics.Paint);
- method public void setScaleX(@FloatRange(from=0) float);
- method public void setSkewX(@FloatRange(from=0) float);
- }
-
public class LineBreaker {
method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int);
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -16603,20 +16586,19 @@
}
public final class PositionedGlyphs {
+ method public float getAdvance();
method public float getAscent();
method public float getDescent();
method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
- method public float getOriginX();
- method public float getOriginY();
- method public float getPositionX(@IntRange(from=0) int);
- method public float getPositionY(@IntRange(from=0) int);
- method @NonNull public android.graphics.text.GlyphStyle getStyle();
- method public float getTotalAdvance();
+ method public float getGlyphX(@IntRange(from=0) int);
+ method public float getGlyphY(@IntRange(from=0) int);
+ method public float getOffsetX();
+ method public float getOffsetY();
method @IntRange(from=0) public int glyphCount();
}
- public class TextShaper {
+ public class TextRunShaper {
method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
}
@@ -50059,10 +50041,6 @@
method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
}
- public class StyledTextShaper {
- method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint);
- }
-
public interface TextDirectionHeuristic {
method public boolean isRtl(char[], int, int);
method public boolean isRtl(CharSequence, int, int);
@@ -50092,6 +50070,14 @@
field @Px public float underlineThickness;
}
+ public class TextShaper {
+ method public static void shapeText(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint, @NonNull android.text.TextShaper.GlyphsConsumer);
+ }
+
+ public static interface TextShaper.GlyphsConsumer {
+ method public void accept(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.text.PositionedGlyphs, @NonNull android.text.TextPaint);
+ }
+
public class TextUtils {
method @Deprecated public static CharSequence commaEllipsize(CharSequence, android.text.TextPaint, float, String, String);
method public static CharSequence concat(java.lang.CharSequence...);
diff --git a/core/java/android/text/StyledTextShaper.java b/core/java/android/text/StyledTextShaper.java
deleted file mode 100644
index bf90614..0000000
--- a/core/java/android/text/StyledTextShaper.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.text;
-
-import android.annotation.NonNull;
-import android.graphics.Paint;
-import android.graphics.text.PositionedGlyphs;
-import android.graphics.text.TextShaper;
-
-import java.util.List;
-
-/**
- * Provides text shaping for multi-styled text.
- *
- * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
- * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
- * @see StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint)
- */
-public class StyledTextShaper {
- private StyledTextShaper() {}
-
-
- /**
- * Shape multi-styled text.
- *
- * @param text a styled text.
- * @param start a start index of shaping target in the text.
- * @param count a length of shaping target in the text.
- * @param dir a text direction.
- * @param paint a paint
- * @return a shape result.
- */
- public static @NonNull List<PositionedGlyphs> shapeText(
- @NonNull CharSequence text, int start, int count,
- @NonNull TextDirectionHeuristic dir, @NonNull TextPaint paint) {
- MeasuredParagraph mp = MeasuredParagraph.buildForBidi(
- text, start, start + count, dir, null);
- TextLine tl = TextLine.obtain();
- try {
- tl.set(paint, text, start, start + count,
- mp.getParagraphDir(),
- mp.getDirections(start, start + count),
- false /* tabstop is not supported */,
- null,
- -1, -1 // ellipsis is not supported.
- );
- return tl.shape();
- } finally {
- TextLine.recycle(tl);
- }
- }
-
-}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index b826832..4471056 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -24,7 +24,7 @@
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.text.PositionedGlyphs;
-import android.graphics.text.TextShaper;
+import android.graphics.text.TextRunShaper;
import android.os.Build;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
@@ -37,7 +37,6 @@
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.List;
/**
* Represents a line of styled text, for measuring in visual order and
@@ -312,8 +311,7 @@
/**
* Shape the TextLine.
*/
- List<PositionedGlyphs> shape() {
- List<PositionedGlyphs> glyphs = new ArrayList<>();
+ void shape(TextShaper.GlyphsConsumer consumer) {
float horizontal = 0;
float x = 0;
final int runCount = mDirections.getRunCount();
@@ -326,7 +324,7 @@
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
- horizontal += shapeRun(glyphs, segStart, j, runIsRtl, x + horizontal,
+ horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal,
runIndex != (runCount - 1) || j != mLen);
if (j != runLimit) { // charAt(j) == TAB_CHAR
@@ -336,7 +334,6 @@
}
}
}
- return glyphs;
}
/**
@@ -546,7 +543,7 @@
/**
* Shape a unidirectional (but possibly multi-styled) run of text.
*
- * @param glyphs the output positioned glyphs list
+ * @param consumer the consumer of the shape result
* @param start the line-relative start
* @param limit the line-relative limit
* @param runIsRtl true if the run is right-to-left
@@ -555,16 +552,17 @@
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
- private float shapeRun(List<PositionedGlyphs> glyphs, int start,
+ private float shapeRun(TextShaper.GlyphsConsumer consumer, int start,
int limit, boolean runIsRtl, float x, boolean needWidth) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
float w = -measureRun(start, limit, limit, runIsRtl, null);
- handleRun(start, limit, limit, runIsRtl, null, glyphs, x + w, 0, 0, 0, null, false);
+ handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, false);
return w;
}
- return handleRun(start, limit, limit, runIsRtl, null, glyphs, x, 0, 0, 0, null, needWidth);
+ return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null,
+ needWidth);
}
@@ -899,7 +897,7 @@
* @param end the end of the text
* @param runIsRtl true if the run is right-to-left
* @param c the canvas, can be null if rendering is not needed
- * @param glyphs the output positioned glyph list, can be null if not necessary
+ * @param consumer the output positioned glyph list, can be null if not necessary
* @param x the edge of the run closest to the leading margin
* @param top the top of the line
* @param y the baseline
@@ -913,7 +911,7 @@
*/
private float handleText(TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl,
- Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom,
+ Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
FontMetricsInt fmi, boolean needWidth, int offset,
@Nullable ArrayList<DecorationInfo> decorations) {
@@ -946,8 +944,8 @@
rightX = x + totalWidth;
}
- if (glyphs != null) {
- shapeTextRun(glyphs, wp, start, end, contextStart, contextEnd, runIsRtl, leftX);
+ if (consumer != null) {
+ shapeTextRun(consumer, wp, start, end, contextStart, contextEnd, runIsRtl, leftX);
}
if (c != null) {
@@ -1135,7 +1133,7 @@
* @param limit the limit of the run
* @param runIsRtl true if the run is right-to-left
* @param c the canvas, can be null
- * @param glyphs the output positioned glyphs, can be null
+ * @param consumer the output positioned glyphs, can be null
* @param x the end of the run closest to the leading margin
* @param top the top of the line
* @param y the baseline
@@ -1147,7 +1145,7 @@
*/
private float handleRun(int start, int measureLimit,
int limit, boolean runIsRtl, Canvas c,
- List<PositionedGlyphs> glyphs, float x, int top, int y,
+ TextShaper.GlyphsConsumer consumer, float x, int top, int y,
int bottom, FontMetricsInt fmi, boolean needWidth) {
if (measureLimit < start || measureLimit > limit) {
@@ -1180,7 +1178,7 @@
wp.set(mPaint);
wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit()));
wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
- return handleText(wp, start, limit, start, limit, runIsRtl, c, glyphs, x, top,
+ return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
y, bottom, fmi, needWidth, measureLimit, null);
}
@@ -1262,7 +1260,7 @@
activePaint.setEndHyphenEdit(
adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c,
- glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
+ consumer, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations);
activeStart = j;
@@ -1288,7 +1286,7 @@
adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
activePaint.setEndHyphenEdit(
adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
- x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x,
+ x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations);
}
@@ -1327,7 +1325,7 @@
/**
* Shape a text run with the set-up paint.
*
- * @param glyphs the output positioned glyphs list
+ * @param consumer the output positioned glyphs list
* @param paint the paint used to render the text
* @param start the start of the run
* @param end the end of the run
@@ -1336,30 +1334,32 @@
* @param runIsRtl true if the run is right-to-left
* @param x the x position of the left edge of the run
*/
- private void shapeTextRun(List<PositionedGlyphs> glyphs, TextPaint paint,
+ private void shapeTextRun(TextShaper.GlyphsConsumer consumer, TextPaint paint,
int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x) {
int count = end - start;
int contextCount = contextEnd - contextStart;
+ PositionedGlyphs glyphs;
if (mCharsValid) {
- glyphs.add(TextShaper.shapeTextRun(
+ glyphs = TextRunShaper.shapeTextRun(
mChars,
start, count,
contextStart, contextCount,
x, 0f,
runIsRtl,
paint
- ));
+ );
} else {
- glyphs.add(TextShaper.shapeTextRun(
+ glyphs = TextRunShaper.shapeTextRun(
mText,
mStart + start, count,
mStart + contextStart, contextCount,
x, 0f,
runIsRtl,
paint
- ));
+ );
}
+ consumer.accept(start, count, glyphs, paint);
}
diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java
new file mode 100644
index 0000000..dd25704
--- /dev/null
+++ b/core/java/android/text/TextShaper.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 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 android.text;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Paint;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
+
+/**
+ * Provides text shaping for multi-styled text.
+ *
+ * Here is an example of animating text size and letter spacing for simple text.
+ * <pre>
+ * <code>
+ * // 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)
+ * }
+ * </code>
+ * </pre>
+ * @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
+ * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
+ * @see TextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint,
+ * GlyphsConsumer)
+ */
+public class TextShaper {
+ private TextShaper() {}
+
+ /**
+ * An consumer interface for accepting text shape result.
+ */
+ public interface GlyphsConsumer {
+ /**
+ * Accept text shape result.
+ *
+ * The implementation must not keep reference of paint since it will be mutated for the
+ * subsequent styles. Also, for saving heap size, keep only necessary members in the
+ * {@link TextPaint} instead of copying {@link TextPaint} object.
+ *
+ * @param start The start index of the shaped text.
+ * @param count The length of the shaped text.
+ * @param glyphs The shape result.
+ * @param paint The paint to be used for drawing.
+ */
+ void accept(
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int count,
+ @NonNull PositionedGlyphs glyphs,
+ @NonNull TextPaint paint);
+ }
+
+ /**
+ * Shape multi-styled text.
+ *
+ * @param text a styled text.
+ * @param start a start index of shaping target in the text.
+ * @param count a length of shaping target in the text.
+ * @param dir a text direction.
+ * @param paint a paint
+ * @param consumer a consumer of the shape result.
+ */
+ public static void shapeText(
+ @NonNull CharSequence text, @IntRange(from = 0) int start,
+ @IntRange(from = 0) int count, @NonNull TextDirectionHeuristic dir,
+ @NonNull TextPaint paint, @NonNull GlyphsConsumer consumer) {
+ MeasuredParagraph mp = MeasuredParagraph.buildForBidi(
+ text, start, start + count, dir, null);
+ TextLine tl = TextLine.obtain();
+ try {
+ tl.set(paint, text, start, start + count,
+ mp.getParagraphDir(),
+ mp.getDirections(start, start + count),
+ false /* tabstop is not supported */,
+ null,
+ -1, -1 // ellipsis is not supported.
+ );
+ tl.shape(consumer);
+ } finally {
+ TextLine.recycle(tl);
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/text/TextShaperTest.java b/core/tests/coretests/src/android/text/TextShaperTest.java
index f92ea99..8237cb0 100644
--- a/core/tests/coretests/src/android/text/TextShaperTest.java
+++ b/core/tests/coretests/src/android/text/TextShaperTest.java
@@ -18,16 +18,12 @@
import static com.google.common.truth.Truth.assertThat;
-import android.graphics.text.PositionedGlyphs;
-
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextShaperTest {
@@ -36,11 +32,10 @@
public void testFontWithPath() {
TextPaint p = new TextPaint();
p.setFontFeatureSettings("'wght' 900");
- List<PositionedGlyphs> glyphs = StyledTextShaper.shapeText("a", 0, 1,
- TextDirectionHeuristics.LTR, p);
- assertThat(glyphs.size()).isEqualTo(1);
- // This test only passes if the font of the Latin font is variable font.
- assertThat(glyphs.get(0).getFont(0).getFile()).isNotNull();
-
+ TextShaper.shapeText("a", 0, 1, TextDirectionHeuristics.LTR, p,
+ (start, end, glyphs, paint) -> {
+ // This test only passes if the font of the Latin font is variable font.
+ assertThat(glyphs.getFont(0).getFile()).isNotNull();
+ });
}
}
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 05df250..54f9fa8 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -26,11 +26,13 @@
import android.graphics.Canvas.VertexMode;
import android.graphics.fonts.Font;
import android.graphics.text.MeasuredText;
+import android.graphics.text.TextRunShaper;
import android.text.GraphicsOperations;
import android.text.MeasuredParagraph;
import android.text.PrecomputedText;
import android.text.SpannableString;
import android.text.SpannedString;
+import android.text.TextShaper;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
@@ -471,8 +473,8 @@
* @param font Font used for drawing.
* @param paint Paint used for drawing. The typeface set to this paint is ignored.
*
- * @see android.graphics.text.TextShaper
- * @see android.text.StyledTextShaper
+ * @see TextRunShaper
+ * @see TextShaper
*/
public void drawGlyphs(
@NonNull int[] glyphIds,
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 5e07d15..829d0f4 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -26,7 +26,9 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.Font;
import android.graphics.text.MeasuredText;
+import android.graphics.text.TextRunShaper;
import android.os.Build;
+import android.text.TextShaper;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -2053,7 +2055,7 @@
* Draw array of glyphs with specified font.
*
* @param glyphIds Array of glyph IDs. The length of array must be greater than or equal to
- * {@code glyphStart + glyphCount}.
+ * {@code glyphIdOffset + glyphCount}.
* @param glyphIdOffset Number of elements to skip before drawing in <code>glyphIds</code>
* array.
* @param positions A flattened X and Y position array. The first glyph X position must be
@@ -2071,8 +2073,8 @@
* @param font Font used for drawing.
* @param paint Paint used for drawing. The typeface set to this paint is ignored.
*
- * @see android.graphics.text.TextShaper
- * @see android.text.StyledTextShaper
+ * @see TextRunShaper
+ * @see TextShaper
*/
public void drawGlyphs(
@NonNull int[] glyphIds,
diff --git a/graphics/java/android/graphics/text/GlyphStyle.java b/graphics/java/android/graphics/text/GlyphStyle.java
deleted file mode 100644
index cc8c4d2..0000000
--- a/graphics/java/android/graphics/text/GlyphStyle.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.graphics.text;
-
-import android.annotation.ColorInt;
-import android.annotation.FloatRange;
-import android.annotation.NonNull;
-import android.graphics.Paint;
-
-import java.util.Objects;
-
-/**
- * Represents subset of Paint parameters such as font size, scaleX that is used to draw a glyph.
- *
- * Glyph is a most primitive unit of text drawing.
- *
- */
-public class GlyphStyle {
- private @ColorInt int mColor;
- private float mFontSize;
- private float mScaleX;
- private float mSkewX;
- private int mFlags;
-
- /**
- * @param color a color.
- * @param fontSize a font size in pixels.
- * @param scaleX a horizontal scale factor.
- * @param skewX a horizontal skew factor
- * @param flags paint flags
- *
- * @see Paint#getFlags()
- * @see Paint#setFlags(int)
- */
- public GlyphStyle(
- @ColorInt int color,
- @FloatRange(from = 0) float fontSize,
- @FloatRange(from = 0) float scaleX,
- @FloatRange(from = 0) float skewX,
- int flags) {
- mColor = color;
- mFontSize = fontSize;
- mScaleX = scaleX;
- mSkewX = skewX;
- mFlags = flags;
- }
-
- /**
- * Create glyph style from Paint
- *
- * @param paint a paint
- */
- public GlyphStyle(@NonNull Paint paint) {
- setFromPaint(paint);
- }
-
- /**
- * Gets the color.
- *
- * @return a color
- * @see Paint#getColor()
- * @see Paint#setColor(int)
- */
- public @ColorInt int getColor() {
- return mColor;
- }
-
- /**
- * Sets the color.
- *
- * @param color a color
- * @see Paint#getColor()
- * @see Paint#setColor(int)
- */
- public void setColor(@ColorInt int color) {
- mColor = color;
- }
-
- /**
- * Gets the font size in pixels.
- *
- * @return font size
- * @see Paint#getTextSize()
- * @see Paint#setTextSize(float)
- */
- public @FloatRange(from = 0) float getFontSize() {
- return mFontSize;
- }
-
- /**
- * Sets the font size in pixels.
- *
- * @param fontSize font size in pixel
- * @see Paint#getTextSize()
- * @see Paint#setTextSize(float)
- */
- public void setFontSize(@FloatRange(from = 0) float fontSize) {
- mFontSize = fontSize;
- }
-
- /**
- * Return the horizontal scale factor
- *
- * @return a horizontal scale factor
- * @see Paint#getTextScaleX()
- * @see Paint#setTextScaleX(float)
- */
- public @FloatRange(from = 0) float getScaleX() {
- return mScaleX;
- }
-
- /**
- * Set the horizontal scale factor
- *
- * @param scaleX a horizontal scale factor
- * @see Paint#getTextScaleX()
- * @see Paint#setTextScaleX(float)
- */
- public void setScaleX(@FloatRange(from = 0) float scaleX) {
- mScaleX = scaleX;
- }
-
- /**
- * Return the horizontal skew factor
- *
- * @return a horizontal skew factor
- * @see Paint#getTextSkewX()
- * @see Paint#setTextSkewX(float)
- */
- public @FloatRange(from = 0) float getSkewX() {
- return mSkewX;
- }
-
- /**
- * Set the horizontal skew factor
- *
- * @param skewX a horizontal skew factor
- * @see Paint#getTextSkewX()
- * @see Paint#setTextSkewX(float)
- */
- public void setSkewX(@FloatRange(from = 0) float skewX) {
- mSkewX = skewX;
- }
-
- /**
- * Returns the Paint flags.
- *
- * @return a paint flags
- * @see Paint#getFlags()
- * @see Paint#setFlags(int)
- */
- public int getFlags() {
- return mFlags;
- }
-
- /**
- * Set the Paint flags.
- *
- * @param flags a paint flags
- * @see Paint#getFlags()
- * @see Paint#setFlags(int)
- */
- public void setFlags(int flags) {
- mFlags = flags;
- }
-
- /**
- * Applies glyph style to the paint object.
- *
- * @param paint a paint object
- */
- public void applyToPaint(@NonNull Paint paint) {
- paint.setColor(mColor);
- paint.setTextSize(mFontSize);
- paint.setTextScaleX(mScaleX);
- paint.setTextSkewX(mSkewX);
- paint.setFlags(mFlags);
- }
-
- /**
- * Copy parameters from a Paint object.
- *
- * @param paint a paint object
- */
- public void setFromPaint(@NonNull Paint paint) {
- mColor = paint.getColor();
- mFontSize = paint.getTextSize();
- mScaleX = paint.getTextScaleX();
- mSkewX = paint.getTextSkewX();
- mFlags = paint.getFlags();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof GlyphStyle)) return false;
- GlyphStyle that = (GlyphStyle) o;
- return that.mColor == mColor
- && Float.compare(that.mFontSize, mFontSize) == 0
- && Float.compare(that.mScaleX, mScaleX) == 0
- && Float.compare(that.mSkewX, mSkewX) == 0
- && mFlags == that.mFlags;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mColor, mFontSize, mScaleX, mSkewX, mFlags);
- }
-
- @Override
- public String toString() {
- return "GlyphStyle{"
- + "mColor=" + mColor
- + ", mFontSize=" + mFontSize
- + ", mScaleX=" + mScaleX
- + ", mSkewX=" + mSkewX
- + ", mFlags=" + mFlags
- + '}';
- }
-}
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 7364d54..ecbc45c 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -35,11 +35,12 @@
* Text shaping result object for single style text.
*
* You can get text shaping result by
- * {@link TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and
- * {@link TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}.
+ * {@link TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and
+ * {@link TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean,
+ * Paint)}.
*
- * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
- * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
+ * @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
+ * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
*/
public final class PositionedGlyphs {
private static final NativeAllocationRegistry REGISTRY =
@@ -49,7 +50,6 @@
private final long mLayoutPtr;
private final float mXOffset;
private final float mYOffset;
- private final GlyphStyle mGlyphStyle;
private final ArrayList<Font> mFonts;
/**
@@ -62,7 +62,7 @@
*
* @return total amount of advance
*/
- public float getTotalAdvance() {
+ public float getAdvance() {
return nGetTotalAdvance(mLayoutPtr);
}
@@ -91,21 +91,11 @@
}
/**
- * Returns the glyph style used for drawing the glyph at the given index.
- *
- * @return A glyph style
- */
- @NonNull
- public GlyphStyle getStyle() {
- return mGlyphStyle;
- }
-
- /**
* Returns the amount of X offset added to glyph position.
*
* @return The X offset added to glyph position.
*/
- public float getOriginX() {
+ public float getOffsetX() {
return mXOffset;
}
@@ -114,7 +104,7 @@
*
* @return The Y offset added to glyph position.
*/
- public float getOriginY() {
+ public float getOffsetY() {
return mYOffset;
}
@@ -144,7 +134,7 @@
* Returns the glyph ID used for drawing the glyph at the given index.
*
* @param index the glyph index
- * @return A font object
+ * @return An glyph ID of the font.
*/
@IntRange(from = 0)
public int getGlyphId(@IntRange(from = 0) int index) {
@@ -158,7 +148,7 @@
* @param index the glyph index
* @return A X offset in pixels
*/
- public float getPositionX(@IntRange(from = 0) int index) {
+ public float getGlyphX(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetX(mLayoutPtr, index) + mXOffset;
}
@@ -169,7 +159,7 @@
* @param index the glyph index
* @return A Y offset in pixels.
*/
- public float getPositionY(@IntRange(from = 0) int index) {
+ public float getGlyphY(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetY(mLayoutPtr, index) + mYOffset;
}
@@ -184,7 +174,6 @@
*/
public PositionedGlyphs(long layoutPtr, @NonNull Paint paint, float xOffset, float yOffset) {
mLayoutPtr = layoutPtr;
- mGlyphStyle = new GlyphStyle(paint);
int glyphCount = nGetGlyphCount(layoutPtr);
mFonts = new ArrayList<>(glyphCount);
mXOffset = xOffset;
@@ -229,14 +218,13 @@
if (!(o instanceof PositionedGlyphs)) return false;
PositionedGlyphs that = (PositionedGlyphs) o;
- if (!mGlyphStyle.equals(that.mGlyphStyle)) return false;
if (mXOffset != that.mXOffset || mYOffset != that.mYOffset) return false;
if (glyphCount() != that.glyphCount()) return false;
for (int i = 0; i < glyphCount(); ++i) {
if (getGlyphId(i) != that.getGlyphId(i)) return false;
- if (getPositionX(i) != that.getPositionX(i)) return false;
- if (getPositionY(i) != that.getPositionY(i)) return false;
+ if (getGlyphX(i) != that.getGlyphX(i)) return false;
+ if (getGlyphY(i) != that.getGlyphY(i)) return false;
// Intentionally using reference equality since font equality is heavy due to buffer
// compare.
if (getFont(i) != that.getFont(i)) return false;
@@ -247,10 +235,10 @@
@Override
public int hashCode() {
- int hashCode = Objects.hash(mXOffset, mYOffset, mGlyphStyle);
+ int hashCode = Objects.hash(mXOffset, mYOffset);
for (int i = 0; i < glyphCount(); ++i) {
hashCode = Objects.hash(hashCode,
- getGlyphId(i), getPositionX(i), getPositionY(i), getFont(i));
+ getGlyphId(i), getGlyphX(i), getGlyphY(i), getFont(i));
}
return hashCode;
}
@@ -263,7 +251,7 @@
sb.append(", ");
}
sb.append("[ ID = " + getGlyphId(i) + ","
- + " pos = (" + getPositionX(i) + "," + getPositionY(i) + ")"
+ + " pos = (" + getGlyphX(i) + "," + getGlyphY(i) + ")"
+ " font = " + getFont(i) + " ]");
}
sb.append("]");
@@ -271,7 +259,6 @@
+ "glyphs = " + sb.toString()
+ ", mXOffset=" + mXOffset
+ ", mYOffset=" + mYOffset
- + ", mGlyphStyle=" + mGlyphStyle
+ '}';
}
}
diff --git a/graphics/java/android/graphics/text/TextShaper.java b/graphics/java/android/graphics/text/TextRunShaper.java
similarity index 88%
rename from graphics/java/android/graphics/text/TextShaper.java
rename to graphics/java/android/graphics/text/TextRunShaper.java
index f40ed8f..b73436e 100644
--- a/graphics/java/android/graphics/text/TextShaper.java
+++ b/graphics/java/android/graphics/text/TextRunShaper.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.graphics.Paint;
import android.text.TextDirectionHeuristic;
+import android.text.TextPaint;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
@@ -31,15 +32,18 @@
* Text shaping is a preprocess for drawing text into canvas with glyphs. The glyph is a most
* primitive unit of the text drawing, consist of glyph identifier in the font file and its position
* and style. You can draw the shape result to Canvas by calling Canvas#drawGlyphs.
-
*
- * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
- * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
- * @see android.text.StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic,
- * TextPaint)
+ * For most of the use cases, {@link android.text.TextShaper} will provide text shaping
+ * functionalities needed. {@link TextRunShaper} is a lower level API that is used by
+ * {@link android.text.TextShaper}.
+ *
+ * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
+ * @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
+ * @see android.text.TextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint,
+ * TextShaper.GlyphsConsumer)
*/
-public class TextShaper {
- private TextShaper() {} // Do not instantiate
+public class TextRunShaper {
+ private TextRunShaper() {} // Do not instantiate
/**
* Shape non-styled text.
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 9d9e91f..9785aa5 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -196,7 +196,7 @@
};
int register_android_graphics_text_TextShaper(JNIEnv* env) {
- return RegisterMethodsOrDie(env, "android/graphics/text/TextShaper", gMethods,
+ return RegisterMethodsOrDie(env, "android/graphics/text/TextRunShaper", gMethods,
NELEM(gMethods))
+ RegisterMethodsOrDie(env, "android/graphics/text/PositionedGlyphs",
gResultMethods, NELEM(gResultMethods));
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 58e4ccc..fbe3d32 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -16507,23 +16507,6 @@
package android.graphics.text {
- public class GlyphStyle {
- ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int);
- ctor public GlyphStyle(@NonNull android.graphics.Paint);
- method public void applyToPaint(@NonNull android.graphics.Paint);
- method @ColorInt public int getColor();
- method public int getFlags();
- method @FloatRange(from=0) public float getFontSize();
- method @FloatRange(from=0) public float getScaleX();
- method @FloatRange(from=0) public float getSkewX();
- method public void setColor(@ColorInt int);
- method public void setFlags(int);
- method public void setFontSize(@FloatRange(from=0) float);
- method public void setFromPaint(@NonNull android.graphics.Paint);
- method public void setScaleX(@FloatRange(from=0) float);
- method public void setSkewX(@FloatRange(from=0) float);
- }
-
public class LineBreaker {
method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int);
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -16585,20 +16568,19 @@
}
public final class PositionedGlyphs {
+ method public float getAdvance();
method public float getAscent();
method public float getDescent();
method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
- method public float getOriginX();
- method public float getOriginY();
- method public float getPositionX(@IntRange(from=0) int);
- method public float getPositionY(@IntRange(from=0) int);
- method @NonNull public android.graphics.text.GlyphStyle getStyle();
- method public float getTotalAdvance();
+ method public float getGlyphX(@IntRange(from=0) int);
+ method public float getGlyphY(@IntRange(from=0) int);
+ method public float getOffsetX();
+ method public float getOffsetY();
method @IntRange(from=0) public int glyphCount();
}
- public class TextShaper {
+ public class TextRunShaper {
method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
}
@@ -48193,10 +48175,6 @@
method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
}
- public class StyledTextShaper {
- method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint);
- }
-
public interface TextDirectionHeuristic {
method public boolean isRtl(char[], int, int);
method public boolean isRtl(CharSequence, int, int);
@@ -48226,6 +48204,14 @@
field @Px public float underlineThickness;
}
+ public class TextShaper {
+ method public static void shapeText(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint, @NonNull android.text.TextShaper.GlyphsConsumer);
+ }
+
+ public static interface TextShaper.GlyphsConsumer {
+ method public void accept(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.text.PositionedGlyphs, @NonNull android.text.TextPaint);
+ }
+
public class TextUtils {
method @Deprecated public static CharSequence commaEllipsize(CharSequence, android.text.TextPaint, float, String, String);
method public static CharSequence concat(java.lang.CharSequence...);