diff --git a/api/current.txt b/api/current.txt index a550bf8797177..1c20e78371f7c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16525,23 +16525,6 @@ package android.graphics.pdf { 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 @@ package android.graphics.text { } 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 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } - public class StyledTextShaper { - method @NonNull public static java.util.List 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 @@ package android.text { 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 bf906143bc563..0000000000000 --- 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 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 b826832609856..4471056e23dd2 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -24,7 +24,7 @@ import android.graphics.Canvas; 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.annotations.VisibleForTesting; 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 @@ public class TextLine { /** * Shape the TextLine. */ - List shape() { - List glyphs = new ArrayList<>(); + void shape(TextShaper.GlyphsConsumer consumer) { float horizontal = 0; float x = 0; final int runCount = mDirections.getRunCount(); @@ -326,7 +324,7 @@ public class TextLine { 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 @@ public class TextLine { } } } - return glyphs; } /** @@ -546,7 +543,7 @@ public class TextLine { /** * 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 @@ public class TextLine { * @return the signed width of the run, based on the paragraph direction. * Only valid if needWidth is true. */ - private float shapeRun(List 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 @@ public class TextLine { * @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 @@ public class TextLine { */ private float handleText(TextPaint wp, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, - Canvas c, List 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 decorations) { @@ -946,8 +944,8 @@ public class TextLine { 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 @@ public class TextLine { * @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 @@ public class TextLine { */ private float handleRun(int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, - List 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 @@ public class TextLine { 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 @@ public class TextLine { 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 @@ public class TextLine { 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 @@ public class TextLine { /** * 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 @@ public class TextLine { * @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 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 0000000000000..dd2570401b3eb --- /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. + *
+ * 
+ * // 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)
+ * }
+ * 
+ * 
+ * @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 f92ea99e8f641..8237cb0bb2430 100644 --- a/core/tests/coretests/src/android/text/TextShaperTest.java +++ b/core/tests/coretests/src/android/text/TextShaperTest.java @@ -18,16 +18,12 @@ package android.text; 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 class TextShaperTest { public void testFontWithPath() { TextPaint p = new TextPaint(); p.setFontFeatureSettings("'wght' 900"); - List 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 05df250fa6b9a..54f9fa81b9b79 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -26,11 +26,13 @@ import android.compat.annotation.UnsupportedAppUsage; 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 @@ public abstract class BaseCanvas { * @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 5e07d156a06af..829d0f4be5137 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -26,7 +26,9 @@ import android.annotation.Size; 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 @@ public class Canvas extends BaseCanvas { * 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 glyphIds * array. * @param positions A flattened X and Y position array. The first glyph X position must be @@ -2071,8 +2073,8 @@ public class Canvas extends BaseCanvas { * @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 cc8c4d26fb5e4..0000000000000 --- 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 7364d545a4526..ecbc45c54d85c 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -35,11 +35,12 @@ import java.util.Objects; * 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 @@ public final class PositionedGlyphs { private final long mLayoutPtr; private final float mXOffset; private final float mYOffset; - private final GlyphStyle mGlyphStyle; private final ArrayList mFonts; /** @@ -62,7 +62,7 @@ public final class PositionedGlyphs { * * @return total amount of advance */ - public float getTotalAdvance() { + public float getAdvance() { return nGetTotalAdvance(mLayoutPtr); } @@ -90,22 +90,12 @@ public final class PositionedGlyphs { return nGetDescent(mLayoutPtr); } - /** - * 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 @@ public final class PositionedGlyphs { * * @return The Y offset added to glyph position. */ - public float getOriginY() { + public float getOffsetY() { return mYOffset; } @@ -144,7 +134,7 @@ public final class PositionedGlyphs { * 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 @@ public final class PositionedGlyphs { * @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 @@ public final class PositionedGlyphs { * @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 final class PositionedGlyphs { */ 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 @@ public final class PositionedGlyphs { 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 @@ public final class PositionedGlyphs { @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 @@ public final class PositionedGlyphs { 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 @@ public final class PositionedGlyphs { + "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 f40ed8f8f653a..b73436e36ae07 100644 --- a/graphics/java/android/graphics/text/TextShaper.java +++ b/graphics/java/android/graphics/text/TextRunShaper.java @@ -19,6 +19,7 @@ package android.graphics.text; 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 @@ import dalvik.annotation.optimization.FastNative; * 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 9d9e91f19851e..9785aa537f65d 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -196,7 +196,7 @@ static const JNINativeMethod gResultMethods[] = { }; 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 58e4ccc17a675..fbe3d323a8461 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -16507,23 +16507,6 @@ package android.graphics.pdf { 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 @@ package android.graphics.text { } 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 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } - public class StyledTextShaper { - method @NonNull public static java.util.List 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 @@ package android.text { 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...);