Add elegantTextHeight text appearance attribute

This patch adds an elegantTextHeight text appearance attribute and
plumbs it through to the paint. This attribute selects the elegant
variant of fonts (when appropriate, which is typically Arabic and indic
scripts), and also specifies larger vertical metrics, to avoid clipping.

The intent is for this to be the default for quantum themes, but this
patch doesn't change any default behavior, just adds the attribute.

The larger vertical metrics are applied to top and bottom, but should
not affect line spacing in the common case. Also, with the setting,
metrics are no longer dependent on the font, so setting a custom font
will preserve layout and spacing.

Change-Id: If3b7d41f141deff50ca078f479ca90c2aa07829a
diff --git a/api/current.txt b/api/current.txt
index ffc7ad4..3b2ce93c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10035,6 +10035,7 @@
     method public android.graphics.Xfermode getXfermode();
     method public final boolean isAntiAlias();
     method public final boolean isDither();
+    method public boolean isElegantTextHeight();
     method public final boolean isFakeBoldText();
     method public final boolean isFilterBitmap();
     method public final boolean isLinearText();
@@ -10053,6 +10054,7 @@
     method public void setColor(int);
     method public android.graphics.ColorFilter setColorFilter(android.graphics.ColorFilter);
     method public void setDither(boolean);
+    method public void setElegantTextHeight(boolean);
     method public void setFakeBoldText(boolean);
     method public void setFilterBitmap(boolean);
     method public void setFlags(int);
@@ -34903,6 +34905,7 @@
     method public void setCursorVisible(boolean);
     method public void setCustomSelectionActionModeCallback(android.view.ActionMode.Callback);
     method public final void setEditableFactory(android.text.Editable.Factory);
+    method public void setElegantTextHeight(boolean);
     method public void setEllipsize(android.text.TextUtils.TruncateAt);
     method public void setEms(int);
     method public void setError(java.lang.CharSequence);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a7278da..b91111d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -652,6 +652,7 @@
         boolean allCaps = false;
         int shadowcolor = 0;
         float dx = 0, dy = 0, r = 0;
+        boolean elegant = false;
 
         final Resources.Theme theme = context.getTheme();
 
@@ -728,6 +729,10 @@
                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
                     r = appearance.getFloat(attr, 0);
                     break;
+
+                case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
+                    elegant = appearance.getBoolean(attr, false);
+                    break;
                 }
             }
 
@@ -1065,6 +1070,10 @@
             case com.android.internal.R.styleable.TextView_textAllCaps:
                 allCaps = a.getBoolean(attr, false);
                 break;
+
+            case com.android.internal.R.styleable.TextView_elegantTextHeight:
+                elegant = a.getBoolean(attr, false);
+                break;
             }
         }
         a.recycle();
@@ -1245,6 +1254,7 @@
             setHighlightColor(textColorHighlight);
         }
         setRawTextSize(textSize);
+        setElegantTextHeight(elegant);
 
         if (allCaps) {
             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -2468,6 +2478,11 @@
             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
         }
 
+        if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) {
+            setElegantTextHeight(appearance.getBoolean(
+                com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false));
+        }
+
         appearance.recycle();
     }
 
@@ -2615,6 +2630,17 @@
     }
 
     /**
+     * Set the TextView's elegant height metrics flag. This setting selects font
+     * variants that have not been compacted to fit Latin-based vertical
+     * metrics, and also increases top and bottom bounds to provide more space.
+     *
+     * @param elegant set the paint's elegant metrics flag.
+     */
+    public void setElegantTextHeight(boolean elegant) {
+        mTextPaint.setElegantTextHeight(elegant);
+    }
+
+    /**
      * Sets the text color for all the states (normal, selected,
      * focused) to be this color.
      *
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index f77a389..08a88d1 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -357,6 +357,24 @@
         obj->setPaintOptionsAndroid(paintOpts);
     }
 
+    static jboolean isElegantTextHeight(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        SkPaint* obj = GraphicsJNI::getNativePaint(env, paint);
+        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
+        return paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant;
+    }
+
+    static void setElegantTextHeight(JNIEnv* env, jobject paint, jboolean aa) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        SkPaint* obj = GraphicsJNI::getNativePaint(env, paint);
+        SkPaintOptionsAndroid::FontVariant variant =
+            aa ? SkPaintOptionsAndroid::kElegant_Variant :
+            SkPaintOptionsAndroid::kDefault_Variant;
+        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
+        paintOpts.setFontVariant(variant);
+        obj->setPaintOptionsAndroid(paintOpts);
+    }
+
     static jfloat getTextSize(JNIEnv* env, jobject paint) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getTextSize());
@@ -401,10 +419,30 @@
         return SkScalarToFloat(metrics.fDescent);
     }
 
+    static SkScalar getMetricsInternal(SkPaint *paint, SkPaint::FontMetrics *metrics) {
+        const int kElegantTop = 2500;
+        const int kElegantBottom = -1000;
+        const int kElegantAscent = 1946;
+        const int kElegantDescent = -512;
+        const int kElegantLeading = 0;
+        SkScalar spacing = paint->getFontMetrics(metrics);
+        SkPaintOptionsAndroid paintOpts = paint->getPaintOptionsAndroid();
+        if (paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant) {
+            SkScalar size = paint->getTextSize();
+            metrics->fTop = -size * kElegantTop / 2048;
+            metrics->fBottom = -size * kElegantBottom / 2048;
+            metrics->fAscent = -size * kElegantAscent / 2048;
+            metrics->fDescent = -size * kElegantDescent / 2048;
+            metrics->fLeading = size * kElegantLeading / 2048;
+            spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
+        }
+        return spacing;
+    }
+
     static jfloat getFontMetrics(JNIEnv* env, jobject paint, jobject metricsObj) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         SkPaint::FontMetrics metrics;
-        SkScalar             spacing = GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+        SkScalar spacing = getMetricsInternal(GraphicsJNI::getNativePaint(env, paint), &metrics);
 
         if (metricsObj) {
             SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class));
@@ -421,7 +459,7 @@
         NPE_CHECK_RETURN_ZERO(env, paint);
         SkPaint::FontMetrics metrics;
 
-        GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+        getMetricsInternal(GraphicsJNI::getNativePaint(env, paint), &metrics);
         int ascent = SkScalarRoundToInt(metrics.fAscent);
         int descent = SkScalarRoundToInt(metrics.fDescent);
         int leading = SkScalarRoundToInt(metrics.fLeading);
@@ -894,6 +932,8 @@
     {"native_getTextAlign","(J)I", (void*) SkPaintGlue::getTextAlign},
     {"native_setTextAlign","(JI)V", (void*) SkPaintGlue::setTextAlign},
     {"native_setTextLocale","(JLjava/lang/String;)V", (void*) SkPaintGlue::setTextLocale},
+    {"isElegantTextHeight","()Z", (void*) SkPaintGlue::isElegantTextHeight},
+    {"setElegantTextHeight","(Z)V", (void*) SkPaintGlue::setElegantTextHeight},
     {"getTextSize","()F", (void*) SkPaintGlue::getTextSize},
     {"setTextSize","(F)V", (void*) SkPaintGlue::setTextSize},
     {"getTextScaleX","()F", (void*) SkPaintGlue::getTextScaleX},
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f364bd0..88f2c54 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3357,6 +3357,8 @@
         <attr name="shadowDy" format="float" />
         <!-- Radius of the shadow. -->
         <attr name="shadowRadius" format="float" />
+        <!-- Elegant text height, especially for less compacted complex script text. -->
+        <attr name="elegantTextHeight" format="boolean" />
     </declare-styleable>
     <declare-styleable name="TextClock">
         <!-- Specifies the formatting pattern used to show the time and/or date
@@ -3648,6 +3650,8 @@
         <attr name="textIsSelectable" />
         <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
         <attr name="textAllCaps" />
+        <!-- Elegant text height, especially for less compacted complex script text. -->
+        <attr name="elegantTextHeight" />
     </declare-styleable>
     <declare-styleable name="TextViewAppearance">
         <!-- Base text color, typeface, size, and style. -->
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 916cb5a..1e1128e 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -500,6 +500,7 @@
 
         mBidiFlags = BIDI_DEFAULT_LTR;
         setTextLocale(Locale.getDefault());
+        setElegantTextHeight(false);
     }
     
     /**
@@ -1221,6 +1222,22 @@
     }
 
     /**
+     * Get the elegant metrics flag.
+     *
+     * @return true if elegant metrics are enabled for text drawing.
+     */
+    public native boolean isElegantTextHeight();
+
+    /**
+     * Set the paint's elegant height metrics flag. This setting selects font
+     * variants that have not been compacted to fit Latin-based vertical
+     * metrics, and also increases top and bottom bounds to provide more space.
+     *
+     * @param elegant set the paint's elegant metrics flag for drawing text.
+     */
+    public native void setElegantTextHeight(boolean elegant);
+
+    /**
      * Return the paint's text size.
      *
      * @return the paint's text size.