1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text.style;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.res.ColorStateList;
22 import android.content.res.TypedArray;
23 import android.graphics.LeakyTypefaceStorage;
24 import android.graphics.Typeface;
25 import android.graphics.fonts.FontStyle;
26 import android.os.LocaleList;
27 import android.os.Parcel;
28 import android.text.ParcelableSpan;
29 import android.text.TextPaint;
30 import android.text.TextUtils;
31 
32 /**
33  * Sets the text appearance using the given
34  * {@link android.R.styleable#TextAppearance TextAppearance} attributes.
35  * By default {@link TextAppearanceSpan} only changes the specified attributes in XML.
36  * {@link android.R.styleable#TextAppearance_textColorHighlight textColorHighlight},
37  * {@link android.R.styleable#TextAppearance_textColorHint textColorHint},
38  * {@link android.R.styleable#TextAppearance_textAllCaps textAllCaps} and
39  * {@link android.R.styleable#TextAppearance_fallbackLineSpacing fallbackLineSpacing}
40  * are not supported by {@link TextAppearanceSpan}.
41  *
42  * {@see android.widget.TextView#setTextAppearance(int)}
43  *
44  * @attr ref android.R.styleable#TextAppearance_fontFamily
45  * @attr ref android.R.styleable#TextAppearance_textColor
46  * @attr ref android.R.styleable#TextAppearance_textColorLink
47  * @attr ref android.R.styleable#TextAppearance_textFontWeight
48  * @attr ref android.R.styleable#TextAppearance_textSize
49  * @attr ref android.R.styleable#TextAppearance_textStyle
50  * @attr ref android.R.styleable#TextAppearance_typeface
51  * @attr ref android.R.styleable#TextAppearance_shadowColor
52  * @attr ref android.R.styleable#TextAppearance_shadowDx
53  * @attr ref android.R.styleable#TextAppearance_shadowDy
54  * @attr ref android.R.styleable#TextAppearance_shadowRadius
55  * @attr ref android.R.styleable#TextAppearance_elegantTextHeight
56  * @attr ref android.R.styleable#TextAppearance_letterSpacing
57  * @attr ref android.R.styleable#TextAppearance_fontFeatureSettings
58  * @attr ref android.R.styleable#TextAppearance_fontVariationSettings
59  *
60  */
61 public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
62     private final String mFamilyName;
63     private final int mStyle;
64     private final int mTextSize;
65     private final ColorStateList mTextColor;
66     private final ColorStateList mTextColorLink;
67     private final Typeface mTypeface;
68 
69     private final int mTextFontWeight;
70     private final LocaleList mTextLocales;
71 
72     private final float mShadowRadius;
73     private final float mShadowDx;
74     private final float mShadowDy;
75     private final int mShadowColor;
76 
77     private final boolean mHasElegantTextHeight;
78     private final boolean mElegantTextHeight;
79     private final boolean mHasLetterSpacing;
80     private final float mLetterSpacing;
81 
82     private final String mFontFeatureSettings;
83     private final String mFontVariationSettings;
84 
85     /**
86      * Uses the specified TextAppearance resource to determine the
87      * text appearance.  The <code>appearance</code> should be, for example,
88      * <code>android.R.style.TextAppearance_Small</code>.
89      */
TextAppearanceSpan(Context context, int appearance)90     public TextAppearanceSpan(Context context, int appearance) {
91         this(context, appearance, -1);
92     }
93 
94     /**
95      * Uses the specified TextAppearance resource to determine the
96      * text appearance, and the specified text color resource
97      * to determine the color.  The <code>appearance</code> should be,
98      * for example, <code>android.R.style.TextAppearance_Small</code>,
99      * and the <code>colorList</code> should be, for example,
100      * <code>android.R.styleable.Theme_textColorPrimary</code>.
101      */
TextAppearanceSpan(Context context, int appearance, int colorList)102     public TextAppearanceSpan(Context context, int appearance, int colorList) {
103         ColorStateList textColor;
104 
105         TypedArray a =
106             context.obtainStyledAttributes(appearance,
107                                            com.android.internal.R.styleable.TextAppearance);
108 
109         textColor = a.getColorStateList(com.android.internal.R.styleable.
110                                         TextAppearance_textColor);
111         mTextColorLink = a.getColorStateList(com.android.internal.R.styleable.
112                                         TextAppearance_textColorLink);
113         mTextSize = a.getDimensionPixelSize(com.android.internal.R.styleable.
114                                         TextAppearance_textSize, -1);
115 
116         mStyle = a.getInt(com.android.internal.R.styleable.TextAppearance_textStyle, 0);
117         if (!context.isRestricted() && context.canLoadUnsafeResources()) {
118             mTypeface = a.getFont(com.android.internal.R.styleable.TextAppearance_fontFamily);
119         } else {
120             mTypeface = null;
121         }
122         if (mTypeface != null) {
123             mFamilyName = null;
124         } else {
125             String family = a.getString(com.android.internal.R.styleable.TextAppearance_fontFamily);
126             if (family != null) {
127                 mFamilyName = family;
128             } else {
129                 int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0);
130 
131                 switch (tf) {
132                     case 1:
133                         mFamilyName = "sans";
134                         break;
135 
136                     case 2:
137                         mFamilyName = "serif";
138                         break;
139 
140                     case 3:
141                         mFamilyName = "monospace";
142                         break;
143 
144                     default:
145                         mFamilyName = null;
146                         break;
147                 }
148             }
149         }
150 
151         mTextFontWeight = a.getInt(com.android.internal.R.styleable
152                 .TextAppearance_textFontWeight, -1);
153 
154         final String localeString = a.getString(com.android.internal.R.styleable
155                 .TextAppearance_textLocale);
156         if (localeString != null) {
157             LocaleList localeList = LocaleList.forLanguageTags(localeString);
158             if (!localeList.isEmpty()) {
159                 mTextLocales = localeList;
160             } else {
161                 mTextLocales = null;
162             }
163         } else {
164             mTextLocales = null;
165         }
166 
167         mShadowRadius = a.getFloat(com.android.internal.R.styleable
168                 .TextAppearance_shadowRadius, 0.0f);
169         mShadowDx = a.getFloat(com.android.internal.R.styleable
170                 .TextAppearance_shadowDx, 0.0f);
171         mShadowDy = a.getFloat(com.android.internal.R.styleable
172                 .TextAppearance_shadowDy, 0.0f);
173         mShadowColor = a.getInt(com.android.internal.R.styleable
174                 .TextAppearance_shadowColor, 0);
175 
176         mHasElegantTextHeight = a.hasValue(com.android.internal.R.styleable
177                 .TextAppearance_elegantTextHeight);
178         mElegantTextHeight = a.getBoolean(com.android.internal.R.styleable
179                 .TextAppearance_elegantTextHeight, false);
180 
181         mHasLetterSpacing = a.hasValue(com.android.internal.R.styleable
182                 .TextAppearance_letterSpacing);
183         mLetterSpacing = a.getFloat(com.android.internal.R.styleable
184                 .TextAppearance_letterSpacing, 0.0f);
185 
186         mFontFeatureSettings = a.getString(com.android.internal.R.styleable
187                 .TextAppearance_fontFeatureSettings);
188 
189         mFontVariationSettings = a.getString(com.android.internal.R.styleable
190                 .TextAppearance_fontVariationSettings);
191 
192         a.recycle();
193 
194         if (colorList >= 0) {
195             a = context.obtainStyledAttributes(com.android.internal.R.style.Theme,
196                                             com.android.internal.R.styleable.Theme);
197 
198             textColor = a.getColorStateList(colorList);
199             a.recycle();
200         }
201 
202         mTextColor = textColor;
203     }
204 
205     /**
206      * Makes text be drawn with the specified typeface, size, style,
207      * and colors.
208      */
TextAppearanceSpan(String family, int style, int size, ColorStateList color, ColorStateList linkColor)209     public TextAppearanceSpan(String family, int style, int size,
210                               ColorStateList color, ColorStateList linkColor) {
211         mFamilyName = family;
212         mStyle = style;
213         mTextSize = size;
214         mTextColor = color;
215         mTextColorLink = linkColor;
216         mTypeface = null;
217 
218         mTextFontWeight = -1;
219         mTextLocales = null;
220 
221         mShadowRadius = 0.0f;
222         mShadowDx = 0.0f;
223         mShadowDy = 0.0f;
224         mShadowColor = 0;
225 
226         mHasElegantTextHeight = false;
227         mElegantTextHeight = false;
228         mHasLetterSpacing = false;
229         mLetterSpacing = 0.0f;
230 
231         mFontFeatureSettings = null;
232         mFontVariationSettings = null;
233     }
234 
TextAppearanceSpan(Parcel src)235     public TextAppearanceSpan(Parcel src) {
236         mFamilyName = src.readString();
237         mStyle = src.readInt();
238         mTextSize = src.readInt();
239         if (src.readInt() != 0) {
240             mTextColor = ColorStateList.CREATOR.createFromParcel(src);
241         } else {
242             mTextColor = null;
243         }
244         if (src.readInt() != 0) {
245             mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
246         } else {
247             mTextColorLink = null;
248         }
249         mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
250 
251         mTextFontWeight = src.readInt();
252         mTextLocales = src.readParcelable(LocaleList.class.getClassLoader());
253 
254         mShadowRadius = src.readFloat();
255         mShadowDx = src.readFloat();
256         mShadowDy = src.readFloat();
257         mShadowColor = src.readInt();
258 
259         mHasElegantTextHeight = src.readBoolean();
260         mElegantTextHeight = src.readBoolean();
261         mHasLetterSpacing = src.readBoolean();
262         mLetterSpacing = src.readFloat();
263 
264         mFontFeatureSettings = src.readString();
265         mFontVariationSettings = src.readString();
266     }
267 
getSpanTypeId()268     public int getSpanTypeId() {
269         return getSpanTypeIdInternal();
270     }
271 
272     /** @hide */
getSpanTypeIdInternal()273     public int getSpanTypeIdInternal() {
274         return TextUtils.TEXT_APPEARANCE_SPAN;
275     }
276 
describeContents()277     public int describeContents() {
278         return 0;
279     }
280 
writeToParcel(Parcel dest, int flags)281     public void writeToParcel(Parcel dest, int flags) {
282         writeToParcelInternal(dest, flags);
283     }
284 
285     /** @hide */
writeToParcelInternal(Parcel dest, int flags)286     public void writeToParcelInternal(Parcel dest, int flags) {
287         dest.writeString(mFamilyName);
288         dest.writeInt(mStyle);
289         dest.writeInt(mTextSize);
290         if (mTextColor != null) {
291             dest.writeInt(1);
292             mTextColor.writeToParcel(dest, flags);
293         } else {
294             dest.writeInt(0);
295         }
296         if (mTextColorLink != null) {
297             dest.writeInt(1);
298             mTextColorLink.writeToParcel(dest, flags);
299         } else {
300             dest.writeInt(0);
301         }
302         LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest);
303 
304         dest.writeInt(mTextFontWeight);
305         dest.writeParcelable(mTextLocales, flags);
306 
307         dest.writeFloat(mShadowRadius);
308         dest.writeFloat(mShadowDx);
309         dest.writeFloat(mShadowDy);
310         dest.writeInt(mShadowColor);
311 
312         dest.writeBoolean(mHasElegantTextHeight);
313         dest.writeBoolean(mElegantTextHeight);
314         dest.writeBoolean(mHasLetterSpacing);
315         dest.writeFloat(mLetterSpacing);
316 
317         dest.writeString(mFontFeatureSettings);
318         dest.writeString(mFontVariationSettings);
319     }
320 
321     /**
322      * Returns the typeface family specified by this span, or <code>null</code>
323      * if it does not specify one.
324      */
getFamily()325     public String getFamily() {
326         return mFamilyName;
327     }
328 
329     /**
330      * Returns the text color specified by this span, or <code>null</code>
331      * if it does not specify one.
332      */
getTextColor()333     public ColorStateList getTextColor() {
334         return mTextColor;
335     }
336 
337     /**
338      * Returns the link color specified by this span, or <code>null</code>
339      * if it does not specify one.
340      */
getLinkTextColor()341     public ColorStateList getLinkTextColor() {
342         return mTextColorLink;
343     }
344 
345     /**
346      * Returns the text size specified by this span, or <code>-1</code>
347      * if it does not specify one.
348      */
getTextSize()349     public int getTextSize() {
350         return mTextSize;
351     }
352 
353     /**
354      * Returns the text style specified by this span, or <code>0</code>
355      * if it does not specify one.
356      */
getTextStyle()357     public int getTextStyle() {
358         return mStyle;
359     }
360 
361     /**
362      * Returns the text font weight specified by this span, or <code>-1</code>
363      * if it does not specify one.
364      */
getTextFontWeight()365     public int getTextFontWeight() {
366         return mTextFontWeight;
367     }
368 
369     /**
370      * Returns the {@link android.os.LocaleList} specified by this span, or <code>null</code>
371      * if it does not specify one.
372      */
373     @Nullable
getTextLocales()374     public LocaleList getTextLocales() {
375         return mTextLocales;
376     }
377 
378     /**
379      * Returns the typeface specified by this span, or <code>null</code>
380      * if it does not specify one.
381      */
382     @Nullable
getTypeface()383     public Typeface getTypeface() {
384         return mTypeface;
385     }
386 
387     /**
388      * Returns the color of the text shadow specified by this span, or <code>0</code>
389      * if it does not specify one.
390      */
getShadowColor()391     public int getShadowColor() {
392         return mShadowColor;
393     }
394 
395     /**
396      * Returns the horizontal offset of the text shadow specified by this span, or <code>0.0f</code>
397      * if it does not specify one.
398      */
getShadowDx()399     public float getShadowDx() {
400         return mShadowDx;
401     }
402 
403     /**
404      * Returns the vertical offset of the text shadow specified by this span, or <code>0.0f</code>
405      * if it does not specify one.
406      */
getShadowDy()407     public float getShadowDy() {
408         return mShadowDy;
409     }
410 
411     /**
412      * Returns the blur radius of the text shadow specified by this span, or <code>0.0f</code>
413      * if it does not specify one.
414      */
getShadowRadius()415     public float getShadowRadius() {
416         return mShadowRadius;
417     }
418 
419     /**
420      * Returns the font feature settings specified by this span, or <code>null</code>
421      * if it does not specify one.
422      */
423     @Nullable
getFontFeatureSettings()424     public String getFontFeatureSettings() {
425         return mFontFeatureSettings;
426     }
427 
428     /**
429      * Returns the font variation settings specified by this span, or <code>null</code>
430      * if it does not specify one.
431      */
432     @Nullable
getFontVariationSettings()433     public String getFontVariationSettings() {
434         return mFontVariationSettings;
435     }
436 
437     /**
438      * Returns the value of elegant height metrics flag specified by this span,
439      * or <code>false</code> if it does not specify one.
440      */
isElegantTextHeight()441     public boolean isElegantTextHeight() {
442         return mElegantTextHeight;
443     }
444 
445     @Override
updateDrawState(TextPaint ds)446     public void updateDrawState(TextPaint ds) {
447         updateMeasureState(ds);
448 
449         if (mTextColor != null) {
450             ds.setColor(mTextColor.getColorForState(ds.drawableState, 0));
451         }
452 
453         if (mTextColorLink != null) {
454             ds.linkColor = mTextColorLink.getColorForState(ds.drawableState, 0);
455         }
456 
457         if (mShadowColor != 0) {
458             ds.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor);
459         }
460     }
461 
462     @Override
updateMeasureState(TextPaint ds)463     public void updateMeasureState(TextPaint ds) {
464         final Typeface styledTypeface;
465         int style = 0;
466 
467         if (mTypeface != null) {
468             style = mStyle;
469             styledTypeface = Typeface.create(mTypeface, style);
470         } else if (mFamilyName != null || mStyle != 0) {
471             Typeface tf = ds.getTypeface();
472 
473             if (tf != null) {
474                 style = tf.getStyle();
475             }
476 
477             style |= mStyle;
478 
479             if (mFamilyName != null) {
480                 styledTypeface = Typeface.create(mFamilyName, style);
481             } else if (tf == null) {
482                 styledTypeface = Typeface.defaultFromStyle(style);
483             } else {
484                 styledTypeface = Typeface.create(tf, style);
485             }
486         } else {
487             styledTypeface = null;
488         }
489 
490         if (styledTypeface != null) {
491             final Typeface readyTypeface;
492             if (mTextFontWeight >= 0) {
493                 final int weight = Math.min(FontStyle.FONT_WEIGHT_MAX, mTextFontWeight);
494                 final boolean italic = (style & Typeface.ITALIC) != 0;
495                 readyTypeface = ds.setTypeface(Typeface.create(styledTypeface, weight, italic));
496             } else {
497                 readyTypeface = styledTypeface;
498             }
499 
500             int fake = style & ~readyTypeface.getStyle();
501 
502             if ((fake & Typeface.BOLD) != 0) {
503                 ds.setFakeBoldText(true);
504             }
505 
506             if ((fake & Typeface.ITALIC) != 0) {
507                 ds.setTextSkewX(-0.25f);
508             }
509 
510             ds.setTypeface(readyTypeface);
511         }
512 
513         if (mTextSize > 0) {
514             ds.setTextSize(mTextSize);
515         }
516 
517         if (mTextLocales != null) {
518             ds.setTextLocales(mTextLocales);
519         }
520 
521         if (mHasElegantTextHeight) {
522             ds.setElegantTextHeight(mElegantTextHeight);
523         }
524 
525         if (mHasLetterSpacing) {
526             ds.setLetterSpacing(mLetterSpacing);
527         }
528 
529         if (mFontFeatureSettings != null) {
530             ds.setFontFeatureSettings(mFontFeatureSettings);
531         }
532 
533         if (mFontVariationSettings != null) {
534             ds.setFontVariationSettings(mFontVariationSettings);
535         }
536     }
537 }
538