Start moving text measurement into native code

We want to move text measurement into native code, mostly so that
alternate measurement for hyphens can be performant. This patch begins
that migration, in the main StaticLayout case, but still surfaces the
widths array to Java (for ellipsis calculation), and also includes a
hack (used mostly for testing) for computing widths in Java and sending
them down to native code when TextPaint is subclassed.

Change-Id: I476c9e8b3aa8e4e3552eb18f66c4bcd5683f3a72
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 0d35f9c..ee39e27 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -166,18 +166,48 @@
             return this;
         }
 
-        /* @hide */
-        public void setLocale(Locale locale) {
+        /**
+         * Measurement and break iteration is done in native code. The protocol for using
+         * the native code is as follows.
+         *
+         * For each paragraph, do a nSetText of the paragraph text. Then, for each run within the
+         * paragraph:
+         *  - setLocale (this must be done at least for the first run, optional afterwards)
+         *  - one of the following, depending on the type of run:
+         *    + addStyleRun (a text run, to be measured in native code)
+         *    + addMeasuredRun (a run already measured in Java, passed into native code)
+         *    + addReplacementRun (a replacement run, width is given)
+         *
+         * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
+         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+         *
+         * After all paragraphs, call finish() to release expensive buffers.
+         */
+
+        private void setLocale(Locale locale) {
             if (!locale.equals(mLocale)) {
-                nBuilderSetLocale(mNativePtr, locale.toLanguageTag());
+                nSetLocale(mNativePtr, locale.toLanguageTag());
                 mLocale = locale;
             }
         }
 
+        /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
+            return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
+                    start, end, isRtl);
+        }
+
+        /* package */ void addMeasuredRun(int start, int end, float[] widths) {
+            nAddMeasuredRun(mNativePtr, start, end, widths);
+        }
+
+        /* package */ void addReplacementRun(int start, int end, float width) {
+            nAddReplacementRun(mNativePtr, start, end, width);
+        }
+
         public StaticLayout build() {
             // TODO: can optimize based on whether ellipsis is needed
             StaticLayout result = new StaticLayout(mText);
-            result.initFromBuilder(this);
+            result.generate(this, this.mIncludePad, this.mIncludePad);
             recycle(this);
             return result;
         }
@@ -321,7 +351,7 @@
         mLines = new int[mLineDirections.length];
         mMaximumVisibleLineCount = maxLines;
 
-        initFromBuilder(b);
+        generate(b, b.mIncludePad, b.mIncludePad);
 
         Builder.recycle(b);
     }
@@ -334,10 +364,6 @@
         mLines = new int[mLineDirections.length];
     }
 
-    private void initFromBuilder(Builder b) {
-        generate(b, b.mIncludePad, b.mIncludePad);
-    }
-
     /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
         CharSequence source = b.mText;
         int bufStart = b.mStart;
@@ -427,12 +453,13 @@
                 }
             }
 
-            measured.setPara(source, paraStart, paraEnd, textDir);
+            measured.setPara(source, paraStart, paraEnd, textDir, b);
             char[] chs = measured.mChars;
             float[] widths = measured.mWidths;
             byte[] chdirs = measured.mLevels;
             int dir = measured.mDir;
             boolean easy = measured.mEasy;
+            nSetText(b.mNativePtr, chs, paraEnd - paraStart);
 
             // measurement has to be done before performing line breaking
             // but we don't want to recompute fontmetrics or span ranges the
@@ -493,7 +520,8 @@
                 }
             }
 
-            int breakCount = nComputeLineBreaks(b.mNativePtr, chs, widths, paraEnd - paraStart, firstWidth,
+            nGetWidths(b.mNativePtr, widths);
+            int breakCount = nComputeLineBreaks(b.mNativePtr, paraEnd - paraStart, firstWidth,
                     firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
                     lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
 
@@ -576,7 +604,7 @@
                 mLineCount < mMaximumVisibleLineCount) {
             // Log.e("text", "output last " + bufEnd);
 
-            measured.setPara(source, bufEnd, bufEnd, textDir);
+            measured.setPara(source, bufEnd, bufEnd, textDir, b);
 
             paint.getFontMetricsInt(fm);
 
@@ -933,21 +961,33 @@
         return mEllipsizedWidth;
     }
 
+    private static native long nNewBuilder();
+    private static native void nFreeBuilder(long nativePtr);
+    private static native void nFinishBuilder(long nativePtr);
+    private static native void nSetLocale(long nativePtr, String locale);
+
+    private static native void nSetText(long nativePtr, char[] text, int length);
+
+    private static native float nAddStyleRun(long nativePtr, long nativePaint,
+            long nativeTypeface, int start, int end, boolean isRtl);
+
+    private static native void nAddMeasuredRun(long nativePtr,
+            int start, int end, float[] widths);
+
+    private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
+
+    private static native void nGetWidths(long nativePtr, float[] widths);
+
     // populates LineBreaks and returns the number of breaks found
     //
     // the arrays inside the LineBreaks objects are passed in as well
     // 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, char[] text, float[] widths,
+    private static native int nComputeLineBreaks(long nativePtr,
             int length, float firstWidth, int firstWidthLineCount, float restWidth,
             int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
             int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
 
-    private static native long nNewBuilder();
-    private static native void nFreeBuilder(long nativePtr);
-    private static native void nFinishBuilder(long nativePtr);
-    private static native void nBuilderSetLocale(long nativePtr, String locale);
-
     private int mLineCount;
     private int mTopPadding, mBottomPadding;
     private int mColumns;