summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java91
-rw-r--r--api/current.txt1
-rw-r--r--api/system-current.txt1
-rw-r--r--api/test-current.txt1
-rw-r--r--core/java/android/text/StaticLayout.java51
-rw-r--r--core/jni/android_text_StaticLayout.cpp26
-rw-r--r--core/tests/coretests/assets/fonts/ascent1em-descent2em.ttfbin0 -> 1776 bytes
-rw-r--r--core/tests/coretests/assets/fonts/ascent1em-descent2em.ttx181
-rw-r--r--core/tests/coretests/assets/fonts/ascent3em-descent4em.ttfbin0 -> 1768 bytes
-rw-r--r--core/tests/coretests/assets/fonts/ascent3em-descent4em.ttx180
-rw-r--r--core/tests/coretests/src/android/text/StaticLayoutTest.java138
-rw-r--r--graphics/java/android/graphics/Paint.java15
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp11
-rw-r--r--libs/hwui/hwui/MinikinSkia.h3
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp2
15 files changed, 684 insertions, 17 deletions
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 000000000000..74d136654c62
--- /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 6bf11bbf69c2..69323c1a126e 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 df2df2b18a90..973eb9968a8d 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 aa08dd967998..fe66ec9741a9 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 6a7db4ed4e27..dd82e1e0b5d2 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;
@@ -228,6 +229,24 @@ public class StaticLayout extends Layout {
}
/**
+ * 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.
+ *
+ * <p>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}
* passed to {@link #obtain}.
@@ -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 82a6411826c9..ed6942eb5423 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<minikin::LineBreaker*>(nativePtr);
size_t nBreaks = b->computeBreaks();
- recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleFlags, recycleLength,
- nBreaks, b->getBreaks(), b->getWidths(), b->getFlags());
+ recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
+ recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(),
+ b->getDescents(), b->getFlags());
b->finish();
@@ -205,7 +217,7 @@ static const JNINativeMethod gMethods[] = {
{"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun},
{"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun},
{"nGetWidths", "(J[F)V", (void*) nGetWidths},
- {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[II)I",
+ {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II)I",
(void*) nComputeLineBreaks}
};
@@ -216,6 +228,8 @@ int register_android_text_StaticLayout(JNIEnv* env)
gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I");
gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F");
+ gLineBreaks_fieldID.ascents = GetFieldIDOrDie(env, gLineBreaks_class, "ascents", "[F");
+ gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F");
gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I");
return RegisterMethodsOrDie(env, "android/text/StaticLayout", gMethods, NELEM(gMethods));
diff --git a/core/tests/coretests/assets/fonts/ascent1em-descent2em.ttf b/core/tests/coretests/assets/fonts/ascent1em-descent2em.ttf
new file mode 100644
index 000000000000..f34698f3ebce
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/ascent1em-descent2em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/ascent1em-descent2em.ttx b/core/tests/coretests/assets/fonts/ascent1em-descent2em.ttx
new file mode 100644
index 000000000000..68d43239575f
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/ascent1em-descent2em.ttx
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x10000"/>
+ <ascent value="1000"/>
+ <descent value="-2000"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1000" lsb="0"/>
+ <mtx name="1em" width="1000" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0020" name="1em" /> <!-- SPACE -->
+ <map code="0x0061" name="1em" /> <!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/core/tests/coretests/assets/fonts/ascent3em-descent4em.ttf b/core/tests/coretests/assets/fonts/ascent3em-descent4em.ttf
new file mode 100644
index 000000000000..085d13377231
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/ascent3em-descent4em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/ascent3em-descent4em.ttx b/core/tests/coretests/assets/fonts/ascent3em-descent4em.ttx
new file mode 100644
index 000000000000..432331b2bc46
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/ascent3em-descent4em.ttx
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x10000"/>
+ <ascent value="3000"/>
+ <descent value="-4000"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1000" lsb="0"/>
+ <mtx name="1em" width="1000" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0062" name="1em" /> <!-- LATIN SMALL LETTER B -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 2dfab87906c1..1bf32ca31c70 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -21,20 +21,33 @@ import static android.text.Layout.Alignment.ALIGN_NORMAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
+import android.content.res.AssetManager;
import android.graphics.Canvas;
+import android.graphics.FontFamily;
import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Typeface;
import android.os.LocaleList;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.Layout.Alignment;
import android.text.method.EditorState;
import android.text.style.LocaleSpan;
+import android.util.ArrayMap;
import android.util.Log;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
@@ -799,4 +812,129 @@ public class StaticLayoutTest {
layout.drawText(canvas, 0, 0);
assertEquals(31, paint.getHyphenEdit());
}
+
+ private String getTestFontsDir() {
+ final Context targetCtx = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final File cacheDir = new File(targetCtx.getCacheDir(), "StaticLayoutTest");
+ if (!cacheDir.isDirectory()) {
+ final boolean dirsCreated = cacheDir.mkdirs();
+ if (!dirsCreated) {
+ throw new RuntimeException("Creating test directories for fonts failed.");
+ }
+ }
+ return cacheDir.getAbsolutePath() + "/";
+ }
+
+ private TextPaint setupPaintForFallbackFonts(String[] fontFiles, String xml) {
+ final String testFontsDir = getTestFontsDir();
+ final String testFontsXml = new File(testFontsDir, "fonts.xml").getAbsolutePath();
+ final AssetManager am =
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ for (String fontFile : fontFiles) {
+ final String sourceInAsset = "fonts/" + fontFile;
+ final File outInCache = new File(testFontsDir, fontFile);
+ try (InputStream is = am.open(sourceInAsset)) {
+ Files.copy(is, outInCache.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ try (FileOutputStream fos = new FileOutputStream(testFontsXml)) {
+ fos.write(xml.getBytes(Charset.forName("UTF-8")));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+ Typeface.buildSystemFallback(testFontsXml, testFontsDir, fontMap, fallbackMap);
+
+ final TextPaint paint = new TextPaint();
+ final Typeface testTypeface = fontMap.get("sans-serif");
+ paint.setTypeface(testTypeface);
+ return paint;
+ }
+
+ void destroyFallbackFonts(String[] fontFiles) {
+ final String testFontsDir = getTestFontsDir();
+ for (String fontFile : fontFiles) {
+ final File outInCache = new File(testFontsDir, fontFile);
+ outInCache.delete();
+ }
+ }
+
+ @Test
+ public void testFallbackLineSpacing() {
+ // All glyphs in the fonts are 1em wide.
+ final String[] testFontFiles = {
+ // ascent == 1em, descent == 2em, only supports 'a' and space
+ "ascent1em-descent2em.ttf",
+ // ascent == 3em, descent == 4em, only supports 'b'
+ "ascent3em-descent4em.ttf"
+ };
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font weight='400' style='normal'>ascent1em-descent2em.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>ascent3em-descent4em.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+
+ try {
+ final TextPaint paint = setupPaintForFallbackFonts(testFontFiles, xml);
+ final int textSize = 100;
+ paint.setTextSize(textSize);
+ assertEquals(-textSize, paint.ascent(), 0.0f);
+ assertEquals(2 * textSize, paint.descent(), 0.0f);
+
+ final int paraWidth = 5 * textSize;
+ final String text = "aaaaa aabaa aaaaa"; // This should result in three lines.
+
+ // Old line spacing. All lines should get their ascent and descents from the first font.
+ StaticLayout layout = StaticLayout.Builder
+ .obtain(text, 0, text.length(), paint, paraWidth)
+ .setIncludePad(false)
+ .setUseLineSpacingFromFallbacks(false)
+ .build();
+ assertEquals(3, layout.getLineCount());
+ assertEquals(-textSize, layout.getLineAscent(0));
+ assertEquals(2 * textSize, layout.getLineDescent(0));
+ assertEquals(-textSize, layout.getLineAscent(1));
+ assertEquals(2 * textSize, layout.getLineDescent(1));
+ assertEquals(-textSize, layout.getLineAscent(2));
+ assertEquals(2 * textSize, layout.getLineDescent(2));
+
+ // New line spacing. The second line has a 'b', so it needs more ascent and descent.
+ layout = StaticLayout.Builder
+ .obtain(text, 0, text.length(), paint, paraWidth)
+ .setIncludePad(false)
+ .setUseLineSpacingFromFallbacks(true)
+ .build();
+ assertEquals(3, layout.getLineCount());
+ assertEquals(-textSize, layout.getLineAscent(0));
+ assertEquals(2 * textSize, layout.getLineDescent(0));
+ assertEquals(-3 * textSize, layout.getLineAscent(1));
+ assertEquals(4 * textSize, layout.getLineDescent(1));
+ assertEquals(-textSize, layout.getLineAscent(2));
+ assertEquals(2 * textSize, layout.getLineDescent(2));
+
+ // The default is the old line spacing, for backward compatibility.
+ layout = StaticLayout.Builder
+ .obtain(text, 0, text.length(), paint, paraWidth)
+ .setIncludePad(false)
+ .build();
+ assertEquals(3, layout.getLineCount());
+ assertEquals(-textSize, layout.getLineAscent(0));
+ assertEquals(2 * textSize, layout.getLineDescent(0));
+ assertEquals(-textSize, layout.getLineAscent(1));
+ assertEquals(2 * textSize, layout.getLineDescent(1));
+ assertEquals(-textSize, layout.getLineAscent(2));
+ assertEquals(2 * textSize, layout.getLineDescent(2));
+ } finally {
+ destroyFallbackFonts(testFontFiles);
+ }
+ }
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 46ada23c632f..1a06a5683cd3 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1729,6 +1729,9 @@ public class Paint {
* Return the distance above (negative) the baseline (ascent) based on the
* current typeface and text size.
*
+ * <p>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.
*
+ * <p>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.
*
+ * <p>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().
*
+ * <p>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.
*
+ * <p>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 ba4e3a4df578..2b29542fb623 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 6c12485845fd..a19f4a769444 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<SkTypeface> RefSkTypeface() const;
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 5e7f1cf2da1c..5577bbf2bfc9 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -68,7 +68,7 @@ float MinikinUtils::measureText(const Paint* paint, int bidiFlags, const Typefac
minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface);
const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinStyle,
- minikinPaint, resolvedTypeface->fFontCollection, advances);
+ minikinPaint, resolvedTypeface->fFontCollection, advances, nullptr /* extent */);
}
bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {