diff --git a/library/src/main/java/ru/noties/markwon/ReverseSpannableStringBuilder.java b/library/src/main/java/ru/noties/markwon/ReverseSpannableStringBuilder.java
new file mode 100644
index 00000000..1bcf0b7a
--- /dev/null
+++ b/library/src/main/java/ru/noties/markwon/ReverseSpannableStringBuilder.java
@@ -0,0 +1,42 @@
+package ru.noties.markwon;
+
+import android.text.SpannableStringBuilder;
+
+/**
+ * Copied as is from @see Uncodin/bypass
+ */
+public class ReverseSpannableStringBuilder extends SpannableStringBuilder {
+
+ public ReverseSpannableStringBuilder() {
+ super();
+ }
+
+ public ReverseSpannableStringBuilder(CharSequence text, int start, int end) {
+ super(text, start, end);
+ }
+
+ @Override
+ public T[] getSpans(int queryStart, int queryEnd, Class kind) {
+ T[] ret = super.getSpans(queryStart, queryEnd, kind);
+ reverse(ret);
+ return ret;
+ }
+
+ private static void reverse(Object[] arr) {
+ if (arr == null) {
+ return;
+ }
+
+ int i = 0;
+ int j = arr.length - 1;
+ Object tmp;
+ while (j > i) {
+ tmp = arr[j];
+ arr[j] = arr[i];
+ arr[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+}
diff --git a/library/src/main/java/ru/noties/markwon/SpannableBuilder.java b/library/src/main/java/ru/noties/markwon/SpannableBuilder.java
deleted file mode 100644
index 5cc2420f..00000000
--- a/library/src/main/java/ru/noties/markwon/SpannableBuilder.java
+++ /dev/null
@@ -1,241 +0,0 @@
-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);
- }
-
- // now, let's remove trailing newLines (so small amounts of text are displayed correctly)
- // @since 1.0.2
-
- final int length = impl.length();
- if (length > 0) {
- int amount = 0;
- for (int i = length - 1; i >= 0; i--) {
- if (Character.isWhitespace(impl.charAt(i))) {
- amount += 1;
- } else {
- break;
- }
- }
- if (amount > 0) {
- impl.replace(length - amount, length, "");
- }
- }
-
- 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);
- final int length = spans != null
- ? spans.length
- : 0;
-
- if (length > 0) {
- if (reverse) {
- Object o;
- for (int i = length - 1; i >= 0; i--) {
- o = spans[i];
- setSpan(
- o,
- index + spanned.getSpanStart(o),
- index + spanned.getSpanEnd(o),
- spanned.getSpanFlags(o)
- );
- }
- } else {
- Object o;
- for (int i = 0; i < length; i++) {
- o = spans[i];
- 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;
- }
- }
-}
diff --git a/library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java b/library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java
deleted file mode 100644
index 7b29440b..00000000
--- a/library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index 3fd7f566..00000000
--- a/library/src/main/java/ru/noties/markwon/SpannedReversed.java
+++ /dev/null
@@ -1,9 +0,0 @@
-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/renderer/SpannableMarkdownVisitor.java b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
index b984ab71..0b5886d6 100644
--- a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
+++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
@@ -2,6 +2,7 @@ package ru.noties.markwon.renderer;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@@ -39,7 +40,7 @@ import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
-import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.ReverseSpannableStringBuilder;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.SpannableFactory;
import ru.noties.markwon.renderer.html.SpannableHtmlParser;
@@ -52,7 +53,7 @@ import ru.noties.markwon.tasklist.TaskListItem;
public class SpannableMarkdownVisitor extends AbstractVisitor {
private final SpannableConfiguration configuration;
- private final SpannableBuilder builder;
+ private final SpannableStringBuilder builder;
private final Deque htmlInlineItems;
private final SpannableTheme theme;
@@ -67,7 +68,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
public SpannableMarkdownVisitor(
@NonNull SpannableConfiguration configuration,
- @NonNull SpannableBuilder builder
+ @NonNull SpannableStringBuilder builder
) {
this.configuration = configuration;
this.builder = builder;
@@ -363,10 +364,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
if (pendingTableRow == null) {
pendingTableRow = new ArrayList<>(2);
}
-
pendingTableRow.add(new TableRowSpan.Cell(
tableCellAlignment(cell.getAlignment()),
- builder.removeFromEnd(length)
+ removeFromEnd(length)
));
tableRowIsHeader = cell.isHeader();
@@ -378,6 +378,22 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
return handled;
}
+ @NonNull
+ private CharSequence removeFromEnd(int start) {
+
+ // this method is not intended to be used by clients
+ // it's a workaround to support tables
+
+ final int end = builder.length();
+
+ // as we do not expose builder and do no apply spans to it, we are safe to NOT to convert to String
+ final SpannableStringBuilder impl = new ReverseSpannableStringBuilder(builder, start, end);
+ builder.delete(start, end);
+
+ return impl;
+ }
+
+
@Override
public void visit(Paragraph paragraph) {
@@ -501,7 +517,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
private void newLine() {
if (builder.length() > 0
- && '\n' != builder.lastChar()) {
+ && '\n' != builder.charAt(builder.length() - 1)) {
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 32d620dd..15acf2a3 100644
--- a/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java
+++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java
@@ -1,18 +1,19 @@
package ru.noties.markwon.renderer;
import android.support.annotation.NonNull;
+import android.text.SpannableStringBuilder;
import org.commonmark.node.Node;
-import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.ReverseSpannableStringBuilder;
import ru.noties.markwon.SpannableConfiguration;
public class SpannableRenderer {
@NonNull
public CharSequence render(@NonNull SpannableConfiguration configuration, @NonNull Node node) {
- final SpannableBuilder builder = new SpannableBuilder();
+ final SpannableStringBuilder builder = new ReverseSpannableStringBuilder();
node.accept(new SpannableMarkdownVisitor(configuration, builder));
- return builder.text();
+ return builder;
}
}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java
index d373ff75..e6c6b8c4 100644
--- a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java
@@ -1,25 +1,25 @@
package ru.noties.markwon.sample.extension;
import android.support.annotation.NonNull;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
-import android.widget.TextView;
import org.commonmark.node.CustomNode;
-import ru.noties.markwon.SpannableBuilder;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
@SuppressWarnings("WeakerAccess")
public class IconVisitor extends SpannableMarkdownVisitor {
- private final SpannableBuilder builder;
+ private final SpannableStringBuilder builder;
private final IconSpanProvider iconSpanProvider;
public IconVisitor(
@NonNull SpannableConfiguration configuration,
- @NonNull SpannableBuilder builder,
+ @NonNull SpannableStringBuilder builder,
@NonNull IconSpanProvider iconSpanProvider
) {
super(configuration, builder);
@@ -51,7 +51,7 @@ public class IconVisitor extends SpannableMarkdownVisitor {
final int length = builder.length();
builder.append(name);
- builder.setSpan(iconSpanProvider.provide(name, color, size), length);
+ builder.setSpan(iconSpanProvider.provide(name, color, size), length, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
builder.append(' ');
return true;
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java
index 19a69704..bf705fb3 100644
--- a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java
@@ -3,6 +3,7 @@ package ru.noties.markwon.sample.extension;
import android.app.Activity;
import android.graphics.Typeface;
import android.os.Bundle;
+import android.text.SpannableStringBuilder;
import android.widget.TextView;
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
@@ -12,7 +13,7 @@ import org.commonmark.parser.Parser;
import java.util.Arrays;
-import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.ReverseSpannableStringBuilder;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.spans.SpannableTheme;
import ru.noties.markwon.tasklist.TaskListExtension;
@@ -45,7 +46,7 @@ public class MainActivity extends Activity {
final Node node = parser.parse(markdown);
- final SpannableBuilder builder = new SpannableBuilder();
+ final SpannableStringBuilder builder = new ReverseSpannableStringBuilder();
// please note that here I am passing `0` as fallback it means that if markdown references
// unknown icon, it will try to load fallback one and will fail with ResourceNotFound. It's
@@ -70,6 +71,6 @@ public class MainActivity extends Activity {
node.accept(visitor);
// apply
- textView.setText(builder.text());
+ textView.setText(builder);
}
}