From fa80f7491df82d71b7084500519a2195afbea706 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Thu, 17 Jul 2014 19:10:39 -0400 Subject: [PATCH] Add letter-spacing to Paint and TextView New API is hidden. Bug: 15594400 Change-Id: I5cbe7aebef0b7280eb13924f2a706c0cb4a4688e --- core/java/android/widget/TextView.java | 51 ++++++++++++++++++++++ core/jni/android/graphics/MinikinUtils.cpp | 9 ++-- core/jni/android/graphics/Paint.cpp | 12 +++++ core/jni/android/graphics/Paint.h | 9 ++++ core/jni/android/graphics/PaintImpl.cpp | 10 +++-- core/res/res/values/attrs.xml | 4 ++ core/res/res/values/public.xml | 1 + graphics/java/android/graphics/Paint.java | 27 ++++++++++++ 8 files changed, 117 insertions(+), 6 deletions(-) diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fac0eb269a267..30831cd708598 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -222,6 +222,7 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; * @attr ref android.R.styleable#TextView_imeActionId * @attr ref android.R.styleable#TextView_editorExtras * @attr ref android.R.styleable#TextView_elegantTextHeight + * @attr ref android.R.styleable#TextView_letterSpacing */ @RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { @@ -657,6 +658,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int shadowcolor = 0; float dx = 0, dy = 0, r = 0; boolean elegant = false; + float letterSpacing = 0; final Resources.Theme theme = context.getTheme(); @@ -737,6 +739,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: elegant = appearance.getBoolean(attr, false); break; + + case com.android.internal.R.styleable.TextAppearance_letterSpacing: + letterSpacing = appearance.getFloat(attr, 0); + break; } } @@ -1078,6 +1084,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_elegantTextHeight: elegant = a.getBoolean(attr, false); break; + + case com.android.internal.R.styleable.TextView_letterSpacing: + letterSpacing = a.getFloat(attr, 0); + break; } } a.recycle(); @@ -1259,6 +1269,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } setRawTextSize(textSize); setElegantTextHeight(elegant); + setLetterSpacing(letterSpacing); if (allCaps) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); @@ -2487,6 +2498,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false)); } + if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_letterSpacing)) { + setLetterSpacing(appearance.getFloat( + com.android.internal.R.styleable.TextAppearance_letterSpacing, 0)); + } + appearance.recycle(); } @@ -2666,6 +2682,41 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextPaint.setElegantTextHeight(elegant); } + /** + * @return the extent by which text is currently being letter-spaced. + * This will normally be 0. + * + * @see #setLetterSpacing(float) + * @hide + */ + public float getLetterSpacing() { + return mTextPaint.getLetterSpacing(); + } + + /** + * Sets text letter-spacing. The value is in 'EM' units. Typical values + * for slight expansion will be around 0.05. Negative values tighten text. + * + * @see #getLetterSpacing() + * @see Paint#setFlags + * + * @attr ref android.R.styleable#TextView_letterSpacing + * @hide + */ + @android.view.RemotableViewMethod + public void setLetterSpacing(float letterSpacing) { + if (letterSpacing != mTextPaint.getLetterSpacing()) { + mTextPaint.setLetterSpacing(letterSpacing); + + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + } + + /** * Sets the text color for all the states (normal, selected, * focused) to be this color. diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp index c66437a54bce3..802f2abea212e 100644 --- a/core/jni/android/graphics/MinikinUtils.cpp +++ b/core/jni/android/graphics/MinikinUtils.cpp @@ -29,6 +29,8 @@ namespace android { // Do an sprintf starting at offset n, abort on overflow +static int snprintfcat(char* buf, int off, int size, const char* format, ...) + __attribute__((__format__(__printf__, 4, 5))); static int snprintfcat(char* buf, int off, int size, const char* format, ...) { va_list args; va_start(args, format); @@ -43,17 +45,18 @@ std::string MinikinUtils::setLayoutProperties(Layout* layout, const Paint* paint TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface); layout->setFontCollection(resolvedFace->fFontCollection); FontStyle style = resolvedFace->fStyle; - char css[256]; + char css[512]; int off = snprintfcat(css, 0, sizeof(css), "font-size: %d; font-scale-x: %f; font-skew-x: %f; -paint-flags: %d;" - " font-weight: %d; font-style: %s; -minikin-bidi: %d;", + " font-weight: %d; font-style: %s; -minikin-bidi: %d; letter-spacing: %f;", (int)paint->getTextSize(), paint->getTextScaleX(), paint->getTextSkewX(), MinikinFontSkia::packPaintFlags(paint), style.getWeight() * 100, style.getItalic() ? "italic" : "normal", - bidiFlags); + bidiFlags, + paint->getLetterSpacing()); SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag(); off = snprintfcat(css, off, sizeof(css), " lang: %s;", langString.c_str()); SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant(); diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index c06f0d2bfcd36..e2b3684c9a9d1 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -423,6 +423,16 @@ public: GraphicsJNI::getNativePaint(env, paint)->setTextSkewX(skewX); } + static jfloat getLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle) { + Paint* paint = reinterpret_cast(paintHandle); + return paint->getLetterSpacing(); + } + + static void setLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat letterSpacing) { + Paint* paint = reinterpret_cast(paintHandle); + paint->setLetterSpacing(letterSpacing); + } + static SkScalar getMetricsInternal(JNIEnv* env, jobject jpaint, Paint::FontMetrics *metrics) { const int kElegantTop = 2500; const int kElegantBottom = -1000; @@ -988,6 +998,8 @@ static JNINativeMethod methods[] = { {"setTextScaleX","(F)V", (void*) PaintGlue::setTextScaleX}, {"getTextSkewX","()F", (void*) PaintGlue::getTextSkewX}, {"setTextSkewX","(F)V", (void*) PaintGlue::setTextSkewX}, + {"native_getLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing}, + {"native_setLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing}, {"ascent","()F", (void*) PaintGlue::ascent}, {"descent","()F", (void*) PaintGlue::descent}, {"getFontMetrics", "(Landroid/graphics/Paint$FontMetrics;)F", (void*)PaintGlue::getFontMetrics}, diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h index 239b217ef7bea..7235cc4509599 100644 --- a/core/jni/android/graphics/Paint.h +++ b/core/jni/android/graphics/Paint.h @@ -34,7 +34,16 @@ public: return !(a == b); } + void setLetterSpacing(float letterSpacing) { + mLetterSpacing = letterSpacing; + } + + float getLetterSpacing() const { + return mLetterSpacing; + } + private: + float mLetterSpacing; }; } // namespace android diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp index 6baae7607375d..ff2bbc514ad85 100644 --- a/core/jni/android/graphics/PaintImpl.cpp +++ b/core/jni/android/graphics/PaintImpl.cpp @@ -22,10 +22,12 @@ namespace android { -Paint::Paint() : SkPaint() { +Paint::Paint() : SkPaint(), + mLetterSpacing(0) { } -Paint::Paint(const Paint& paint) : SkPaint(paint) { +Paint::Paint(const Paint& paint) : SkPaint(paint), + mLetterSpacing(0) { } Paint::~Paint() { @@ -33,11 +35,13 @@ Paint::~Paint() { Paint& Paint::operator=(const Paint& other) { SkPaint::operator=(other); + mLetterSpacing = other.mLetterSpacing; return *this; } bool operator==(const Paint& a, const Paint& b) { - return static_cast(a) == static_cast(b); + return static_cast(a) == static_cast(b) + && a.mLetterSpacing == b.mLetterSpacing; } } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2782b523a33b4..0e878935e6e62 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3755,6 +3755,8 @@ + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index a6e85e934ff8e..b1902ddd5543a 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2268,6 +2268,7 @@ + diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 3f73a03ac68df..58a1bf24c62ee 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1259,6 +1259,29 @@ public class Paint { */ public native void setTextSkewX(float skewX); + /** + * Return the paint's letter-spacing for text. The default value + * is 0. + * + * @return the paint's letter-spacing for drawing text. + * @hide + */ + public float getLetterSpacing() { + return native_getLetterSpacing(mNativePaint); + } + + /** + * Set the paint's letter-spacing for text. The default value + * is 0. The value is in 'EM' units. Typical values for slight + * expansion will be around 0.05. Negative values tighten text. + * + * @param letterSpacing set the paint's letter-spacing for drawing text. + * @hide + */ + public void setLetterSpacing(float letterSpacing) { + native_setLetterSpacing(mNativePaint, letterSpacing); + } + /** * Return the distance above (negative) the baseline (ascent) based on the * current typeface and text size. @@ -2232,4 +2255,8 @@ public class Paint { private static native void native_setShadowLayer(long native_object, float radius, float dx, float dy, int color); private static native boolean native_hasShadowLayer(long native_object); + + private static native float native_getLetterSpacing(long native_object); + private static native void native_setLetterSpacing(long native_object, + float letterSpacing); }