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/SpanFactoryDef.java b/library/src/main/java/ru/noties/markwon/SpanFactoryDef.java
new file mode 100644
index 00000000..ba4a185a
--- /dev/null
+++ b/library/src/main/java/ru/noties/markwon/SpanFactoryDef.java
@@ -0,0 +1,127 @@
+package ru.noties.markwon;
+
+import android.support.annotation.NonNull;
+import android.text.style.StrikethroughSpan;
+
+import ru.noties.markwon.renderer.ImageSizeResolver;
+import ru.noties.markwon.spans.AsyncDrawable;
+import ru.noties.markwon.spans.AsyncDrawableSpan;
+import ru.noties.markwon.spans.BlockQuoteSpan;
+import ru.noties.markwon.spans.BulletListItemSpan;
+import ru.noties.markwon.spans.CodeSpan;
+import ru.noties.markwon.spans.EmphasisSpan;
+import ru.noties.markwon.spans.HeadingSpan;
+import ru.noties.markwon.spans.LinkSpan;
+import ru.noties.markwon.spans.OrderedListItemSpan;
+import ru.noties.markwon.spans.SpanFactory;
+import ru.noties.markwon.spans.SpannableTheme;
+import ru.noties.markwon.spans.StrongEmphasisSpan;
+import ru.noties.markwon.spans.TaskListSpan;
+import ru.noties.markwon.spans.ThematicBreakSpan;
+
+public class SpanFactoryDef implements SpanFactory {
+ @NonNull
+ private final SpannableTheme theme;
+
+ @NonNull
+ private final LinkSpan.Resolver linkResolver;
+
+ @NonNull
+ private final AsyncDrawable.Loader drawableLoader;
+
+ @NonNull
+ private final ImageSizeResolver imageSizeResolver;
+
+ public SpanFactoryDef(@NonNull SpannableTheme theme,
+ @NonNull LinkSpan.Resolver linkResolver,
+ @NonNull AsyncDrawable.Loader drawableLoader,
+ @NonNull ImageSizeResolver imageSizeResolver) {
+ this.theme = theme;
+ this.linkResolver = linkResolver;
+ this.drawableLoader = drawableLoader;
+ this.imageSizeResolver = imageSizeResolver;
+ }
+
+ @NonNull
+ @Override
+ public Object createBlockQuote() {
+ return new BlockQuoteSpan(theme);
+ }
+
+ @NonNull
+ @Override
+ public Object createBulletListItem(int level) {
+ return new BulletListItemSpan(theme, level);
+ }
+
+ @NonNull
+ @Override
+ public Object createCode(boolean multiline) {
+ return new CodeSpan(theme, multiline);
+ }
+
+ @NonNull
+ @Override
+ public Object createEmphasis() {
+ return new EmphasisSpan();
+ }
+
+ @NonNull
+ @Override
+ public Object createHeading(int level) {
+ return new HeadingSpan(theme, level);
+ }
+
+ @NonNull
+ @Override
+ public Object createImage(@NonNull String destination, boolean link) {
+ return new AsyncDrawableSpan(
+ theme,
+ new AsyncDrawable(
+ destination,
+ drawableLoader,
+ imageSizeResolver,
+ null
+ ),
+ AsyncDrawableSpan.ALIGN_BOTTOM,
+ link
+ );
+ }
+
+ @NonNull
+ @Override
+ public Object createLink(@NonNull String destination) {
+ return new LinkSpan(theme, destination, linkResolver);
+ }
+
+ @NonNull
+ @Override
+ public Object createOrderedListItem(int order) {
+ // todo| in order to provide real RTL experience there must be a way to provide this string
+ return new OrderedListItemSpan(theme, String.valueOf(order) + "." + '\u00a0');
+ }
+
+ @NonNull
+ @Override
+ public Object createStrikethrough() {
+ return new StrikethroughSpan();
+ }
+
+ @NonNull
+ @Override
+ public Object createStrongEmphasis() {
+ return new StrongEmphasisSpan();
+ }
+
+ @NonNull
+ @Override
+ public Object createTaskList(int indent, boolean done) {
+ return new TaskListSpan(theme, indent, done);
+ }
+
+ @NonNull
+ @Override
+ public Object createThematicBreak() {
+ return new ThematicBreakSpan(theme);
+ }
+}
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 95570900..00000000
--- a/library/src/main/java/ru/noties/markwon/SpannableBuilder.java
+++ /dev/null
@@ -1,245 +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);
-
- 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 3aa18cb1..8dbe3304 100644
--- a/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java
+++ b/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java
@@ -8,6 +8,7 @@ import ru.noties.markwon.renderer.ImageSizeResolverDef;
import ru.noties.markwon.renderer.html.SpannableHtmlParser;
import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan;
+import ru.noties.markwon.spans.SpanFactory;
import ru.noties.markwon.spans.SpannableTheme;
@SuppressWarnings("WeakerAccess")
@@ -31,6 +32,7 @@ public class SpannableConfiguration {
private final UrlProcessor urlProcessor;
private final SpannableHtmlParser htmlParser;
private final ImageSizeResolver imageSizeResolver;
+ private final SpanFactory spanFactory;
private SpannableConfiguration(@NonNull Builder builder) {
this.theme = builder.theme;
@@ -40,6 +42,7 @@ public class SpannableConfiguration {
this.urlProcessor = builder.urlProcessor;
this.htmlParser = builder.htmlParser;
this.imageSizeResolver = builder.imageSizeResolver;
+ this.spanFactory = builder.spanFactory;
}
@NonNull
@@ -77,6 +80,11 @@ public class SpannableConfiguration {
return imageSizeResolver;
}
+ @NonNull
+ public SpanFactory spanFactory() {
+ return spanFactory;
+ }
+
@SuppressWarnings("unused")
public static class Builder {
@@ -88,6 +96,7 @@ public class SpannableConfiguration {
private UrlProcessor urlProcessor;
private SpannableHtmlParser htmlParser;
private ImageSizeResolver imageSizeResolver;
+ private SpanFactory spanFactory;
Builder(@NonNull Context context) {
this.context = context;
@@ -138,6 +147,12 @@ public class SpannableConfiguration {
return this;
}
+ @NonNull
+ public Builder spanFactory(@NonNull SpanFactory spanFactory) {
+ this.spanFactory = spanFactory;
+ return this;
+ }
+
@NonNull
public SpannableConfiguration build() {
@@ -165,6 +180,10 @@ public class SpannableConfiguration {
imageSizeResolver = new ImageSizeResolverDef();
}
+ if (spanFactory == null) {
+ spanFactory = new SpanFactoryDef(theme, linkResolver, asyncDrawableLoader, imageSizeResolver);
+ }
+
if (htmlParser == null) {
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver, imageSizeResolver);
}
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 ceb22ef0..8ee7a4b3 100644
--- a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
+++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
@@ -2,9 +2,9 @@ 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;
-import android.text.style.StrikethroughSpan;
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
import org.commonmark.ext.gfm.tables.TableBody;
@@ -40,22 +40,11 @@ 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.renderer.html.SpannableHtmlParser;
-import ru.noties.markwon.spans.AsyncDrawable;
-import ru.noties.markwon.spans.AsyncDrawableSpan;
-import ru.noties.markwon.spans.BlockQuoteSpan;
-import ru.noties.markwon.spans.BulletListItemSpan;
-import ru.noties.markwon.spans.CodeSpan;
-import ru.noties.markwon.spans.EmphasisSpan;
-import ru.noties.markwon.spans.HeadingSpan;
-import ru.noties.markwon.spans.LinkSpan;
-import ru.noties.markwon.spans.OrderedListItemSpan;
-import ru.noties.markwon.spans.StrongEmphasisSpan;
+import ru.noties.markwon.spans.SpanFactory;
import ru.noties.markwon.spans.TableRowSpan;
-import ru.noties.markwon.spans.TaskListSpan;
-import ru.noties.markwon.spans.ThematicBreakSpan;
import ru.noties.markwon.tasklist.TaskListBlock;
import ru.noties.markwon.tasklist.TaskListItem;
@@ -63,7 +52,8 @@ import ru.noties.markwon.tasklist.TaskListItem;
public class SpannableMarkdownVisitor extends AbstractVisitor {
private final SpannableConfiguration configuration;
- private final SpannableBuilder builder;
+ private final SpanFactory spanFactory;
+ private final SpannableStringBuilder builder;
private final Deque htmlInlineItems;
private int blockQuoteIndent;
@@ -75,9 +65,10 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
public SpannableMarkdownVisitor(
@NonNull SpannableConfiguration configuration,
- @NonNull SpannableBuilder builder
+ @NonNull SpannableStringBuilder builder
) {
this.configuration = configuration;
+ this.spanFactory = configuration.spanFactory();
this.builder = builder;
this.htmlInlineItems = new ArrayDeque<>(2);
}
@@ -91,14 +82,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
public void visit(StrongEmphasis strongEmphasis) {
final int length = builder.length();
visitChildren(strongEmphasis);
- setSpan(length, new StrongEmphasisSpan());
+ setSpan(length, spanFactory.createStrongEmphasis());
}
@Override
public void visit(Emphasis emphasis) {
final int length = builder.length();
visitChildren(emphasis);
- setSpan(length, new EmphasisSpan());
+ setSpan(length, spanFactory.createEmphasis());
}
@Override
@@ -115,7 +106,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(blockQuote);
- setSpan(length, new BlockQuoteSpan(configuration.theme()));
+ setSpan(length, spanFactory.createBlockQuote());
blockQuoteIndent -= 1;
@@ -136,10 +127,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
builder.append(code.getLiteral());
builder.append('\u00a0');
- setSpan(length, new CodeSpan(
- configuration.theme(),
- false
- ));
+ setSpan(length, spanFactory.createCode(false));
}
@Override
@@ -174,10 +162,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
);
builder.append('\u00a0').append('\n');
- setSpan(length, new CodeSpan(
- configuration.theme(),
- true
- ));
+ setSpan(length, spanFactory.createCode(true));
newLine();
builder.append('\n');
@@ -217,11 +202,7 @@ 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'
- ));
+ setSpan(length, spanFactory.createOrderedListItem(start));
// after we have visited the children increment start number
final OrderedList orderedList = (OrderedList) parent;
@@ -231,10 +212,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(listItem);
- setSpan(length, new BulletListItemSpan(
- configuration.theme(),
- listLevel - 1
- ));
+ setSpan(length, spanFactory.createBulletListItem(listLevel - 1));
}
blockQuoteIndent -= 1;
@@ -250,7 +228,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
builder.append(' '); // without space it won't render
- setSpan(length, new ThematicBreakSpan(configuration.theme()));
+ setSpan(length, spanFactory.createThematicBreak());
newLine();
builder.append('\n');
@@ -263,7 +241,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
visitChildren(heading);
- setSpan(length, new HeadingSpan(configuration.theme(), heading.getLevel()));
+ setSpan(length, spanFactory.createHeading(heading.getLevel()));
newLine();
@@ -305,7 +283,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
visitChildren(customNode);
- setSpan(length, new StrikethroughSpan());
+ setSpan(length, spanFactory.createStrikethrough());
} else if (customNode instanceof TaskListItem) {
@@ -319,11 +297,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(customNode);
- setSpan(length, new TaskListSpan(
- configuration.theme(),
- blockQuoteIndent,
- listItem.done()
- ));
+ setSpan(length, spanFactory.createTaskList(blockQuoteIndent, listItem.done()));
newLine();
@@ -381,10 +355,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();
@@ -396,6 +369,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) {
@@ -432,20 +421,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final boolean link = parent != null && parent instanceof Link;
final String destination = configuration.urlProcessor().process(image.getDestination());
- setSpan(
- length,
- new AsyncDrawableSpan(
- configuration.theme(),
- new AsyncDrawable(
- destination,
- configuration.asyncDrawableLoader(),
- configuration.imageSizeResolver(),
- null
- ),
- AsyncDrawableSpan.ALIGN_BOTTOM,
- link
- )
- );
+ setSpan(length, spanFactory.createImage(destination, link));
// todo, maybe, if image is not inside a link, we should make it clickable, so
// user can open it in external viewer?
@@ -504,16 +480,22 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
visitChildren(link);
final String destination = configuration.urlProcessor().process(link.getDestination());
- setSpan(length, new LinkSpan(configuration.theme(), destination, configuration.linkResolver()));
+ setSpan(length, spanFactory.createLink(destination));
}
- private void setSpan(int start, @NonNull Object span) {
- builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ private void setSpan(int start, @NonNull Object spans) {
+ if (spans instanceof Object[]) {
+ for (final Object span : (Object[]) spans) {
+ builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ } else {
+ builder.setSpan(spans, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
}
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/library/src/main/java/ru/noties/markwon/spans/SpanFactory.java b/library/src/main/java/ru/noties/markwon/spans/SpanFactory.java
new file mode 100644
index 00000000..5a605763
--- /dev/null
+++ b/library/src/main/java/ru/noties/markwon/spans/SpanFactory.java
@@ -0,0 +1,42 @@
+package ru.noties.markwon.spans;
+
+import android.support.annotation.NonNull;
+
+public interface SpanFactory {
+ @NonNull
+ Object createBlockQuote();
+
+ @NonNull
+ Object createBulletListItem(int level);
+
+ @NonNull
+ Object createCode(boolean multiline);
+
+ @NonNull
+ Object createEmphasis();
+
+ @NonNull
+ Object createHeading(int level);
+
+ @NonNull
+ Object createImage(@NonNull String destination, boolean link);
+
+ @NonNull
+ Object createLink(@NonNull String destination);
+
+ @NonNull
+ Object createOrderedListItem(int order);
+
+ @NonNull
+ Object createStrongEmphasis();
+
+ @NonNull
+ Object createStrikethrough();
+
+ @NonNull
+ Object createTaskList(int indent, boolean done);
+
+ @NonNull
+ Object createThematicBreak();
+
+}
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 a81f8eb0..64e994b3 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
@@ -2,6 +2,7 @@ package ru.noties.markwon.sample.extension;
import android.app.Activity;
import android.os.Bundle;
+import android.text.SpannableStringBuilder;
import android.widget.TextView;
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
@@ -11,7 +12,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.tasklist.TaskListExtension;
@@ -43,7 +44,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
@@ -61,6 +62,6 @@ public class MainActivity extends Activity {
node.accept(visitor);
// apply
- textView.setText(builder.text());
+ textView.setText(builder);
}
}