content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.text.style;
  
  import android.annotation.Nullable;
  import android.content.Context;
  import android.content.res.ColorStateList;
  import android.content.res.TypedArray;
  import android.graphics.LeakyTypefaceStorage;
  import android.graphics.Typeface;
  import android.graphics.fonts.FontStyle;
  import android.os.LocaleList;
  import android.os.Parcel;
  import android.text.ParcelableSpan;
  import android.text.TextPaint;
  import android.text.TextUtils;
  
  /**
   * Sets the text appearance using the given
   * {@link android.R.styleable#TextAppearance TextAppearance} attributes.
   * By default {@link TextAppearanceSpan} only changes the specified attributes in XML.
   * {@link android.R.styleable#TextAppearance_textColorHighlight textColorHighlight},
   * {@link android.R.styleable#TextAppearance_textColorHint textColorHint},
   * {@link android.R.styleable#TextAppearance_textAllCaps textAllCaps} and
   * {@link android.R.styleable#TextAppearance_fallbackLineSpacing fallbackLineSpacing}
   * are not supported by {@link TextAppearanceSpan}.
   *
   * {@see android.widget.TextView#setTextAppearance(int)}
   *
   * @attr ref android.R.styleable#TextAppearance_fontFamily
   * @attr ref android.R.styleable#TextAppearance_textColor
   * @attr ref android.R.styleable#TextAppearance_textColorLink
   * @attr ref android.R.styleable#TextAppearance_textFontWeight
   * @attr ref android.R.styleable#TextAppearance_textSize
   * @attr ref android.R.styleable#TextAppearance_textStyle
   * @attr ref android.R.styleable#TextAppearance_typeface
   * @attr ref android.R.styleable#TextAppearance_shadowColor
   * @attr ref android.R.styleable#TextAppearance_shadowDx
   * @attr ref android.R.styleable#TextAppearance_shadowDy
   * @attr ref android.R.styleable#TextAppearance_shadowRadius
   * @attr ref android.R.styleable#TextAppearance_elegantTextHeight
   * @attr ref android.R.styleable#TextAppearance_letterSpacing
   * @attr ref android.R.styleable#TextAppearance_fontFeatureSettings
   * @attr ref android.R.styleable#TextAppearance_fontVariationSettings
   *
   */
  public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
      private final String mFamilyName;
      private final int mStyle;
      private final int mTextSize;
      private final ColorStateList mTextColor;
      private final ColorStateList mTextColorLink;
      private final Typeface mTypeface;
  
      private final int mTextFontWeight;
      private final LocaleList mTextLocales;
  
      private final float mShadowRadius;
      private final float mShadowDx;
      private final float mShadowDy;
      private final int mShadowColor;
  
      private final boolean mHasElegantTextHeight;
      private final boolean mElegantTextHeight;
      private final boolean mHasLetterSpacing;
      private final float mLetterSpacing;
  
      private final String mFontFeatureSettings;
      private final String mFontVariationSettings;
  
      /**
       * Uses the specified TextAppearance resource to determine the
       * text appearance.  The <code>appearance</code> should be, for example,
       * <code>android.R.style.TextAppearance_Small</code>.
       */
      public TextAppearanceSpan(Context context, int appearance) {
          this(context, appearance, -1);
      }
  
      /**
       * Uses the specified TextAppearance resource to determine the
       * text appearance, and the specified text color resource
       * to determine the color.  The <code>appearance</code> should be,
       * for example, <code>android.R.style.TextAppearance_Small</code>,
       * and the <code>colorList</code> should be, for example,
       * <code>android.R.styleable.Theme_textColorPrimary</code>.
       */
      public TextAppearanceSpan(Context context, int appearance, int colorList) {
          ColorStateList textColor;
  
          TypedArray a =
              context.obtainStyledAttributes(appearance,
                                             com.android.internal.R.styleable.TextAppearance);
  
          textColor = a.getColorStateList(com.android.internal.R.styleable.
                                          TextAppearance_textColor);
          mTextColorLink = a.getColorStateList(com.android.internal.R.styleable.
                                          TextAppearance_textColorLink);
          mTextSize = a.getDimensionPixelSize(com.android.internal.R.styleable.
                                          TextAppearance_textSize, -1);
  
          mStyle = a.getInt(com.android.internal.R.styleable.TextAppearance_textStyle, 0);
          if (!context.isRestricted() && context.canLoadUnsafeResources()) {
              mTypeface = a.getFont(com.android.internal.R.styleable.TextAppearance_fontFamily);
          } else {
              mTypeface = null;
          }
          if (mTypeface != null) {
              mFamilyName = null;
          } else {
              String family = a.getString(com.android.internal.R.styleable.TextAppearance_fontFamily);
              if (family != null) {
                  mFamilyName = family;
              } else {
                  int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0);
  
                  switch (tf) {
                      case 1:
                          mFamilyName = "sans";
                          break;
  
                      case 2:
                          mFamilyName = "serif";
                          break;
  
                      case 3:
                          mFamilyName = "monospace";
                          break;
  
                      default:
                          mFamilyName = null;
                          break;
                  }
              }
          }
  
          mTextFontWeight = a.getInt(com.android.internal.R.styleable
                  .TextAppearance_textFontWeight, -1);
  
          final String localeString = a.getString(com.android.internal.R.styleable
                  .TextAppearance_textLocale);
          if (localeString != null) {
              LocaleList localeList = LocaleList.forLanguageTags(localeString);
              if (!localeList.isEmpty()) {
                  mTextLocales = localeList;
              } else {
                  mTextLocales = null;
              }
          } else {
              mTextLocales = null;
          }
  
          mShadowRadius = a.getFloat(com.android.internal.R.styleable
                  .TextAppearance_shadowRadius, 0.0f);
          mShadowDx = a.getFloat(com.android.internal.R.styleable
                  .TextAppearance_shadowDx, 0.0f);
          mShadowDy = a.getFloat(com.android.internal.R.styleable
                  .TextAppearance_shadowDy, 0.0f);
          mShadowColor = a.getInt(com.android.internal.R.styleable
                  .TextAppearance_shadowColor, 0);
  
          mHasElegantTextHeight = a.hasValue(com.android.internal.R.styleable
                  .TextAppearance_elegantTextHeight);
          mElegantTextHeight = a.getBoolean(com.android.internal.R.styleable
                  .TextAppearance_elegantTextHeight, false);
  
          mHasLetterSpacing = a.hasValue(com.android.internal.R.styleable
                  .TextAppearance_letterSpacing);
          mLetterSpacing = a.getFloat(com.android.internal.R.styleable
                  .TextAppearance_letterSpacing, 0.0f);
  
          mFontFeatureSettings = a.getString(com.android.internal.R.styleable
                  .TextAppearance_fontFeatureSettings);
  
          mFontVariationSettings = a.getString(com.android.internal.R.styleable
                  .TextAppearance_fontVariationSettings);
  
          a.recycle();
  
          if (colorList >= 0) {
              a = context.obtainStyledAttributes(com.android.internal.R.style.Theme,
                                              com.android.internal.R.styleable.Theme);
  
              textColor = a.getColorStateList(colorList);
              a.recycle();
          }
  
          mTextColor = textColor;
      }
  
      /**
       * Makes text be drawn with the specified typeface, size, style,
       * and colors.
       */
      public TextAppearanceSpan(String family, int style, int size,
                                ColorStateList color, ColorStateList linkColor) {
          mFamilyName = family;
          mStyle = style;
          mTextSize = size;
          mTextColor = color;
          mTextColorLink = linkColor;
          mTypeface = null;
  
          mTextFontWeight = -1;
          mTextLocales = null;
  
          mShadowRadius = 0.0f;
          mShadowDx = 0.0f;
          mShadowDy = 0.0f;
          mShadowColor = 0;
  
          mHasElegantTextHeight = false;
          mElegantTextHeight = false;
          mHasLetterSpacing = false;
          mLetterSpacing = 0.0f;
  
          mFontFeatureSettings = null;
          mFontVariationSettings = null;
      }
  
      public TextAppearanceSpan(Parcel src) {
          mFamilyName = src.readString();
          mStyle = src.readInt();
          mTextSize = src.readInt();
          if (src.readInt() != 0) {
              mTextColor = ColorStateList.CREATOR.createFromParcel(src);
          } else {
              mTextColor = null;
          }
          if (src.readInt() != 0) {
              mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
          } else {
              mTextColorLink = null;
          }
          mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
  
          mTextFontWeight = src.readInt();
          mTextLocales = src.readParcelable(LocaleList.class.getClassLoader());
  
          mShadowRadius = src.readFloat();
          mShadowDx = src.readFloat();
          mShadowDy = src.readFloat();
          mShadowColor = src.readInt();
  
          mHasElegantTextHeight = src.readBoolean();
          mElegantTextHeight = src.readBoolean();
          mHasLetterSpacing = src.readBoolean();
          mLetterSpacing = src.readFloat();
  
          mFontFeatureSettings = src.readString();
          mFontVariationSettings = src.readString();
      }
  
      public int getSpanTypeId() {
          return getSpanTypeIdInternal();
      }
  
      /** @hide */
      public int getSpanTypeIdInternal() {
          return TextUtils.TEXT_APPEARANCE_SPAN;
      }
  
      public int describeContents() {
          return 0;
      }
  
      public void writeToParcel(Parcel dest, int flags) {
          writeToParcelInternal(dest, flags);
      }
  
      /** @hide */
      public void writeToParcelInternal(Parcel dest, int flags) {
          dest.writeString(mFamilyName);
          dest.writeInt(mStyle);
          dest.writeInt(mTextSize);
          if (mTextColor != null) {
              dest.writeInt(1);
              mTextColor.writeToParcel(dest, flags);
          } else {
              dest.writeInt(0);
          }
          if (mTextColorLink != null) {
              dest.writeInt(1);
              mTextColorLink.writeToParcel(dest, flags);
          } else {
              dest.writeInt(0);
          }
          LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest);
  
          dest.writeInt(mTextFontWeight);
          dest.writeParcelable(mTextLocales, flags);
  
          dest.writeFloat(mShadowRadius);
          dest.writeFloat(mShadowDx);
          dest.writeFloat(mShadowDy);
          dest.writeInt(mShadowColor);
  
          dest.writeBoolean(mHasElegantTextHeight);
          dest.writeBoolean(mElegantTextHeight);
          dest.writeBoolean(mHasLetterSpacing);
          dest.writeFloat(mLetterSpacing);
  
          dest.writeString(mFontFeatureSettings);
          dest.writeString(mFontVariationSettings);
      }
  
      /**
       * Returns the typeface family specified by this span, or <code>null</code>
       * if it does not specify one.
       */
      public String getFamily() {
          return mFamilyName;
      }
  
      /**
       * Returns the text color specified by this span, or <code>null</code>
       * if it does not specify one.
       */
      public ColorStateList getTextColor() {
          return mTextColor;
      }
  
      /**
       * Returns the link color specified by this span, or <code>null</code>
       * if it does not specify one.
       */
      public ColorStateList getLinkTextColor() {
          return mTextColorLink;
      }
  
      /**
       * Returns the text size specified by this span, or <code>-1</code>
       * if it does not specify one.
       */
      public int getTextSize() {
          return mTextSize;
      }
  
      /**
       * Returns the text style specified by this span, or <code>0</code>
       * if it does not specify one.
       */
      public int getTextStyle() {
          return mStyle;
      }
  
      /**
       * Returns the text font weight specified by this span, or <code>-1</code>
       * if it does not specify one.
       */
      public int getTextFontWeight() {
          return mTextFontWeight;
      }
  
      /**
       * Returns the {@link android.os.LocaleList} specified by this span, or <code>null</code>
       * if it does not specify one.
       */
      @Nullable
      public LocaleList getTextLocales() {
          return mTextLocales;
      }
  
      /**
       * Returns the typeface specified by this span, or <code>null</code>
       * if it does not specify one.
       */
      @Nullable
      public Typeface getTypeface() {
          return mTypeface;
      }
  
      /**
       * Returns the color of the text shadow specified by this span, or <code>0</code>
       * if it does not specify one.
       */
      public int getShadowColor() {
          return mShadowColor;
      }
  
      /**
       * Returns the horizontal offset of the text shadow specified by this span, or <code>0.0f</code>
       * if it does not specify one.
       */
      public float getShadowDx() {
          return mShadowDx;
      }
  
      /**
       * Returns the vertical offset of the text shadow specified by this span, or <code>0.0f</code>
       * if it does not specify one.
       */
      public float getShadowDy() {
          return mShadowDy;
      }
  
      /**
       * Returns the blur radius of the text shadow specified by this span, or <code>0.0f</code>
       * if it does not specify one.
       */
      public float getShadowRadius() {
          return mShadowRadius;
      }
  
      /**
       * Returns the font feature settings specified by this span, or <code>null</code>
       * if it does not specify one.
       */
      @Nullable
      public String getFontFeatureSettings() {
          return mFontFeatureSettings;
      }
  
      /**
       * Returns the font variation settings specified by this span, or <code>null</code>
       * if it does not specify one.
       */
      @Nullable
      public String getFontVariationSettings() {
          return mFontVariationSettings;
      }
  
      /**
       * Returns the value of elegant height metrics flag specified by this span,
       * or <code>false</code> if it does not specify one.
       */
      public boolean isElegantTextHeight() {
          return mElegantTextHeight;
      }
  
      @Override
      public void updateDrawState(TextPaint ds) {
          updateMeasureState(ds);
  
          if (mTextColor != null) {
              ds.setColor(mTextColor.getColorForState(ds.drawableState, 0));
          }
  
          if (mTextColorLink != null) {
              ds.linkColor = mTextColorLink.getColorForState(ds.drawableState, 0);
          }
  
          if (mShadowColor != 0) {
              ds.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor);
          }
      }
  
      @Override
      public void updateMeasureState(TextPaint ds) {
          final Typeface styledTypeface;
          int style = 0;
  
          if (mTypeface != null) {
              style = mStyle;
              styledTypeface = Typeface.create(mTypeface, style);
          } else if (mFamilyName != null || mStyle != 0) {
              Typeface tf = ds.getTypeface();
  
              if (tf != null) {
                  style = tf.getStyle();
              }
  
              style |= mStyle;
  
              if (mFamilyName != null) {
                  styledTypeface = Typeface.create(mFamilyName, style);
              } else if (tf == null) {
                  styledTypeface = Typeface.defaultFromStyle(style);
              } else {
                  styledTypeface = Typeface.create(tf, style);
              }
          } else {
              styledTypeface = null;
          }
  
          if (styledTypeface != null) {
              final Typeface readyTypeface;
              if (mTextFontWeight >= 0) {
                  final int weight = Math.min(FontStyle.FONT_WEIGHT_MAX, mTextFontWeight);
                  final boolean italic = (style & Typeface.ITALIC) != 0;
                  readyTypeface = ds.setTypeface(Typeface.create(styledTypeface, weight, italic));
              } else {
                  readyTypeface = styledTypeface;
              }
  
              int fake = style & ~readyTypeface.getStyle();
  
              if ((fake & Typeface.BOLD) != 0) {
                  ds.setFakeBoldText(true);
              }
  
              if ((fake & Typeface.ITALIC) != 0) {
                  ds.setTextSkewX(-0.25f);
              }
  
              ds.setTypeface(readyTypeface);
          }
  
          if (mTextSize > 0) {
              ds.setTextSize(mTextSize);
          }
  
          if (mTextLocales != null) {
              ds.setTextLocales(mTextLocales);
          }
  
          if (mHasElegantTextHeight) {
              ds.setElegantTextHeight(mElegantTextHeight);
          }
  
          if (mHasLetterSpacing) {
              ds.setLetterSpacing(mLetterSpacing);
          }
  
          if (mFontFeatureSettings != null) {
              ds.setFontFeatureSettings(mFontFeatureSettings);
          }
  
          if (mFontVariationSettings != null) {
              ds.setFontVariationSettings(mFontVariationSettings);
          }
      }
  }
  