diff --git a/core/api/current.txt b/core/api/current.txt index 5c3f077e816fe..cc5144aa5631e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16448,6 +16448,7 @@ package android.graphics.text { public class MeasuredText { method public void getBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Rect); method @FloatRange(from=0.0f) @Px public float getCharWidthAt(@IntRange(from=0) int); + method public void getFontMetricsInt(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Paint.FontMetricsInt); method @FloatRange(from=0.0) @Px public float getWidth(@IntRange(from=0) int, @IntRange(from=0) int); } @@ -45010,6 +45011,7 @@ package android.text { method public char charAt(int); method public static android.text.PrecomputedText create(@NonNull CharSequence, @NonNull android.text.PrecomputedText.Params); method public void getBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Rect); + method public void getFontMetricsInt(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Paint.FontMetricsInt); method @IntRange(from=0) public int getParagraphCount(); method @IntRange(from=0) public int getParagraphEnd(@IntRange(from=0) int); method @IntRange(from=0) public int getParagraphStart(@IntRange(from=0) int); diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index 748d55123b9a4..bedd4097d2dba 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -286,6 +286,16 @@ public class MeasuredParagraph { mMeasuredText.getBounds(start, end, bounds); } + /** + * Retrieves the font metrics for the given range. + * + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + */ + public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end, + @NonNull Paint.FontMetricsInt fmi) { + mMeasuredText.getFontMetricsInt(start, end, fmi); + } + /** * Returns a width of the character at the offset. * diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index be66db2a4c055..9307e566b5c3a 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Paint; import android.graphics.Rect; import android.graphics.text.LineBreakConfig; import android.graphics.text.MeasuredText; @@ -696,6 +697,38 @@ public class PrecomputedText implements Spannable { getMeasuredParagraph(paraIndex).getBounds(start - paraStart, end - paraStart, bounds); } + /** + * Retrieves the text font metrics for the given range. + * Both {@code start} and {@code end} offset need to be in the same paragraph, otherwise + * IllegalArgumentException will be thrown. + * + * @param start the inclusive start offset in the text + * @param end the exclusive end offset in the text + * @param outMetrics the output font metrics + * @throws IllegalArgumentException if start and end offset are in the different paragraph. + */ + public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end, + @NonNull Paint.FontMetricsInt outMetrics) { + Preconditions.checkArgument(0 <= start && start <= mText.length(), "invalid start offset"); + Preconditions.checkArgument(0 <= end && end <= mText.length(), "invalid end offset"); + Preconditions.checkArgument(start <= end, "start offset can not be larger than end offset"); + Objects.requireNonNull(outMetrics); + if (start == end) { + mParams.getTextPaint().getFontMetricsInt(outMetrics); + return; + } + final int paraIndex = findParaIndex(start); + final int paraStart = getParagraphStart(paraIndex); + final int paraEnd = getParagraphEnd(paraIndex); + if (start < paraStart || paraEnd < end) { + throw new IllegalArgumentException("Cannot measured across the paragraph:" + + "para: (" + paraStart + ", " + paraEnd + "), " + + "request: (" + start + ", " + end + ")"); + } + getMeasuredParagraph(paraIndex).getFontMetricsInt(start - paraStart, + end - paraStart, outMetrics); + } + /** * Returns a width of a character at offset * diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 49e21110d6796..e39231c9d64b8 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -866,8 +866,12 @@ public class TextLine { wp.getFontMetricsInt(mChars, start, count, contextStart, contextCount, runIsRtl, fmi); } else { - wp.getFontMetricsInt(mText, mStart + start, count, mStart + contextStart, contextCount, - runIsRtl, fmi); + if (mComputed == null) { + wp.getFontMetricsInt(mText, mStart + start, count, mStart + contextStart, + contextCount, runIsRtl, fmi); + } else { + mComputed.getFontMetricsInt(mStart + start, mStart + end, fmi); + } } updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 6d691c122576b..3f7f0880d1604 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -34,6 +34,7 @@ import libcore.util.NativeAllocationRegistry; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * Result of text shaping of the single paragraph string. @@ -56,18 +57,22 @@ import java.lang.annotation.RetentionPolicy; public class MeasuredText { private static final String TAG = "MeasuredText"; - private long mNativePtr; - private boolean mComputeHyphenation; - private boolean mComputeLayout; - private @NonNull char[] mChars; + private final long mNativePtr; + private final boolean mComputeHyphenation; + private final boolean mComputeLayout; + @NonNull private final char[] mChars; + private final int mTop; + private final int mBottom; // Use builder instead. private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation, - boolean computeLayout) { + boolean computeLayout, int top, int bottom) { mNativePtr = ptr; mChars = chars; mComputeHyphenation = computeHyphenation; mComputeLayout = computeLayout; + mTop = top; + mBottom = bottom; } /** @@ -123,6 +128,30 @@ public class MeasuredText { nGetBounds(mNativePtr, mChars, start, end, rect); } + /** + * Retrieves the font metrics of the given range + * + * @param start an inclusive start index of the range + * @param end an exclusive end index of the range + * @param outMetrics an output metrics object + */ + public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end, + @NonNull Paint.FontMetricsInt outMetrics) { + Preconditions.checkArgument(0 <= start && start <= mChars.length, + "start(%d) must be 0 <= start <= %d", start, mChars.length); + Preconditions.checkArgument(0 <= end && end <= mChars.length, + "end(%d) must be 0 <= end <= %d", end, mChars.length); + Preconditions.checkArgument(start <= end, + "start(%d) is larger than end(%d)", start, end); + Objects.requireNonNull(outMetrics); + + long packed = nGetExtent(mNativePtr, mChars, start, end); + outMetrics.ascent = (int) (packed >> 32); + outMetrics.descent = (int) (packed & 0xFFFFFFFF); + outMetrics.top = Math.min(outMetrics.ascent, mTop); + outMetrics.bottom = Math.max(outMetrics.descent, mBottom); + } + /** * Returns the width of the character at the given offset. * @@ -160,6 +189,8 @@ public class MeasuredText { @CriticalNative private static native float nGetCharWidthAt(long nativePtr, int offset); + private static native long nGetExtent(long nativePtr, char[] buf, int start, int end); + /** * Helper class for creating a {@link MeasuredText}. *

@@ -189,6 +220,9 @@ public class MeasuredText { private boolean mFastHyphenation = false; private int mCurrentOffset = 0; private @Nullable MeasuredText mHintMt = null; + private int mTop = 0; + private int mBottom = 0; + private Paint.FontMetricsInt mCachedMetrics = new Paint.FontMetricsInt(); /** * Construct a builder. @@ -269,6 +303,10 @@ public class MeasuredText { nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, mCurrentOffset, end, isRtl); mCurrentOffset = end; + + paint.getFontMetricsInt(mCachedMetrics); + mTop = Math.min(mTop, mCachedMetrics.top); + mBottom = Math.max(mBottom, mCachedMetrics.bottom); return this; } @@ -419,7 +457,7 @@ public class MeasuredText { long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation, mComputeLayout, mFastHyphenation); final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation, - mComputeLayout); + mComputeLayout, mTop, mBottom); sRegistry.registerNativeAllocation(res, ptr); return res; } finally { diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp index 76ea2d5c9ff35..c13c800651efc 100644 --- a/libs/hwui/jni/text/MeasuredText.cpp +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -134,6 +134,21 @@ static void nGetBounds(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jin GraphicsJNI::irect_to_jrect(ir, env, bounds); } +// Regular JNI +static jlong nGetExtent(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jint start, + jint end) { + ScopedCharArrayRO text(env, javaText); + const minikin::U16StringPiece textBuffer(text.get(), text.size()); + const minikin::Range range(start, end); + + minikin::MinikinExtent extent = toMeasuredParagraph(ptr)->getExtent(textBuffer, range); + + int32_t ascent = SkScalarRoundToInt(extent.ascent); + int32_t descent = SkScalarRoundToInt(extent.descent); + + return (((jlong)(ascent)) << 32) | ((jlong)descent); +} + // CriticalNative static jlong nGetReleaseFunc(CRITICAL_JNI_PARAMS) { return toJLong(&releaseMeasuredParagraph); @@ -153,12 +168,13 @@ static const JNINativeMethod gMTBuilderMethods[] = { }; static const JNINativeMethod gMTMethods[] = { - // MeasuredParagraph native functions. - {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives - {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*) nGetBounds}, // Regular JNI - {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives - {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native - {"nGetCharWidthAt", "(JI)F", (void*) nGetCharWidthAt}, // Critical Native + // MeasuredParagraph native functions. + {"nGetWidth", "(JII)F", (void*)nGetWidth}, // Critical Natives + {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*)nGetBounds}, // Regular JNI + {"nGetExtent", "(J[CII)J", (void*)nGetExtent}, // Regular JNI + {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc}, // Critical Natives + {"nGetMemoryUsage", "(J)I", (void*)nGetMemoryUsage}, // Critical Native + {"nGetCharWidthAt", "(JI)F", (void*)nGetCharWidthAt}, // Critical Native }; int register_android_graphics_text_MeasuredText(JNIEnv* env) {