From 9f532df75279bbebcd9d15b1691039a077b00734 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sat, 21 Jul 2018 13:35:49 +0300 Subject: [PATCH] Switch to use SpannableFactory --- .../markwon/SpannableConfiguration.java | 22 ++++ .../ru/noties/markwon/SpannableFactory.java | 73 +++++++++++ .../noties/markwon/SpannableFactoryDef.java | 121 ++++++++++++++++++ .../renderer/SpannableMarkdownVisitor.java | 87 +++++++------ 4 files changed, 259 insertions(+), 44 deletions(-) create mode 100644 library/src/main/java/ru/noties/markwon/SpannableFactory.java create mode 100644 library/src/main/java/ru/noties/markwon/SpannableFactoryDef.java diff --git a/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java b/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java index 3aa18cb1..73c8e57b 100644 --- a/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java +++ b/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java @@ -31,6 +31,7 @@ public class SpannableConfiguration { private final UrlProcessor urlProcessor; private final SpannableHtmlParser htmlParser; private final ImageSizeResolver imageSizeResolver; + private final SpannableFactory spannableFactory; // @since 1.1.0 private SpannableConfiguration(@NonNull Builder builder) { this.theme = builder.theme; @@ -40,6 +41,7 @@ public class SpannableConfiguration { this.urlProcessor = builder.urlProcessor; this.htmlParser = builder.htmlParser; this.imageSizeResolver = builder.imageSizeResolver; + this.spannableFactory = builder.spannableFactory; } @NonNull @@ -77,6 +79,11 @@ public class SpannableConfiguration { return imageSizeResolver; } + @NonNull + public SpannableFactory factory() { + return spannableFactory; + } + @SuppressWarnings("unused") public static class Builder { @@ -88,6 +95,7 @@ public class SpannableConfiguration { private UrlProcessor urlProcessor; private SpannableHtmlParser htmlParser; private ImageSizeResolver imageSizeResolver; + private SpannableFactory spannableFactory; Builder(@NonNull Context context) { this.context = context; @@ -138,6 +146,15 @@ public class SpannableConfiguration { return this; } + /** + * @since 1.1.0 + */ + @NonNull + public Builder spannableFactory(@NonNull SpannableFactory spannableFactory) { + this.spannableFactory = spannableFactory; + return this; + } + @NonNull public SpannableConfiguration build() { @@ -165,6 +182,11 @@ public class SpannableConfiguration { imageSizeResolver = new ImageSizeResolverDef(); } + // @since 1.1.0 + if (spannableFactory == null) { + spannableFactory = SpannableFactoryDef.create(); + } + if (htmlParser == null) { htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver, imageSizeResolver); } diff --git a/library/src/main/java/ru/noties/markwon/SpannableFactory.java b/library/src/main/java/ru/noties/markwon/SpannableFactory.java new file mode 100644 index 00000000..a9dbcd04 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/SpannableFactory.java @@ -0,0 +1,73 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; + +import ru.noties.markwon.renderer.ImageSize; +import ru.noties.markwon.renderer.ImageSizeResolver; +import ru.noties.markwon.spans.AsyncDrawable; +import ru.noties.markwon.spans.LinkSpan; +import ru.noties.markwon.spans.SpannableTheme; +import ru.noties.markwon.spans.TableRowSpan; + +/** + * Each method can return null or a Span object or an array of spans + * + * @since 1.1.0 + */ +public interface SpannableFactory { + + @Nullable + Object strongEmphasis(); + + @Nullable + Object emphasis(); + + @Nullable + Object blockQuote(@NonNull SpannableTheme theme); + + @Nullable + Object code(@NonNull SpannableTheme theme, boolean multiline); + + @Nullable + Object orderedListItem(@NonNull SpannableTheme theme, int startNumber); + + @Nullable + Object bulletListItem(@NonNull SpannableTheme theme, int level); + + @Nullable + Object thematicBreak(@NonNull SpannableTheme theme); + + @Nullable + Object heading(@NonNull SpannableTheme theme, int level); + + @Nullable + Object strikethrough(); + + @Nullable + Object taskListItem(@NonNull SpannableTheme theme, int blockIndent, boolean isDone); + + @Nullable + Object tableRow( + @NonNull SpannableTheme theme, + @NonNull List cells, + boolean isHeader, + boolean isOdd); + + @Nullable + Object image( + @NonNull SpannableTheme theme, + @NonNull String destination, + @NonNull AsyncDrawable.Loader loader, + @NonNull ImageSizeResolver imageSizeResolver, + @Nullable ImageSize imageSize, + boolean replacementTextIsLink); + + @Nullable + Object link( + @NonNull SpannableTheme theme, + @NonNull String destination, + @NonNull LinkSpan.Resolver resolver); +} diff --git a/library/src/main/java/ru/noties/markwon/SpannableFactoryDef.java b/library/src/main/java/ru/noties/markwon/SpannableFactoryDef.java new file mode 100644 index 00000000..2ff060e3 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/SpannableFactoryDef.java @@ -0,0 +1,121 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.style.StrikethroughSpan; + +import java.util.List; + +import ru.noties.markwon.renderer.ImageSize; +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.SpannableTheme; +import ru.noties.markwon.spans.StrongEmphasisSpan; +import ru.noties.markwon.spans.TableRowSpan; +import ru.noties.markwon.spans.TaskListSpan; +import ru.noties.markwon.spans.ThematicBreakSpan; + +public class SpannableFactoryDef implements SpannableFactory { + + @NonNull + public static SpannableFactoryDef create() { + return new SpannableFactoryDef(); + } + + @Nullable + @Override + public Object strongEmphasis() { + return new StrongEmphasisSpan(); + } + + @Nullable + @Override + public Object emphasis() { + return new EmphasisSpan(); + } + + @Nullable + @Override + public Object blockQuote(@NonNull SpannableTheme theme) { + return new BlockQuoteSpan(theme); + } + + @Nullable + @Override + public Object code(@NonNull SpannableTheme theme, boolean multiline) { + return new CodeSpan(theme, multiline); + } + + @Nullable + @Override + public Object orderedListItem(@NonNull SpannableTheme theme, int startNumber) { + // todo| in order to provide real RTL experience there must be a way to provide this string + return new OrderedListItemSpan(theme, String.valueOf(startNumber) + "." + '\u00a0'); + } + + @Nullable + @Override + public Object bulletListItem(@NonNull SpannableTheme theme, int level) { + return new BulletListItemSpan(theme, level); + } + + @Nullable + @Override + public Object thematicBreak(@NonNull SpannableTheme theme) { + return new ThematicBreakSpan(theme); + } + + @Nullable + @Override + public Object heading(@NonNull SpannableTheme theme, int level) { + return new HeadingSpan(theme, level); + } + + @Nullable + @Override + public Object strikethrough() { + return new StrikethroughSpan(); + } + + @Nullable + @Override + public Object taskListItem(@NonNull SpannableTheme theme, int blockIndent, boolean isDone) { + return new TaskListSpan(theme, blockIndent, isDone); + } + + @Nullable + @Override + public Object tableRow(@NonNull SpannableTheme theme, @NonNull List cells, boolean isHeader, boolean isOdd) { + return new TableRowSpan(theme, cells, isHeader, isOdd); + } + + @Nullable + @Override + public Object image(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) { + return new AsyncDrawableSpan( + theme, + new AsyncDrawable( + destination, + loader, + imageSizeResolver, + imageSize + ), + AsyncDrawableSpan.ALIGN_BOTTOM, + replacementTextIsLink + ); + } + + @Nullable + @Override + public Object link(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull LinkSpan.Resolver resolver) { + return new LinkSpan(theme, destination, resolver); + } +} 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..3898f5d0 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java +++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java @@ -42,6 +42,7 @@ import java.util.List; import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.SpannableFactory; import ru.noties.markwon.renderer.html.SpannableHtmlParser; import ru.noties.markwon.spans.AsyncDrawable; import ru.noties.markwon.spans.AsyncDrawableSpan; @@ -52,6 +53,7 @@ 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.SpannableTheme; import ru.noties.markwon.spans.StrongEmphasisSpan; import ru.noties.markwon.spans.TableRowSpan; import ru.noties.markwon.spans.TaskListSpan; @@ -66,6 +68,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { private final SpannableBuilder builder; private final Deque htmlInlineItems; + private final SpannableTheme theme; + private final SpannableFactory factory; + private int blockQuoteIndent; private int listLevel; @@ -80,6 +85,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { this.configuration = configuration; this.builder = builder; this.htmlInlineItems = new ArrayDeque<>(2); + + this.theme = configuration.theme(); + this.factory = configuration.factory(); } @Override @@ -91,14 +99,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { public void visit(StrongEmphasis strongEmphasis) { final int length = builder.length(); visitChildren(strongEmphasis); - setSpan(length, new StrongEmphasisSpan()); + setSpan(length, factory.strongEmphasis()); } @Override public void visit(Emphasis emphasis) { final int length = builder.length(); visitChildren(emphasis); - setSpan(length, new EmphasisSpan()); + setSpan(length, factory.emphasis()); } @Override @@ -115,7 +123,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { visitChildren(blockQuote); - setSpan(length, new BlockQuoteSpan(configuration.theme())); + setSpan(length, factory.blockQuote(theme)); blockQuoteIndent -= 1; @@ -136,10 +144,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { builder.append(code.getLiteral()); builder.append('\u00a0'); - setSpan(length, new CodeSpan( - configuration.theme(), - false - )); + setSpan(length, factory.code(theme, false)); } @Override @@ -174,10 +179,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { ); builder.append('\u00a0').append('\n'); - setSpan(length, new CodeSpan( - configuration.theme(), - true - )); + setSpan(length, factory.code(theme, true)); newLine(); builder.append('\n'); @@ -217,11 +219,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, factory.orderedListItem(theme, start)); // after we have visited the children increment start number final OrderedList orderedList = (OrderedList) parent; @@ -231,10 +229,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { visitChildren(listItem); - setSpan(length, new BulletListItemSpan( - configuration.theme(), - listLevel - 1 - )); + setSpan(length, factory.bulletListItem(theme, listLevel - 1)); } blockQuoteIndent -= 1; @@ -250,7 +245,8 @@ 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, factory.thematicBreak(theme)); newLine(); builder.append('\n'); @@ -263,7 +259,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { final int length = builder.length(); visitChildren(heading); - setSpan(length, new HeadingSpan(configuration.theme(), heading.getLevel())); + setSpan(length, factory.heading(theme, heading.getLevel())); newLine(); @@ -305,7 +301,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { final int length = builder.length(); visitChildren(customNode); - setSpan(length, new StrikethroughSpan()); + setSpan(length, factory.strikethrough()); } else if (customNode instanceof TaskListItem) { @@ -319,11 +315,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { visitChildren(customNode); - setSpan(length, new TaskListSpan( - configuration.theme(), - blockQuoteIndent, - listItem.done() - )); + setSpan(length, factory.taskListItem(theme, blockQuoteIndent, listItem.done())); newLine(); @@ -356,12 +348,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { // trimmed from the final result builder.append('\u00a0'); - final TableRowSpan span = new TableRowSpan( - configuration.theme(), + final Object span = factory.tableRow( + theme, pendingTableRow, tableRowIsHeader, - tableRows % 2 == 1 - ); + tableRows % 2 == 1); tableRows = tableRowIsHeader ? 0 @@ -434,15 +425,12 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { setSpan( length, - new AsyncDrawableSpan( - configuration.theme(), - new AsyncDrawable( - destination, - configuration.asyncDrawableLoader(), - configuration.imageSizeResolver(), - null - ), - AsyncDrawableSpan.ALIGN_BOTTOM, + factory.image( + theme, + destination, + configuration.asyncDrawableLoader(), + configuration.imageSizeResolver(), + null, link ) ); @@ -504,11 +492,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, factory.link(theme, destination, configuration.linkResolver())); } - private void setSpan(int start, @NonNull Object span) { - builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + private void setSpan(int start, @Nullable Object span) { + if (span != null) { + + final int length = builder.length(); + + if (span.getClass().isArray()) { + for (Object o: ((Object[]) span)) { + builder.setSpan(o, start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } else { + builder.setSpan(span, start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } } private void newLine() {