From c928750ff08f18fb9c305756b1c82bd31cd5e864 Mon Sep 17 00:00:00 2001 From: chengjunzhang61 Date: Wed, 8 Dec 2021 09:59:36 -0500 Subject: [PATCH] Create additional spans --- markwon-ext-spans/.gitignore | 1 + markwon-ext-spans/build.gradle | 37 ++++ markwon-ext-spans/consumer-rules.pro | 0 markwon-ext-spans/proguard-rules.pro | 21 ++ .../markwon/ext/ExampleInstrumentedTest.java | 26 +++ .../src/main/AndroidManifest.xml | 5 + .../io/noties/markwon/ext/BlockQuoteSpan.java | 82 ++++++++ .../io/noties/markwon/ext/BulletSpan.java | 190 ++++++++++++++++++ .../noties/markwon/ext/CWClickableSpan.java | 28 +++ .../io/noties/markwon/ext/CWDrawableSpan.java | 31 +++ .../noties/markwon/ext/CenteredImageSpan.java | 57 ++++++ .../io/noties/markwon/ext/CodeBlockSpan.java | 133 ++++++++++++ .../noties/markwon/ext/GifDrawableSpan.java | 33 +++ .../io/noties/markwon/ext/HashtagSpan.java | 48 +++++ .../io/noties/markwon/ext/NumberedSpan.java | 60 ++++++ .../io/noties/markwon/ext/ObjectsPool.java | 33 +++ .../noties/markwon/ext/ExampleUnitTest.java | 17 ++ 17 files changed, 802 insertions(+) create mode 100644 markwon-ext-spans/.gitignore create mode 100644 markwon-ext-spans/build.gradle create mode 100644 markwon-ext-spans/consumer-rules.pro create mode 100644 markwon-ext-spans/proguard-rules.pro create mode 100644 markwon-ext-spans/src/androidTest/java/io/noties/markwon/ext/ExampleInstrumentedTest.java create mode 100644 markwon-ext-spans/src/main/AndroidManifest.xml create mode 100755 markwon-ext-spans/src/main/java/io/noties/markwon/ext/BlockQuoteSpan.java create mode 100755 markwon-ext-spans/src/main/java/io/noties/markwon/ext/BulletSpan.java create mode 100644 markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWClickableSpan.java create mode 100755 markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWDrawableSpan.java create mode 100755 markwon-ext-spans/src/main/java/io/noties/markwon/ext/CenteredImageSpan.java create mode 100755 markwon-ext-spans/src/main/java/io/noties/markwon/ext/CodeBlockSpan.java create mode 100755 markwon-ext-spans/src/main/java/io/noties/markwon/ext/GifDrawableSpan.java create mode 100644 markwon-ext-spans/src/main/java/io/noties/markwon/ext/HashtagSpan.java create mode 100644 markwon-ext-spans/src/main/java/io/noties/markwon/ext/NumberedSpan.java create mode 100755 markwon-ext-spans/src/main/java/io/noties/markwon/ext/ObjectsPool.java create mode 100644 markwon-ext-spans/src/test/java/io/noties/markwon/ext/ExampleUnitTest.java diff --git a/markwon-ext-spans/.gitignore b/markwon-ext-spans/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/markwon-ext-spans/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/markwon-ext-spans/build.gradle b/markwon-ext-spans/build.gradle new file mode 100644 index 00000000..eba181c7 --- /dev/null +++ b/markwon-ext-spans/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 31 + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/markwon-ext-spans/consumer-rules.pro b/markwon-ext-spans/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/markwon-ext-spans/proguard-rules.pro b/markwon-ext-spans/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/markwon-ext-spans/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/markwon-ext-spans/src/androidTest/java/io/noties/markwon/ext/ExampleInstrumentedTest.java b/markwon-ext-spans/src/androidTest/java/io/noties/markwon/ext/ExampleInstrumentedTest.java new file mode 100644 index 00000000..22300fdc --- /dev/null +++ b/markwon-ext-spans/src/androidTest/java/io/noties/markwon/ext/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package io.noties.markwon.ext; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("io.noties.markwon.ext.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/markwon-ext-spans/src/main/AndroidManifest.xml b/markwon-ext-spans/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f9db4d94 --- /dev/null +++ b/markwon-ext-spans/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/BlockQuoteSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/BlockQuoteSpan.java new file mode 100755 index 00000000..eac74282 --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/BlockQuoteSpan.java @@ -0,0 +1,82 @@ +package io.noties.markwon.; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.text.Layout; +import android.text.Spanned; +import android.text.style.LeadingMarginSpan; +import android.text.style.LineHeightSpan; +import io.noties.markwon.utils.ColorUtils; + +public class BlockQuoteSpan implements LeadingMarginSpan, LineHeightSpan { + protected int BLOCK_QUOTE_DEF_COLOR_ALPHA = 100; + protected static final int BLOCK_QUOTE_WIDTH = 10; + public static int BLOCK_QUOTE_MARGIN = 100; + protected int BLOCK_COLOR = Color.LTGRAY; + private final int VERTICAL_SPACING = 20; + private final Rect rect = ObjectsPool.rect(); + private final Paint paint = ObjectsPool.paint(); + + public BlockQuoteSpan() { + + } + + @Override + public int getLeadingMargin(boolean first) { + return BLOCK_QUOTE_MARGIN; + } + + @Override + 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) { + + final int width = BLOCK_QUOTE_WIDTH; + + paint.set(p); + + applyBlockQuoteStyle(paint); + + rect.set(x, top, x + dir * width, bottom); + + c.drawRect(rect, paint); + } + + public void applyBlockQuoteStyle(Paint paint) { + final int color = ColorUtils.applyAlpha(BLOCK_COLOR, BLOCK_QUOTE_DEF_COLOR_ALPHA); + paint.setStyle(Paint.Style.FILL); + paint.setColor(color); + } + + @Override + public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int lineHeight, Paint.FontMetricsInt fm) { + final Spanned txt = (Spanned)text; + final int spanEnd = txt.getSpanEnd(this); + final int spanStart = txt.getSpanStart(this); + + // add top spacing to first line + if (start == spanStart) { + fm.ascent -= VERTICAL_SPACING; + fm.top -= VERTICAL_SPACING; + } + + // add bottom spacing to last line + if (Math.abs(spanEnd - end) <= 1) { + fm.descent += VERTICAL_SPACING; + fm.bottom += VERTICAL_SPACING; + } + } +} + diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/BulletSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/BulletSpan.java new file mode 100755 index 00000000..6607965c --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/BulletSpan.java @@ -0,0 +1,190 @@ +package io.noties.markwon.span.ext; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Parcel; +import android.text.Layout; +import android.text.ParcelableSpan; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.LeadingMarginSpan; +import android.text.style.MetricAffectingSpan; + +import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; + +import com.campuswire.android.messenger.model.THEMETYPE; + + +public class BulletSpan implements LeadingMarginSpan, ParcelableSpan { + // Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices. + private static final int STANDARD_BULLET_RADIUS = 6; + public static final int STANDARD_GAP_WIDTH = 2; + private static final int STANDARD_COLOR = 0; + public static final int BULLET_SPAN = 8; + @Px + private final int mGapWidth; + @Px + private final int mBulletRadius; + private Path mBulletPath = null; + @ColorInt + private final int mColor; + private final boolean mWantColor; + + /** + * Creates a {@link android.text.style.BulletSpan} with the default values. + */ + public BulletSpan() { + this(STANDARD_GAP_WIDTH, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS); + } + + /** + * Creates a {@link android.text.style.BulletSpan} based on a gap width + * + * @param gapWidth the distance, in pixels, between the bullet point and the paragraph. + */ + public BulletSpan(int gapWidth) { + this(gapWidth, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS); + } + + public BulletSpan(int gapWidth, @ColorInt int color) { + this(gapWidth, color, true, STANDARD_BULLET_RADIUS); + } + + public BulletSpan(int gapWidth, @ColorInt int color, @IntRange(from = 0) int bulletRadius) { + this(gapWidth, color, true, bulletRadius); + } + + private BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor, + @IntRange(from = 0) int bulletRadius) { + mGapWidth = gapWidth; + mBulletRadius = bulletRadius; + mColor = color; + mWantColor = wantColor; + } + + public BulletSpan(@NonNull Parcel src) { + mGapWidth = src.readInt(); + mWantColor = src.readInt() != 0; + mColor = src.readInt(); + mBulletRadius = src.readInt(); + } + + @Override + public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + + public int getSpanTypeIdInternal() { + return BULLET_SPAN; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { + dest.writeInt(mGapWidth); + dest.writeInt(mWantColor ? 1 : 0); + dest.writeInt(mColor); + dest.writeInt(mBulletRadius); + } + + @Override + public int getLeadingMargin(boolean first) { + return 2 * mBulletRadius + mGapWidth; + } + + /** + * Get the distance, in pixels, between the bullet point and the paragraph. + * + * @return the distance, in pixels, between the bullet point and the paragraph. + */ + public int getGapWidth() { + return mGapWidth; + } + + /** + * Get the radius, in pixels, of the bullet point. + * + * @return the radius, in pixels, of the bullet point. + */ + public int getBulletRadius() { + return mBulletRadius; + } + + /** + * Get the bullet point color. + * + * @return the bullet point color + */ + public int getColor() { + return mColor; + } + + @Override + public void drawLeadingMargin(@NonNull Canvas canvas, @NonNull Paint paint, int x, int dir, + int top, int baseline, int bottom, + @NonNull CharSequence text, int start, int end, + boolean first, @Nullable Layout layout) { + if (((Spanned) text).getSpanStart(this) == start) { + Paint.Style style = paint.getStyle(); + int oldcolor = 0; + + if (mWantColor) { + oldcolor = paint.getColor(); + paint.setColor(mColor); + } + + paint.setStyle(Paint.Style.FILL); + + if (layout != null) { + // "bottom" position might include extra space as a result of line spacing + // configuration. Subtract extra space in order to show bullet in the vertical + // center of characters. + final int line = layout.getLineForOffset(start); + + bottom = bottom - 0;//layout.getLineExtra(line); + } + + final float yPosition = (top + bottom) / 2f; + final float xPosition = x + dir * mBulletRadius; + + if (canvas.isHardwareAccelerated()) { + if (mBulletPath == null) { + mBulletPath = new Path(); + mBulletPath.addCircle(0.0f, 0.0f, mBulletRadius, Path.Direction.CW); + } + + canvas.save(); + canvas.translate(xPosition, yPosition); + canvas.drawPath(mBulletPath, paint); + canvas.restore(); + } else { + canvas.drawCircle(xPosition, yPosition, mBulletRadius, paint); + } + + if (mWantColor) { + paint.setColor(oldcolor); + } + + paint.setStyle(style); + } + } +} diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWClickableSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWClickableSpan.java new file mode 100644 index 00000000..6e1abb6d --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWClickableSpan.java @@ -0,0 +1,28 @@ +package io.noties.markwon.span.ext; + +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.campuswire.android.messenger.interfaces.MessageItemTapListener; + +public class CWClickableSpan extends ClickableSpan { + String userId; + MessageItemTapListener messageItemTapListener; + public CWClickableSpan(String text, MessageItemTapListener listener) { + super(); + userId = text; + messageItemTapListener = listener; + } + @Override + public void onClick(@NonNull View view) { + messageItemTapListener.onClickUser(userId); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setUnderlineText(false); + } +} diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWDrawableSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWDrawableSpan.java new file mode 100755 index 00000000..125afe0a --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWDrawableSpan.java @@ -0,0 +1,31 @@ +package io.noties.markwon.span.ext; + +import android.graphics.drawable.Drawable; +import android.text.style.DynamicDrawableSpan; + +public class CWDrawableSpan extends DynamicDrawableSpan { + private Drawable drawable; + private int requiredWidth = 0; + private int requiredHeight = 0; + + public CWDrawableSpan(Drawable drawable){ + this.drawable = drawable; + } + + public CWDrawableSpan(Drawable drawable, int requiredWidth, int requiredHeight){ + this.requiredWidth = requiredWidth; + this.requiredHeight = requiredHeight; + this.drawable = drawable; + } + + @Override + public Drawable getDrawable() { +// drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + if (requiredHeight != 0 && requiredWidth != 0) { + drawable.setBounds(0, 0, requiredWidth, requiredHeight); + } else { + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + } + return drawable; + } +} diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CenteredImageSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CenteredImageSpan.java new file mode 100755 index 00000000..e03e24ca --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CenteredImageSpan.java @@ -0,0 +1,57 @@ +package io.noties.markwon.span.ext; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; +import android.text.style.DynamicDrawableSpan; +import android.text.style.ImageSpan; + +import java.lang.ref.WeakReference; + +public class CenteredImageSpan extends ImageSpan { + private WeakReference mDrawableRef; + + public CenteredImageSpan(Context context, final int drawableRes) { + super(context, drawableRes); + } + + public CenteredImageSpan(Drawable drawable) { + super(drawable); + } + + @Override + public int getSize(Paint paint, CharSequence text, int start, int end, + Paint.FontMetricsInt fontMetricsInt) { + Drawable drawable = getDrawable(); + Rect rect = drawable.getBounds(); + if (fontMetricsInt != null) { + Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt(); + int fontHeight = fmPaint.descent - fmPaint.ascent; + int drHeight = rect.bottom - rect.top; + int centerY = fmPaint.ascent + fontHeight / 2; + + fontMetricsInt.ascent = centerY - drHeight / 2; + fontMetricsInt.top = fontMetricsInt.ascent; + fontMetricsInt.bottom = centerY + drHeight / 2; + fontMetricsInt.descent = fontMetricsInt.bottom; + } + return rect.right; + } + @Override + public void draw(Canvas canvas, CharSequence text, int start, int end, + float x, int top, int y, int bottom, Paint paint) { + + Drawable drawable = getDrawable(); + canvas.save(); + Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt(); + int fontHeight = fmPaint.descent - fmPaint.ascent; + int centerY = y + fmPaint.descent - fontHeight / 2; + int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2; + canvas.translate(x, transY); + drawable.draw(canvas); + canvas.restore(); + } +} \ No newline at end of file diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CodeBlockSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CodeBlockSpan.java new file mode 100755 index 00000000..8e39183a --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/CodeBlockSpan.java @@ -0,0 +1,133 @@ +package io.noties.markwon.span.ext; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.LeadingMarginSpan; +import android.text.style.LineHeightSpan; +import android.text.style.MetricAffectingSpan; + +import androidx.annotation.NonNull; + +import com.campuswire.android.messenger.model.THEMETYPE; + +import io.noties.markwon.core.MarkwonTheme; + + +public class CodeBlockSpan extends MetricAffectingSpan implements LeadingMarginSpan, LineHeightSpan { + protected static final float CODE_DEF_TEXT_SIZE_RATIO = 1.0F; + protected static final int DEFAULT_LEADING_MARGIN = 20; + protected int BACKGROUND_COLOR; + protected int TEXT_COLOR; + + protected static final int CORNER_RADIUS = 15; + private final int VERTICAL_SPACING = 40; + + private final Rect rect = ObjectsPool.rect(); + private final Paint paint = ObjectsPool.paint(); + + public CodeBlockSpan(String theme) { + if(theme.equalsIgnoreCase(THEMETYPE.DARK.toString())){ + BACKGROUND_COLOR = Color.argb(255, 25, 26, 27); + TEXT_COLOR = Color.WHITE; + }else{ + BACKGROUND_COLOR = Color.argb(255, 246, 246, 246); + TEXT_COLOR = Color.BLACK; + } + } + + @Override + public void updateMeasureState(TextPaint p) { + apply(p); + } + + @Override + public void updateDrawState(TextPaint ds) { + apply(ds); + } + + private void apply(TextPaint p) { + final int textColor = TEXT_COLOR; + + if (textColor != 0) { + paint.setColor(textColor); + } + + paint.setTypeface(Typeface.MONOSPACE); + + final int textSize = 0; + + if (textSize > 0) { + paint.setTextSize(textSize); + } else { + // calculate default value + paint.setTextSize(paint.getTextSize() * CODE_DEF_TEXT_SIZE_RATIO); + } + } + + @Override + public int getLeadingMargin(boolean first) { + return DEFAULT_LEADING_MARGIN; + } + + @Override + 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.setStyle(Paint.Style.FILL); + paint.setColor(BACKGROUND_COLOR); + + final int left; + final int right; + if (dir > 0) { + left = x; + right = c.getWidth(); + } else { + left = x - c.getWidth(); + right = x; + } + + rect.set(left, top, right, bottom); + + final Spanned txt = (Spanned)text; + final int spanEnd = txt.getSpanEnd(this); + final int spanStart = txt.getSpanStart(this); + + // draw rounded corner background + if (start == spanStart) { + c.drawRoundRect(new RectF(rect), CORNER_RADIUS, CORNER_RADIUS, paint); + c.drawRect(new Rect(left, top + CORNER_RADIUS, right, bottom), paint); + } + else if (Math.abs(spanEnd - end) <= 1) { + c.drawRoundRect(new RectF(rect), CORNER_RADIUS, CORNER_RADIUS, paint); + c.drawRect(new Rect(left, top, right, bottom - CORNER_RADIUS), paint); + } + else { + c.drawRect(rect, paint); + } + } + + @Override + public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int lineHeight, Paint.FontMetricsInt fm) { + final Spanned txt = (Spanned)text; + final int spanEnd = txt.getSpanEnd(this); + final int spanStart = txt.getSpanStart(this); + + // add top spacing to first line + if (start == spanStart) { + fm.ascent -= VERTICAL_SPACING; + fm.top -= VERTICAL_SPACING; + } + + // add bottom spacing to last line + if (Math.abs(spanEnd - end) <= 1) { + fm.descent += VERTICAL_SPACING; + fm.bottom += VERTICAL_SPACING; + } + } +} diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/GifDrawableSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/GifDrawableSpan.java new file mode 100755 index 00000000..817d157c --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/GifDrawableSpan.java @@ -0,0 +1,33 @@ +package io.noties.markwon.span.ext; + +import android.graphics.drawable.Drawable; +import android.text.style.DynamicDrawableSpan; + +import com.bumptech.glide.load.resource.gif.GifDrawable; + +public class GifDrawableSpan extends DynamicDrawableSpan { + private GifDrawable drawable; + private int requiredWidth = 0; + private int requiredHeight = 0; + + public GifDrawableSpan(GifDrawable drawable){ + this.drawable = drawable; + } + + public GifDrawableSpan(GifDrawable drawable, int requiredWidth, int requiredHeight){ + this.requiredWidth = requiredWidth; + this.requiredHeight = requiredHeight; + this.drawable = drawable; + } + + @Override + public Drawable getDrawable() { +// drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + if (requiredHeight != 0 && requiredWidth != 0) { + drawable.setBounds(0, 0, requiredWidth, requiredHeight); + } else { + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + } + return drawable; + } +} diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/HashtagSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/HashtagSpan.java new file mode 100644 index 00000000..eef67043 --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/HashtagSpan.java @@ -0,0 +1,48 @@ +package io.noties.markwon.span.ext; + +import android.text.TextPaint; +import android.text.style.URLSpan; +import android.view.View; + +import androidx.annotation.NonNull; + +import io.noties.markwon.LinkResolver; + +public class HashtagSpan extends URLSpan { + private final String link; + private final LinkResolver resolver; + private final int linkColor; + + public HashtagSpan( + @NonNull String link, + @NonNull int linkColor, + @NonNull LinkResolver resolver) { + super(link); + this.link = link; + this.linkColor = linkColor; + this.resolver = resolver; + } + + @Override + public void onClick(View widget) { + resolver.resolve(widget, link); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + applyLinkStyle(ds); + } + + private void applyLinkStyle(TextPaint paint){ + paint.setUnderlineText(true); + paint.setColor(linkColor); + } + + /** + * @since 4.2.0 + */ + @NonNull + public String getLink() { + return link; + } +} diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/NumberedSpan.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/NumberedSpan.java new file mode 100644 index 00000000..4bd43c76 --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/NumberedSpan.java @@ -0,0 +1,60 @@ +package io.noties.markwon.span.ext; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.text.Layout; +import android.text.Spanned; +import android.text.style.LeadingMarginSpan; + +import com.campuswire.android.messenger.model.THEMETYPE; + +class NumberedSpan implements LeadingMarginSpan { + private final int NUMBER_GAP = 14; + private final int mIndex; + private final String mTheme; + private final Paint paint = ObjectsPool.paint(); + + NumberedSpan(int index, String theme) { + mIndex = index; + mTheme = theme; + } + + @Override + public int getLeadingMargin(boolean first) { + final String numText = mIndex + "."; + final Rect textBounds = new Rect(); + paint.getTextBounds(numText, 0, numText.length(), textBounds); + return textBounds.width() + NUMBER_GAP; + } + + @Override + public void drawLeadingMargin(Canvas canvas, Paint paint, int x, int dir, int top, int baseline, + int bottom, CharSequence text, int start, int end, boolean first, + Layout layout) { + // add number to first line + if (((Spanned)text).getSpanStart(this) == start) { + // save previous paint values + final Paint.Style prevStyle = paint.getStyle(); + final int prevColor = paint.getColor(); + + // draw number + paint.setStyle(Paint.Style.FILL); + if(mTheme.equalsIgnoreCase(THEMETYPE.DARK.toString())){ + paint.setColor(Color.WHITE); + }else{ + paint.setColor(Color.BLACK); + } + final String numText = mIndex + "."; + final Rect textBounds = new Rect(); + paint.getTextBounds(numText, 0, numText.length(), textBounds); + final float yVal = (top + bottom + textBounds.height()) / 2f; + canvas.drawText(numText, 0, numText.length(), x, yVal, paint); + + // reset modified paint values + paint.setStyle(prevStyle); + paint.setColor(prevColor); + } + } +} diff --git a/markwon-ext-spans/src/main/java/io/noties/markwon/ext/ObjectsPool.java b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/ObjectsPool.java new file mode 100755 index 00000000..ce7945d8 --- /dev/null +++ b/markwon-ext-spans/src/main/java/io/noties/markwon/ext/ObjectsPool.java @@ -0,0 +1,33 @@ +package io.noties.markwon.span.ext; + +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; + +abstract class ObjectsPool { + + // maybe it's premature optimization, but as all the drawing is done in one thread + // and we apply needed values before actual drawing it's (I assume) safe to reuse some frequently used objects + + // if one of the spans need some really specific handling for Paint object (like colorFilters, masks, etc) + // it should instantiate own instance of it + + private static final Rect RECT = new Rect(); + private static final RectF RECT_F = new RectF(); + private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG); + + static Rect rect() { + return RECT; + } + + static RectF rectF() { + return RECT_F; + } + + static Paint paint() { + return PAINT; + } + + private ObjectsPool() { + } +} diff --git a/markwon-ext-spans/src/test/java/io/noties/markwon/ext/ExampleUnitTest.java b/markwon-ext-spans/src/test/java/io/noties/markwon/ext/ExampleUnitTest.java new file mode 100644 index 00000000..f6f543d5 --- /dev/null +++ b/markwon-ext-spans/src/test/java/io/noties/markwon/ext/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package io.noties.markwon.ext; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file