diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java new file mode 100644 index 0000000000000..74d136654c62f --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.text; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.CharBuffer; +import java.util.Random; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class StaticLayoutPerfTest { + + public StaticLayoutPerfTest() { + } + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private static final String FIXED_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing " + + "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad " + + "minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse " + + "cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " + + "proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + private static final int FIXED_TEXT_LENGTH = FIXED_TEXT.length(); + + private static TextPaint PAINT = new TextPaint(); + private static final int TEXT_WIDTH = 20 * (int) PAINT.getTextSize(); + + @Test + public void testCreate() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + StaticLayout.Builder.obtain(FIXED_TEXT, 0, FIXED_TEXT_LENGTH, PAINT, TEXT_WIDTH) + .build(); + } + } + + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final int ALPHABET_LENGTH = ALPHABET.length(); + + private static final int PARA_LENGTH = 500; + private final char[] mBuffer = new char[PARA_LENGTH]; + private final Random mRandom = new Random(31415926535L); + + private CharSequence generateRandomParagraph(int wordLen) { + for (int i = 0; i < PARA_LENGTH; i++) { + if (i % (wordLen + 1) == wordLen) { + mBuffer[i] = ' '; + } else { + mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH)); + } + } + return CharBuffer.wrap(mBuffer); + } + + // This tries to simulate the case where the cache hit rate is low, and most of the text is + // new text. + @Test + public void testCreateRandom() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + final CharSequence text = generateRandomParagraph(9); + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) + .build(); + } + } +} diff --git a/api/current.txt b/api/current.txt index 6bf11bbf69c21..69323c1a126e8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -41761,6 +41761,7 @@ package android.text { method public android.text.StaticLayout.Builder setMaxLines(int); method public android.text.StaticLayout.Builder setText(java.lang.CharSequence); method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); + method public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } public abstract interface TextDirectionHeuristic { diff --git a/api/system-current.txt b/api/system-current.txt index df2df2b18a90a..973eb9968a8d9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -45362,6 +45362,7 @@ package android.text { method public android.text.StaticLayout.Builder setMaxLines(int); method public android.text.StaticLayout.Builder setText(java.lang.CharSequence); method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); + method public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } public abstract interface TextDirectionHeuristic { diff --git a/api/test-current.txt b/api/test-current.txt index aa08dd9679986..fe66ec9741a99 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -42032,6 +42032,7 @@ package android.text { method public android.text.StaticLayout.Builder setMaxLines(int); method public android.text.StaticLayout.Builder setText(java.lang.CharSequence); method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); + method public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } public abstract interface TextDirectionHeuristic { diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 6a7db4ed4e27f..dd82e1e0b5d2e 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -90,6 +90,7 @@ public class StaticLayout extends Layout { b.mSpacingMult = 1.0f; b.mSpacingAdd = 0.0f; b.mIncludePad = true; + b.mFallbackLineSpacing = false; b.mEllipsizedWidth = width; b.mEllipsize = null; b.mMaxLines = Integer.MAX_VALUE; @@ -227,6 +228,24 @@ public class StaticLayout extends Layout { return this; } + /** + * Set whether to respect the ascent and descent of the fallback fonts that are used in + * displaying the text (which is needed to avoid text from consecutive lines running into + * each other). If set, fallback fonts that end up getting used can increase the ascent + * and descent of the lines that they are used on. + * + *
For backward compatibility reasons, the default is {@code false}, but setting this to
+ * true is strongly recommended. It is required to be true if text could be in languages
+ * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
+ *
+ * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
+ * @return this builder, useful for chaining
+ */
+ public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
+ mFallbackLineSpacing = useLineSpacingFromFallbacks;
+ return this;
+ }
+
/**
* Set the width as used for ellipsizing purposes, if it differs from the
* normal layout width. The default is the {@code width}
@@ -432,6 +451,7 @@ public class StaticLayout extends Layout {
float mSpacingMult;
float mSpacingAdd;
boolean mIncludePad;
+ boolean mFallbackLineSpacing;
int mEllipsizedWidth;
TextUtils.TruncateAt mEllipsize;
int mMaxLines;
@@ -606,6 +626,7 @@ 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;
@@ -784,11 +805,14 @@ public class StaticLayout extends Layout {
nGetWidths(b.mNativePtr, widths);
int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
- lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
+ lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
+ lineBreaks.breaks.length);
- int[] breaks = lineBreaks.breaks;
- float[] lineWidths = lineBreaks.widths;
- int[] flags = lineBreaks.flags;
+ final int[] breaks = lineBreaks.breaks;
+ final float[] lineWidths = lineBreaks.widths;
+ final float[] ascents = lineBreaks.ascents;
+ final float[] descents = lineBreaks.descents;
+ final int[] flags = lineBreaks.flags;
final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
final boolean ellipsisMayBeApplied = ellipsize != null
@@ -799,7 +823,7 @@ public class StaticLayout extends Layout {
&& ellipsisMayBeApplied) {
// Calculate width and flag.
float width = 0;
- int flag = 0;
+ int flag = 0; // XXX May need to also have starting hyphen edit
for (int i = remainingLineCount - 1; i < breakCount; i++) {
if (i == breakCount - 1) {
width += lineWidths[i];
@@ -808,7 +832,7 @@ public class StaticLayout extends Layout {
width += widths[j];
}
}
- flag |= flags[i] & TAB_MASK; // XXX May need to also have starting hyphen edit
+ flag |= flags[i] & TAB_MASK;
}
// Treat the last line and overflowed lines as a single line.
breaks[remainingLineCount - 1] = breaks[breakCount - 1];
@@ -859,8 +883,14 @@ public class StaticLayout extends Layout {
boolean moreChars = (endPos < bufEnd);
+ final int ascent = fallbackLineSpacing
+ ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex]))
+ : fmAscent;
+ final int descent = fallbackLineSpacing
+ ? Math.max(fmDescent, (int) Math.round(descents[breakIndex]))
+ : fmDescent;
v = out(source, here, endPos,
- fmAscent, fmDescent, fmTop, fmBottom,
+ ascent, descent, fmTop, fmBottom,
v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
addLastLineSpacing, chs, widths, paraStart, ellipsize,
@@ -891,8 +921,6 @@ public class StaticLayout extends Layout {
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
mLineCount < mMaximumVisibleLineCount) {
- // Log.e("text", "output last " + bufEnd);
-
measured.setPara(source, bufEnd, bufEnd, textDir, b);
paint.getFontMetricsInt(fm);
@@ -1470,7 +1498,8 @@ public class StaticLayout extends Layout {
// to reduce the number of JNI calls in the common case where the
// arrays do not have to be resized
private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
- int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
+ int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
+ float[] recycleDescents, int[] recycleFlags, int recycleLength);
private int mLineCount;
private int mTopPadding, mBottomPadding;
@@ -1529,6 +1558,8 @@ public class StaticLayout extends Layout {
private static final int INITIAL_SIZE = 16;
public int[] breaks = new int[INITIAL_SIZE];
public float[] widths = new float[INITIAL_SIZE];
+ public float[] ascents = new float[INITIAL_SIZE];
+ public float[] descents = new float[INITIAL_SIZE];
public int[] flags = new int[INITIAL_SIZE]; // hasTab
// breaks, widths, and flags should all have the same length
}
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 82a6411826c90..ed6942eb54232 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -44,6 +44,8 @@ namespace android {
struct JLineBreaksID {
jfieldID breaks;
jfieldID widths;
+ jfieldID ascents;
+ jfieldID descents;
jfieldID flags;
};
@@ -73,35 +75,45 @@ static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray tex
}
static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
- jfloatArray recycleWidths, jintArray recycleFlags,
+ jfloatArray recycleWidths, jfloatArray recycleAscents,
+ jfloatArray recycleDescents, jintArray recycleFlags,
jint recycleLength, size_t nBreaks, const jint* breaks,
- const jfloat* widths, const jint* flags) {
+ const jfloat* widths, const jfloat* ascents, const jfloat* descents,
+ const jint* flags) {
if ((size_t)recycleLength < nBreaks) {
// have to reallocate buffers
recycleBreaks = env->NewIntArray(nBreaks);
recycleWidths = env->NewFloatArray(nBreaks);
+ recycleAscents = env->NewFloatArray(nBreaks);
+ recycleDescents = env->NewFloatArray(nBreaks);
recycleFlags = env->NewIntArray(nBreaks);
env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks);
env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths);
+ env->SetObjectField(recycle, gLineBreaks_fieldID.ascents, recycleAscents);
+ env->SetObjectField(recycle, gLineBreaks_fieldID.descents, recycleDescents);
env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags);
}
// copy data
env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, breaks);
env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, widths);
+ env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, ascents);
+ env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, descents);
env->SetIntArrayRegion(recycleFlags, 0, nBreaks, flags);
}
static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
jobject recycle, jintArray recycleBreaks,
- jfloatArray recycleWidths, jintArray recycleFlags,
+ jfloatArray recycleWidths, jfloatArray recycleAscents,
+ jfloatArray recycleDescents, jintArray recycleFlags,
jint recycleLength) {
minikin::LineBreaker* b = reinterpret_cast Note that this is the ascent of the main typeface, and actual text rendered may need a
+ * larger ascent because fallback fonts may get used in rendering the text.
+ *
* @return the distance above (negative) the baseline (ascent) based on the
* current typeface and text size.
*/
@@ -1740,6 +1743,9 @@ public class Paint {
* Return the distance below (positive) the baseline (descent) based on the
* current typeface and text size.
*
+ * Note that this is the descent of the main typeface, and actual text rendered may need a
+ * larger descent because fallback fonts may get used in rendering the text.
+ *
* @return the distance below (positive) the baseline (descent) based on
* the current typeface and text size.
*/
@@ -1783,6 +1789,9 @@ public class Paint {
* settings for typeface, textSize, etc. If metrics is not null, return the
* fontmetric values in it.
*
+ * Note that these are the values for the main typeface, and actual text rendered may need a
+ * larger set of values because fallback fonts may get used in rendering the text.
+ *
* @param metrics If this object is not null, its fields are filled with
* the appropriate values given the paint's text attributes.
* @return the font's recommended interline spacing.
@@ -1844,6 +1853,9 @@ public class Paint {
* and clipping. If you want more control over the rounding, call
* getFontMetrics().
*
+ * Note that these are the values for the main typeface, and actual text rendered may need a
+ * larger set of values because fallback fonts may get used in rendering the text.
+ *
* @return the font's interline spacing.
*/
public int getFontMetricsInt(FontMetricsInt fmi) {
@@ -1860,6 +1872,9 @@ public class Paint {
* Return the recommend line spacing based on the current typeface and
* text size.
*
+ * Note that this is the value for the main typeface, and actual text rendered may need a
+ * larger value because fallback fonts may get used in rendering the text.
+ *
* @return recommend line spacing based on the current typeface and
* text size.
*/
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index ba4e3a4df578a..2b29542fb6238 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -67,6 +67,17 @@ void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id,
bounds->mBottom = skBounds.fBottom;
}
+void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent,
+ const minikin::MinikinPaint& paint) const {
+ SkPaint skPaint;
+ MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint);
+ SkPaint::FontMetrics metrics;
+ skPaint.getFontMetrics(&metrics);
+ extent->ascent = metrics.fAscent;
+ extent->descent = metrics.fDescent;
+ extent->line_gap = metrics.fLeading;
+}
+
SkTypeface *MinikinFontSkia::GetSkTypeface() const {
return mTypeface.get();
}
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 6c12485845fd0..a19f4a7694443 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -37,6 +37,9 @@ public:
void GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id,
const minikin::MinikinPaint &paint) const;
+ void GetFontExtent(minikin::MinikinExtent* extent,
+ const minikin::MinikinPaint &paint) const;
+
SkTypeface* GetSkTypeface() const;
sk_sp