Create additional spans
This commit is contained in:
parent
d93a5f8ee6
commit
e4f7e9b901
1
markwon-span-ext/.gitignore
vendored
Normal file
1
markwon-span-ext/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
50
markwon-span-ext/build.gradle
Normal file
50
markwon-span-ext/build.gradle
Normal file
@ -0,0 +1,50 @@
|
||||
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 {
|
||||
|
||||
api project(':markwon-core')
|
||||
api deps['glide']
|
||||
|
||||
|
||||
deps.with {
|
||||
// add a compileOnly dependency, so if this artifact is present
|
||||
// we will try to obtain a SpanFactory for a Strikethrough node and use
|
||||
// it to be consistent with markdown (please note that we do not use markwon plugin
|
||||
// for that in case if different implementation is used)
|
||||
compileOnly it['commonmark-strikethrough']
|
||||
|
||||
testImplementation it['ix-java']
|
||||
}
|
||||
|
||||
deps.test.with {
|
||||
testImplementation it['junit']
|
||||
testImplementation it['robolectric']
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.noties.markwon.span.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.span.ext.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
5
markwon-span-ext/src/main/AndroidManifest.xml
Normal file
5
markwon-span-ext/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.span.ext">
|
||||
|
||||
</manifest>
|
@ -0,0 +1,82 @@
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
188
markwon-span-ext/src/main/java/io/noties/markwon/span/ext/BulletSpan.java
Executable file
188
markwon-span-ext/src/main/java/io/noties/markwon/span/ext/BulletSpan.java
Executable file
@ -0,0 +1,188 @@
|
||||
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;
|
||||
|
||||
|
||||
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,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;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
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 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();
|
||||
}
|
||||
}
|
126
markwon-span-ext/src/main/java/io/noties/markwon/span/ext/CodeBlockSpan.java
Executable file
126
markwon-span-ext/src/main/java/io/noties/markwon/span/ext/CodeBlockSpan.java
Executable file
@ -0,0 +1,126 @@
|
||||
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;
|
||||
|
||||
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("dark")){
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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,58 @@
|
||||
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;
|
||||
|
||||
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("dark")){
|
||||
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-span-ext/src/main/java/io/noties/markwon/span/ext/ObjectsPool.java
Executable file
33
markwon-span-ext/src/main/java/io/noties/markwon/span/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.span.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