Better default values + ObjectsPool

This commit is contained in:
Dimitry Ivanov 2017-05-11 17:50:07 +03:00
parent 6f5fd08de4
commit d280095281
9 changed files with 149 additions and 210 deletions

View File

@ -7,6 +7,7 @@ import ru.noties.markwon.spans.BlockQuoteSpan;
import ru.noties.markwon.spans.BulletListItemSpan; import ru.noties.markwon.spans.BulletListItemSpan;
import ru.noties.markwon.spans.CodeSpan; import ru.noties.markwon.spans.CodeSpan;
import ru.noties.markwon.spans.HeadingSpan; import ru.noties.markwon.spans.HeadingSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
public class SpannableConfiguration { public class SpannableConfiguration {
@ -23,12 +24,14 @@ public class SpannableConfiguration {
private final CodeSpan.Config codeConfig; private final CodeSpan.Config codeConfig;
private final BulletListItemSpan.Config bulletListConfig; private final BulletListItemSpan.Config bulletListConfig;
private final HeadingSpan.Config headingConfig; private final HeadingSpan.Config headingConfig;
private final ThematicBreakSpan.Config thematicConfig;
private SpannableConfiguration(Builder builder) { private SpannableConfiguration(Builder builder) {
this.blockQuoteConfig = builder.blockQuoteConfig; this.blockQuoteConfig = builder.blockQuoteConfig;
this.codeConfig = builder.codeConfig; this.codeConfig = builder.codeConfig;
this.bulletListConfig = builder.bulletListConfig; this.bulletListConfig = builder.bulletListConfig;
this.headingConfig = builder.headingConfig; this.headingConfig = builder.headingConfig;
this.thematicConfig = builder.thematicConfig;
} }
public BlockQuoteSpan.Config getBlockQuoteConfig() { public BlockQuoteSpan.Config getBlockQuoteConfig() {
@ -47,6 +50,10 @@ public class SpannableConfiguration {
return headingConfig; return headingConfig;
} }
public ThematicBreakSpan.Config getThematicConfig() {
return thematicConfig;
}
public static class Builder { public static class Builder {
private final Context context; private final Context context;
@ -54,6 +61,7 @@ public class SpannableConfiguration {
private CodeSpan.Config codeConfig; private CodeSpan.Config codeConfig;
private BulletListItemSpan.Config bulletListConfig; private BulletListItemSpan.Config bulletListConfig;
private HeadingSpan.Config headingConfig; private HeadingSpan.Config headingConfig;
private ThematicBreakSpan.Config thematicConfig;
public Builder(Context context) { public Builder(Context context) {
this.context = context; this.context = context;
@ -79,13 +87,18 @@ public class SpannableConfiguration {
return this; return this;
} }
public Builder setThematicConfig(@NonNull ThematicBreakSpan.Config thematicConfig) {
this.thematicConfig = thematicConfig;
return this;
}
// todo, change to something more reliable // todo, change to something more reliable
public SpannableConfiguration build() { public SpannableConfiguration build() {
if (blockQuoteConfig == null) { if (blockQuoteConfig == null) {
blockQuoteConfig = new BlockQuoteSpan.Config( blockQuoteConfig = new BlockQuoteSpan.Config(
px(16), px(16),
px(4), 0,
0xFFcccccc 0
); );
} }
if (codeConfig == null) { if (codeConfig == null) {
@ -99,6 +112,9 @@ public class SpannableConfiguration {
if (headingConfig == null) { if (headingConfig == null) {
headingConfig = new HeadingSpan.Config(px(1), 0); headingConfig = new HeadingSpan.Config(px(1), 0);
} }
if (thematicConfig == null) {
thematicConfig = new ThematicBreakSpan.Config(0, px(2));
}
return new SpannableConfiguration(this); return new SpannableConfiguration(this);
} }

View File

@ -194,10 +194,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
newLine(); newLine();
// todo, new lines...
final int length = builder.length(); final int length = builder.length();
builder.append(' '); // without space it won't render 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(); newLine();
} }

View File

@ -11,15 +11,17 @@ import android.text.style.LeadingMarginSpan;
public class BlockQuoteSpan implements LeadingMarginSpan { public class BlockQuoteSpan implements LeadingMarginSpan {
private static final int DEF_COLOR_ALPHA = 50;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public static class Config { public static class Config {
final int totalWidth; final int totalWidth;
final int quoteWidth; final int quoteWidth; // by default 1/4 of width
final int quoteColor; // by default textColor with 0.1 alpha final int quoteColor; // by default textColor with 0.2 alpha
public Config( public Config(
@IntRange(from = 0) int totalWidth, @IntRange(from = 1) int totalWidth,
@IntRange(from = 0) int quoteWidth, @IntRange(from = 0) int quoteWidth,
@ColorInt int quoteColor) { @ColorInt int quoteColor) {
this.totalWidth = totalWidth; this.totalWidth = totalWidth;
@ -29,16 +31,13 @@ public class BlockQuoteSpan implements LeadingMarginSpan {
} }
private final Config config; private final Config config;
private final Rect rect = new Rect(); private final Rect rect = ObjectsPool.rect();
private final Paint paint = new Paint(); private final Paint paint = ObjectsPool.paint();
private final int indent; private final int indent;
public BlockQuoteSpan(@NonNull Config config, int indent) { public BlockQuoteSpan(@NonNull Config config, int indent) {
this.config = config; this.config = config;
this.indent = indent; this.indent = indent;
paint.setStyle(Paint.Style.FILL);
paint.setColor(config.quoteColor);
} }
@Override @Override
@ -61,13 +60,25 @@ public class BlockQuoteSpan implements LeadingMarginSpan {
boolean first, boolean first,
Layout layout) { Layout layout) {
final int save = c.save(); final int width;
try { 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); final int left = config.totalWidth * (indent - 1);
rect.set(left, top, left + config.quoteWidth, bottom); rect.set(left, top, left + width, bottom);
c.drawRect(rect, paint); c.drawRect(rect, paint);
} finally {
c.restoreToCount(save);
}
} }
} }

View File

@ -30,10 +30,10 @@ public class BulletListItemSpan implements LeadingMarginSpan {
} }
private final Config config; private final Config config;
private final Paint paint = new Paint();
private RectF circle; private final Paint paint = ObjectsPool.paint();
private Rect rectangle; private final RectF circle = ObjectsPool.rectF();
private final Rect rectangle = ObjectsPool.rect();
private final int blockIndent; private final int blockIndent;
private final int level; private final int level;
@ -101,11 +101,6 @@ public class BulletListItemSpan implements LeadingMarginSpan {
if (level == 0 if (level == 0
|| level == 1) { || level == 1) {
// ensure we have circle rectF
if (circle == null) {
circle = new RectF();
}
circle.set(l, t, r, b); circle.set(l, t, r, b);
final Paint.Style style = level == 0 final Paint.Style style = level == 0
@ -116,11 +111,6 @@ public class BulletListItemSpan implements LeadingMarginSpan {
c.drawOval(circle, paint); c.drawOval(circle, paint);
} else { } else {
// ensure rectangle
if (rectangle == null) {
rectangle = new Rect();
}
rectangle.set(l, t, r, b); rectangle.set(l, t, r, b);
paint.setStyle(Paint.Style.FILL); paint.setStyle(Paint.Style.FILL);

View File

@ -14,14 +14,7 @@ import android.text.style.MetricAffectingSpan;
public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan { public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
// the thing is.. we cannot use replacementSpan, because it won't let us create multiline code.. private static final int DEF_COLOR_ALPHA = 25;
// 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
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public static class Config { public static class Config {
@ -87,17 +80,14 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
} }
private final Config config; private final Config config;
private final Rect rect = new Rect(); private final Rect rect = ObjectsPool.rect();
private final Paint paint = new Paint(); private final Paint paint = ObjectsPool.paint();
private final boolean multiline; private final boolean multiline;
public CodeSpan(@NonNull Config config, boolean multiline) { public CodeSpan(@NonNull Config config, boolean multiline) {
this.config = config; this.config = config;
this.multiline = multiline; this.multiline = multiline;
paint.setStyle(Paint.Style.FILL);
paint.setTypeface(config.typeface);
} }
@Override @Override
@ -111,7 +101,7 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
if (!multiline) { if (!multiline) {
final int color; final int color;
if (config.backgroundColor == 0) { if (config.backgroundColor == 0) {
color = applyAlpha(ds.getColor(), 25); color = ColorUtils.applyAlpha(ds.getColor(), DEF_COLOR_ALPHA);
} else { } else {
color = config.backgroundColor; color = config.backgroundColor;
} }
@ -141,167 +131,16 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
final int color; final int color;
if (config.backgroundColor == 0) { if (config.backgroundColor == 0) {
color = applyAlpha(p.getColor(), 25); color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA);
} else { } else {
color = config.backgroundColor; color = config.backgroundColor;
} }
paint.setStyle(Paint.Style.FILL);
paint.setColor(color); paint.setColor(color);
rect.set(x, top, c.getWidth(), bottom); rect.set(x, top, c.getWidth(), bottom);
c.drawRect(rect, paint); 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);
// }
// }
} }

View File

@ -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() {
}
}

View File

@ -18,10 +18,12 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
2.F, 1.5F, 1.17F, 1.F, .83F, .67F, 2.F, 1.5F, 1.17F, 1.F, .83F, .67F,
}; };
private static final int DEF_BREAK_COLOR_ALPHA = 127;
public static class Config { public static class Config {
final int breakHeight; // by default stroke width 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) { public Config(@IntRange(from = 0) int breakHeight, @ColorInt int breakColor) {
this.breakHeight = breakHeight; this.breakHeight = breakHeight;
@ -30,8 +32,8 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
} }
private final Config config; private final Config config;
private final Rect rect = new Rect(); private final Rect rect = ObjectsPool.rect();
private final Paint paint = new Paint(); private final Paint paint = ObjectsPool.paint();
private final int level; private final int level;
private final int end; private final int end;
@ -39,8 +41,6 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
this.config = config; this.config = config;
this.level = level - 1; this.level = level - 1;
this.end = end; this.end = end;
paint.setStyle(Paint.Style.FILL);
} }
@Override @Override
@ -77,7 +77,7 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
final int color; final int color;
final int breakHeight; final int breakHeight;
if (config.breakColor == 0) { if (config.breakColor == 0) {
color = p.getColor(); color = ColorUtils.applyAlpha(p.getColor(), DEF_BREAK_COLOR_ALPHA);
} else { } else {
color = config.breakColor; color = config.breakColor;
} }
@ -86,6 +86,7 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
} else { } else {
breakHeight = config.breakHeight; breakHeight = config.breakHeight;
} }
paint.setStyle(Paint.Style.FILL);
paint.setColor(color); paint.setColor(color);
rect.set(x, bottom - breakHeight, c.getWidth(), bottom); rect.set(x, bottom - breakHeight, c.getWidth(), bottom);

View File

@ -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() {
}
}

View File

@ -3,23 +3,62 @@ package ru.noties.markwon.spans;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;
import android.text.Layout; import android.text.Layout;
import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan;
public class ThematicBreakSpan implements 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 @Override
public int getLeadingMargin(boolean first) { public int getLeadingMargin(boolean first) {
return 1; return 0;
} }
@Override @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) { 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 int middle = top + ((bottom - top) / 2);
final Paint paint = new Paint();
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.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); c.drawRect(rect, paint);
} }
} }