diff options
| -rw-r--r-- | apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java | 91 | ||||
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 51 | ||||
| -rw-r--r-- | core/jni/android_text_StaticLayout.cpp | 26 | ||||
| -rw-r--r-- | core/tests/coretests/assets/fonts/ascent1em-descent2em.ttf | bin | 0 -> 1776 bytes | |||
| -rw-r--r-- | core/tests/coretests/assets/fonts/ascent1em-descent2em.ttx | 181 | ||||
| -rw-r--r-- | core/tests/coretests/assets/fonts/ascent3em-descent4em.ttf | bin | 0 -> 1768 bytes | |||
| -rw-r--r-- | core/tests/coretests/assets/fonts/ascent3em-descent4em.ttx | 180 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/text/StaticLayoutTest.java | 138 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Paint.java | 15 | ||||
| -rw-r--r-- | libs/hwui/hwui/MinikinSkia.cpp | 11 | ||||
| -rw-r--r-- | libs/hwui/hwui/MinikinSkia.h | 3 | ||||
| -rw-r--r-- | libs/hwui/hwui/MinikinUtils.cpp | 2 |
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 Binary files differnew file mode 100644 index 000000000000..f34698f3ebce --- /dev/null +++ b/core/tests/coretests/assets/fonts/ascent1em-descent2em.ttf 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 Binary files differnew file mode 100644 index 000000000000..085d13377231 --- /dev/null +++ b/core/tests/coretests/assets/fonts/ascent3em-descent4em.ttf 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) { |