content: /*
   * Copyright (C) 2020 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 androidx.constraintlayout.utils.widget;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.Bitmap;
  import android.graphics.BitmapShader;
  import android.graphics.Canvas;
  import android.graphics.Matrix;
  import android.graphics.Outline;
  import android.graphics.Paint;
  import android.graphics.Path;
  import android.graphics.Rect;
  import android.graphics.RectF;
  import android.graphics.Shader;
  import android.graphics.Typeface;
  import android.graphics.drawable.Drawable;
  import android.os.Build;
  import android.text.Layout;
  import android.text.TextPaint;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.TypedValue;
  import android.view.Gravity;
  import android.view.View;
  import android.view.ViewOutlineProvider;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.annotation.RequiresApi;
  import androidx.constraintlayout.motion.widget.Debug;
  import androidx.constraintlayout.motion.widget.FloatLayout;
  import androidx.constraintlayout.widget.R;
  import androidx.core.widget.TextViewCompat;
  
  import java.util.Objects;
  
  /**
   * This class is designed to create complex animated single line text in MotionLayout.
   * Its API are designed with animation in mine.
   * for example it uses setTextPanX(float x) where 0 is centered -1 is left +1 is right
   *
   * It supports the following features:
   * <ul>
   * <li>color outlines</li>
   * <li>Textured text</li>
   * <li>Blured Textured Text</li>
   * <li>Scrolling of Texture in text</li>
   * <li>PanX, PanY instead of Gravity</li>
   * </ul>
   */
  public class MotionLabel extends View implements FloatLayout {
      static final String TAG = "MotionLabel";
      TextPaint mPaint = new TextPaint();
      Path mPath = new Path();
      private int mTextFillColor = 0xFFFF;
      private int mTextOutlineColor = 0xFFFF;
      private boolean mUseOutline = false;
      private float mRoundPercent = 0; // rounds the corners as a percent
      private float mRound = Float.NaN; // rounds the corners in dp if NaN RoundPercent is in effect
      ViewOutlineProvider mViewOutlineProvider;
      RectF mRect;
  
      private float mTextSize = 48;
  
      private float mBaseTextSize = Float.NaN;
      private int mStyleIndex;
      private int mTypefaceIndex;
      private float mTextOutlineThickness = 0;
      private String mText = "Hello World";
      boolean mNotBuilt = true;
      private Rect mTextBounds = new Rect();
      private int mPaddingLeft = 1;
      private int mPaddingRight = 1;
      private int mPaddingTop = 1;
      private int mPaddingBottom = 1;
      private String mFontFamily;
      //    private StaticLayout mStaticLayout;
      private Layout mLayout;
      private static final int SANS = 1;
      private static final int SERIF = 2;
      private static final int MONOSPACE = 3;
      private int mGravity = Gravity.TOP | Gravity.START;
      private int mAutoSizeTextType = TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
      private boolean mAutoSize = false; // decided during measure
      private float mDeltaLeft;
      private float mFloatWidth, mFloatHeight;
      private Drawable mTextBackground;
      Matrix mOutlinePositionMatrix;
      private Bitmap mTextBackgroundBitmap;
      private BitmapShader mTextShader;
      private Matrix mTextShaderMatrix;
      private float mTextureHeight = Float.NaN;
      private float mTextureWidth = Float.NaN;
      private float mTextPanX = 0;
      private float mTextPanY = 0;
      Paint mPaintCache = new Paint();
      private int mTextureEffect = 0;
  
      public MotionLabel(Context context) {
          super(context);
          init(context, null);
      }
  
      public MotionLabel(Context context, @Nullable AttributeSet attrs) {
          super(context, attrs);
          init(context, attrs);
      }
  
      public MotionLabel(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init(context, attrs);
      }
  
      private void init(Context context, AttributeSet attrs) {
          setUpTheme(context);
  
          if (attrs != null) {
              TypedArray a = getContext()
                      .obtainStyledAttributes(attrs, R.styleable.MotionLabel);
              final int count = a.getIndexCount();
  
              for (int i = 0; i < count; i++) {
                  int attr = a.getIndex(i);
                  if (attr == R.styleable.MotionLabel_android_text) {
                      setText(a.getText(attr));
                  } else if (attr == R.styleable.MotionLabel_android_fontFamily) {
                      mFontFamily = a.getString(attr);
                  } else if (attr == R.styleable.MotionLabel_scaleFromTextSize) {
                      mBaseTextSize = a.getDimensionPixelSize(attr, (int) mBaseTextSize);
                  } else if (attr == R.styleable.MotionLabel_android_textSize) {
                      mTextSize = a.getDimensionPixelSize(attr, (int) mTextSize);
                  } else if (attr == R.styleable.MotionLabel_android_textStyle) {
                      mStyleIndex = a.getInt(attr, mStyleIndex);
                  } else if (attr == R.styleable.MotionLabel_android_typeface) {
                      mTypefaceIndex = a.getInt(attr, mTypefaceIndex);
                  } else if (attr == R.styleable.MotionLabel_android_textColor) {
                      mTextFillColor = a.getColor(attr, mTextFillColor);
                  } else if (attr == R.styleable.MotionLabel_borderRound) {
                      mRound = a.getDimension(attr, mRound);
                      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                          setRound(mRound);
                      }
                  } else if (attr == R.styleable.MotionLabel_borderRoundPercent) {
                      mRoundPercent = a.getFloat(attr, mRoundPercent);
                      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                          setRoundPercent(mRoundPercent);
                      }
                  } else if (attr == R.styleable.MotionLabel_android_gravity) {
                      setGravity(a.getInt(attr, -1));
                  } else if (attr == R.styleable.MotionLabel_android_autoSizeTextType) {
                      mAutoSizeTextType = a.getInt(attr, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
                  } else if (attr == R.styleable.MotionLabel_textOutlineColor) {
                      mTextOutlineColor = a.getInt(attr, mTextOutlineColor);
                      mUseOutline = true;
                  } else if (attr == R.styleable.MotionLabel_textOutlineThickness) {
                      mTextOutlineThickness = a.getDimension(attr, mTextOutlineThickness);
                      mUseOutline = true;
                  } else if (attr == R.styleable.MotionLabel_textBackground) {
                      mTextBackground = a.getDrawable(attr);
                      mUseOutline = true;
                  } else if (attr == R.styleable.MotionLabel_textBackgroundPanX) {
                      mBackgroundPanX = a.getFloat(attr, mBackgroundPanX);
                  } else if (attr == R.styleable.MotionLabel_textBackgroundPanY) {
                      mBackgroundPanY = a.getFloat(attr, mBackgroundPanY);
                  } else if (attr == R.styleable.MotionLabel_textPanX) {
                      mTextPanX = a.getFloat(attr, mTextPanX);
                  } else if (attr == R.styleable.MotionLabel_textPanY) {
                      mTextPanY = a.getFloat(attr, mTextPanY);
                  } else if (attr == R.styleable.MotionLabel_textBackgroundRotate) {
                      mRotate = a.getFloat(attr, mRotate);
                  } else if (attr == R.styleable.MotionLabel_textBackgroundZoom) {
                      mZoom = a.getFloat(attr, mZoom);
                  } else if (attr == R.styleable.MotionLabel_textureHeight) {
                      mTextureHeight = a.getDimension(attr, mTextureHeight);
                  } else if (attr == R.styleable.MotionLabel_textureWidth) {
                      mTextureWidth = a.getDimension(attr, mTextureWidth);
                  } else if (attr == R.styleable.MotionLabel_textureEffect) {
                      mTextureEffect = a.getInt(attr, mTextureEffect);
                  }
              }
              a.recycle();
          }
  
          setupTexture();
          setupPath();
      }
  
      Bitmap blur(Bitmap bitmapOriginal, int factor) {
          int w = bitmapOriginal.getWidth();
          int h = bitmapOriginal.getHeight();
  
          w /= 2;
          h /= 2;
  
          Bitmap ret = Bitmap.createScaledBitmap(bitmapOriginal,
                  w, h, true);
          for (int i = 0; i < factor; i++) {
              if (w < 32 || h < 32) {
                  break;
              }
              w /= 2;
              h /= 2;
              ret = Bitmap.createScaledBitmap(ret, w, h, true);
          }
          return ret;
      }
  
      private void setupTexture() {
          if (mTextBackground != null) {
              mTextShaderMatrix = new Matrix();
              int iw = mTextBackground.getIntrinsicWidth();
              int ih = mTextBackground.getIntrinsicHeight();
              if (iw <= 0) {
                  int w = getWidth();
                  if (w == 0) {
                      w = (Float.isNaN(mTextureWidth) ? 128 : (int) mTextureWidth);
                  }
                  iw = w;
              }
              if (ih <= 0) {
                  int h = getHeight();
                  if (h == 0) {
                      h = (Float.isNaN(mTextureHeight) ? 128 : (int) mTextureHeight);
                  }
                  ih = h;
              }
  
              if (mTextureEffect != 0) {
                  iw /= 2;
                  ih /= 2;
              }
              mTextBackgroundBitmap = Bitmap.createBitmap(iw, ih, Bitmap.Config.ARGB_8888);
              Canvas canvas = new Canvas(mTextBackgroundBitmap);
  
              mTextBackground.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
              mTextBackground.setFilterBitmap(true);
              mTextBackground.draw(canvas);
              if (mTextureEffect != 0) {
                  mTextBackgroundBitmap = blur(mTextBackgroundBitmap, 4);
              }
              mTextShader = new BitmapShader(mTextBackgroundBitmap,
                      Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
          }
      }
  
      private void adjustTexture(float l, float t, float r, float b) {
          if (mTextShaderMatrix == null) {
              return;
          }
  
          mFloatWidth = r - l;
          mFloatHeight = b - t;
          updateShaderMatrix();
      }
  
      /**
       * Sets the horizontal alignment of the text and the
       * vertical gravity that will be used when there is extra space
       * in the TextView beyond what is required for the text itself.
       *
       * @attr ref android.R.styleable#TextView_gravity
       * @see android.view.Gravity
       */
      @SuppressLint("RtlHardcoded")
      public void setGravity(int gravity) {
          if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
              gravity |= Gravity.START;
          }
          if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
              gravity |= Gravity.TOP;
          }
          @SuppressWarnings("unused")
          boolean newLayout = false;
          if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
                  != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
              newLayout = true;
          }
          if (gravity != mGravity) {
              invalidate();
          }
  
          mGravity = gravity;
          switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
              case Gravity.TOP:
                  mTextPanY = -1;
                  break;
              case Gravity.BOTTOM:
                  mTextPanY = 1;
                  break;
              default:
                  mTextPanY = 0;
          }
  
          switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
              case Gravity.START:
              case Gravity.LEFT:
                  mTextPanX = -1;
                  break;
              case Gravity.END:
              case Gravity.RIGHT:
                  mTextPanX = 1;
                  break;
              default:
                  mTextPanX = 0;
          }
      }
  
      private float getHorizontalOffset() {
          float scale = Float.isNaN(mBaseTextSize) ? 1.0f : mTextSize / mBaseTextSize;
  
          float textWidth = scale * mPaint.measureText(mText, 0, mText.length());
          float boxWidth = (Float.isNaN(mFloatWidth) ? getMeasuredWidth() : mFloatWidth)
                  - getPaddingLeft()
                  - getPaddingRight();
          return (boxWidth - textWidth) * (1 + mTextPanX) / 2.f;
      }
  
      private float getVerticalOffset() {
          float scale = Float.isNaN(mBaseTextSize) ? 1.0f : mTextSize / mBaseTextSize;
  
          Paint.FontMetrics fm = mPaint.getFontMetrics();
  
          float boxHeight = (Float.isNaN(mFloatHeight) ? getMeasuredHeight() : mFloatHeight)
                  - getPaddingTop()
                  - getPaddingBottom();
  
          float textHeight = scale * (fm.descent - fm.ascent);
          return (boxHeight - textHeight) * (1 - mTextPanY) / 2 -  (scale * fm.ascent);
      }
  
      private void setUpTheme(Context context) {
          TypedValue typedValue = new TypedValue();
          final Resources.Theme theme = context.getTheme();
          theme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, typedValue, true);
          mPaint.setColor(mTextFillColor = typedValue.data);
      }
  
      /**
       * set text
       * @param text
       */
      public void setText(CharSequence text) {
          mText = text.toString();
          invalidate();
      }
  
      void setupPath() {
          mPaddingLeft = getPaddingLeft();
          mPaddingRight = getPaddingRight();
          mPaddingTop = getPaddingTop();
          mPaddingBottom = getPaddingBottom();
          setTypefaceFromAttrs(mFontFamily, mTypefaceIndex, mStyleIndex);
          mPaint.setColor(mTextFillColor);
          mPaint.setStrokeWidth(mTextOutlineThickness);
          mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
          mPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG);
          setTextSize(mTextSize);
          mPaint.setAntiAlias(true);
      }
  
      void buildShape(float scale) {
          if (!mUseOutline && scale == 1.0f) {
              return;
          }
          mPath.reset();
          String str = mText;
          int len = str.length();
          mPaint.getTextBounds(str, 0, len, mTextBounds);
          mPaint.getTextPath(str, 0, len, 0, 0, mPath);
          if (scale != 1.0f) {
              Log.v(TAG, Debug.getLoc() + " scale " + scale);
              Matrix matrix = new Matrix();
              matrix.postScale(scale, scale);
              mPath.transform(matrix);
          }
          mTextBounds.right--;
          mTextBounds.left++;
          mTextBounds.bottom++;
          mTextBounds.top--;
  
          RectF rect = new RectF();
          rect.bottom = getHeight();
          rect.right = getWidth();
          mNotBuilt = false;
      }
  
      Rect mTempRect;
      Paint mTempPaint;
      float mPaintTextSize;
  
      @Override
      public void layout(int l, int t, int r, int b) {
          super.layout(l, t, r, b);
          boolean normalScale = Float.isNaN(mBaseTextSize);
          float scaleText = normalScale ? 1 : mTextSize / mBaseTextSize;
          mFloatWidth = r - l;
          mFloatHeight = b - t;
          if (mAutoSize) {
  
              if (mTempRect == null) {
                  mTempPaint = new Paint();
                  mTempRect = new Rect();
                  mTempPaint.set(mPaint);
                  mPaintTextSize = mTempPaint.getTextSize();
              }
  
              mTempPaint.getTextBounds(mText, 0, mText.length(), mTempRect);
              int tw = mTempRect.width();
              int th = (int) (1.3f * mTempRect.height());
  
              float vw = mFloatWidth - mPaddingRight - mPaddingLeft;
              float vh = mFloatHeight - mPaddingBottom - mPaddingTop;
              if (normalScale) {
                  if (tw * vh > th * vw) { // width limited tw/vw > th/vh
                      mPaint.setTextSize((mPaintTextSize * vw) / tw);
                  } else { // height limited
                      mPaint.setTextSize((mPaintTextSize * vh) / th);
                  }
              } else {
                  scaleText = (tw * vh > th * vw) ? vw / (float) tw : vh / (float) th;
              }
          }
          if (mUseOutline || !normalScale) {
              adjustTexture(l, t, r, b);
              buildShape(scaleText);
          }
      }
  
      @Override
      public void layout(float l, float t, float r, float b) {
          mDeltaLeft = l - (int) (0.5f + l);
          int w = (int) (0.5f + r) - (int) (0.5f + l);
          int h = (int) (0.5f + b) - (int) (0.5f + t);
          mFloatWidth = r - l;
          mFloatHeight = b - t;
          adjustTexture(l, t, r, b);
          if (getMeasuredHeight() != h || getMeasuredWidth() != w) {
              int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
              int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
              measure(widthMeasureSpec, heightMeasureSpec);
              super.layout((int) (0.5f + l), (int) (0.5f + t), (int) (0.5f + r), (int) (0.5f + b));
          } else {
              super.layout((int) (0.5f + l), (int) (0.5f + t), (int) (0.5f + r), (int) (0.5f + b));
          }
          if (mAutoSize) {
              if (mTempRect == null) {
                  mTempPaint = new Paint();
                  mTempRect = new Rect();
                  mTempPaint.set(mPaint);
                  mPaintTextSize = mTempPaint.getTextSize();
              }
              mFloatWidth = r - l;
              mFloatHeight = b - t;
  
              mTempPaint.getTextBounds(mText, 0, mText.length(), mTempRect);
              int tw = mTempRect.width();
              float th = 1.3f * mTempRect.height();
              float vw = r - l - mPaddingRight - mPaddingLeft;
              float vh = b - t - mPaddingBottom - mPaddingTop;
              if (tw * vh > th * vw) { // width limited tw/vw > th/vh
                  mPaint.setTextSize((mPaintTextSize * vw) / tw);
              } else { // height limited
                  mPaint.setTextSize((mPaintTextSize * vh) / th);
              }
              if (mUseOutline || !Float.isNaN(mBaseTextSize)) {
                  buildShape(Float.isNaN(mBaseTextSize) ? 1.0f : mTextSize / mBaseTextSize);
              }
          }
      }
  
      @Override
      protected void onDraw(@NonNull Canvas canvas) {
          float scale = Float.isNaN(mBaseTextSize) ? 1.0f : mTextSize / mBaseTextSize;
          super.onDraw(canvas);
          if (!mUseOutline && scale == 1.0f) {
              float x = mPaddingLeft + getHorizontalOffset();
              float y = mPaddingTop + getVerticalOffset();
              canvas.drawText(mText, mDeltaLeft + x, y, mPaint);
              return;
          }
          if (mNotBuilt) {
              buildShape(scale);
          }
          if (mOutlinePositionMatrix == null) {
              mOutlinePositionMatrix = new Matrix();
          }
          if (mUseOutline) {
              mPaintCache.set(mPaint);
              mOutlinePositionMatrix.reset();
              float x = mPaddingLeft + getHorizontalOffset();
              float y = mPaddingTop + getVerticalOffset();
              mOutlinePositionMatrix.postTranslate(x, y);
              mOutlinePositionMatrix.preScale(scale, scale);
              mPath.transform(mOutlinePositionMatrix);
  
              if (mTextShader != null) {
                  mPaint.setFilterBitmap(true);
                  mPaint.setShader(mTextShader);
              } else {
                  mPaint.setColor(mTextFillColor);
              }
              mPaint.setStyle(Paint.Style.FILL);
              mPaint.setStrokeWidth(mTextOutlineThickness);
              canvas.drawPath(mPath, mPaint);
              if (mTextShader != null) {
                  mPaint.setShader(null);
              }
              mPaint.setColor(mTextOutlineColor);
              mPaint.setStyle(Paint.Style.STROKE);
              mPaint.setStrokeWidth(mTextOutlineThickness);
              canvas.drawPath(mPath, mPaint);
  
              mOutlinePositionMatrix.reset();
              mOutlinePositionMatrix.postTranslate(-x, -y);
              mPath.transform(mOutlinePositionMatrix);
              mPaint.set(mPaintCache);
          } else {
              float x = mPaddingLeft + getHorizontalOffset();
              float y = mPaddingTop + getVerticalOffset();
              mOutlinePositionMatrix.reset();
              mOutlinePositionMatrix.preTranslate(x, y);
              mPath.transform(mOutlinePositionMatrix);
              mPaint.setColor(mTextFillColor);
              mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
              mPaint.setStrokeWidth(mTextOutlineThickness);
              canvas.drawPath(mPath, mPaint);
              mOutlinePositionMatrix.reset();
              mOutlinePositionMatrix.preTranslate(-x, -y);
              mPath.transform(mOutlinePositionMatrix);
          }
      }
  
      /**
       * Set outline thickness
       * @param width
       */
      public void setTextOutlineThickness(float width) {
          mTextOutlineThickness = width;
          mUseOutline = true;
          if (Float.isNaN(mTextOutlineThickness)) {
              mTextOutlineThickness = 1;
              mUseOutline = false;
          }
          invalidate();
      }
  
      /**
       * Set the color of the text.
       *
       * @param color the color of the text
       */
      public void setTextFillColor(int color) {
          mTextFillColor = color;
          invalidate();
      }
  
      /**
       * Sets the color of the text outline.
       *
       * @param color the color of the outline of the text
       */
      public void setTextOutlineColor(int color) {
          mTextOutlineColor = color;
          mUseOutline = true;
          invalidate();
      }
  
      private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
          Typeface tf = null;
          if (familyName != null) {
              tf = Typeface.create(familyName, styleIndex);
              if (tf != null) {
                  setTypeface(tf);
                  return;
              }
          }
          switch (typefaceIndex) {
              case SANS:
                  tf = Typeface.SANS_SERIF;
                  break;
              case SERIF:
                  tf = Typeface.SERIF;
                  break;
              case MONOSPACE:
                  tf = Typeface.MONOSPACE;
                  break;
          }
  
          if (styleIndex > 0) {
              if (tf == null) {
                  tf = Typeface.defaultFromStyle(styleIndex);
              } else {
                  tf = Typeface.create(tf, styleIndex);
              }
              setTypeface(tf);
              // now compute what (if any) algorithmic styling is needed
              int typefaceStyle = tf != null ? tf.getStyle() : 0;
              int need = styleIndex & ~typefaceStyle;
              mPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
              mPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
          } else {
              mPaint.setFakeBoldText(false);
              mPaint.setTextSkewX(0);
              setTypeface(tf);
          }
      }
  
      /**
       * set the typeface
       * @param tf
       */
      public void setTypeface(Typeface tf) {
          if (!Objects.equals(mPaint.getTypeface(), tf)) {
              mPaint.setTypeface(tf);
              if (mLayout != null) {
                  mLayout = null;
                  requestLayout();
                  invalidate();
              }
          }
      }
  
      /**
       * @return the current typeface and style in which the text is being
       * displayed.
       * @see #setTypeface(Typeface)
       */
      public Typeface getTypeface() {
          return mPaint.getTypeface();
      }
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
          int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
          int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
          int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
          int width = widthSize;
          int height = heightSize;
  
          mAutoSize = false;
  
          mPaddingLeft = getPaddingLeft();
          mPaddingRight = getPaddingRight();
          mPaddingTop = getPaddingTop();
          mPaddingBottom = getPaddingBottom();
          if (widthMode != View.MeasureSpec.EXACTLY || heightMode != View.MeasureSpec.EXACTLY) {
              mPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
              // WIDTH
              if (widthMode != View.MeasureSpec.EXACTLY) {
                  width = (int) (mTextBounds.width() + 0.99999f);
              }
              width += mPaddingLeft + mPaddingRight;
  
              if (heightMode != View.MeasureSpec.EXACTLY) {
                  int desired = (int) (mPaint.getFontMetricsInt(null) + 0.99999f);
                  if (heightMode == View.MeasureSpec.AT_MOST) {
                      height = Math.min(height, desired);
                  } else {
                      height = desired;
                  }
                  height += mPaddingTop + mPaddingBottom;
              }
          } else {
              if (mAutoSizeTextType != TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE) {
                  mAutoSize = true;
              }
  
          }
  
          setMeasuredDimension(width, height);
      }
  
      //============================= rounding ==============================================
  
      /**
       * Set the corner radius of curvature  as a fraction of the smaller side.
       * For squares 1 will result in a circle
       *
       * @param round the radius of curvature as a fraction of the smaller width
       */
      @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
      public void setRoundPercent(float round) {
          boolean change = (mRoundPercent != round);
          mRoundPercent = round;
          if (mRoundPercent != 0.0f) {
              if (mPath == null) {
                  mPath = new Path();
              }
              if (mRect == null) {
                  mRect = new RectF();
              }
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  if (mViewOutlineProvider == null) {
                      mViewOutlineProvider = new ViewOutlineProvider() {
                          @Override
                          public void getOutline(View view, Outline outline) {
                              int w = getWidth();
                              int h = getHeight();
                              float r = Math.min(w, h) * mRoundPercent / 2;
                              outline.setRoundRect(0, 0, w, h, r);
                          }
                      };
                      setOutlineProvider(mViewOutlineProvider);
                  }
                  setClipToOutline(true);
              }
              int w = getWidth();
              int h = getHeight();
              float r = Math.min(w, h) * mRoundPercent / 2;
              mRect.set(0, 0, w, h);
              mPath.reset();
              mPath.addRoundRect(mRect, r, r, Path.Direction.CW);
          } else {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  setClipToOutline(false);
              }
          }
          if (change) {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  invalidateOutline();
              }
          }
      }
  
      /**
       * Set the corner radius of curvature
       *
       * @param round the radius of curvature  NaN = default meaning roundPercent in effect
       */
      @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
      public void setRound(float round) {
          if (Float.isNaN(round)) {
              mRound = round;
              float tmp = mRoundPercent;
              mRoundPercent = -1;
              setRoundPercent(tmp); // force eval of roundPercent
              return;
          }
          boolean change = (mRound != round);
          mRound = round;
  
          if (mRound != 0.0f) {
              if (mPath == null) {
                  mPath = new Path();
              }
              if (mRect == null) {
                  mRect = new RectF();
              }
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  if (mViewOutlineProvider == null) {
                      mViewOutlineProvider = new ViewOutlineProvider() {
                          @Override
                          public void getOutline(View view, Outline outline) {
                              int w = getWidth();
                              int h = getHeight();
                              outline.setRoundRect(0, 0, w, h, mRound);
                          }
                      };
                      setOutlineProvider(mViewOutlineProvider);
                  }
                  setClipToOutline(true);
  
              }
              int w = getWidth();
              int h = getHeight();
              mRect.set(0, 0, w, h);
              mPath.reset();
              mPath.addRoundRect(mRect, mRound, mRound, Path.Direction.CW);
          } else {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  setClipToOutline(false);
              }
          }
          if (change) {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  invalidateOutline();
              }
          }
  
      }
  
      /**
       * Get the fractional corner radius of curvature.
       *
       * @return Fractional radius of curvature with respect to smallest size
       */
      public float getRoundPercent() {
          return mRoundPercent;
      }
  
      /**
       * Get the corner radius of curvature NaN = RoundPercent in effect.
       *
       * @return Radius of curvature
       */
      public float getRound() {
          return mRound;
      }
      //===========================================================================================
  
      /**
       * set text size
       *
       * @param size the size of the text
       * @see Paint#setTextSize(float)
       */
      public void setTextSize(float size) {
          mTextSize = size;
          mPaint.setTextSize(Float.isNaN(mBaseTextSize) ? size : mBaseTextSize);
          buildShape(Float.isNaN(mBaseTextSize) ? 1.0f : mTextSize / mBaseTextSize);
          requestLayout();
          invalidate();
      }
  
      public int getTextOutlineColor() {
          return mTextOutlineColor;
      }
  
      // ============================ TextureTransformLogic ===============================//
      float mBackgroundPanX = Float.NaN;
      float mBackgroundPanY = Float.NaN;
      float mZoom = Float.NaN;
      float mRotate = Float.NaN;
  
      /**
       * Gets the pan from the center
       * pan of 1 the image is "all the way to the right"
       * if the images width is greater than the screen width,
       * pan = 1 results in the left edge lining up
       * if the images width is less than the screen width,
       * pan = 1 results in the right edges lining up
       * if image width == screen width it does nothing
       *
       * @return the pan in X. Where 0 is centered = Float. NaN if not set
       */
      public float getTextBackgroundPanX() {
          return mBackgroundPanX;
      }
  
      /**
       * gets the pan from the center
       * pan of 1 the image is "all the way to the bottom"
       * if the images width is greater than the screen height,
       * pan = 1 results in the bottom edge lining up
       * if the images width is less than the screen height,
       * pan = 1 results in the top edges lining up
       * if image height == screen height it does nothing
       *
       * @return pan in y. Where 0 is centered NaN if not set
       */
      public float getTextBackgroundPanY() {
          return mBackgroundPanY;
      }
  
      /**
       * gets the zoom where 1 scales the image just enough to fill the view
       *
       * @return the zoom factor
       */
      public float getTextBackgroundZoom() {
          return mZoom;
      }
  
      /**
       * gets the rotation
       *
       * @return the rotation in degrees
       */
      public float getTextBackgroundRotate() {
          return mRotate;
      }
  
      /**
       * sets the pan from the center
       * pan of 1 the image is "all the way to the right"
       * if the images width is greater than the screen width,
       * pan = 1 results in the left edge lining up
       * if the images width is less than the screen width,
       * pan = 1 results in the right edges lining up
       * if image width == screen width it does nothing
       *
       * @param pan sets the pan in X. Where 0 is centered
       */
      public void setTextBackgroundPanX(float pan) {
          mBackgroundPanX = pan;
          updateShaderMatrix();
          invalidate();
      }
  
      /**
       * sets the pan from the center
       * pan of 1 the image is "all the way to the bottom"
       * if the images width is greater than the screen height,
       * pan = 1 results in the bottom edge lining up
       * if the images width is less than the screen height,
       * pan = 1 results in the top edges lining up
       * if image height == screen height it does nothing
       *
       * @param pan sets the pan in X. Where 0 is centered
       */
      public void setTextBackgroundPanY(float pan) {
          mBackgroundPanY = pan;
          updateShaderMatrix();
          invalidate();
      }
  
      /**
       * sets the zoom where 1 scales the image just enough to fill the view
       *
       * @param zoom the zoom factor
       */
      public void setTextBackgroundZoom(float zoom) {
          mZoom = zoom;
          updateShaderMatrix();
          invalidate();
      }
  
      /**
       * sets the rotation angle of the image in degrees
       *
       * @param rotation angle in degrees
       */
      public void setTextBackgroundRotate(float rotation) {
          mRotate = rotation;
          updateShaderMatrix();
          invalidate();
      }
  
      private void updateShaderMatrix() {
          float panX = Float.isNaN(mBackgroundPanX) ? 0 : mBackgroundPanX;
          float panY = Float.isNaN(mBackgroundPanY) ? 0 : mBackgroundPanY;
          float zoom = Float.isNaN(mZoom) ? 1 : mZoom;
          float rota = Float.isNaN(mRotate) ? 0 : mRotate;
  
          mTextShaderMatrix.reset();
          float iw = mTextBackgroundBitmap.getWidth();
          float ih = mTextBackgroundBitmap.getHeight();
          float sw = Float.isNaN(mTextureWidth) ? mFloatWidth : mTextureWidth;
          float sh = Float.isNaN(mTextureHeight) ? mFloatHeight : mTextureHeight;
  
          float scale = zoom * ((iw * sh < ih * sw) ? sw / iw : sh / ih);
          mTextShaderMatrix.postScale(scale, scale);
          float gapx = sw - scale * iw;
          float gapy = sh - scale * ih;
          if (!Float.isNaN(mTextureHeight)) {
              gapy = mTextureHeight / 2;
          }
          if (!Float.isNaN(mTextureWidth)) {
              gapx = mTextureWidth / 2;
          }
          float tx = 0.5f * (panX * gapx + sw - (scale * iw));
          float ty = 0.5f * (panY * gapy + sh - (scale * ih));
  
          mTextShaderMatrix.postTranslate(tx, ty);
          mTextShaderMatrix.postRotate(rota, sw / 2, sh / 2);
          mTextShader.setLocalMatrix(mTextShaderMatrix);
      }
  
      /**
       * Pan the Texture in the text in the x axis.
       *
       * @return pan of the Text -1 = left 0 = center +1 = right
       */
      public float getTextPanX() {
          return mTextPanX;
      }
  
      /**
       * Pan the Texture in the text in the x axis.
       *
       * @param textPanX pan of the Text -1 = left 0 = center +1 = right
       */
      public void setTextPanX(float textPanX) {
          mTextPanX = textPanX;
          invalidate();
      }
  
      /**
       * Pan the Texture in the text in the y axis.
       *
       * @return the pan value 0 being centered in the center of screen.
       */
      public float getTextPanY() {
          return mTextPanY;
      }
  
      /**
       * Pan the Texture in the text in the y axis.
       *
       * @param textPanY pan of the Text -1 = top 0 = center +1 = bottom
       */
      public void setTextPanY(float textPanY) {
          mTextPanY = textPanY;
          invalidate();
      }
  
      /**
       * Pan the Texture in the text in the y axis.
       *
       * @return pan of the Text -1 = top 0 = center +1 = bottom
       */
      public float getTextureHeight() {
          return mTextureHeight;
      }
  
      /**
       * set the height of the texture. Setting Float.NaN is the default Use the view size.
       *
       * @param mTextureHeight the height of the texture
       */
      public void setTextureHeight(float mTextureHeight) {
          this.mTextureHeight = mTextureHeight;
          updateShaderMatrix();
          invalidate();
      }
  
      /**
       * get the width of the texture. Setting Float.NaN is the default Use the view size.
       *
       * @return the width of the texture
       */
      public float getTextureWidth() {
          return mTextureWidth;
      }
  
      /**
       * set the width of the texture. Setting Float.NaN is the default Use the view size
       *
       * @param mTextureWidth set the width of the texture Float.NaN clears setting
       */
      public void setTextureWidth(float mTextureWidth) {
          this.mTextureWidth = mTextureWidth;
          updateShaderMatrix();
          invalidate();
      }
  
      /**
       * if set the font is rendered to polygons at this size and then scaled to the size set by
       * textSize.
       *
       * @return size to pre render font or NaN if not used.
       */
      public float getScaleFromTextSize() {
          return mBaseTextSize;
      }
  
      /**
       * if set the font is rendered to polygons at this size and then scaled to the size set by
       * textSize.
       * This allows smooth efficient animation of fonts size.
       *
       * @param size the size to pre render the font or NaN if not used.
       */
      public void setScaleFromTextSize(float size) {
          this.mBaseTextSize = size;
      }
  
  }
  