content: package com.jingewenku.abrahamcaijin.commonutil;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.graphics.*;
  import android.graphics.drawable.BitmapDrawable;
  import android.graphics.drawable.Drawable;
  import android.net.Uri;
  import android.support.annotation.*;
  import android.support.v4.content.ContextCompat;
  import android.text.Layout;
  import android.text.Layout.Alignment;
  import android.text.SpannableStringBuilder;
  import android.text.Spanned;
  import android.text.TextPaint;
  import android.text.style.*;
  import android.util.Log;
  import com.jingewenku.abrahamcaijin.commonutil.application.AppUtils;
  
  import java.io.InputStream;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.ref.WeakReference;
  
  /**
   * @Description:主要功能:用于设置文字的前景色、背景色、Typeface、粗体、斜体、字号、超链接、删除线、下划线、上下标等
   * @Prject: CommonUtilLibrary
   * @Package: com.jingewenku.abrahamcaijin.commonutil
   * @author: AbrahamCaiJin
   * @date: 2017年05月19日 13:24
   * @Copyright: 个人版权所有
   * @Company:
   * @version: 1.0.0
   */
  
  public class SpannableStringUtils {
      private SpannableStringUtils() {
          throw new UnsupportedOperationException("u can't instantiate me...");
      }
  
      public static final int ALIGN_BOTTOM = 0;
  
      public static final int ALIGN_BASELINE = 1;
  
      public static final int ALIGN_CENTER = 2;
  
      public static final int ALIGN_TOP = 3;
  
      @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
      @Retention(RetentionPolicy.SOURCE)
      public @interface Align {
      }
  
      private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  
      public static class Builder {
  
          private int defaultValue = 0x12000000;
  
          private CharSequence text;
          private int flag;
          @ColorInt
          private int foregroundColor;
          @ColorInt
          private int backgroundColor;
          @ColorInt
          private int quoteColor;
          private int stripeWidth;
          private int quoteGapWidth;
          private boolean isLeadingMargin;
          private int first;
          private int rest;
          private int margin;
          private boolean isBullet;
          private int bulletColor;
          private int bulletRadius;
          private int bulletGapWidth;
          private int fontSize;
          private boolean fontSizeIsDp;
          private float proportion;
          private float xProportion;
          private boolean isStrikethrough;
          private boolean isUnderline;
          private boolean isSuperscript;
          private boolean isSubscript;
          private boolean isBold;
          private boolean isItalic;
          private boolean isBoldItalic;
          private String fontFamily;
          private Typeface typeface;
          private Alignment alignment;
          private boolean imageIsBitmap;
          private Bitmap bitmap;
          private boolean imageIsDrawable;
          private Drawable drawable;
          private boolean imageIsUri;
          private Uri uri;
          private boolean imageIsResourceId;
          @DrawableRes
          private int resourceId;
          @Align
          int align;
  
          private ClickableSpan clickSpan;
          private String url;
  
          private boolean isBlur;
          private float blurRadius;
          private BlurMaskFilter.Blur style;
  
          private SpannableStringBuilder mBuilder;
  
          public Builder() {
              flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
              foregroundColor = defaultValue;
              backgroundColor = defaultValue;
              quoteColor = defaultValue;
              margin = -1;
              fontSize = -1;
              proportion = -1;
              xProportion = -1;
              align = ALIGN_BOTTOM;
              mBuilder = new SpannableStringBuilder();
          }
  
          /**
           * 设置标识
           *
           * @param flag <ul>
           *             <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li>
           *             <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li>
           *             <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li>
           *             <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li>
           *             </ul>
           * @return {@link Builder}
           */
          public Builder setFlag(int flag) {
              this.flag = flag;
              return this;
          }
  
          /**
           * 设置前景色
           *
           * @param color 前景色
           * @return {@link Builder}
           */
          public Builder setForegroundColor(@ColorInt int color) {
              this.foregroundColor = color;
              return this;
          }
  
          /**
           * 设置背景色
           *
           * @param color 背景色
           * @return {@link Builder}
           */
          public Builder setBackgroundColor(@ColorInt int color) {
              this.backgroundColor = color;
              return this;
          }
  
          /**
           * 设置引用线的颜色
           *
           * @param color 引用线的颜色
           * @return {@link Builder}
           */
          public Builder setQuoteColor(@ColorInt int color) {
              this.quoteColor = color;
              this.stripeWidth = 2;
              this.quoteGapWidth = 2;
              return this;
          }
  
          /**
           * 设置引用线的颜色
           *
           * @param color         引用线的颜色
           * @param stripeWidth   引用线线宽
           * @param quoteGapWidth 引用线和文字间距
           * @return {@link Builder}
           */
          public Builder setQuoteColor(@ColorInt int color, int stripeWidth, int quoteGapWidth) {
              this.quoteColor = color;
              this.stripeWidth = stripeWidth;
              this.quoteGapWidth = quoteGapWidth;
              return this;
          }
  
          /**
           * 设置缩进
           *
           * @param first 首行缩进
           * @param rest  剩余行缩进
           * @return {@link Builder}
           */
          public Builder setLeadingMargin(int first, int rest) {
              this.first = first;
              this.rest = rest;
              isLeadingMargin = true;
              return this;
          }
  
          /**
           * 设置间距
           *
           * @param margin 间距
           * @return {@link Builder}
           */
          public Builder setMargin(int margin) {
              this.margin = margin;
              this.text = " " + this.text;
              return this;
          }
  
          /**
           * 设置列表标记
           *
           * @param gapWidth 列表标记和文字间距离
           * @return {@link Builder}
           */
          public Builder setBullet(@ColorInt int gapWidth) {
              this.bulletColor = 0;
              this.bulletRadius = 3;
              this.bulletGapWidth = gapWidth;
              isBullet = true;
              return this;
          }
  
          /**
           * 设置列表标记
           *
           * @param color    列表标记的颜色
           * @param radius   列表标记颜色
           * @param gapWidth 列表标记和文字间距离
           * @return {@link Builder}
           */
          public Builder setBullet(@ColorInt int color, int radius, int gapWidth) {
              this.bulletColor = color;
              this.bulletRadius = radius;
              this.bulletGapWidth = gapWidth;
              isBullet = true;
              return this;
          }
  
          /**
           * 设置字体尺寸
           *
           * @param size 尺寸
           * @return {@link Builder}
           */
          public Builder setFontSize(int size) {
              this.fontSize = size;
              this.fontSizeIsDp = false;
              return this;
          }
  
          /**
           * 设置字体尺寸
           *
           * @param size 尺寸
           * @param isDp 是否使用dip
           * @return {@link Builder}
           */
          public Builder setFontSize(int size, boolean isDp) {
              this.fontSize = size;
              this.fontSizeIsDp = isDp;
              return this;
          }
  
          /**
           * 设置字体比例
           *
           * @param proportion 比例
           * @return {@link Builder}
           */
          public Builder setFontProportion(float proportion) {
              this.proportion = proportion;
              return this;
          }
  
          /**
           * 设置字体横向比例
           *
           * @param proportion 比例
           * @return {@link Builder}
           */
          public Builder setFontXProportion(float proportion) {
              this.xProportion = proportion;
              return this;
          }
  
          /**
           * 设置删除线
           *
           * @return {@link Builder}
           */
          public Builder setStrikethrough() {
              this.isStrikethrough = true;
              return this;
          }
  
          /**
           * 设置下划线
           *
           * @return {@link Builder}
           */
          public Builder setUnderline() {
              this.isUnderline = true;
              return this;
          }
  
          /**
           * 设置上标
           *
           * @return {@link Builder}
           */
          public Builder setSuperscript() {
              this.isSuperscript = true;
              return this;
          }
  
          /**
           * 设置下标
           *
           * @return {@link Builder}
           */
          public Builder setSubscript() {
              this.isSubscript = true;
              return this;
          }
  
          /**
           * 设置粗体
           *
           * @return {@link Builder}
           */
          public Builder setBold() {
              isBold = true;
              return this;
          }
  
          /**
           * 设置斜体
           *
           * @return {@link Builder}
           */
          public Builder setItalic() {
              isItalic = true;
              return this;
          }
  
          /**
           * 设置粗斜体
           *
           * @return {@link Builder}
           */
          public Builder setBoldItalic() {
              isBoldItalic = true;
              return this;
          }
  
          /**
           * 设置字体系列
           *
           * @param fontFamily 字体系列
           *                   <ul>
           *                   <li>monospace</li>
           *                   <li>serif</li>
           *                   <li>sans-serif</li>
           *                   </ul>
           * @return {@link Builder}
           */
          public Builder setFontFamily(@NonNull String fontFamily) {
              this.fontFamily = fontFamily;
              return this;
          }
  
          /**
           * 设置字体
           *
           * @param typeface 字体
           * @return {@link Builder}
           */
          public Builder setTypeface(@NonNull Typeface typeface) {
              this.typeface = typeface;
              return this;
          }
  
          /**
           * 设置对齐
           *
           * @param alignment 对其方式
           *                  <ul>
           *                  <li>{@link Alignment#ALIGN_NORMAL}正常</li>
           *                  <li>{@link Alignment#ALIGN_OPPOSITE}相反</li>
           *                  <li>{@link Alignment#ALIGN_CENTER}居中</li>
           *                  </ul>
           * @return {@link Builder}
           */
          public Builder setAlign(@NonNull Alignment alignment) {
              this.alignment = alignment;
              return this;
          }
  
          /**
           * 设置图片
           *
           * @param bitmap 图片位图
           * @return {@link Builder}
           */
          public Builder setBitmap(@NonNull Bitmap bitmap) {
              return setBitmap(bitmap, align);
          }
  
          /**
           * 设置图片
           *
           * @param bitmap 图片位图
           * @param align  对齐
           *               <ul>
           *               <li>{@link Align#ALIGN_TOP}顶部对齐</li>
           *               <li>{@link Align#ALIGN_CENTER}居中对齐</li>
           *               <li>{@link Align#ALIGN_BASELINE}基线对齐</li>
           *               <li>{@link Align#ALIGN_BOTTOM}底部对齐</li>
           *               </ul>
           * @return {@link Builder}
           */
          public Builder setBitmap(@NonNull Bitmap bitmap, @Align int align) {
              this.bitmap = bitmap;
              this.align = align;
              this.text = " " + this.text;
              imageIsBitmap = true;
              return this;
          }
  
          /**
           * 设置图片
           *
           * @param drawable 图片资源
           * @return {@link Builder}
           */
          public Builder setDrawable(@NonNull Drawable drawable) {
              return setDrawable(drawable, align);
          }
  
          /**
           * 设置图片
           *
           * @param drawable 图片资源
           * @param align    对齐
           *                 <ul>
           *                 <li>{@link Align#ALIGN_TOP}顶部对齐</li>
           *                 <li>{@link Align#ALIGN_CENTER}居中对齐</li>
           *                 <li>{@link Align#ALIGN_BASELINE}基线对齐</li>
           *                 <li>{@link Align#ALIGN_BOTTOM}底部对齐</li>
           *                 </ul>
           * @return {@link Builder}
           */
          public Builder setDrawable(@NonNull Drawable drawable, @Align int align) {
              this.drawable = drawable;
              this.align = align;
              this.text = " " + this.text;
              imageIsDrawable = true;
              return this;
          }
  
          /**
           * 设置图片
           *
           * @param uri 图片uri
           * @return {@link Builder}
           */
          public Builder setUri(@NonNull Uri uri) {
              setUri(uri, ALIGN_BOTTOM);
              return this;
          }
  
          /**
           * 设置图片
           *
           * @param uri   图片uri
           * @param align 对齐
           *              <ul>
           *              <li>{@link Align#ALIGN_TOP}顶部对齐</li>
           *              <li>{@link Align#ALIGN_CENTER}居中对齐</li>
           *              <li>{@link Align#ALIGN_BASELINE}基线对齐</li>
           *              <li>{@link Align#ALIGN_BOTTOM}底部对齐</li>
           *              </ul>
           * @return {@link Builder}
           */
          public Builder setUri(@NonNull Uri uri, @Align int align) {
              this.uri = uri;
              this.align = align;
              this.text = " " + this.text;
              imageIsUri = true;
              return this;
          }
  
          /**
           * 设置图片
           *
           * @param resourceId 图片资源id
           * @return {@link Builder}
           */
          public Builder setResourceId(@DrawableRes int resourceId) {
              return setResourceId(resourceId, align);
          }
  
          /**
           * 设置图片
           *
           * @param resourceId 图片资源id
           * @param align      对齐
           * @return {@link Builder}
           */
          public Builder setResourceId(@DrawableRes int resourceId, @Align int align) {
              this.resourceId = resourceId;
              this.align = align;
              this.text = " " + this.text;
              imageIsResourceId = true;
              return this;
          }
  
          /**
           * 设置点击事件
           * <p>需添加view.setMovementMethod(LinkMovementMethod.getInstance())</p>
           *
           * @param clickSpan 点击事件
           * @return {@link Builder}
           */
          public Builder setClickSpan(@NonNull ClickableSpan clickSpan) {
              this.clickSpan = clickSpan;
              return this;
          }
  
          /**
           * 设置超链接
           * <p>需添加view.setMovementMethod(LinkMovementMethod.getInstance())</p>
           *
           * @param url 超链接
           * @return {@link Builder}
           */
          public Builder setUrl(@NonNull String url) {
              this.url = url;
              return this;
          }
  
          /**
           * 设置模糊
           * <p>尚存bug，其他地方存在相同的字体的话，相同字体出现在之前的话那么就不会模糊，出现在之后的话那会一起模糊</p>
           * <p>推荐还是把所有字体都模糊这样使用</p>
           *
           * @param radius 模糊半径（需大于0）
           * @param style  模糊样式<ul>
           *               <li>{@link BlurMaskFilter.Blur#NORMAL}</li>
           *               <li>{@link BlurMaskFilter.Blur#SOLID}</li>
           *               <li>{@link BlurMaskFilter.Blur#OUTER}</li>
           *               <li>{@link BlurMaskFilter.Blur#INNER}</li>
           *               </ul>
           * @return {@link Builder}
           */
          public Builder setBlur(float radius, BlurMaskFilter.Blur style) {
              this.blurRadius = radius;
              this.style = style;
              this.isBlur = true;
              return this;
          }
  
          /**
           * 追加样式一行字符串
           *
           * @param text 样式字符串文本
           * @return {@link Builder}
           */
          public Builder appendLine(@NonNull CharSequence text) {
              return append(text + LINE_SEPARATOR);
          }
  
          /**
           * 追加样式字符串
           *
           * @param text 样式字符串文本
           * @return {@link Builder}
           */
          public Builder append(@NonNull CharSequence text) {
              setSpan();
              this.text = text;
              return this;
          }
  
          /**
           * 创建样式字符串
           *
           * @return 样式字符串
           */
          public SpannableStringBuilder create() {
              setSpan();
              return mBuilder;
          }
  
          /**
           * 设置样式
           */
          private void setSpan() {
              if (text == null || text.length() == 0) return;
              int start = mBuilder.length();
              mBuilder.append(this.text);
              int end = mBuilder.length();
              if (backgroundColor != defaultValue) {
                  mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
                  backgroundColor = defaultValue;
              }
              if (foregroundColor != defaultValue) {
                  mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
                  foregroundColor = defaultValue;
              }
              if (isLeadingMargin) {
                  mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
                  isLeadingMargin = false;
              }
              if (margin != -1) {
                  mBuilder.setSpan(new MarginSpan(margin), start, end, flag);
                  margin = -1;
              }
              if (quoteColor != defaultValue) {
                  mBuilder.setSpan(new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth), start, end, flag);
                  quoteColor = defaultValue;
              }
              if (isBullet) {
                  mBuilder.setSpan(new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth), start, end, flag);
                  isBullet = false;
              }
              if (fontSize != -1) {
                  mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);
                  fontSize = -1;
                  fontSizeIsDp = false;
              }
              if (proportion != -1) {
                  mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
                  proportion = -1;
              }
              if (xProportion != -1) {
                  mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
                  xProportion = -1;
              }
              if (isStrikethrough) {
                  mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
                  isStrikethrough = false;
              }
              if (isUnderline) {
                  mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
                  isUnderline = false;
              }
              if (isSuperscript) {
                  mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
                  isSuperscript = false;
              }
              if (isSubscript) {
                  mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
                  isSubscript = false;
              }
              if (isBold) {
                  mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
                  isBold = false;
              }
              if (isItalic) {
                  mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
                  isItalic = false;
              }
              if (isBoldItalic) {
                  mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
                  isBoldItalic = false;
              }
              if (fontFamily != null) {
                  mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
                  fontFamily = null;
              }
              if (typeface != null) {
                  mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
                  typeface = null;
              }
              if (alignment != null) {
                  mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
                  alignment = null;
              }
              if (imageIsBitmap || imageIsDrawable || imageIsUri || imageIsResourceId) {
                  if (imageIsBitmap) {
                      mBuilder.setSpan(new CustomImageSpan(AppUtils.getContext(), bitmap, align), start, end, flag);
                      bitmap = null;
                      imageIsBitmap = false;
                  } else if (imageIsDrawable) {
                      mBuilder.setSpan(new CustomImageSpan(drawable, align), start, end, flag);
                      drawable = null;
                      imageIsDrawable = false;
                  } else if (imageIsUri) {
                      mBuilder.setSpan(new CustomImageSpan(AppUtils.getContext(), uri, align), start, end, flag);
                      uri = null;
                      imageIsUri = false;
                  } else {
                      mBuilder.setSpan(new CustomImageSpan(AppUtils.getContext(), resourceId, align), start, end, flag);
                      resourceId = 0;
                      imageIsResourceId = false;
                  }
              }
              if (clickSpan != null) {
                  mBuilder.setSpan(clickSpan, start, end, flag);
                  clickSpan = null;
              }
              if (url != null) {
                  mBuilder.setSpan(new URLSpan(url), start, end, flag);
                  url = null;
              }
              if (isBlur) {
                  mBuilder.setSpan(new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)), start, end, flag);
                  isBlur = false;
              }
              flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
          }
      }
  
      static class MarginSpan extends ReplacementSpan {
  
          private final int margin;
  
          private MarginSpan(int margin) {
              super();
              this.margin = margin;
          }
  
          @Override
          public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) {
              text = " ";
              return margin;
          }
  
          @Override
          public void draw(@NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
  
          }
      }
  
      static class CustomQuoteSpan implements LeadingMarginSpan {
  
          private final int color;
          private final int stripeWidth;
          private final int gapWidth;
  
          private CustomQuoteSpan(@ColorInt int color, int stripeWidth, int gapWidth) {
              super();
              this.color = color;
              this.stripeWidth = stripeWidth;
              this.gapWidth = gapWidth;
          }
  
          public int getLeadingMargin(boolean first) {
              return stripeWidth + gapWidth;
          }
  
          public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
              int top, int baseline, int bottom,
              CharSequence text, int start, int end,
              boolean first, Layout layout) {
              Paint.Style style = p.getStyle();
              int color = p.getColor();
  
              p.setStyle(Paint.Style.FILL);
              p.setColor(this.color);
  
              c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
  
              p.setStyle(style);
              p.setColor(color);
          }
      }
  
      static class CustomBulletSpan implements LeadingMarginSpan {
  
          private final int color;
          private final int radius;
          private final int gapWidth;
  
          private static Path sBulletPath = null;
  
          private CustomBulletSpan(int color, int radius, int gapWidth) {
              this.color = color;
              this.radius = radius;
              this.gapWidth = gapWidth;
          }
  
          public int getLeadingMargin(boolean first) {
              return 2 * radius + gapWidth;
          }
  
          public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
              int top, int baseline, int bottom,
              CharSequence text, int start, int end,
              boolean first, Layout l) {
              if (((Spanned) text).getSpanStart(this) == start) {
                  Paint.Style style = p.getStyle();
                  int oldColor = 0;
                  oldColor = p.getColor();
                  p.setColor(color);
                  p.setStyle(Paint.Style.FILL);
                  if (c.isHardwareAccelerated()) {
                      if (sBulletPath == null) {
                          sBulletPath = new Path();
                          // Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
                          sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
                      }
                      c.save();
                      c.translate(x + dir * radius, (top + bottom) / 2.0f);
                      c.drawPath(sBulletPath, p);
                      c.restore();
                  } else {
                      c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
                  }
                  p.setColor(oldColor);
                  p.setStyle(style);
              }
          }
      }
  
      @SuppressLint("ParcelCreator")
      static class CustomTypefaceSpan extends TypefaceSpan {
  
          private final Typeface newType;
  
          private CustomTypefaceSpan(Typeface type) {
              super("");
              newType = type;
          }
  
          @Override
          public void updateDrawState(TextPaint textPaint) {
              apply(textPaint, newType);
          }
  
          @Override
          public void updateMeasureState(TextPaint paint) {
              apply(paint, newType);
          }
  
          private static void apply(Paint paint, Typeface tf) {
              int oldStyle;
              Typeface old = paint.getTypeface();
              if (old == null) {
                  oldStyle = 0;
              } else {
                  oldStyle = old.getStyle();
              }
  
              int fake = oldStyle & ~tf.getStyle();
              if ((fake & Typeface.BOLD) != 0) {
                  paint.setFakeBoldText(true);
              }
  
              if ((fake & Typeface.ITALIC) != 0) {
                  paint.setTextSkewX(-0.25f);
              }
  
              paint.setTypeface(tf);
          }
      }
  
      static class CustomImageSpan extends CustomDynamicDrawableSpan {
          private Drawable mDrawable;
          private Uri mContentUri;
          private int mResourceId;
          private Context mContext;
  
          CustomImageSpan(Context context, Bitmap b, int verticalAlignment) {
              super(verticalAlignment);
              mContext = context;
              mDrawable = context != null
                  ? new BitmapDrawable(context.getResources(), b)
                  : new BitmapDrawable(b);
              int width = mDrawable.getIntrinsicWidth();
              int height = mDrawable.getIntrinsicHeight();
              mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0);
          }
  
          CustomImageSpan(Drawable d, int verticalAlignment) {
              super(verticalAlignment);
              mDrawable = d;
              mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(),
                  mDrawable.getIntrinsicHeight());
          }
  
          CustomImageSpan(Context context, Uri uri, int verticalAlignment) {
              super(verticalAlignment);
              mContext = context;
              mContentUri = uri;
          }
  
          CustomImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) {
              super(verticalAlignment);
              mContext = context;
              mResourceId = resourceId;
          }
  
          @Override
          public Drawable getDrawable() {
              Drawable drawable = null;
              if (mDrawable != null) {
                  drawable = mDrawable;
              } else if (mContentUri != null) {
                  Bitmap bitmap = null;
                  try {
                      InputStream is = mContext.getContentResolver().openInputStream(
                          mContentUri);
                      bitmap = BitmapFactory.decodeStream(is);
                      drawable = new BitmapDrawable(mContext.getResources(), bitmap);
                      drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
                          drawable.getIntrinsicHeight());
                      if (is != null) {
                          is.close();
                      }
                  } catch (Exception e) {
                      Log.e("sms", "Failed to loaded content " + mContentUri, e);
                  }
              } else {
                  try {
                      drawable = ContextCompat.getDrawable(mContext, mResourceId);
                      drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
                          drawable.getIntrinsicHeight());
                  } catch (Exception e) {
                      Log.e("sms", "Unable to find resource: " + mResourceId);
                  }
              }
              return drawable;
          }
      }
  
      static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
  
          static final int ALIGN_BOTTOM = 0;
  
          static final int ALIGN_BASELINE = 1;
  
          static final int ALIGN_CENTER = 2;
  
          static final int ALIGN_TOP = 3;
  
          final int mVerticalAlignment;
  
          CustomDynamicDrawableSpan() {
              mVerticalAlignment = ALIGN_BOTTOM;
          }
  
          CustomDynamicDrawableSpan(int verticalAlignment) {
              mVerticalAlignment = verticalAlignment;
          }
  
          public abstract Drawable getDrawable();
  
          @Override
          public int getSize(@NonNull Paint paint, CharSequence text,
              int start, int end,
              Paint.FontMetricsInt fm) {
              Drawable d = getCachedDrawable();
              Rect rect = d.getBounds();
              final int fontHeight = (int) (paint.getFontMetrics().descent - paint.getFontMetrics().ascent);
              if (fm != null) { // this is the fucking code which I waste 3 days
                  if (rect.height() > fontHeight) {
                      if (mVerticalAlignment == ALIGN_TOP) {
                          fm.descent += rect.height() - fontHeight;
                      } else if (mVerticalAlignment == ALIGN_CENTER) {
                          fm.ascent -= (rect.height() - fontHeight) / 2;
                          fm.descent += (rect.height() - fontHeight) / 2;
                      } else if (mVerticalAlignment == ALIGN_BASELINE) {
                          fm.ascent -= rect.height() - fontHeight + fm.descent;
                      } else {
                          fm.ascent -= rect.height() - fontHeight;
                      }
                  }
              }
  
              return rect.right;
          }
  
          @Override
          public void draw(@NonNull Canvas canvas, CharSequence text,
              int start, int end, float x,
              int top, int y, int bottom, @NonNull Paint paint) {
              Drawable d = getCachedDrawable();
              Rect rect = d.getBounds();
              canvas.save();
              final float fontHeight = paint.getFontMetrics().descent - paint.getFontMetrics().ascent;
              int transY = bottom - rect.bottom;
              if (rect.height() < fontHeight) { // this is the fucking code which I waste 3 days
                  if (mVerticalAlignment == ALIGN_BASELINE) {
                      transY -= paint.getFontMetricsInt().descent;
                  } else if (mVerticalAlignment == ALIGN_CENTER) {
                      transY -= (fontHeight - rect.height()) / 2;
                  } else if (mVerticalAlignment == ALIGN_TOP) {
                      transY -= fontHeight - rect.height();
                  }
              } else {
                  if (mVerticalAlignment == ALIGN_BASELINE) {
                      transY -= paint.getFontMetricsInt().descent;
                  }
              }
              canvas.translate(x, transY);
              d.draw(canvas);
              canvas.restore();
          }
  
          private Drawable getCachedDrawable() {
              WeakReference<Drawable> wr = mDrawableRef;
              Drawable d = null;
              if (wr != null)
                  d = wr.get();
              if (d == null) {
                  d = getDrawable();
                  mDrawableRef = new WeakReference<>(d);
              }
              return getDrawable();
          }
          private WeakReference<Drawable> mDrawableRef;
      }
  }
  