diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java index 079c3d5c..13f53f26 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java @@ -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); } diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java index a9a01bf3..0e7507da 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java @@ -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(); } diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java b/library-spans/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java index 35f73e96..81a82b59 100644 --- a/library-spans/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java +++ b/library-spans/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java @@ -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); } } diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java b/library-spans/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java index ba92f59d..f6c52b38 100644 --- a/library-spans/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java +++ b/library-spans/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java @@ -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); diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/CodeSpan.java b/library-spans/src/main/java/ru/noties/markwon/spans/CodeSpan.java index b36d51b2..7bb29632 100644 --- a/library-spans/src/main/java/ru/noties/markwon/spans/CodeSpan.java +++ b/library-spans/src/main/java/ru/noties/markwon/spans/CodeSpan.java @@ -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); -// } -// } } diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/ColorUtils.java b/library-spans/src/main/java/ru/noties/markwon/spans/ColorUtils.java new file mode 100644 index 00000000..c59b01fd --- /dev/null +++ b/library-spans/src/main/java/ru/noties/markwon/spans/ColorUtils.java @@ -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() { + } +} diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/HeadingSpan.java b/library-spans/src/main/java/ru/noties/markwon/spans/HeadingSpan.java index 1ac74fb6..c0675d8f 100644 --- a/library-spans/src/main/java/ru/noties/markwon/spans/HeadingSpan.java +++ b/library-spans/src/main/java/ru/noties/markwon/spans/HeadingSpan.java @@ -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); diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/ObjectsPool.java b/library-spans/src/main/java/ru/noties/markwon/spans/ObjectsPool.java new file mode 100644 index 00000000..089abbea --- /dev/null +++ b/library-spans/src/main/java/ru/noties/markwon/spans/ObjectsPool.java @@ -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() { + } +} diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java b/library-spans/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java index a110078f..27e99271 100644 --- a/library-spans/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java +++ b/library-spans/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java @@ -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); } }