From 7f5db84bbe7f99e38e389c337455d7a5d10e4f75 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 11 May 2017 21:03:46 +0300 Subject: [PATCH] Bullet & ordered lists --- app/src/main/assets/test.md | 41 +++++ .../renderer/SpannableConfiguration.java | 21 ++- .../renderer/SpannableMarkdownVisitor.java | 172 ++++++++++++------ .../markwon/spans/BulletListItemSpan.java | 23 ++- .../markwon/spans/OrderedListItemSpan.java | 66 +++++++ .../spans/SimpleLeadingMarginSpan.java | 25 +++ 6 files changed, 283 insertions(+), 65 deletions(-) create mode 100644 library-spans/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java create mode 100644 library-spans/src/main/java/ru/noties/markwon/spans/SimpleLeadingMarginSpan.java diff --git a/app/src/main/assets/test.md b/app/src/main/assets/test.md index 55aaab47..9957a19f 100644 --- a/app/src/main/assets/test.md +++ b/app/src/main/assets/test.md @@ -13,9 +13,15 @@

Yo! Omg + ddffdg

+1. First +2. Second +3. Third +* Interesting +4. Forth ## Unordered list @@ -35,6 +41,41 @@ ddffdg * it's also here 2. and this 3. and that + 1. Another one nested this time and a lot of text here, well, at least some to check how multiline will be handled + 1. And it goes on and on + 2. And it goes on and on + 3. And it goes on and on + 4. And it goes on and on + 5. And it goes on and on + 6. And it goes on and on + 7. And it goes on and on + 8. And it goes on and on + 9. And it goes on and on + 10. And it goes on and on + 11. And it goes on and on + 12. And it goes on and on + 13. And it goes on and on + 14. And it goes on and on + 15. And it goes on and on + 16. And it goes on and on + 17. And it goes on and on + 18. And it goes on and on + 19. And it goes on and on + 20. And it goes on and on + 21. And it goes on and on + 22. And it goes on and on + 23. And it goes on and on + 24. And it goes on and on + 25. And it goes on and on + 26. And it goes on and on + 27. And it goes on and on + 28. And it goes on and on + 29. And it goes on and on + 30. And it goes on and on + 31. And it goes on and on + 32. And it goes on and on + 33. And it goes on and on + ### Quoted list 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 13f53f26..58a0225b 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.OrderedListItemSpan; import ru.noties.markwon.spans.ThematicBreakSpan; public class SpannableConfiguration { @@ -25,6 +26,7 @@ public class SpannableConfiguration { private final BulletListItemSpan.Config bulletListConfig; private final HeadingSpan.Config headingConfig; private final ThematicBreakSpan.Config thematicConfig; + private final OrderedListItemSpan.Config orderedListConfig; private SpannableConfiguration(Builder builder) { this.blockQuoteConfig = builder.blockQuoteConfig; @@ -32,6 +34,7 @@ public class SpannableConfiguration { this.bulletListConfig = builder.bulletListConfig; this.headingConfig = builder.headingConfig; this.thematicConfig = builder.thematicConfig; + this.orderedListConfig = builder.orderedListConfig; } public BlockQuoteSpan.Config getBlockQuoteConfig() { @@ -54,6 +57,10 @@ public class SpannableConfiguration { return thematicConfig; } + public OrderedListItemSpan.Config getOrderedListConfig() { + return orderedListConfig; + } + public static class Builder { private final Context context; @@ -62,6 +69,7 @@ public class SpannableConfiguration { private BulletListItemSpan.Config bulletListConfig; private HeadingSpan.Config headingConfig; private ThematicBreakSpan.Config thematicConfig; + private OrderedListItemSpan.Config orderedListConfig; public Builder(Context context) { this.context = context; @@ -92,11 +100,17 @@ public class SpannableConfiguration { return this; } + public Builder setOrderedListConfig(@NonNull OrderedListItemSpan.Config orderedListConfig) { + this.orderedListConfig = orderedListConfig; + return this; + } + // todo, change to something more reliable + // todo, must mention that bullet/ordered/quote must have the same margin (or maybe we can just enforce it?) public SpannableConfiguration build() { if (blockQuoteConfig == null) { blockQuoteConfig = new BlockQuoteSpan.Config( - px(16), + px(24), 0, 0 ); @@ -107,7 +121,7 @@ public class SpannableConfiguration { .build(); } if (bulletListConfig == null) { - bulletListConfig = new BulletListItemSpan.Config(0, px(16), px(1)); + bulletListConfig = new BulletListItemSpan.Config(px(24), 0, px(8), px(1)); } if (headingConfig == null) { headingConfig = new HeadingSpan.Config(px(1), 0); @@ -115,6 +129,9 @@ public class SpannableConfiguration { if (thematicConfig == null) { thematicConfig = new ThematicBreakSpan.Config(0, px(2)); } + if (orderedListConfig == null) { + orderedListConfig = new OrderedListItemSpan.Config(px(24), 0); + } 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 26c09d24..dea44e6f 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 @@ -16,7 +16,6 @@ import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.HardLineBreak; import org.commonmark.node.Heading; import org.commonmark.node.HtmlBlock; -import org.commonmark.node.Image; import org.commonmark.node.ListBlock; import org.commonmark.node.ListItem; import org.commonmark.node.Node; @@ -33,6 +32,8 @@ 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.OrderedListItemSpan; +import ru.noties.markwon.spans.SimpleLeadingMarginSpan; import ru.noties.markwon.spans.StrongEmphasisSpan; import ru.noties.markwon.spans.ThematicBreakSpan; @@ -54,13 +55,13 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(Text text) { - Debug.i(text); +// Debug.i(text); builder.append(text.getLiteral()); } @Override public void visit(StrongEmphasis strongEmphasis) { - Debug.i(strongEmphasis); +// Debug.i(strongEmphasis); final int length = builder.length(); visitChildren(strongEmphasis); setSpan(length, new StrongEmphasisSpan()); @@ -68,7 +69,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(Emphasis emphasis) { - Debug.i(emphasis); +// Debug.i(emphasis); final int length = builder.length(); visitChildren(emphasis); setSpan(length, new EmphasisSpan()); @@ -77,7 +78,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(BlockQuote blockQuote) { - Debug.i(blockQuote); +// Debug.i(blockQuote); newLine(); if (blockQuoteIndent != 0) { @@ -106,7 +107,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(Code code) { - Debug.i(code); +// Debug.i(code); final int length = builder.length(); @@ -125,7 +126,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(FencedCodeBlock fencedCodeBlock) { - Debug.i(fencedCodeBlock); +// Debug.i(fencedCodeBlock); newLine(); @@ -141,33 +142,35 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { builder.append('\n'); } - @Override - public void visit(Image image) { - - Debug.i(image); - - final int length = builder.length(); - - visitChildren(image); - - if (length == builder.length()) { - // nothing is added, and we need at least one symbol - builder.append(' '); - } - - -//// final int length = builder.length(); -// final TestDrawable drawable = new TestDrawable(); -// final DrawableSpan span = new DrawableSpan(drawable); -// builder.append(" "); -// builder.setSpan(span, length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } +// @Override +// public void visit(Image image) { +// +// Debug.i(image); +// +//// final int length = builder.length(); +// +// visitChildren(image); +// +//// if (length == builder.length()) { +//// // nothing is added, and we need at least one symbol +//// builder.append(' '); +//// } +// } @Override public void visit(BulletList bulletList) { - Debug.i(bulletList); + visitList(bulletList); + } + + @Override + public void visit(OrderedList orderedList) { + visitList(orderedList); + } + + private void visitList(Node node) { + Debug.i(node); newLine(); - visitChildren(bulletList); + visitChildren(node); newLine(); if (listLevel == 0 && blockQuoteIndent == 0) { builder.append('\n'); @@ -180,16 +183,67 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { Debug.i(listItem); final int length = builder.length(); + blockQuoteIndent += 1; listLevel += 1; - visitChildren(listItem); + // todo, can be a bullet list & ordered list (with leading numbers... looks like we need to `draw` numbers... - setSpan(length, new BulletListItemSpan( - configuration.getBulletListConfig(), - blockQuoteIndent, - listLevel - 1, - length - )); + + final Node parent = listItem.getParent(); + if (parent instanceof OrderedList) { + +// // let's build ordered number +// final StringBuilder lead = new StringBuilder(); +// Node p = parent; +// while (p != null && p instanceof OrderedList) { +// lead.insert(0, ((OrderedList) p).getDelimiter()); +// lead.insert(0, ((OrderedList) p).getStartNumber()); +// p = p.getParent(); +// if (p instanceof ListItem) { +// p = p.getParent(); +// } +// } +// +// builder.append(lead) +// .append('\u00a0'); +// +// blockQuoteIndent -= 1; + + final int start = ((OrderedList) parent).getStartNumber(); + + visitChildren(listItem); + + setSpan(length, new OrderedListItemSpan( + configuration.getOrderedListConfig(), + String.valueOf(start) + "." + '\u00a0', + blockQuoteIndent, + length + )); + +// blockQuoteIndent += 1; + +// if (listLevel != 1) { +// setSpan(length, new SimpleLeadingMarginSpan(32)); +// } + + // after we have visited the children increment start number + final OrderedList orderedList = (OrderedList) parent; + orderedList.setStartNumber(orderedList.getStartNumber() + 1); + + } else { + + visitChildren(listItem); + + // if we are inside orderedList increase the margin? + + setSpan(length, new BulletListItemSpan( + configuration.getBulletListConfig(), + blockQuoteIndent, + listLevel - 1, + length + )); + } + blockQuoteIndent -= 1; listLevel -= 1; @@ -199,7 +253,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(ThematicBreak thematicBreak) { - Debug.i(thematicBreak); +// Debug.i(thematicBreak); newLine(); @@ -211,27 +265,10 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { builder.append('\n'); } - @Override - public void visit(OrderedList orderedList) { - - Debug.i(orderedList); - - newLine(); - -// Debug.i(orderedList, orderedList.getDelimiter(), orderedList.getStartNumber()); - // todo, ordering numbers - super.visit(orderedList); - - newLine(); - if (listLevel == 0 && blockQuoteIndent == 0) { - builder.append('\n'); - } - } - @Override public void visit(Heading heading) { - Debug.i(heading); +// Debug.i(heading); newLine(); @@ -264,7 +301,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(CustomNode customNode) { - Debug.i(customNode); +// Debug.i(customNode); if (customNode instanceof Strikethrough) { final int length = builder.length(); @@ -280,7 +317,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { final boolean inTightList = isInTightList(paragraph); - Debug.i(paragraph, inTightList, listLevel); +// Debug.i(paragraph, inTightList, listLevel); if (!inTightList) { newLine(); @@ -326,4 +363,23 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { } return false; } + + private static String dump(Node node) { + final StringBuilder builder = new StringBuilder(); + node.accept(new DumpVisitor(builder)); + return builder.toString(); + } + + private static class DumpVisitor extends AbstractVisitor { + private final StringBuilder builder; + + DumpVisitor(StringBuilder builder) { + this.builder = builder; + } + + @Override + public void visit(Text text) { + builder.append(text.getLiteral()); + } + } } 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 f6c52b38..1aa6b89a 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 @@ -17,14 +17,20 @@ public class BulletListItemSpan implements LeadingMarginSpan { public static class Config { - final int bulletColor; // by default uses text color final int marginWidth; + final int bulletColor; // by default uses text color + final int bulletSide; final int bulletStrokeWidth; // from 0 but it makes sense to provide something wider - public Config(@ColorInt int bulletColor, @IntRange(from = 0) int marginWidth, int bulletStrokeWidth) { - this.bulletColor = bulletColor; + public Config( + @IntRange(from = 0) int marginWidth, + @ColorInt int bulletColor, + @IntRange(from = 0) int bulletSide, + @IntRange(from = 0) int bulletStrokeWidth) { this.marginWidth = marginWidth; + this.bulletColor = bulletColor; + this.bulletSide = bulletSide; this.bulletStrokeWidth = bulletStrokeWidth; } } @@ -58,7 +64,7 @@ public class BulletListItemSpan 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 there was a line break, we don't need to draw it + // if there was a line break, we don't need to draw anything if (this.start != start) { return; } @@ -88,7 +94,14 @@ public class BulletListItemSpan implements LeadingMarginSpan { final int width = config.marginWidth; final int height = bottom - top; - final int side = Math.min(config.marginWidth, height) / 2; + final int min = Math.min(config.marginWidth, height) / 2; + final int side; + if (config.bulletSide == 0 + || config.bulletSide > min) { + side = min; + } else { + side = config.bulletSide; + } final int marginLeft = (width - side) / 2; final int marginTop = (height - side) / 2; diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java b/library-spans/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java new file mode 100644 index 00000000..4d1e0451 --- /dev/null +++ b/library-spans/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java @@ -0,0 +1,66 @@ +package ru.noties.markwon.spans; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.support.annotation.ColorInt; +import android.support.annotation.IntRange; +import android.support.annotation.NonNull; +import android.text.Layout; +import android.text.style.LeadingMarginSpan; + +public class OrderedListItemSpan implements LeadingMarginSpan { + + public static class Config { + + final int marginWidth; // by default 0 + final int numberColor; // by default color of the main text + + public Config(@IntRange(from = 0) int marginWidth, @ColorInt int numberColor) { + this.marginWidth = marginWidth; + this.numberColor = numberColor; + } + } + + private final Config config; + private final String number; + private final int blockIndent; + private final int start; + + public OrderedListItemSpan( + @NonNull Config config, + @NonNull String number, + @IntRange(from = 0) int blockIndent, + @IntRange(from = 0) int start + ) { + this.config = config; + this.number = number; + this.blockIndent = blockIndent; + this.start = start; + } + + @Override + public int getLeadingMargin(boolean first) { + return config.marginWidth; + } + + @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 there was a line break, we don't need to draw anything + if (this.start != start) { + return; + } + + if (config.numberColor != 0) { + p.setColor(config.numberColor); + } + + final int width = config.marginWidth; + final int numberWidth = (int) (p.measureText(number) + .5F); + final int numberX = (width * blockIndent) - numberWidth; + + final int numberY = bottom - ((bottom - top) / 2) - (int) ((p.descent() + p.ascent()) / 2); + + c.drawText(number, numberX, numberY, p); + } +} diff --git a/library-spans/src/main/java/ru/noties/markwon/spans/SimpleLeadingMarginSpan.java b/library-spans/src/main/java/ru/noties/markwon/spans/SimpleLeadingMarginSpan.java new file mode 100644 index 00000000..b442adba --- /dev/null +++ b/library-spans/src/main/java/ru/noties/markwon/spans/SimpleLeadingMarginSpan.java @@ -0,0 +1,25 @@ +package ru.noties.markwon.spans; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.Layout; +import android.text.style.LeadingMarginSpan; + +public class SimpleLeadingMarginSpan implements LeadingMarginSpan { + + private final int margin; + + public SimpleLeadingMarginSpan(int margin) { + this.margin = margin; + } + + @Override + public int getLeadingMargin(boolean first) { + return margin; + } + + @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) { + // no op + } +}