Bullet & ordered lists
This commit is contained in:
		
							parent
							
								
									c3b6c1ce39
								
							
						
					
					
						commit
						7f5db84bbe
					
				| @ -13,9 +13,15 @@ | ||||
| <h1>Yo! | ||||
| Omg | ||||
| 
 | ||||
| 
 | ||||
| ddffdg | ||||
| </h1> | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -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(' '); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| //    @Override | ||||
| //    public void visit(Image image) { | ||||
| // | ||||
| //        Debug.i(image); | ||||
| // | ||||
| ////        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); | ||||
|     } | ||||
| // | ||||
| //        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... | ||||
| 
 | ||||
|         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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov