Support extra linespacing based on fallback fonts

* Increase the ascent and descent of individual lines in StaticLayout
  as needed, if any fallback fonts that end up getting used call for
  it. For backward compatibility, this is hidden behind a builder
  flag.
* Document in Paint.java that the returned parameters are only for
  the default font, and a layout may need more space based on
  fallbacks used.

Also update for changes in minikin API:

* MinikinFont now requires a method for getting vertical extents
  (ascent, descent, and line gap).
* minikin API now allows asking for vertical extents of laid out
  text.
* minikin API's LineBreaker now returns ascents and descents for each
  line.

Finally, added performances test for creating a StaticLayout.
Follwing are the numbers on a marlin with a stable clock before and
after this CL.

For fixed text almost always hitting the cache:
Before: mean=260684 median=260188 min=258532 standardDeviation=1897
After:  mean=262432 median=261509 min=260429 standardDeviation=2185

For random text almost never hitting the cache:
Before: mean=5971827 median=5991126 min=5886871 standardDeviation=83724
After:  mean=6337093 median=6317010 min=6311222 standardDeviation=40213

Bug: 28963299
Bug: 29063863
Bug: 32057121
Bug: 37756858
Test: bit FrameworksCoreTests:android.text.
Test: bit CtsTextTestCases:*
Change-Id: I482a98ff8f472e8bab4f0ba9d1d7b368858038ff
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6a7db4e..dd82e1e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -90,6 +90,7 @@
             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 @@
         }
 
         /**
+         * 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 @@
         float mSpacingMult;
         float mSpacingAdd;
         boolean mIncludePad;
+        boolean mFallbackLineSpacing;
         int mEllipsizedWidth;
         TextUtils.TruncateAt mEllipsize;
         int mMaxLines;
@@ -606,6 +626,7 @@
         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 @@
 
             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 @@
                     && 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 @@
                             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 @@
 
                     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 @@
 
         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 @@
     // 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 @@
         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
     }