Implement fallback line spacing for BoringLayout
The fallback line spacing is a feature of extending the line height
when the fallback font has taller glyph. This was implemented to
StaticLayout in Android P but not yet implemented in BoringLayout.
This CL enables this feature to the BoringLayout as well.
Not to break existing apps, change this behavior only if the
targetSdk version is T or later.
Bug: 210923482
Test: atest FallbackLineSpacingTest BoringLayoutFallbackLineSpacingTest
Test: atest CtsGraphicsTestCases
Test: atest CtsTextTestCases
Change-Id: I6214d52cde25a044bc6e246d2118e35d3a243c9d
diff --git a/core/api/current.txt b/core/api/current.txt
index 70c09a8..e0414c0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15973,6 +15973,8 @@
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 @@
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 @@
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 @@
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 3ee1a90..4ee02f0 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 @@
}
/**
+ * 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,57 @@
mEllipsizedStart = 0;
mEllipsizedCount = 0;
- init(source, paint, align, metrics, includePad, true);
+ init(source, paint, align, metrics, includePad, true, false /* 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.
+ *
+ * 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 @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, 1f, 0f);
+
+ mEllipsizedWidth = outerWidth;
+ mEllipsizedStart = 0;
+ mEllipsizedCount = 0;
+ trust = true;
+ } else {
+ replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
+ paint, outerWidth, align, 1f, 0f);
+
+ mEllipsizedWidth = ellipsizedWidth;
+ trust = false;
+ }
+
+ init(getText(), paint, align, metrics, includePad, trust,
+ useFallbackLineSpacing);
return this;
}
@@ -137,25 +221,8 @@
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) {
- boolean trust;
-
- if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
- replaceWith(source, paint, outerWidth, align, spacingMult, spacingAdd);
-
- mEllipsizedWidth = outerWidth;
- mEllipsizedStart = 0;
- mEllipsizedCount = 0;
- trust = true;
- } else {
- replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
- paint, outerWidth, align, spacingMult, spacingAdd);
-
- mEllipsizedWidth = ellipsizedWidth;
- trust = false;
- }
-
- init(getText(), paint, align, metrics, includePad, trust);
- return this;
+ return replaceOrMake(source, paint, outerWidth, align, metrics,
+ includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */);
}
/**
@@ -178,7 +245,7 @@
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 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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 da3e9b6..95adb77 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -591,7 +591,8 @@
} 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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
}
}
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 4789231..b1bc766 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -612,7 +612,6 @@
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 @@
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 @@
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 @@
return mEllipsizedWidth;
}
+ @Override
+ public boolean isFallbackLineSpacingEnabled() {
+ return mFallbackLineSpacing;
+ }
+
/**
* Return the total height of this layout.
*
@@ -1407,6 +1412,7 @@
@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 1a7ec7f..2b39661 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -71,6 +71,8 @@
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 @@
tl.mTabs = null;
tl.mChars = null;
tl.mComputed = null;
+ tl.mUseFallbackExtent = false;
tl.mMetricAffectingSpanSpanSet.recycle();
tl.mCharacterStyleSpanSet.recycle();
@@ -171,17 +174,20 @@
* @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 @@
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 @@
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 02fd7b4..a1d6cc8 100644
--- a/core/java/android/text/TextShaper.java
+++ b/core/java/android/text/TextShaper.java
@@ -222,7 +222,8 @@
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 60ce651..6284bc2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1073,7 +1073,7 @@
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 0143401..7327214 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -48,6 +48,9 @@
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 @@
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 @@
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 @@
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 @@
* @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 @@
*/
@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 @@
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- mHintBoring);
+ isFallbackLineSpacingForBoringLayout(), mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -9190,7 +9240,7 @@
.setTextDirection(mTextDir)
.setLineSpacing(mSpacingAdd, mSpacingMult)
.setIncludePad(mIncludePad)
- .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
+ .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
.setBreakStrategy(mBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency)
.setJustificationMode(mJustificationMode)
@@ -9250,7 +9300,7 @@
.setTextDirection(mTextDir)
.setLineSpacing(mSpacingAdd, mSpacingMult)
.setIncludePad(mIncludePad)
- .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
+ .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
.setBreakStrategy(mBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency)
.setJustificationMode(mJustificationMode)
@@ -9259,7 +9309,8 @@
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 @@
.setTextDirection(mTextDir)
.setLineSpacing(mSpacingAdd, mSpacingMult)
.setIncludePad(mIncludePad)
- .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
+ .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
.setBreakStrategy(mBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency)
.setJustificationMode(mJustificationMode)
@@ -9430,7 +9481,8 @@
}
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 @@
}
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 @@
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 90ce305..412d6ec 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -48,7 +48,7 @@
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 @@
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 @@
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 @@
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 @@
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 42e470b..eefad8d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -46,6 +46,7 @@
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 @@
}
/**
+ * 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 @@
" 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 @@
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 @@
@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 b802908..e359145 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -95,6 +95,16 @@
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 a15803a..009b84b 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -56,6 +56,10 @@
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 22a1e1f..f768632 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -541,26 +541,6 @@
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 @@
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 @@
{"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 @@
{"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
};
+
int register_android_graphics_Paint(JNIEnv* env) {
return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods));
}