Create additional spans
This commit is contained in:
parent
6006812f56
commit
c928750ff0
1
markwon-ext-spans/.gitignore
vendored
Normal file
1
markwon-ext-spans/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
37
markwon-ext-spans/build.gradle
Normal file
37
markwon-ext-spans/build.gradle
Normal file
@ -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'
|
||||||
|
}
|
0
markwon-ext-spans/consumer-rules.pro
Normal file
0
markwon-ext-spans/consumer-rules.pro
Normal file
21
markwon-ext-spans/proguard-rules.pro
vendored
Normal file
21
markwon-ext-spans/proguard-rules.pro
vendored
Normal file
@ -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
|
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
5
markwon-ext-spans/src/main/AndroidManifest.xml
Normal file
5
markwon-ext-spans/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="io.noties.markwon.ext">
|
||||||
|
|
||||||
|
</manifest>
|
82
markwon-ext-spans/src/main/java/io/noties/markwon/ext/BlockQuoteSpan.java
Executable file
82
markwon-ext-spans/src/main/java/io/noties/markwon/ext/BlockQuoteSpan.java
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
190
markwon-ext-spans/src/main/java/io/noties/markwon/ext/BulletSpan.java
Executable file
190
markwon-ext-spans/src/main/java/io/noties/markwon/ext/BulletSpan.java
Executable file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
31
markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWDrawableSpan.java
Executable file
31
markwon-ext-spans/src/main/java/io/noties/markwon/ext/CWDrawableSpan.java
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
57
markwon-ext-spans/src/main/java/io/noties/markwon/ext/CenteredImageSpan.java
Executable file
57
markwon-ext-spans/src/main/java/io/noties/markwon/ext/CenteredImageSpan.java
Executable file
@ -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<Drawable> 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();
|
||||||
|
}
|
||||||
|
}
|
133
markwon-ext-spans/src/main/java/io/noties/markwon/ext/CodeBlockSpan.java
Executable file
133
markwon-ext-spans/src/main/java/io/noties/markwon/ext/CodeBlockSpan.java
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
markwon-ext-spans/src/main/java/io/noties/markwon/ext/GifDrawableSpan.java
Executable file
33
markwon-ext-spans/src/main/java/io/noties/markwon/ext/GifDrawableSpan.java
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
markwon-ext-spans/src/main/java/io/noties/markwon/ext/ObjectsPool.java
Executable file
33
markwon-ext-spans/src/main/java/io/noties/markwon/ext/ObjectsPool.java
Executable file
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user