• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 @android.ravenwood.annotation.RavenwoodKeepWholeClass
62 public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
63     private final String mFamilyName;
64     private final int mStyle;
65     private final int mTextSize;
66     private final ColorStateList mTextColor;
67     private final ColorStateList mTextColorLink;
68     private final Typeface mTypeface;
69 
70     private final int mTextFontWeight;
71     private final LocaleList mTextLocales;
72 
73     private final float mShadowRadius;
74     private final float mShadowDx;
75     private final float mShadowDy;
76     private final int mShadowColor;
77 
78     private final boolean mHasElegantTextHeight;
79     private final boolean mElegantTextHeight;
80     private final boolean mHasLetterSpacing;
81     private final float mLetterSpacing;
82 
83     private final String mFontFeatureSettings;
84     private final String mFontVariationSettings;
85 
86     /**
87      * Uses the specified TextAppearance resource to determine the
88      * text appearance.  The <code>appearance</code> should be, for example,
89      * <code>android.R.style.TextAppearance_Small</code>.
90      */
TextAppearanceSpan(Context context, int appearance)91     public TextAppearanceSpan(Context context, int appearance) {
92         this(context, appearance, -1);
93     }
94 
95     /**
96      * Uses the specified TextAppearance resource to determine the
97      * text appearance, and the specified text color resource
98      * to determine the color.  The <code>appearance</code> should be,
99      * for example, <code>android.R.style.TextAppearance_Small</code>,
100      * and the <code>colorList</code> should be, for example,
101      * <code>android.R.styleable.Theme_textColorPrimary</code>.
102      */
TextAppearanceSpan(Context context, int appearance, int colorList)103     public TextAppearanceSpan(Context context, int appearance, int colorList) {
104         ColorStateList textColor;
105 
106         TypedArray a =
107             context.obtainStyledAttributes(appearance,
108                                            com.android.internal.R.styleable.TextAppearance);
109 
110         textColor = a.getColorStateList(com.android.internal.R.styleable.
111                                         TextAppearance_textColor);
112         mTextColorLink = a.getColorStateList(com.android.internal.R.styleable.
113                                         TextAppearance_textColorLink);
114         mTextSize = a.getDimensionPixelSize(com.android.internal.R.styleable.
115                                         TextAppearance_textSize, -1);
116 
117         mStyle = a.getInt(com.android.internal.R.styleable.TextAppearance_textStyle, 0);
118         if (!context.isRestricted() && context.canLoadUnsafeResources()) {
119             mTypeface = a.getFont(com.android.internal.R.styleable.TextAppearance_fontFamily);
120         } else {
121             mTypeface = null;
122         }
123         if (mTypeface != null) {
124             mFamilyName = null;
125         } else {
126             String family = a.getString(com.android.internal.R.styleable.TextAppearance_fontFamily);
127             if (family != null) {
128                 mFamilyName = family;
129             } else {
130                 int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0);
131 
132                 switch (tf) {
133                     case 1:
134                         mFamilyName = "sans";
135                         break;
136 
137                     case 2:
138                         mFamilyName = "serif";
139                         break;
140 
141                     case 3:
142                         mFamilyName = "monospace";
143                         break;
144 
145                     default:
146                         mFamilyName = null;
147                         break;
148                 }
149             }
150         }
151 
152         mTextFontWeight = a.getInt(com.android.internal.R.styleable
153                 .TextAppearance_textFontWeight, /*defValue*/ FontStyle.FONT_WEIGHT_UNSPECIFIED);
154 
155         final String localeString = a.getString(com.android.internal.R.styleable
156                 .TextAppearance_textLocale);
157         if (localeString != null) {
158             LocaleList localeList = LocaleList.forLanguageTags(localeString);
159             if (!localeList.isEmpty()) {
160                 mTextLocales = localeList;
161             } else {
162                 mTextLocales = null;
163             }
164         } else {
165             mTextLocales = null;
166         }
167 
168         mShadowRadius = a.getFloat(com.android.internal.R.styleable
169                 .TextAppearance_shadowRadius, 0.0f);
170         mShadowDx = a.getFloat(com.android.internal.R.styleable
171                 .TextAppearance_shadowDx, 0.0f);
172         mShadowDy = a.getFloat(com.android.internal.R.styleable
173                 .TextAppearance_shadowDy, 0.0f);
174         mShadowColor = a.getInt(com.android.internal.R.styleable
175                 .TextAppearance_shadowColor, 0);
176 
177         mHasElegantTextHeight = a.hasValue(com.android.internal.R.styleable
178                 .TextAppearance_elegantTextHeight);
179         mElegantTextHeight = a.getBoolean(com.android.internal.R.styleable
180                 .TextAppearance_elegantTextHeight, false);
181 
182         mHasLetterSpacing = a.hasValue(com.android.internal.R.styleable
183                 .TextAppearance_letterSpacing);
184         mLetterSpacing = a.getFloat(com.android.internal.R.styleable
185                 .TextAppearance_letterSpacing, 0.0f);
186 
187         mFontFeatureSettings = a.getString(com.android.internal.R.styleable
188                 .TextAppearance_fontFeatureSettings);
189 
190         mFontVariationSettings = a.getString(com.android.internal.R.styleable
191                 .TextAppearance_fontVariationSettings);
192 
193         a.recycle();
194 
195         if (colorList >= 0) {
196             a = context.obtainStyledAttributes(com.android.internal.R.style.Theme,
197                                             com.android.internal.R.styleable.Theme);
198 
199             textColor = a.getColorStateList(colorList);
200             a.recycle();
201         }
202 
203         mTextColor = textColor;
204     }
205 
206     /**
207      * Makes text be drawn with the specified typeface, size, style,
208      * and colors.
209      */
TextAppearanceSpan(String family, int style, int size, ColorStateList color, ColorStateList linkColor)210     public TextAppearanceSpan(String family, int style, int size,
211                               ColorStateList color, ColorStateList linkColor) {
212         mFamilyName = family;
213         mStyle = style;
214         mTextSize = size;
215         mTextColor = color;
216         mTextColorLink = linkColor;
217         mTypeface = null;
218 
219         mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
220         mTextLocales = null;
221 
222         mShadowRadius = 0.0f;
223         mShadowDx = 0.0f;
224         mShadowDy = 0.0f;
225         mShadowColor = 0;
226 
227         mHasElegantTextHeight = false;
228         mElegantTextHeight = false;
229         mHasLetterSpacing = false;
230         mLetterSpacing = 0.0f;
231 
232         mFontFeatureSettings = null;
233         mFontVariationSettings = null;
234     }
235 
TextAppearanceSpan(Parcel src)236     public TextAppearanceSpan(Parcel src) {
237         this(/* familyName= */ src.readString(),
238                 /* style= */ src.readInt(),
239                 /* textSize= */ src.readInt(),
240                 /* textColor= */ (src.readInt() != 0)
241                         ? ColorStateList.CREATOR.createFromParcel(src) : null,
242                 /* textColorLink= */ (src.readInt() != 0)
243                         ? ColorStateList.CREATOR.createFromParcel(src) : null,
244                 /* typeface= */ LeakyTypefaceStorage.readTypefaceFromParcel(src),
245                 /* textFontWeight= */ src.readInt(),
246                 /* textLocales= */
247                 src.readParcelable(LocaleList.class.getClassLoader(), LocaleList.class),
248                 /* shadowRadius= */ src.readFloat(),
249                 /* shadowDx= */ src.readFloat(),
250                 /* shadowDy= */ src.readFloat(),
251                 /* shadowColor= */ src.readInt(),
252                 /* hasElegantTextHeight= */ src.readBoolean(),
253                 /* elegantTextHeight= */ src.readBoolean(),
254                 /* hasLetterSpacing= */ src.readBoolean(),
255                 /* letterSpacing= */ src.readFloat(),
256                 /* fontFeatureSettings= */ src.readString(),
257                 /* fontVariationSettings= */ src.readString());
258     }
259 
260     /** @hide */
TextAppearanceSpan(@ullable String familyName, int style, int textSize, @Nullable ColorStateList textColor, @Nullable ColorStateList textColorLink, @Nullable Typeface typeface, int textFontWeight, @Nullable LocaleList textLocales, float shadowRadius, float shadowDx, float shadowDy, int shadowColor, boolean hasElegantTextHeight, boolean elegantTextHeight, boolean hasLetterSpacing, float letterSpacing, @Nullable String fontFeatureSettings, @Nullable String fontVariationSettings)261     public TextAppearanceSpan(@Nullable String familyName, int style, int textSize,
262             @Nullable ColorStateList textColor, @Nullable ColorStateList textColorLink,
263             @Nullable Typeface typeface,
264             int textFontWeight, @Nullable LocaleList textLocales, float shadowRadius,
265             float shadowDx, float shadowDy, int shadowColor, boolean hasElegantTextHeight,
266             boolean elegantTextHeight, boolean hasLetterSpacing, float letterSpacing,
267             @Nullable String fontFeatureSettings, @Nullable String fontVariationSettings) {
268         mFamilyName = familyName;
269         mStyle = style;
270         mTextSize = textSize;
271         mTextColor = textColor;
272         mTextColorLink = textColorLink;
273         mTypeface = typeface;
274 
275         mTextFontWeight = textFontWeight;
276         mTextLocales = textLocales;
277 
278         mShadowRadius = shadowRadius;
279         mShadowDx = shadowDx;
280         mShadowDy = shadowDy;
281         mShadowColor = shadowColor;
282 
283         mHasElegantTextHeight = hasElegantTextHeight;
284         mElegantTextHeight = elegantTextHeight;
285         mHasLetterSpacing = hasLetterSpacing;
286         mLetterSpacing = letterSpacing;
287 
288         mFontFeatureSettings = fontFeatureSettings;
289         mFontVariationSettings = fontVariationSettings;
290     }
291 
getSpanTypeId()292     public int getSpanTypeId() {
293         return getSpanTypeIdInternal();
294     }
295 
296     /** @hide */
getSpanTypeIdInternal()297     public int getSpanTypeIdInternal() {
298         return TextUtils.TEXT_APPEARANCE_SPAN;
299     }
300 
describeContents()301     public int describeContents() {
302         return 0;
303     }
304 
writeToParcel(Parcel dest, int flags)305     public void writeToParcel(Parcel dest, int flags) {
306         writeToParcelInternal(dest, flags);
307     }
308 
309     /** @hide */
writeToParcelInternal(Parcel dest, int flags)310     public void writeToParcelInternal(Parcel dest, int flags) {
311         dest.writeString(mFamilyName);
312         dest.writeInt(mStyle);
313         dest.writeInt(mTextSize);
314         if (mTextColor != null) {
315             dest.writeInt(1);
316             mTextColor.writeToParcel(dest, flags);
317         } else {
318             dest.writeInt(0);
319         }
320         if (mTextColorLink != null) {
321             dest.writeInt(1);
322             mTextColorLink.writeToParcel(dest, flags);
323         } else {
324             dest.writeInt(0);
325         }
326         LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest);
327 
328         dest.writeInt(mTextFontWeight);
329         dest.writeParcelable(mTextLocales, flags);
330 
331         dest.writeFloat(mShadowRadius);
332         dest.writeFloat(mShadowDx);
333         dest.writeFloat(mShadowDy);
334         dest.writeInt(mShadowColor);
335 
336         dest.writeBoolean(mHasElegantTextHeight);
337         dest.writeBoolean(mElegantTextHeight);
338         dest.writeBoolean(mHasLetterSpacing);
339         dest.writeFloat(mLetterSpacing);
340 
341         dest.writeString(mFontFeatureSettings);
342         dest.writeString(mFontVariationSettings);
343     }
344 
345     /**
346      * Returns the typeface family specified by this span, or <code>null</code>
347      * if it does not specify one.
348      */
getFamily()349     public String getFamily() {
350         return mFamilyName;
351     }
352 
353     /**
354      * Returns the text color specified by this span, or <code>null</code>
355      * if it does not specify one.
356      */
getTextColor()357     public ColorStateList getTextColor() {
358         return mTextColor;
359     }
360 
361     /**
362      * Returns the link color specified by this span, or <code>null</code>
363      * if it does not specify one.
364      */
getLinkTextColor()365     public ColorStateList getLinkTextColor() {
366         return mTextColorLink;
367     }
368 
369     /**
370      * Returns the text size specified by this span, or <code>-1</code>
371      * if it does not specify one.
372      */
getTextSize()373     public int getTextSize() {
374         return mTextSize;
375     }
376 
377     /**
378      * Returns the text style specified by this span, or <code>0</code>
379      * if it does not specify one.
380      */
getTextStyle()381     public int getTextStyle() {
382         return mStyle;
383     }
384 
385     /**
386      * Returns the text font weight specified by this span, or
387      * <code>FontStyle.FONT_WEIGHT_UNSPECIFIED</code> if it does not specify one.
388      */
getTextFontWeight()389     public int getTextFontWeight() {
390         return mTextFontWeight;
391     }
392 
393     /**
394      * Returns the {@link android.os.LocaleList} specified by this span, or <code>null</code>
395      * if it does not specify one.
396      */
397     @Nullable
getTextLocales()398     public LocaleList getTextLocales() {
399         return mTextLocales;
400     }
401 
402     /**
403      * Returns the typeface specified by this span, or <code>null</code>
404      * if it does not specify one.
405      */
406     @Nullable
getTypeface()407     public Typeface getTypeface() {
408         return mTypeface;
409     }
410 
411     /**
412      * Returns the color of the text shadow specified by this span, or <code>0</code>
413      * if it does not specify one.
414      */
getShadowColor()415     public int getShadowColor() {
416         return mShadowColor;
417     }
418 
419     /**
420      * Returns the horizontal offset of the text shadow specified by this span, or <code>0.0f</code>
421      * if it does not specify one.
422      */
getShadowDx()423     public float getShadowDx() {
424         return mShadowDx;
425     }
426 
427     /**
428      * Returns the vertical offset of the text shadow specified by this span, or <code>0.0f</code>
429      * if it does not specify one.
430      */
getShadowDy()431     public float getShadowDy() {
432         return mShadowDy;
433     }
434 
435     /**
436      * Returns the blur radius of the text shadow specified by this span, or <code>0.0f</code>
437      * if it does not specify one.
438      */
getShadowRadius()439     public float getShadowRadius() {
440         return mShadowRadius;
441     }
442 
443     /**
444      * Returns the font feature settings specified by this span, or <code>null</code>
445      * if it does not specify one.
446      */
447     @Nullable
getFontFeatureSettings()448     public String getFontFeatureSettings() {
449         return mFontFeatureSettings;
450     }
451 
452     /**
453      * Returns the font variation settings specified by this span, or <code>null</code>
454      * if it does not specify one.
455      */
456     @Nullable
getFontVariationSettings()457     public String getFontVariationSettings() {
458         return mFontVariationSettings;
459     }
460 
461     /**
462      * Returns the value of elegant height metrics flag specified by this span,
463      * or <code>false</code> if it does not specify one.
464      */
isElegantTextHeight()465     public boolean isElegantTextHeight() {
466         return mElegantTextHeight;
467     }
468 
469     /**
470      * Returns the value of letter spacing to be added in em unit.
471      * @return a letter spacing amount
472      */
getLetterSpacing()473     public float getLetterSpacing() {
474         return mLetterSpacing;
475     }
476 
477     @Override
updateDrawState(TextPaint ds)478     public void updateDrawState(TextPaint ds) {
479         updateMeasureState(ds);
480 
481         if (mTextColor != null) {
482             ds.setColor(mTextColor.getColorForState(ds.drawableState, 0));
483         }
484 
485         if (mTextColorLink != null) {
486             ds.linkColor = mTextColorLink.getColorForState(ds.drawableState, 0);
487         }
488 
489         if (mShadowColor != 0) {
490             ds.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor);
491         }
492     }
493 
494     @Override
updateMeasureState(TextPaint ds)495     public void updateMeasureState(TextPaint ds) {
496         final Typeface styledTypeface;
497         int style = 0;
498 
499         if (mTypeface != null) {
500             style = mStyle;
501             styledTypeface = Typeface.create(mTypeface, style);
502         } else if (mFamilyName != null || mStyle != 0) {
503             Typeface tf = ds.getTypeface();
504 
505             if (tf != null) {
506                 style = tf.getStyle();
507             }
508 
509             style |= mStyle;
510 
511             if (mFamilyName != null) {
512                 styledTypeface = Typeface.create(mFamilyName, style);
513             } else if (tf == null) {
514                 styledTypeface = Typeface.defaultFromStyle(style);
515             } else {
516                 styledTypeface = Typeface.create(tf, style);
517             }
518         } else {
519             styledTypeface = null;
520         }
521 
522         if (styledTypeface != null) {
523             final Typeface readyTypeface;
524             if (mTextFontWeight >= 0) {
525                 final int weight = Math.min(FontStyle.FONT_WEIGHT_MAX, mTextFontWeight);
526                 final boolean italic = (style & Typeface.ITALIC) != 0;
527                 readyTypeface = ds.setTypeface(Typeface.create(styledTypeface, weight, italic));
528             } else {
529                 readyTypeface = styledTypeface;
530             }
531 
532             int fake = style & ~readyTypeface.getStyle();
533 
534             if ((fake & Typeface.BOLD) != 0) {
535                 ds.setFakeBoldText(true);
536             }
537 
538             if ((fake & Typeface.ITALIC) != 0) {
539                 ds.setTextSkewX(-0.25f);
540             }
541 
542             ds.setTypeface(readyTypeface);
543         }
544 
545         if (mTextSize > 0) {
546             ds.setTextSize(mTextSize);
547         }
548 
549         if (mTextLocales != null) {
550             ds.setTextLocales(mTextLocales);
551         }
552 
553         if (mHasElegantTextHeight) {
554             ds.setElegantTextHeight(mElegantTextHeight);
555         }
556 
557         if (mHasLetterSpacing) {
558             ds.setLetterSpacing(mLetterSpacing);
559         }
560 
561         if (mFontFeatureSettings != null) {
562             ds.setFontFeatureSettings(mFontFeatureSettings);
563         }
564 
565         if (mFontVariationSettings != null) {
566             ds.setFontVariationSettings(mFontVariationSettings);
567         }
568     }
569 
570     @Override
toString()571     public String toString() {
572         return "TextAppearanceSpan{"
573                 + "familyName='" + getFamily() + '\''
574                 + ", style=" + getTextStyle()
575                 + ", textSize=" + getTextSize()
576                 + ", textColor=" + getTextColor()
577                 + ", textColorLink=" + getLinkTextColor()
578                 + ", typeface=" + getTypeface()
579                 + ", textFontWeight=" + getTextFontWeight()
580                 + ", textLocales=" + getTextLocales()
581                 + ", shadowRadius=" + getShadowRadius()
582                 + ", shadowDx=" + getShadowDx()
583                 + ", shadowDy=" + getShadowDy()
584                 + ", shadowColor=" + String.format("#%08X", getShadowColor())
585                 + ", elegantTextHeight=" + isElegantTextHeight()
586                 + ", letterSpacing=" + getLetterSpacing()
587                 + ", fontFeatureSettings='" + getFontFeatureSettings() + '\''
588                 + ", fontVariationSettings='" + getFontVariationSettings() + '\''
589                 + '}';
590     }
591 
592     /** @hide */
hasElegantTextHeight()593     public boolean hasElegantTextHeight() {
594         return mHasElegantTextHeight;
595     }
596 
597     /** @hide */
hasLetterSpacing()598     public boolean hasLetterSpacing() {
599         return mHasLetterSpacing;
600     }
601 }
602