diff options
| -rw-r--r-- | core/api/current.txt | 7 | ||||
| -rw-r--r-- | core/java/android/text/BoringLayout.java | 149 | ||||
| -rw-r--r-- | core/java/android/text/Layout.java | 33 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 14 | ||||
| -rw-r--r-- | core/java/android/text/TextLine.java | 37 | ||||
| -rw-r--r-- | core/java/android/text/TextShaper.java | 3 | ||||
| -rw-r--r-- | core/java/android/widget/Editor.java | 2 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 81 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/text/TextLineTest.java | 13 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Paint.java | 136 | ||||
| -rw-r--r-- | libs/hwui/hwui/MinikinUtils.cpp | 10 | ||||
| -rw-r--r-- | libs/hwui/hwui/MinikinUtils.h | 4 | ||||
| -rw-r--r-- | libs/hwui/jni/Paint.cpp | 87 |
13 files changed, 507 insertions, 69 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 70c09a83f063..e0414c056914 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15973,6 +15973,8 @@ package android.graphics { method public String getFontFeatureSettings(); method public float getFontMetrics(android.graphics.Paint.FontMetrics); method public android.graphics.Paint.FontMetrics getFontMetrics(); + method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); + method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt); method public android.graphics.Paint.FontMetricsInt getFontMetricsInt(); method public float getFontSpacing(); @@ -44649,6 +44651,7 @@ package android.text { public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback { ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); + ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public void ellipsized(int, int); method public int getBottomPadding(); method public int getEllipsisCount(int); @@ -44663,9 +44666,12 @@ package android.text { method public int getTopPadding(); method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint); method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics); + method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); + method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); + method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); } @@ -44874,6 +44880,7 @@ package android.text { method public abstract int getTopPadding(); method public final int getWidth(); method public final void increaseWidthTo(int); + method public boolean isFallbackLineSpacingEnabled(); method public boolean isRtlCharAt(int); method protected final boolean isSpanned(); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 3ee1a9000188..4ee02f01f330 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -16,6 +16,9 @@ package android.text; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; @@ -85,6 +88,37 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback } /** + * Utility function to construct a BoringLayout instance. + * + * The spacing multiplier and additional amount spacing are not used by BoringLayout. + * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will + * return 0.0. + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested width + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerWidth} is used instead + */ + public static @NonNull BoringLayout make( + @NonNull CharSequence source, @NonNull TextPaint paint, + @IntRange(from = 0) int outerWidth, + @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, + boolean includePad, @NonNull TextUtils.TruncateAt ellipsize, + @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) { + return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad, + ellipsize, ellipsizedWidth, useFallbackLineSpacing); + } + + /** * Returns a BoringLayout for the specified text, potentially reusing * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. @@ -109,7 +143,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedStart = 0; mEllipsizedCount = 0; - init(source, paint, align, metrics, includePad, true); + init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); return this; } @@ -118,12 +152,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. * + * The spacing multiplier and additional amount spacing are not used by BoringLayout. + * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will + * return 0.0. + * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text - * @param spacingMult this value is no longer used by BoringLayout - * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is @@ -134,13 +170,15 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerwidth} is used instead */ - public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, - Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, - boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source, + @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, + @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, + @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + boolean useFallbackLineSpacing) { boolean trust; if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { - replaceWith(source, paint, outerWidth, align, spacingMult, spacingAdd); + replaceWith(source, paint, outerWidth, align, 1f, 0f); mEllipsizedWidth = outerWidth; mEllipsizedStart = 0; @@ -148,17 +186,46 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = true; } else { replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), - paint, outerWidth, align, spacingMult, spacingAdd); + paint, outerWidth, align, 1f, 0f); mEllipsizedWidth = ellipsizedWidth; trust = false; } - init(getText(), paint, align, metrics, includePad, trust); + init(getText(), paint, align, metrics, includePad, trust, + useFallbackLineSpacing); return this; } /** + * Returns a BoringLayout for the specified text, potentially reusing + * this one if it is already suitable. The caller must make sure that + * no one is still using this Layout. + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param spacingMult this value is no longer used by BoringLayout + * @param spacingAdd this value is no longer used by BoringLayout + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested width + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerwidth} is used instead + */ + public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, + Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, + boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + return replaceOrMake(source, paint, outerWidth, align, metrics, + includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */); + } + + /** * @param source the text to render * @param paint the default paint for the layout * @param outerwidth the wrapping width for the text @@ -178,7 +245,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedStart = 0; mEllipsizedCount = 0; - init(source, paint, align, metrics, includePad, true); + init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); } /** @@ -202,6 +269,34 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + this(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad, + ellipsize, ellipsizedWidth, false /* fallbackLineSpacing */); + } + + /** + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param spacingMult this value is no longer used by BoringLayout + * @param spacingAdd this value is no longer used by BoringLayout + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested {@code outerwidth} + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerwidth} is used instead + */ + public BoringLayout( + @NonNull CharSequence source, @NonNull TextPaint paint, + @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult, + float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad, + @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + boolean useFallbackLineSpacing) { /* * It is silly to have to call super() and then replaceWith(), * but we can't use "this" for the callback until the call to @@ -224,11 +319,12 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = false; } - init(getText(), paint, align, metrics, includePad, trust); + init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing); } /* package */ void init(CharSequence source, TextPaint paint, Alignment align, - BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth) { + BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth, + boolean useFallbackLineSpacing) { int spacing; if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) { @@ -260,7 +356,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback TextLine line = TextLine.obtain(); line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, - mEllipsizedStart, mEllipsizedStart + mEllipsizedCount); + mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing); mMax = (int) Math.ceil(line.metrics(null)); TextLine.recycle(line); } @@ -336,6 +432,24 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback @UnsupportedAppUsage public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics) { + return isBoring(text, paint, textDir, false /* useFallbackLineSpacing */, metrics); + } + + /** + * Returns null if not boring; the width, ascent, and descent in the + * provided Metrics object (or a new one if the provided one was null) + * if boring. + * + * @param text a text to be calculated text layout. + * @param paint a paint object used for styling. + * @param textDir a text direction. + * @param useFallbackLineSpacing true if use fallback line spacing, otherwise false. + * @param metrics the out metrics. + * @return metrics on success. null if text cannot be rendered by BoringLayout. + */ + public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, + @Nullable Metrics metrics) { final int textLength = text.length(); if (hasAnyInterestingChars(text, textLength)) { return null; // There are some interesting characters. Not boring. @@ -362,7 +476,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */, - 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */); + 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */, + useFallbackLineSpacing); fm.width = (int) Math.ceil(line.metrics(fm)); TextLine.recycle(line); @@ -450,6 +565,11 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback return mEllipsizedWidth; } + @Override + public boolean isFallbackLineSpacingEnabled() { + return mUseFallbackLineSpacing; + } + // Override draw so it will be faster. @Override public void draw(Canvas c, Path highlight, Paint highlightpaint, @@ -471,6 +591,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback private String mDirect; private Paint mPaint; + private boolean mUseFallbackLineSpacing; /* package */ int mBottom, mDesc; // for Direct private int mTopPadding, mBottomPadding; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index da3e9b6d509c..95adb7765f1e 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -591,7 +591,8 @@ public abstract class Layout { } else { tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops, getEllipsisStart(lineNum), - getEllipsisStart(lineNum) + getEllipsisCount(lineNum)); + getEllipsisStart(lineNum) + getEllipsisCount(lineNum), + isFallbackLineSpacingEnabled()); if (justify) { tl.justify(right - left - indentWidth); } @@ -960,6 +961,15 @@ public abstract class Layout { } /** + * Return true if the fallback line space is enabled in this Layout. + * + * @return true if the fallback line space is enabled. Otherwise returns false. + */ + public boolean isFallbackLineSpacingEnabled() { + return false; + } + + /** * Returns true if the character at offset and the preceding character * are at different run levels (and thus there's a split caret). * @param offset the offset @@ -1231,7 +1241,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); float wid = tl.measure(offset - start, trailing, null); TextLine.recycle(tl); @@ -1271,7 +1282,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line); if (!primary) { for (int offset = 0; offset < trailings.length; ++offset) { @@ -1456,7 +1468,8 @@ public abstract class Layout { paint.setStartHyphenEdit(getStartHyphenEdit(line)); paint.setEndHyphenEdit(getEndHyphenEdit(line)); tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { tl.justify(getJustifyWidth(line)); } @@ -1486,7 +1499,8 @@ public abstract class Layout { paint.setStartHyphenEdit(getStartHyphenEdit(line)); paint.setEndHyphenEdit(getEndHyphenEdit(line)); tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { tl.justify(getJustifyWidth(line)); } @@ -1572,7 +1586,8 @@ public abstract class Layout { // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here. tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs, false, null, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); final HorizontalMeasurementProvider horizontal = new HorizontalMeasurementProvider(line, primary); @@ -1828,7 +1843,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); // XXX: we don't care about tabs tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft); TextLine.recycle(tl); return caret; @@ -2202,7 +2218,8 @@ public abstract class Layout { } } tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops, - 0 /* ellipsisStart */, 0 /* ellipsisEnd */); + 0 /* ellipsisStart */, 0 /* ellipsisEnd */, + false /* use fallback line spacing. unused */); return margin + Math.abs(tl.metrics(null)); } finally { TextLine.recycle(tl); diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 4789231b0404..b1bc7667da16 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -612,7 +612,6 @@ public class StaticLayout extends Layout { TextPaint paint = b.mPaint; int outerWidth = b.mWidth; TextDirectionHeuristic textDir = b.mTextDir; - final boolean fallbackLineSpacing = b.mFallbackLineSpacing; float spacingmult = b.mSpacingMult; float spacingadd = b.mSpacingAdd; float ellipsizedWidth = b.mEllipsizedWidth; @@ -630,6 +629,7 @@ public class StaticLayout extends Layout { mLineCount = 0; mEllipsized = false; mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT; + mFallbackLineSpacing = b.mFallbackLineSpacing; int v = 0; boolean needMultiply = (spacingmult != 1 || spacingadd != 0); @@ -867,17 +867,17 @@ public class StaticLayout extends Layout { boolean moreChars = (endPos < bufEnd); - final int ascent = fallbackLineSpacing + final int ascent = mFallbackLineSpacing ? Math.min(fmAscent, Math.round(ascents[breakIndex])) : fmAscent; - final int descent = fallbackLineSpacing + final int descent = mFallbackLineSpacing ? Math.max(fmDescent, Math.round(descents[breakIndex])) : fmDescent; // The fallback ascent/descent may be larger than top/bottom of the default font // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected // clipping. - if (fallbackLineSpacing) { + if (mFallbackLineSpacing) { if (ascent < fmTop) { fmTop = ascent; } @@ -1381,6 +1381,11 @@ public class StaticLayout extends Layout { return mEllipsizedWidth; } + @Override + public boolean isFallbackLineSpacingEnabled() { + return mFallbackLineSpacing; + } + /** * Return the total height of this layout. * @@ -1407,6 +1412,7 @@ public class StaticLayout extends Layout { @UnsupportedAppUsage private int mColumns; private int mEllipsizedWidth; + private boolean mFallbackLineSpacing; /** * Keeps track if ellipsize is applied to the text. diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 1a7ec7f99c95..2b396612cf3c 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -71,6 +71,8 @@ public class TextLine { private Spanned mSpanned; private PrecomputedText mComputed; + private boolean mUseFallbackExtent = false; + // The start and end of a potentially existing ellipsis on this text line. // We use them to filter out replacement and metric affecting spans on ellipsized away chars. private int mEllipsisStart; @@ -141,6 +143,7 @@ public class TextLine { tl.mTabs = null; tl.mChars = null; tl.mComputed = null; + tl.mUseFallbackExtent = false; tl.mMetricAffectingSpanSpanSet.recycle(); tl.mCharacterStyleSpanSet.recycle(); @@ -171,17 +174,20 @@ public class TextLine { * @param ellipsisStart the start of the ellipsis relative to the line * @param ellipsisEnd the end of the ellipsis relative to the line. When there * is no ellipsis, this should be equal to ellipsisStart. + * @param useFallbackLineSpacing true for enabling fallback line spacing. false for disabling + * fallback line spacing. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void set(TextPaint paint, CharSequence text, int start, int limit, int dir, Directions directions, boolean hasTabs, TabStops tabStops, - int ellipsisStart, int ellipsisEnd) { + int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) { mPaint = paint; mText = text; mStart = start; mLen = limit - start; mDir = dir; mDirections = directions; + mUseFallbackExtent = useFallbackLineSpacing; if (mDirections == null) { throw new IllegalArgumentException("Directions cannot be null"); } @@ -845,6 +851,31 @@ public class TextLine { previousLeading); } + private void expandMetricsFromPaint(TextPaint wp, int start, int end, + int contextStart, int contextEnd, boolean runIsRtl, FontMetricsInt fmi) { + + final int previousTop = fmi.top; + final int previousAscent = fmi.ascent; + final int previousDescent = fmi.descent; + final int previousBottom = fmi.bottom; + final int previousLeading = fmi.leading; + + if (mCharsValid) { + int count = end - start; + int contextCount = contextEnd - contextStart; + wp.getFontMetricsInt(mChars, start, count, contextStart, contextCount, runIsRtl, + fmi); + } else { + int delta = mStart; + wp.getFontMetricsInt(mText, delta + start, delta + end, + delta + contextStart, delta + contextEnd, runIsRtl, fmi); + } + + updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, + previousLeading); + } + + static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent, int previousDescent, int previousBottom, int previousLeading) { fmi.top = Math.min(fmi.top, previousTop); @@ -949,6 +980,10 @@ public class TextLine { shapeTextRun(consumer, wp, start, end, contextStart, contextEnd, runIsRtl, leftX); } + if (mUseFallbackExtent && fmi != null) { + expandMetricsFromPaint(wp, start, end, contextStart, contextEnd, runIsRtl, fmi); + } + if (c != null) { if (wp.bgColor != 0) { int previousColor = wp.getColor(); diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java index 02fd7b4470f0..a1d6cc8e283a 100644 --- a/core/java/android/text/TextShaper.java +++ b/core/java/android/text/TextShaper.java @@ -222,7 +222,8 @@ public class TextShaper { mp.getDirections(0, count), false /* tabstop is not supported */, null, - -1, -1 // ellipsis is not supported. + -1, -1, // ellipsis is not supported. + false /* fallback line spacing is not used */ ); tl.shape(consumer); } finally { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 60ce65153890..6284bc2f3513 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1073,7 +1073,7 @@ public class Editor { com.android.internal.R.dimen.textview_error_popup_default_width); final StaticLayout l = StaticLayout.Builder.obtain(text, 0, text.length(), tv.getPaint(), defaultWidthInPixels) - .setUseLineSpacingFromFallbacks(tv.mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(tv.isFallbackLineSpacingForStaticLayout()) .build(); float max = 0; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 014340197393..7327214c5389 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -48,6 +48,9 @@ import android.annotation.XmlRes; import android.app.Activity; import android.app.PendingIntent; import android.app.assist.AssistStructure; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ClipDescription; @@ -453,6 +456,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; + /** + * This change ID enables the fallback text line spacing (line height) for BoringLayout. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id + + /** + * This change ID enables the fallback text line spacing (line height) for StaticLayout. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P) + public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id + // System wide time for last cut, copy or text changed action. static long sLastCutCopyOrTextChangedTime; @@ -766,8 +785,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mListenerChanged = false; // True if internationalized input should be used for numbers and date and time. private final boolean mUseInternationalizedInput; - // True if fallback fonts that end up getting used should be allowed to affect line spacing. - /* package */ boolean mUseFallbackLineSpacing; + + // Fallback fonts that end up getting used should be allowed to affect line spacing. + private static final int FALLBACK_LINE_SPACING_NONE = 0; + private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1; + private static final int FALLBACK_LINE_SPACING_ALL = 2; + + private int mUseFallbackLineSpacing; // True if the view text can be padded for compat reasons, when the view is translated. private final boolean mUseTextPaddingForUiTranslation; @@ -1479,7 +1503,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; - mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; + if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL; + } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; + } else { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE; + } // TODO(b/179693024): Use a ChangeId instead. mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; @@ -4541,8 +4571,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_fallbackLineSpacing */ public void setFallbackLineSpacing(boolean enabled) { - if (mUseFallbackLineSpacing != enabled) { - mUseFallbackLineSpacing = enabled; + int fallbackStrategy; + if (enabled) { + if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { + fallbackStrategy = FALLBACK_LINE_SPACING_ALL; + } else { + fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; + } + } else { + fallbackStrategy = FALLBACK_LINE_SPACING_NONE; + } + if (mUseFallbackLineSpacing != fallbackStrategy) { + mUseFallbackLineSpacing = fallbackStrategy; if (mLayout != null) { nullLayouts(); requestLayout(); @@ -4560,7 +4600,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @InspectableProperty public boolean isFallbackLineSpacing() { - return mUseFallbackLineSpacing; + return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; + } + + private boolean isFallbackLineSpacingForBoringLayout() { + return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; + } + + // Package privte for accessing from Editor.java + /* package */ boolean isFallbackLineSpacingForStaticLayout() { + return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL + || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; } /** @@ -9148,7 +9198,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (hintBoring == UNKNOWN_BORING) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, - mHintBoring); + isFallbackLineSpacingForBoringLayout(), mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } @@ -9190,7 +9240,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -9250,7 +9300,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -9259,7 +9309,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener result = builder.build(); } else { if (boring == UNKNOWN_BORING) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mBoring); if (boring != null) { mBoring = boring; } @@ -9303,7 +9354,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -9430,7 +9481,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (des < 0) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mBoring); if (boring != null) { mBoring = boring; } @@ -9463,7 +9515,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (hintDes < 0) { - hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); + hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } @@ -9667,7 +9720,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(getBreakStrategy()) .setHyphenationFrequency(getHyphenationFrequency()) .setJustificationMode(getJustificationMode()) diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java index 90ce305b3dab..412d6ec975ac 100644 --- a/core/tests/coretests/src/android/text/TextLineTest.java +++ b/core/tests/coretests/src/android/text/TextLineTest.java @@ -48,7 +48,7 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */, - 0, 0 /* no ellipsis */); + 0, 0 /* no ellipsis */, false /* useFallbackLinespace */); final float originalWidth = tl.metrics(null); final float expandedWidth = 2 * originalWidth; @@ -105,7 +105,7 @@ public class TextLineTest { tl.set(paint, str, 0, str.length(), TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(str, 0, str.length()) ? -1 : 1, layout.getLineDirections(0), tabStops != null, tabStops, - 0, 0 /* no ellipsis */); + 0, 0 /* no ellipsis */, false /* useFallbackLineSpacing */); return tl; } @@ -276,7 +276,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertFalse(span.mIsUsed); @@ -292,7 +293,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertTrue(span.mIsUsed); @@ -308,7 +310,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertTrue(span.mIsUsed); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 42e470b7f660..eefad8d0e4de 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Locale; +import java.util.Objects; /** * The Paint class holds the style and color information about how to draw @@ -2131,6 +2132,116 @@ public class Paint { } /** + * Returns the font metrics value for the given text. + * + * If the text is rendered with multiple font files, this function returns the large ascent and + * descent that are enough for drawing all font files. + * + * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari, + * changes letter shape based on its location or surrounding characters. + * + * @param text a text to be measured. + * @param start a starting offset in the text. + * @param count a length of the text to be measured. + * @param contextStart a context starting offset in the text. + * @param contextCount a length of the context to be used. + * @param isRtl true if measuring on RTL context, otherwise false. + * @param outMetrics the output font metrics. + */ + public void getFontMetricsInt( + @NonNull CharSequence text, + @IntRange(from = 0) int start, @IntRange(from = 0) int count, + @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, + boolean isRtl, + @NonNull FontMetricsInt outMetrics) { + + if (text == null) { + throw new IllegalArgumentException("text must not be null"); + } + if (start < 0 || start >= text.length()) { + throw new IllegalArgumentException("start argument is out of bounds."); + } + if (count < 0 || start + count > text.length()) { + throw new IllegalArgumentException("count argument is out of bounds."); + } + if (contextStart < 0 || contextStart >= text.length()) { + throw new IllegalArgumentException("ctxStart argument is out of bounds."); + } + if (contextCount < 0 || contextStart + contextCount > text.length()) { + throw new IllegalArgumentException("ctxCount argument is out of bounds."); + } + if (outMetrics == null) { + throw new IllegalArgumentException("outMetrics must not be null."); + } + + if (count == 0) { + getFontMetricsInt(outMetrics); + return; + } + + if (text instanceof String) { + nGetFontMetricsIntForText(mNativePaint, (String) text, start, count, contextStart, + contextCount, isRtl, outMetrics); + } else { + char[] buf = TemporaryBuffer.obtain(contextCount); + TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0); + nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0, + contextCount, isRtl, outMetrics); + } + + } + + /** + * Returns the font metrics value for the given text. + * + * If the text is rendered with multiple font files, this function returns the large ascent and + * descent that are enough for drawing all font files. + * + * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari, + * changes letter shape based on its location or surrounding characters. + * + * @param text a text to be measured. + * @param start a starting offset in the text. + * @param count a length of the text to be measured. + * @param contextStart a context starting offset in the text. + * @param contextCount a length of the context to be used. + * @param isRtl true if measuring on RTL context, otherwise false. + * @param outMetrics the output font metrics. + */ + public void getFontMetricsInt(@NonNull char[] text, + @IntRange(from = 0) int start, @IntRange(from = 0) int count, + @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, + boolean isRtl, + @NonNull FontMetricsInt outMetrics) { + if (text == null) { + throw new IllegalArgumentException("text must not be null"); + } + if (start < 0 || start >= text.length) { + throw new IllegalArgumentException("start argument is out of bounds."); + } + if (count < 0 || start + count > text.length) { + throw new IllegalArgumentException("count argument is out of bounds."); + } + if (contextStart < 0 || contextStart >= text.length) { + throw new IllegalArgumentException("ctxStart argument is out of bounds."); + } + if (contextCount < 0 || contextStart + contextCount > text.length) { + throw new IllegalArgumentException("ctxCount argument is out of bounds."); + } + if (outMetrics == null) { + throw new IllegalArgumentException("outMetrics must not be null."); + } + + if (count == 0) { + getFontMetricsInt(outMetrics); + return; + } + + nGetFontMetricsIntForText(mNativePaint, text, start, count, contextStart, contextCount, + isRtl, outMetrics); + } + + /** * Convenience method for callers that want to have FontMetrics values as * integers. */ @@ -2163,6 +2274,23 @@ public class Paint { " descent=" + descent + " bottom=" + bottom + " leading=" + leading; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FontMetricsInt)) return false; + FontMetricsInt that = (FontMetricsInt) o; + return top == that.top + && ascent == that.ascent + && descent == that.descent + && bottom == that.bottom + && leading == that.leading; + } + + @Override + public int hashCode() { + return Objects.hash(top, ascent, descent, bottom, leading); + } } /** @@ -3117,6 +3245,13 @@ public class Paint { int contextStart, int contextEnd, boolean isRtl, int offset); private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance); + private static native void nGetFontMetricsIntForText(long paintPtr, char[] text, + int start, int count, int ctxStart, int ctxCount, boolean isRtl, + FontMetricsInt outMetrics); + private static native void nGetFontMetricsIntForText(long paintPtr, String text, + int start, int count, int ctxStart, int ctxCount, boolean isRtl, + FontMetricsInt outMetrics); + // ---------------- @FastNative ------------------------ @@ -3130,7 +3265,6 @@ public class Paint { @FastNative private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi); - // ---------------- @CriticalNative ------------------------ @CriticalNative diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index b8029087cb4f..e359145feef7 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -95,6 +95,16 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, endHyphen, advances); } +minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, + const Typeface* typeface, const uint16_t* buf, + size_t start, size_t count, size_t bufSize) { + minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); + const minikin::U16StringPiece textBuf(buf, bufSize); + const minikin::Range range(start, start + count); + + return minikin::getFontExtent(textBuf, range, bidiFlags, minikinPaint); +} + bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs); diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index a15803ad2dca..009b84b140ea 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -56,6 +56,10 @@ public: size_t start, size_t count, size_t bufSize, float* advances); + static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, + const Typeface* typeface, const uint16_t* buf, + size_t start, size_t count, size_t bufSize); + static bool hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs); diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 22a1e1fd94b9..f76863255153 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -541,26 +541,6 @@ namespace PaintGlue { return result; } - // ------------------ @FastNative --------------------------- - - static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { - Paint* obj = reinterpret_cast<Paint*>(objHandle); - ScopedUtfChars localesChars(env, locales); - jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str()); - obj->setMinikinLocaleListId(minikinLocaleListId); - return minikinLocaleListId; - } - - static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - if (!settings) { - paint->setFontFeatureSettings(std::string()); - } else { - ScopedUtfChars settingsChars(env, settings); - paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); - } - } - static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) { const int kElegantTop = 2500; const int kElegantBottom = -1000; @@ -593,6 +573,67 @@ namespace PaintGlue { return spacing; } + static void doFontExtent(JNIEnv* env, jlong paintHandle, const jchar buf[], jint start, + jint count, jint bufSize, jboolean isRtl, jobject fmi) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + minikin::MinikinExtent extent = + MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize); + + SkFontMetrics metrics; + getMetricsInternal(paintHandle, &metrics); + + metrics.fAscent = extent.ascent; + metrics.fDescent = extent.descent; + + // If top/bottom is narrower than ascent/descent, adjust top/bottom to ascent/descent. + metrics.fTop = std::min(metrics.fAscent, metrics.fTop); + metrics.fBottom = std::max(metrics.fDescent, metrics.fBottom); + + GraphicsJNI::set_metrics_int(env, fmi, metrics); + } + + static void getFontMetricsIntForText___C(JNIEnv* env, jclass, jlong paintHandle, + jcharArray text, jint start, jint count, jint ctxStart, + jint ctxCount, jboolean isRtl, jobject fmi) { + ScopedCharArrayRO textArray(env, text); + + doFontExtent(env, paintHandle, textArray.get() + ctxStart, start - ctxStart, count, + ctxCount, isRtl, fmi); + } + + static void getFontMetricsIntForText___String(JNIEnv* env, jclass, jlong paintHandle, + jstring text, jint start, jint count, + jint ctxStart, jint ctxCount, jboolean isRtl, + jobject fmi) { + ScopedStringChars textChars(env, text); + + doFontExtent(env, paintHandle, textChars.get() + ctxStart, start - ctxStart, count, + ctxCount, isRtl, fmi); + } + + // ------------------ @FastNative --------------------------- + + static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + ScopedUtfChars localesChars(env, locales); + jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str()); + obj->setMinikinLocaleListId(minikinLocaleListId); + return minikinLocaleListId; + } + + static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, + jstring settings) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + if (!settings) { + paint->setFontFeatureSettings(std::string()); + } else { + ScopedUtfChars settingsChars(env, settings); + paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); + } + } + static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { SkFontMetrics metrics; SkScalar spacing = getMetricsInternal(paintHandle, &metrics); @@ -1015,6 +1056,11 @@ static const JNINativeMethod methods[] = { {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, + {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___C}, + {"nGetFontMetricsIntForText", + "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___String}, // --------------- @FastNative ---------------------- @@ -1093,6 +1139,7 @@ static const JNINativeMethod methods[] = { {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, }; + int register_android_graphics_Paint(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods)); } |