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