Better default values + ObjectsPool
This commit is contained in:
parent
6f5fd08de4
commit
d280095281
@ -7,6 +7,7 @@ import ru.noties.markwon.spans.BlockQuoteSpan;
|
||||
import ru.noties.markwon.spans.BulletListItemSpan;
|
||||
import ru.noties.markwon.spans.CodeSpan;
|
||||
import ru.noties.markwon.spans.HeadingSpan;
|
||||
import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||
|
||||
public class SpannableConfiguration {
|
||||
|
||||
@ -23,12 +24,14 @@ public class SpannableConfiguration {
|
||||
private final CodeSpan.Config codeConfig;
|
||||
private final BulletListItemSpan.Config bulletListConfig;
|
||||
private final HeadingSpan.Config headingConfig;
|
||||
private final ThematicBreakSpan.Config thematicConfig;
|
||||
|
||||
private SpannableConfiguration(Builder builder) {
|
||||
this.blockQuoteConfig = builder.blockQuoteConfig;
|
||||
this.codeConfig = builder.codeConfig;
|
||||
this.bulletListConfig = builder.bulletListConfig;
|
||||
this.headingConfig = builder.headingConfig;
|
||||
this.thematicConfig = builder.thematicConfig;
|
||||
}
|
||||
|
||||
public BlockQuoteSpan.Config getBlockQuoteConfig() {
|
||||
@ -47,6 +50,10 @@ public class SpannableConfiguration {
|
||||
return headingConfig;
|
||||
}
|
||||
|
||||
public ThematicBreakSpan.Config getThematicConfig() {
|
||||
return thematicConfig;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Context context;
|
||||
@ -54,6 +61,7 @@ public class SpannableConfiguration {
|
||||
private CodeSpan.Config codeConfig;
|
||||
private BulletListItemSpan.Config bulletListConfig;
|
||||
private HeadingSpan.Config headingConfig;
|
||||
private ThematicBreakSpan.Config thematicConfig;
|
||||
|
||||
public Builder(Context context) {
|
||||
this.context = context;
|
||||
@ -79,13 +87,18 @@ public class SpannableConfiguration {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setThematicConfig(@NonNull ThematicBreakSpan.Config thematicConfig) {
|
||||
this.thematicConfig = thematicConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
// todo, change to something more reliable
|
||||
public SpannableConfiguration build() {
|
||||
if (blockQuoteConfig == null) {
|
||||
blockQuoteConfig = new BlockQuoteSpan.Config(
|
||||
px(16),
|
||||
px(4),
|
||||
0xFFcccccc
|
||||
0,
|
||||
0
|
||||
);
|
||||
}
|
||||
if (codeConfig == null) {
|
||||
@ -99,6 +112,9 @@ public class SpannableConfiguration {
|
||||
if (headingConfig == null) {
|
||||
headingConfig = new HeadingSpan.Config(px(1), 0);
|
||||
}
|
||||
if (thematicConfig == null) {
|
||||
thematicConfig = new ThematicBreakSpan.Config(0, px(2));
|
||||
}
|
||||
return new SpannableConfiguration(this);
|
||||
}
|
||||
|
||||
|
@ -194,10 +194,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
newLine();
|
||||
|
||||
// todo, new lines...
|
||||
final int length = builder.length();
|
||||
builder.append(' '); // without space it won't render
|
||||
builder.setSpan(new ThematicBreakSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
setSpan(length, new ThematicBreakSpan(configuration.getThematicConfig()));
|
||||
|
||||
newLine();
|
||||
}
|
||||
|
@ -11,15 +11,17 @@ import android.text.style.LeadingMarginSpan;
|
||||
|
||||
public class BlockQuoteSpan implements LeadingMarginSpan {
|
||||
|
||||
private static final int DEF_COLOR_ALPHA = 50;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static class Config {
|
||||
|
||||
final int totalWidth;
|
||||
final int quoteWidth;
|
||||
final int quoteColor; // by default textColor with 0.1 alpha
|
||||
final int quoteWidth; // by default 1/4 of width
|
||||
final int quoteColor; // by default textColor with 0.2 alpha
|
||||
|
||||
public Config(
|
||||
@IntRange(from = 0) int totalWidth,
|
||||
@IntRange(from = 1) int totalWidth,
|
||||
@IntRange(from = 0) int quoteWidth,
|
||||
@ColorInt int quoteColor) {
|
||||
this.totalWidth = totalWidth;
|
||||
@ -29,16 +31,13 @@ public class BlockQuoteSpan implements LeadingMarginSpan {
|
||||
}
|
||||
|
||||
private final Config config;
|
||||
private final Rect rect = new Rect();
|
||||
private final Paint paint = new Paint();
|
||||
private final Rect rect = ObjectsPool.rect();
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
private final int indent;
|
||||
|
||||
public BlockQuoteSpan(@NonNull Config config, int indent) {
|
||||
this.config = config;
|
||||
this.indent = indent;
|
||||
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(config.quoteColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -61,13 +60,25 @@ public class BlockQuoteSpan implements LeadingMarginSpan {
|
||||
boolean first,
|
||||
Layout layout) {
|
||||
|
||||
final int save = c.save();
|
||||
try {
|
||||
final int left = config.totalWidth * (indent - 1);
|
||||
rect.set(left, top, left + config.quoteWidth, bottom);
|
||||
c.drawRect(rect, paint);
|
||||
} finally {
|
||||
c.restoreToCount(save);
|
||||
final int width;
|
||||
if (config.quoteWidth == 0) {
|
||||
width = (int) (config.totalWidth / 4.F + .5F);
|
||||
} else {
|
||||
width = config.quoteWidth;
|
||||
}
|
||||
|
||||
final int color;
|
||||
if (config.quoteColor != 0) {
|
||||
color = config.quoteColor;
|
||||
} else {
|
||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA);
|
||||
}
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(color);
|
||||
|
||||
final int left = config.totalWidth * (indent - 1);
|
||||
rect.set(left, top, left + width, bottom);
|
||||
|
||||
c.drawRect(rect, paint);
|
||||
}
|
||||
}
|
||||
|
@ -30,10 +30,10 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
}
|
||||
|
||||
private final Config config;
|
||||
private final Paint paint = new Paint();
|
||||
|
||||
private RectF circle;
|
||||
private Rect rectangle;
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
private final RectF circle = ObjectsPool.rectF();
|
||||
private final Rect rectangle = ObjectsPool.rect();
|
||||
|
||||
private final int blockIndent;
|
||||
private final int level;
|
||||
@ -101,11 +101,6 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
if (level == 0
|
||||
|| level == 1) {
|
||||
|
||||
// ensure we have circle rectF
|
||||
if (circle == null) {
|
||||
circle = new RectF();
|
||||
}
|
||||
|
||||
circle.set(l, t, r, b);
|
||||
|
||||
final Paint.Style style = level == 0
|
||||
@ -116,11 +111,6 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
c.drawOval(circle, paint);
|
||||
} else {
|
||||
|
||||
// ensure rectangle
|
||||
if (rectangle == null) {
|
||||
rectangle = new Rect();
|
||||
}
|
||||
|
||||
rectangle.set(l, t, r, b);
|
||||
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
|
@ -14,14 +14,7 @@ import android.text.style.MetricAffectingSpan;
|
||||
|
||||
public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
||||
|
||||
// the thing is.. we cannot use replacementSpan, because it won't let us create multiline code..
|
||||
// and we want new lines when we do not fit the width
|
||||
// plus it complicates the copying
|
||||
|
||||
// replacement span is great because we can have additional paddings & can actually get a hold
|
||||
// of Canvas to draw background, but it implies a lot of manual text handling
|
||||
|
||||
// also, we can reuse Rect instance as long as we apply our dimensions in each draw call
|
||||
private static final int DEF_COLOR_ALPHA = 25;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static class Config {
|
||||
@ -87,17 +80,14 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
||||
}
|
||||
|
||||
private final Config config;
|
||||
private final Rect rect = new Rect();
|
||||
private final Paint paint = new Paint();
|
||||
private final Rect rect = ObjectsPool.rect();
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
|
||||
private final boolean multiline;
|
||||
|
||||
public CodeSpan(@NonNull Config config, boolean multiline) {
|
||||
this.config = config;
|
||||
this.multiline = multiline;
|
||||
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setTypeface(config.typeface);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -111,7 +101,7 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
||||
if (!multiline) {
|
||||
final int color;
|
||||
if (config.backgroundColor == 0) {
|
||||
color = applyAlpha(ds.getColor(), 25);
|
||||
color = ColorUtils.applyAlpha(ds.getColor(), DEF_COLOR_ALPHA);
|
||||
} else {
|
||||
color = config.backgroundColor;
|
||||
}
|
||||
@ -141,167 +131,16 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
||||
|
||||
final int color;
|
||||
if (config.backgroundColor == 0) {
|
||||
color = applyAlpha(p.getColor(), 25);
|
||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA);
|
||||
} else {
|
||||
color = config.backgroundColor;
|
||||
}
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(color);
|
||||
|
||||
rect.set(x, top, c.getWidth(), bottom);
|
||||
|
||||
c.drawRect(rect, paint);
|
||||
}
|
||||
|
||||
// paint.setTextSize(p.getTextSize());
|
||||
//
|
||||
// final int left = (int) (x + .5F);
|
||||
//
|
||||
// final int right;
|
||||
// if (multiline) {
|
||||
// right = c.getWidth();
|
||||
// } else {
|
||||
// final int width = (config.paddingHorizontal * 2) + (int) (paint.measureText(text, start, end) + .5F);
|
||||
// right = left + width;
|
||||
// }
|
||||
//
|
||||
// rect.set(left, top, right, bottom);
|
||||
//
|
||||
// // okay, draw background first
|
||||
// drawBackground(c);
|
||||
|
||||
// then, if any, draw borders
|
||||
// drawBorders(c, this.start == start, this.end == end);
|
||||
|
||||
// final int color;
|
||||
// if (config.textColor == 0) {
|
||||
// color = p.getColor();
|
||||
// } else {
|
||||
// color = config.textColor;
|
||||
// }
|
||||
// paint.setColor(color);
|
||||
//
|
||||
// // draw text
|
||||
// // y center position
|
||||
// final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2);
|
||||
// canvas.drawText(text, start, end, x + config.paddingHorizontal, b, paint);
|
||||
}
|
||||
|
||||
private static int applyAlpha(int color, int alpha) {
|
||||
return (color & 0x00FFFFFF) | (alpha << 24);
|
||||
}
|
||||
|
||||
|
||||
// @Override
|
||||
// public int getSize(
|
||||
// @NonNull Paint p,
|
||||
// CharSequence text,
|
||||
// @IntRange(from = 0) int start,
|
||||
// @IntRange(from = 0) int end,
|
||||
// @Nullable Paint.FontMetricsInt fm
|
||||
// ) {
|
||||
//
|
||||
// paint.setTextSize(p.getTextSize());
|
||||
//
|
||||
// final int width = (config.paddingHorizontal * 2) + (int) (paint.measureText(text, start, end) + .5F);
|
||||
//
|
||||
// if (fm != null) {
|
||||
// // we add a padding top & bottom
|
||||
// final float ratio = .62F; // golden ratio, there is no much point of moving this to config... it seems a bit `specific`...
|
||||
// fm.ascent = fm.ascent - (config.paddingVertical);
|
||||
// fm.descent = (int) (-fm.ascent * ratio);
|
||||
// fm.top = fm.ascent;
|
||||
// fm.bottom = fm.descent;
|
||||
// }
|
||||
//
|
||||
// return width;
|
||||
// }
|
||||
|
||||
// @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 p
|
||||
// ) {
|
||||
//
|
||||
// paint.setTextSize(p.getTextSize());
|
||||
//
|
||||
// final int left = (int) (x + .5F);
|
||||
//
|
||||
// final int right;
|
||||
// if (multiline) {
|
||||
// right = canvas.getWidth();
|
||||
// } else {
|
||||
// final int width = (config.paddingHorizontal * 2) + (int) (paint.measureText(text, start, end) + .5F);
|
||||
// right = left + width;
|
||||
// }
|
||||
//
|
||||
// rect.set(left, top, right, bottom);
|
||||
//
|
||||
// // okay, draw background first
|
||||
// drawBackground(canvas);
|
||||
//
|
||||
// // then, if any, draw borders
|
||||
// drawBorders(canvas, this.start == start, this.end == end);
|
||||
//
|
||||
// final int color;
|
||||
// if (config.textColor == 0) {
|
||||
// color = p.getColor();
|
||||
// } else {
|
||||
// color = config.textColor;
|
||||
// }
|
||||
// paint.setColor(color);
|
||||
//
|
||||
// // draw text
|
||||
// // y center position
|
||||
// final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2);
|
||||
// canvas.drawText(text, start, end, x + config.paddingHorizontal, b, paint);
|
||||
// }
|
||||
|
||||
// private void drawBackground(Canvas canvas) {
|
||||
// final int color = config.backgroundColor;
|
||||
// if (color != 0) {
|
||||
// paint.setColor(color);
|
||||
// canvas.drawRect(rect, paint);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void drawBorders(Canvas canvas, boolean top, boolean bottom) {
|
||||
//
|
||||
// final int color = config.borderColor;
|
||||
// final int width = config.borderWidth;
|
||||
// if (color == 0
|
||||
// || width == 0) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// paint.setColor(color);
|
||||
//
|
||||
// // left and right are always drawn
|
||||
//
|
||||
// // LEFT
|
||||
// borderRect.set(rect.left, rect.top, rect.left + width, rect.bottom);
|
||||
// canvas.drawRect(borderRect, paint);
|
||||
//
|
||||
// // RIGHT
|
||||
// borderRect.set(rect.right - width, rect.top, rect.right, rect.bottom);
|
||||
// canvas.drawRect(borderRect, paint);
|
||||
//
|
||||
// // TOP
|
||||
// if (top) {
|
||||
// borderRect.set(rect.left, rect.top, rect.right, rect.top + width);
|
||||
// canvas.drawRect(borderRect, paint);
|
||||
// }
|
||||
//
|
||||
// // BOTTOM
|
||||
// if (bottom) {
|
||||
// borderRect.set(rect.left, rect.bottom - width, rect.right, rect.bottom);
|
||||
// canvas.drawRect(borderRect, paint);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
package ru.noties.markwon.spans;
|
||||
|
||||
class ColorUtils {
|
||||
|
||||
static int applyAlpha(int color, int alpha) {
|
||||
return (color & 0x00FFFFFF) | (alpha << 24);
|
||||
}
|
||||
|
||||
private ColorUtils() {
|
||||
}
|
||||
}
|
@ -18,10 +18,12 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
|
||||
2.F, 1.5F, 1.17F, 1.F, .83F, .67F,
|
||||
};
|
||||
|
||||
private static final int DEF_BREAK_COLOR_ALPHA = 127;
|
||||
|
||||
public static class Config {
|
||||
|
||||
final int breakHeight; // by default stroke width
|
||||
final int breakColor; // by default -> textColor
|
||||
final int breakColor; // by default -> textColor with 0.5 alpha
|
||||
|
||||
public Config(@IntRange(from = 0) int breakHeight, @ColorInt int breakColor) {
|
||||
this.breakHeight = breakHeight;
|
||||
@ -30,8 +32,8 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
|
||||
}
|
||||
|
||||
private final Config config;
|
||||
private final Rect rect = new Rect();
|
||||
private final Paint paint = new Paint();
|
||||
private final Rect rect = ObjectsPool.rect();
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
private final int level;
|
||||
private final int end;
|
||||
|
||||
@ -39,8 +41,6 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
|
||||
this.config = config;
|
||||
this.level = level - 1;
|
||||
this.end = end;
|
||||
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,7 +77,7 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
|
||||
final int color;
|
||||
final int breakHeight;
|
||||
if (config.breakColor == 0) {
|
||||
color = p.getColor();
|
||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_BREAK_COLOR_ALPHA);
|
||||
} else {
|
||||
color = config.breakColor;
|
||||
}
|
||||
@ -86,6 +86,7 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
|
||||
} else {
|
||||
breakHeight = config.breakHeight;
|
||||
}
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(color);
|
||||
|
||||
rect.set(x, bottom - breakHeight, c.getWidth(), bottom);
|
||||
|
@ -0,0 +1,33 @@
|
||||
package ru.noties.markwon.spans;
|
||||
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
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() {
|
||||
}
|
||||
}
|
@ -3,23 +3,62 @@ package ru.noties.markwon.spans;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.text.Layout;
|
||||
import android.text.style.LeadingMarginSpan;
|
||||
|
||||
public class ThematicBreakSpan implements LeadingMarginSpan {
|
||||
|
||||
private static final int DEF_COLOR_ALPHA = 127;
|
||||
|
||||
public static class Config {
|
||||
|
||||
final int color; // by default textColor with 0.5 alpha
|
||||
final int height; // by default strokeWidth of paint
|
||||
|
||||
public Config(@ColorInt int color, @IntRange(from = 0) int height) {
|
||||
this.color = color;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
private final Config config;
|
||||
private final Rect rect = ObjectsPool.rect();
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
|
||||
public ThematicBreakSpan(Config config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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 middle = (bottom - top) / 2;
|
||||
final Rect rect = new Rect(0, top + middle - 2, c.getWidth(), top + middle + 2);
|
||||
final Paint paint = new Paint();
|
||||
|
||||
final int middle = top + ((bottom - top) / 2);
|
||||
|
||||
final int color;
|
||||
if (config.color == 0) {
|
||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA);
|
||||
} else {
|
||||
color = config.color;
|
||||
}
|
||||
paint.setColor(color);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(0x80000000);
|
||||
|
||||
final int height;
|
||||
if (config.height == 0) {
|
||||
height = (int) (p.getStrokeWidth() + .5F);
|
||||
} else {
|
||||
height = config.height;
|
||||
}
|
||||
final int halfHeight = (int) (height / 2.F + .5F);
|
||||
|
||||
rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight);
|
||||
c.drawRect(rect, paint);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user