diff --git a/library/src/main/java/ru/noties/markwon/Markwon.java b/library/src/main/java/ru/noties/markwon/Markwon.java index e8f4afa7..aa0f566b 100644 --- a/library/src/main/java/ru/noties/markwon/Markwon.java +++ b/library/src/main/java/ru/noties/markwon/Markwon.java @@ -2,8 +2,6 @@ package ru.noties.markwon; import android.content.Context; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.widget.TextView; @@ -61,7 +59,7 @@ public abstract class Markwon { public static void setMarkdown( @NonNull TextView view, @NonNull SpannableConfiguration configuration, - @Nullable String markdown + @NonNull String markdown ) { setText(view, markdown(configuration, markdown)); @@ -98,16 +96,10 @@ public abstract class Markwon { * @return parsed markdown * @since 1.0.0 */ - @Nullable - public static CharSequence markdown(@NonNull Context context, @Nullable String markdown) { - final CharSequence out; - if (TextUtils.isEmpty(markdown)) { - out = null; - } else { - final SpannableConfiguration configuration = SpannableConfiguration.create(context); - out = markdown(configuration, markdown); - } - return out; + @NonNull + public static CharSequence markdown(@NonNull Context context, @NonNull String markdown) { + final SpannableConfiguration configuration = SpannableConfiguration.create(context); + return markdown(configuration, markdown); } /** @@ -119,18 +111,12 @@ public abstract class Markwon { * @see SpannableConfiguration * @since 1.0.0 */ - @Nullable - public static CharSequence markdown(@NonNull SpannableConfiguration configuration, @Nullable String markdown) { - final CharSequence out; - if (TextUtils.isEmpty(markdown)) { - out = null; - } else { - final Parser parser = createParser(); - final Node node = parser.parse(markdown); - final SpannableRenderer renderer = new SpannableRenderer(); - out = renderer.render(configuration, node); - } - return out; + @NonNull + public static CharSequence markdown(@NonNull SpannableConfiguration configuration, @NonNull String markdown) { + final Parser parser = createParser(); + final Node node = parser.parse(markdown); + final SpannableRenderer renderer = new SpannableRenderer(); + return renderer.render(configuration, node); } /** diff --git a/library/src/main/java/ru/noties/markwon/SpannableBuilder.java b/library/src/main/java/ru/noties/markwon/SpannableBuilder.java new file mode 100644 index 00000000..9e25dbe8 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/SpannableBuilder.java @@ -0,0 +1,227 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +/** + * This class is used to _revert_ order of applied spans. Original SpannableStringBuilder + * is using an array to store all the information about spans. So, a span that is added first + * will be drawn first, which leads to subtle bugs (spans receive wrong `x` values when + * requested to draw itself) + * + * @since 1.0.1 + */ +@SuppressWarnings({"WeakerAccess", "unused"}) +public class SpannableBuilder { + + // do not implement CharSequence (or any of Spanned interfaces) + + // we will be using SpannableStringBuilder anyway as a backing store + // as it has tight connection with system (implements some hidden methods, etc) + private final SpannableStringBuilder builder; + + // actually we might be just using ArrayList + private final Deque spans = new ArrayDeque<>(8); + + public SpannableBuilder() { + this(""); + } + + public SpannableBuilder(@NonNull CharSequence cs) { + this.builder = new SpannableStringBuilderImpl(cs.toString()); + copySpans(0, cs); + } + + /** + * Additional method that takes a String, which is proven to NOT contain any spans + * + * @param text String to append + * @return this instance + */ + @NonNull + public SpannableBuilder append(@NonNull String text) { + builder.append(text); + return this; + } + + @NonNull + public SpannableBuilder append(char c) { + builder.append(c); + return this; + } + + @NonNull + public SpannableBuilder append(@NonNull CharSequence cs) { + + copySpans(length(), cs); + + builder.append(cs.toString()); + + return this; + } + + @NonNull + public SpannableBuilder append(@NonNull CharSequence cs, @NonNull Object span) { + final int length = length(); + append(cs); + setSpan(span, length); + return this; + } + + @NonNull + public SpannableBuilder append(@NonNull CharSequence cs, @NonNull Object span, int flags) { + final int length = length(); + append(cs); + setSpan(span, length, length(), flags); + return this; + } + + @NonNull + public SpannableBuilder setSpan(@NonNull Object span, int start) { + return setSpan(span, start, length()); + } + + @NonNull + public SpannableBuilder setSpan(@NonNull Object span, int start, int end) { + return setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + @NonNull + public SpannableBuilder setSpan(@NonNull Object span, int start, int end, int flags) { + spans.push(new Span(span, start, end, flags)); + return this; + } + + public int length() { + return builder.length(); + } + + public char charAt(int index) { + return builder.charAt(index); + } + + public char lastChar() { + return builder.charAt(length() - 1); + } + + @NonNull + public CharSequence removeFromEnd(int start) { + + // this method is not intended to be used by clients + // it's a workaround to support tables + + final int end = length(); + + // as we do not expose builder and do no apply spans to it, we are safe to NOT to convert to String + final SpannableStringBuilderImpl impl = new SpannableStringBuilderImpl(builder.subSequence(start, end)); + + final Iterator iterator = spans.iterator(); + + Span span; + + while (iterator.hasNext() && ((span = iterator.next())) != null) { + if (span.start >= start && span.end <= end) { + impl.setSpan(span.what, span.start - start, span.end - start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + iterator.remove(); + } + } + + builder.replace(start, end, ""); + + return impl; + } + + @Override + @NonNull + public String toString() { + return builder.toString(); + } + + @NonNull + public CharSequence text() { + + // okay, in order to not allow external modification and keep our spans order + // we should not return our builder + // + // plus, if this method was called -> all spans would be applied, which potentially + // breaks the order that we intend to use + // so, we will defensively copy builder + + // as we do not expose builder and do no apply spans to it, we are safe to NOT to convert to String + final SpannableStringBuilderImpl impl = new SpannableStringBuilderImpl(builder); + + for (Span span : spans) { + impl.setSpan(span.what, span.start, span.end, span.flags); + } + + return impl; + } + + private void copySpans(final int index, @Nullable CharSequence cs) { + + // we must identify already reversed Spanned... + // and (!) iterate backwards when adding (to preserve order) + + if (cs instanceof Spanned) { + + final Spanned spanned = (Spanned) cs; + final boolean reverse = spanned instanceof SpannedReversed; + + final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); + + iterate(reverse, spans, new Action() { + @Override + public void apply(Object o) { + setSpan( + o, + index + spanned.getSpanStart(o), + index + spanned.getSpanEnd(o), + spanned.getSpanFlags(o) + ); + } + }); + } + } + + static class Span { + + final Object what; + int start; + int end; + final int flags; + + Span(@NonNull Object what, int start, int end, int flags) { + this.what = what; + this.start = start; + this.end = end; + this.flags = flags; + } + } + + private interface Action { + void apply(Object o); + } + + private static void iterate(boolean reverse, @Nullable Object[] array, @NonNull Action action) { + final int length = array != null + ? array.length + : 0; + if (length > 0) { + if (reverse) { + for (int i = length - 1; i >= 0; i--) { + action.apply(array[i]); + } + } else { + for (int i = 0; i < length; i++) { + action.apply(array[i]); + } + } + } + } +} diff --git a/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java b/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java index 57b11a55..3b9332bd 100644 --- a/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java +++ b/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java @@ -70,6 +70,7 @@ public class SpannableConfiguration { return htmlParser; } + @SuppressWarnings("unused") public static class Builder { private final Context context; diff --git a/library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java b/library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java new file mode 100644 index 00000000..7b29440b --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java @@ -0,0 +1,13 @@ +package ru.noties.markwon; + +import android.text.SpannableStringBuilder; + +/** + * @since 1.0.1 + */ +class SpannableStringBuilderImpl extends SpannableStringBuilder implements SpannedReversed { + + SpannableStringBuilderImpl(CharSequence text) { + super(text); + } +} diff --git a/library/src/main/java/ru/noties/markwon/SpannedReversed.java b/library/src/main/java/ru/noties/markwon/SpannedReversed.java new file mode 100644 index 00000000..3fd7f566 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/SpannedReversed.java @@ -0,0 +1,9 @@ +package ru.noties.markwon; + +import android.text.Spanned; + +/** + * @since 1.0.1 + */ +interface SpannedReversed extends Spanned { +} diff --git a/library/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java b/library/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java index 82faf4e2..b2b09006 100644 --- a/library/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java +++ b/library/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java @@ -5,6 +5,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; +@SuppressWarnings({"unused", "WeakerAccess"}) public class UrlProcessorAndroidAssets implements UrlProcessor { private final UrlProcessorRelativeToAbsolute assetsProcessor diff --git a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java index f0bbf912..75c70c32 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java +++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java @@ -1,7 +1,6 @@ package ru.noties.markwon.renderer; import android.support.annotation.NonNull; -import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.StrikethroughSpan; @@ -39,6 +38,7 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; +import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.SpannableConfiguration; import ru.noties.markwon.renderer.html.SpannableHtmlParser; import ru.noties.markwon.spans.AsyncDrawable; @@ -61,7 +61,7 @@ import ru.noties.markwon.tasklist.TaskListItem; public class SpannableMarkdownVisitor extends AbstractVisitor { private final SpannableConfiguration configuration; - private final SpannableStringBuilder builder; + private final SpannableBuilder builder; private final Deque htmlInlineItems; private int blockQuoteIndent; @@ -73,7 +73,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { public SpannableMarkdownVisitor( @NonNull SpannableConfiguration configuration, - @NonNull SpannableStringBuilder builder + @NonNull SpannableBuilder builder ) { this.configuration = configuration; this.builder = builder; @@ -113,10 +113,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { visitChildren(blockQuote); - setSpan(length, new BlockQuoteSpan( - configuration.theme(), - blockQuoteIndent - )); + setSpan(length, new BlockQuoteSpan(configuration.theme())); blockQuoteIndent -= 1; @@ -201,10 +198,10 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { visitChildren(listItem); + // todo| in order to provide real RTL experience there must be a way to provide this string setSpan(length, new OrderedListItemSpan( configuration.theme(), - String.valueOf(start) + "." + '\u00a0', - blockQuoteIndent + String.valueOf(start) + "." + '\u00a0' )); // after we have visited the children increment start number @@ -217,7 +214,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { setSpan(length, new BulletListItemSpan( configuration.theme(), - blockQuoteIndent, listLevel - 1 )); } @@ -248,11 +244,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { final int length = builder.length(); visitChildren(heading); - setSpan(length, new HeadingSpan( - configuration.theme(), - heading.getLevel(), - builder.length() - length) - ); + setSpan(length, new HeadingSpan(configuration.theme(), heading.getLevel())); newLine(); @@ -311,7 +303,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { setSpan(length, new TaskListSpan( configuration.theme(), blockQuoteIndent, - length, listItem.done() )); @@ -367,11 +358,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { if (pendingTableRow == null) { pendingTableRow = new ArrayList<>(2); } + pendingTableRow.add(new TableRowSpan.Cell( tableCellAlignment(cell.getAlignment()), - builder.subSequence(length, builder.length()) + builder.removeFromEnd(length) )); - builder.replace(length, builder.length(), ""); tableRowIsHeader = cell.isHeader(); @@ -497,7 +488,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { private void newLine() { if (builder.length() > 0 - && '\n' != builder.charAt(builder.length() - 1)) { + && '\n' != builder.lastChar()) { builder.append('\n'); } } diff --git a/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java b/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java index 25073964..32d620dd 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java +++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java @@ -1,25 +1,18 @@ package ru.noties.markwon.renderer; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.SpannableStringBuilder; import org.commonmark.node.Node; +import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.SpannableConfiguration; public class SpannableRenderer { - @Nullable - public CharSequence render(@NonNull SpannableConfiguration configuration, @Nullable Node node) { - final CharSequence out; - if (node == null) { - out = null; - } else { - final SpannableStringBuilder builder = new SpannableStringBuilder(); - node.accept(new SpannableMarkdownVisitor(configuration, builder)); - out = builder; - } - return out; + @NonNull + public CharSequence render(@NonNull SpannableConfiguration configuration, @NonNull Node node) { + final SpannableBuilder builder = new SpannableBuilder(); + node.accept(new SpannableMarkdownVisitor(configuration, builder)); + return builder.text(); } } diff --git a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java b/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java index 14ddc4b8..f9bd8f33 100644 --- a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java @@ -29,9 +29,6 @@ public class AsyncDrawableSpan extends ReplacementSpan { private final int alignment; private final boolean replacementTextIsLink; - private int lastKnownDrawX; - private int lastKnownDrawY; - public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) { this(theme, drawable, ALIGN_BOTTOM); } @@ -112,13 +109,8 @@ public class AsyncDrawableSpan extends ReplacementSpan { int bottom, @NonNull Paint paint) { - // we will pass this value, so when image is obtained drawable will have dimensions - // to resolve image size drawable.initWithKnownDimensions(canvas.getWidth(), paint.getTextSize()); - this.lastKnownDrawX = (int) (x + .5F); - this.lastKnownDrawY = y; - final AsyncDrawable drawable = this.drawable; if (drawable.hasResult()) { @@ -158,12 +150,4 @@ public class AsyncDrawableSpan extends ReplacementSpan { public AsyncDrawable getDrawable() { return drawable; } - - public int lastKnownDrawX() { - return lastKnownDrawX; - } - - public int lastKnownDrawY() { - return lastKnownDrawY; - } } diff --git a/library/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java b/library/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java index 865f9cd2..04dfe78a 100644 --- a/library/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java @@ -12,11 +12,9 @@ public class BlockQuoteSpan implements LeadingMarginSpan { private final SpannableTheme theme; private final Rect rect = ObjectsPool.rect(); private final Paint paint = ObjectsPool.paint(); - private final int indent; - public BlockQuoteSpan(@NonNull SpannableTheme theme, int indent) { + public BlockQuoteSpan(@NonNull SpannableTheme theme) { this.theme = theme; - this.indent = indent; } @Override @@ -43,8 +41,16 @@ public class BlockQuoteSpan implements LeadingMarginSpan { theme.applyBlockQuoteStyle(paint); - final int left = theme.getBlockMargin() * (indent - 1); - rect.set(left, top, left + width, bottom); + final int left; + final int right; + { + final int l = x + (dir * width); + final int r = l + (dir * width); + left = Math.min(l, r); + right = Math.max(l, r); + } + + rect.set(left, top, right, bottom); c.drawRect(rect, paint); } diff --git a/library/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java b/library/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java index c387a504..7beda477 100644 --- a/library/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java @@ -17,15 +17,12 @@ public class BulletListItemSpan implements LeadingMarginSpan { private final RectF circle = ObjectsPool.rectF(); private final Rect rectangle = ObjectsPool.rect(); - private final int blockIndent; private final int level; public BulletListItemSpan( @NonNull SpannableTheme theme, - @IntRange(from = 0) int blockIndent, @IntRange(from = 0) int level) { this.theme = theme; - this.blockIndent = blockIndent; this.level = level; } @@ -38,7 +35,8 @@ public class BulletListItemSpan implements LeadingMarginSpan { 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) { // if there was a line break, we don't need to draw anything - if (!first) { + if (!first + || !LeadingMarginUtils.selfStart(start, text, this)) { return; } @@ -57,9 +55,16 @@ public class BulletListItemSpan implements LeadingMarginSpan { final int marginLeft = (width - side) / 2; final int marginTop = (height - side) / 2; - final int l = (width * (blockIndent - 1)) + marginLeft; + // in order to support RTL + final int l; + final int r; + { + final int left = x + (dir * marginLeft); + final int right = left + (dir * side); + l = Math.min(left, right); + r = Math.max(left, right); + } final int t = top + marginTop; - final int r = l + side; final int b = t + side; if (level == 0 diff --git a/library/src/main/java/ru/noties/markwon/spans/CodeSpan.java b/library/src/main/java/ru/noties/markwon/spans/CodeSpan.java index db4e0553..0149503c 100644 --- a/library/src/main/java/ru/noties/markwon/spans/CodeSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/CodeSpan.java @@ -52,7 +52,17 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan { paint.setStyle(Paint.Style.FILL); paint.setColor(theme.getCodeBackgroundColor(p)); - rect.set(x, top, c.getWidth(), bottom); + 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); c.drawRect(rect, paint); } diff --git a/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java b/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java index fc6227eb..0931170e 100644 --- a/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java @@ -16,12 +16,10 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa private final Rect rect = ObjectsPool.rect(); private final Paint paint = ObjectsPool.paint(); private final int level; - private final int textLength; - public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int textLength) { + public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level) { this.theme = theme; this.level = level; - this.textLength = textLength; } @Override @@ -47,20 +45,28 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa @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) { - if (level == 1 - || level == 2) { + if ((level == 1 || level == 2) + && LeadingMarginUtils.selfEnd(end, text, this)) { - if ((start + textLength) == end) { - paint.set(p); + paint.set(p); - theme.applyHeadingBreakStyle(paint); + theme.applyHeadingBreakStyle(paint); - final float height = paint.getStrokeWidth(); - final int b = (int) (bottom - height + .5F); + final float height = paint.getStrokeWidth(); + final int b = (int) (bottom - height + .5F); - rect.set(x, b, c.getWidth(), bottom); - c.drawRect(rect, paint); + final int left; + final int right; + if (dir > 0) { + left = x; + right = c.getWidth(); + } else { + left = x - c.getWidth(); + right = x; } + + rect.set(left, b, right, bottom); + c.drawRect(rect, paint); } } } diff --git a/library/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java b/library/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java new file mode 100644 index 00000000..ab1a7de3 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.spans; + +import android.text.Spanned; + +abstract class LeadingMarginUtils { + + static boolean selfStart(int start, CharSequence text, Object span) { + return text instanceof Spanned && ((Spanned) text).getSpanStart(span) == start; + } + + static boolean selfEnd(int end, CharSequence text, Object span) { + return text instanceof Spanned && ((Spanned) text).getSpanEnd(span) == end; + } + + private LeadingMarginUtils() { + } +} diff --git a/library/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java b/library/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java index e46f7344..7c76f052 100644 --- a/library/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java @@ -2,7 +2,6 @@ package ru.noties.markwon.spans; import android.graphics.Canvas; import android.graphics.Paint; -import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.text.Layout; import android.text.style.LeadingMarginSpan; @@ -11,16 +10,13 @@ public class OrderedListItemSpan implements LeadingMarginSpan { private final SpannableTheme theme; private final String number; - private final int blockIndent; public OrderedListItemSpan( @NonNull SpannableTheme theme, - @NonNull String number, - @IntRange(from = 0) int blockIndent + @NonNull String number ) { this.theme = theme; this.number = number; - this.blockIndent = blockIndent; } @Override @@ -32,7 +28,8 @@ public class OrderedListItemSpan implements LeadingMarginSpan { 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) { // if there was a line break, we don't need to draw anything - if (!first) { + if (!first + || !LeadingMarginUtils.selfStart(start, text, this)) { return; } @@ -40,10 +37,16 @@ public class OrderedListItemSpan implements LeadingMarginSpan { final int width = theme.getBlockMargin(); final int numberWidth = (int) (p.measureText(number) + .5F); - final int numberX = (width * blockIndent) - numberWidth; + + final int left; + if (dir > 0) { + left = x + (width * dir) - numberWidth; + } else { + left = x + (width * dir) + (width - numberWidth); + } final float numberY = CanvasUtils.textCenterY(top, bottom, p); - c.drawText(number, numberX, numberY, p); + c.drawText(number, left, numberY, p); } } diff --git a/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java b/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java index 1129c781..172b952c 100644 --- a/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java @@ -12,15 +12,17 @@ import android.text.style.LeadingMarginSpan; */ public class TaskListSpan implements LeadingMarginSpan { + private static final int[] STATE_CHECKED = new int[]{android.R.attr.state_checked}; + + private static final int[] STATE_NONE = new int[0]; + private final SpannableTheme theme; private final int blockIndent; - private final int start; private final boolean isDone; - public TaskListSpan(@NonNull SpannableTheme theme, int blockIndent, int start, boolean isDone) { + public TaskListSpan(@NonNull SpannableTheme theme, int blockIndent, boolean isDone) { this.theme = theme; this.blockIndent = blockIndent; - this.start = start; this.isDone = isDone; } @@ -32,7 +34,8 @@ public class TaskListSpan implements LeadingMarginSpan { @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) { - if (!first) { + if (!first + || !LeadingMarginUtils.selfStart(start, text, this)) { return; } @@ -55,14 +58,20 @@ public class TaskListSpan implements LeadingMarginSpan { if (drawable.isStateful()) { final int[] state; if (isDone) { - state = new int[]{android.R.attr.state_checked}; + state = STATE_CHECKED; } else { - state = new int[0]; + state = STATE_NONE; } drawable.setState(state); } - final int l = (width * (blockIndent - 1)) + ((width - w) / 2); + final int l; + if (dir > 0) { + l = x + (width * (blockIndent - 1)) + ((width - w) / 2); + } else { + l = x - (width * blockIndent) + ((width - w) / 2); + } + final int t = top + ((height - h) / 2); c.translate(l, t); diff --git a/library/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java b/library/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java index 1c5355af..316e4312 100644 --- a/library/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java @@ -33,7 +33,17 @@ public class ThematicBreakSpan implements LeadingMarginSpan { final int height = (int) (paint.getStrokeWidth() + .5F); final int halfHeight = (int) (height / 2.F + .5F); - rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight); + final int left; + final int right; + if (dir > 0) { + left = x; + right = c.getWidth(); + } else { + left = x - c.getWidth(); + right = x; + } + + rect.set(left, middle - halfHeight, right, middle + halfHeight); c.drawRect(rect, paint); } }