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