Testing if we can display svg & gif, yeah...
This commit is contained in:
		
							parent
							
								
									87d03793a8
								
							
						
					
					
						commit
						99f2879f6a
					
				| @ -18,5 +18,7 @@ dependencies { | ||||
|     compile project(':library-renderer') | ||||
|     compile 'ru.noties:debug:3.0.0@jar' | ||||
|     compile 'com.squareup.picasso:picasso:2.5.2' | ||||
|     compile 'com.caverock:androidsvg:1.2.1' | ||||
|     compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.7' | ||||
|     compile 'com.squareup.okhttp3:okhttp:3.8.0' | ||||
| } | ||||
|  | ||||
| @ -1,26 +0,0 @@ | ||||
| package ru.noties.markwon; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.InstrumentationRegistry; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Instrumentation test, which will execute on an Android device. | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| public class ExampleInstrumentedTest { | ||||
|     @Test | ||||
|     public void useAppContext() throws Exception { | ||||
|         // Context of the app under test. | ||||
|         Context appContext = InstrumentationRegistry.getTargetContext(); | ||||
| 
 | ||||
|         assertEquals("ru.noties.markwon", appContext.getPackageName()); | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
|  | ||||
|  | ||||
| 
 | ||||
| [](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22scrollable%22) | ||||
| 
 | ||||
| @ -11,7 +11,7 @@ | ||||
| All GIFs here are taken from `sample` application module. | ||||
| 
 | ||||
| 
 | ||||
| <img src="art/scrollable_colorful.gif" width="30%" alt="colorful_sample"/> <img src="art/scrollable_custom_overscroll.gif" width="30%" alt="custom_overscroll_sample"/> <img src="art/scrollable_dialog.gif" width="30%" alt="dialog_sample"/> | ||||
| <img src="https://github.com/noties/Scrollable/raw/master/art/scrollable_colorful.gif" width="30%" alt="colorful_sample"/> <img src="https://github.com/noties/Scrollable/raw/master/art/scrollable_custom_overscroll.gif" width="30%" alt="custom_overscroll_sample"/> <img src="https://github.com/noties/Scrollable/raw/master/art/scrollable_dialog.gif" width="30%" alt="dialog_sample"/> | ||||
| 
 | ||||
| <sup>*Serving suggestion</sup> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										126
									
								
								app/src/main/java/ru/noties/markwon/AsyncDrawableLoader.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								app/src/main/java/ru/noties/markwon/AsyncDrawableLoader.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | ||||
| package ru.noties.markwon; | ||||
| 
 | ||||
| import android.graphics.Picture; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.graphics.drawable.PictureDrawable; | ||||
| import android.net.Uri; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import com.caverock.androidsvg.SVG; | ||||
| import com.squareup.picasso.Picasso; | ||||
| 
 | ||||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.InputStream; | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.Executors; | ||||
| import java.util.concurrent.Future; | ||||
| 
 | ||||
| import okhttp3.OkHttpClient; | ||||
| import pl.droidsonroids.gif.GifDrawable; | ||||
| import ru.noties.debug.Debug; | ||||
| import ru.noties.markwon.spans.AsyncDrawable; | ||||
| 
 | ||||
| public class AsyncDrawableLoader implements AsyncDrawable.Loader { | ||||
| 
 | ||||
|     private final TextView view; | ||||
|     private final Picasso picasso; | ||||
|     private final OkHttpClient client; | ||||
|     private final ExecutorService executorService; | ||||
|     private final Map<String, Future<?>> requests; | ||||
| 
 | ||||
|     // sh*t.. | ||||
|     public AsyncDrawableLoader(TextView view) { | ||||
|         this.view = view; | ||||
|         this.picasso = new Picasso.Builder(view.getContext()) | ||||
|                 .listener(new Picasso.Listener() { | ||||
|                     @Override | ||||
|                     public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) { | ||||
|                         Debug.e(exception, picasso, uri); | ||||
|                     } | ||||
|                 }) | ||||
|                 .build(); | ||||
|         this.client = new OkHttpClient(); | ||||
|         this.executorService = Executors.newCachedThreadPool(); | ||||
|         this.requests = new HashMap<>(3); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { | ||||
| 
 | ||||
|         Debug.i("destination: %s", destination); | ||||
| 
 | ||||
|         if (destination.endsWith(".svg")) { | ||||
|             // load svg | ||||
|             requests.put(destination, loadSvg(destination, drawable)); | ||||
|         } else if (destination.endsWith(".gif")) { | ||||
|             requests.put(destination, loadGif(destination, drawable)); | ||||
|         } else { | ||||
|             picasso | ||||
|                     .load(destination) | ||||
|                     .tag(destination) | ||||
|                     .into(new TextViewTarget(view, drawable)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void cancel(@NonNull String destination) { | ||||
|         Debug.i("destination: %s", destination); | ||||
|         picasso.cancelTag(destination); | ||||
| 
 | ||||
|         final Future<?> future = requests.get(destination); | ||||
|         if (future != null) { | ||||
|             future.cancel(true); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private Future<?> loadSvg(final String destination, final AsyncDrawable asyncDrawable) { | ||||
|         return executorService.submit(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 try { | ||||
|                     final URL url = new URL(destination); | ||||
|                     final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | ||||
|                     final InputStream inputStream = connection.getInputStream(); | ||||
|                     final SVG svg = SVG.getFromInputStream(inputStream); | ||||
|                     final Picture picture = svg.renderToPicture(); | ||||
|                     final Drawable drawable = new PictureDrawable(picture); | ||||
|                     drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); | ||||
|                     asyncDrawable.setResult(drawable); | ||||
| 
 | ||||
|                 } catch (Throwable t) { | ||||
|                     Debug.e(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private Future<?> loadGif(final String destination, final AsyncDrawable asyncDrawable) { | ||||
|         return executorService.submit(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 try { | ||||
|                     final URL url = new URL(destination); | ||||
|                     final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | ||||
|                     final InputStream inputStream = connection.getInputStream(); | ||||
|                     final ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||||
|                     final byte[] buffer = new byte[1024 * 8]; | ||||
|                     int read; | ||||
|                     while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) { | ||||
|                         baos.write(buffer, 0, read); | ||||
|                     } | ||||
|                     final GifDrawable drawable = new GifDrawable(baos.toByteArray()); | ||||
|                     drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); | ||||
|                     asyncDrawable.setResult(drawable); | ||||
|                 } catch (Throwable t) { | ||||
|                     Debug.e(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -3,7 +3,6 @@ package ru.noties.markwon; | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
| import android.os.SystemClock; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.method.LinkMovementMethod; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| @ -33,15 +32,7 @@ public class MainActivity extends Activity { | ||||
| 
 | ||||
|         final TextView textView = (TextView) findViewById(R.id.activity_main); | ||||
| 
 | ||||
| // | ||||
| //        final Picasso picasso = new Picasso.Builder(this) | ||||
| //                .listener(new Picasso.Listener() { | ||||
| //                    @Override | ||||
| //                    public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) { | ||||
| //                        Debug.i(exception, uri); | ||||
| //                    } | ||||
| //                }) | ||||
| //                .build(); | ||||
|         final AsyncDrawable.Loader loader = new AsyncDrawableLoader(textView); | ||||
| 
 | ||||
|         new Thread(new Runnable() { | ||||
|             @Override | ||||
| @ -75,17 +66,7 @@ public class MainActivity extends Activity { | ||||
|                     final long start = SystemClock.uptimeMillis(); | ||||
| 
 | ||||
|                     final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this) | ||||
|                             .asyncDrawableLoader(new AsyncDrawable.Loader() { | ||||
|                                 @Override | ||||
|                                 public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { | ||||
|                                     Debug.i("destination: %s, drawable: %s", destination, drawable); | ||||
|                                 } | ||||
| 
 | ||||
|                                 @Override | ||||
|                                 public void cancel(@NonNull String destination) { | ||||
|                                     Debug.i("destination: %s", destination); | ||||
|                                 } | ||||
|                             }) | ||||
|                             .asyncDrawableLoader(loader) | ||||
|                             .build(); | ||||
| 
 | ||||
|                     final CharSequence text = Markwon.markdown(configuration, md); | ||||
|  | ||||
| @ -1,7 +0,0 @@ | ||||
| //package ru.noties.markwon; | ||||
| // | ||||
| //public class Markwon { | ||||
| // | ||||
| //    // todo, annotation processor to PRE_COMPILE markdown!! no... multiple lnguages and you are out, forget about it | ||||
| //    // view for debugging (to view in preview) x3! | ||||
| //} | ||||
| @ -1,444 +0,0 @@ | ||||
| //package ru.noties.markwon; | ||||
| // | ||||
| //import android.graphics.Canvas; | ||||
| //import android.graphics.ColorFilter; | ||||
| //import android.graphics.drawable.Drawable; | ||||
| //import android.os.Handler; | ||||
| //import android.os.Looper; | ||||
| //import android.support.annotation.IntRange; | ||||
| //import android.support.annotation.NonNull; | ||||
| //import android.support.annotation.Nullable; | ||||
| //import android.text.Html; | ||||
| //import android.text.SpannableStringBuilder; | ||||
| //import android.text.Spanned; | ||||
| //import android.text.style.AbsoluteSizeSpan; | ||||
| //import android.text.style.StrikethroughSpan; | ||||
| //import android.text.style.URLSpan; | ||||
| // | ||||
| //import org.commonmark.ext.gfm.strikethrough.Strikethrough; | ||||
| //import org.commonmark.node.AbstractVisitor; | ||||
| //import org.commonmark.node.BlockQuote; | ||||
| //import org.commonmark.node.BulletList; | ||||
| //import org.commonmark.node.Code; | ||||
| //import org.commonmark.node.CustomBlock; | ||||
| //import org.commonmark.node.CustomNode; | ||||
| //import org.commonmark.node.Document; | ||||
| //import org.commonmark.node.Emphasis; | ||||
| //import org.commonmark.node.FencedCodeBlock; | ||||
| //import org.commonmark.node.HardLineBreak; | ||||
| //import org.commonmark.node.Heading; | ||||
| //import org.commonmark.node.HtmlBlock; | ||||
| //import org.commonmark.node.HtmlInline; | ||||
| //import org.commonmark.node.Image; | ||||
| //import org.commonmark.node.IndentedCodeBlock; | ||||
| //import org.commonmark.node.Link; | ||||
| //import org.commonmark.node.ListItem; | ||||
| //import org.commonmark.node.Node; | ||||
| //import org.commonmark.node.OrderedList; | ||||
| //import org.commonmark.node.Paragraph; | ||||
| //import org.commonmark.node.SoftLineBreak; | ||||
| //import org.commonmark.node.StrongEmphasis; | ||||
| //import org.commonmark.node.Text; | ||||
| //import org.commonmark.node.ThematicBreak; | ||||
| //import org.commonmark.renderer.Renderer; | ||||
| // | ||||
| //import java.util.ArrayDeque; | ||||
| //import java.util.Arrays; | ||||
| //import java.util.Deque; | ||||
| // | ||||
| //import ru.noties.debug.Debug; | ||||
| //import ru.noties.markwon.spans.BlockQuoteSpan; | ||||
| //import ru.noties.markwon.spans.CodeSpan; | ||||
| //import ru.noties.markwon.spans.AsyncDrawableSpan; | ||||
| //import ru.noties.markwon.spans.EmphasisSpan; | ||||
| //import ru.noties.markwon.spans.BulletListItemSpan; | ||||
| //import ru.noties.markwon.spans.StrongEmphasisSpan; | ||||
| //import ru.noties.markwon.spans.SubScriptSpan; | ||||
| //import ru.noties.markwon.spans.SuperScriptSpan; | ||||
| //import ru.noties.markwon.spans.ThematicBreakSpan; | ||||
| // | ||||
| //public class SpannableRenderer implements Renderer { | ||||
| // | ||||
| //    // todo, util to extract all drawables and attach to textView (gif, animations, lazyLoading, etc) | ||||
| // | ||||
| //    @Override | ||||
| //    public void render(Node node, Appendable output) { | ||||
| // | ||||
| //    } | ||||
| // | ||||
| //    @Override | ||||
| //    public String render(Node node) { | ||||
| //        // hm.. doesn't make sense to render to string | ||||
| //        throw null; | ||||
| //    } | ||||
| // | ||||
| //    public CharSequence _render(Node node) { | ||||
| //        final SpannableStringBuilder builder = new SpannableStringBuilder(); | ||||
| //        node.accept(new SpannableNodeRenderer(builder)); | ||||
| //        return builder; | ||||
| //    } | ||||
| // | ||||
| //    private static class SpannableNodeRenderer extends AbstractVisitor { | ||||
| // | ||||
| ////        private static final float[] HEADING_SIZES = { | ||||
| ////                1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f, | ||||
| ////        }; | ||||
| // | ||||
| //        private final SpannableStringBuilder builder; | ||||
| // | ||||
| //        private int blockQuoteIndent; | ||||
| //        private int listLevel; | ||||
| // | ||||
| //        SpannableNodeRenderer(SpannableStringBuilder builder) { | ||||
| //            this.builder = builder; | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(HardLineBreak hardLineBreak) { | ||||
| //            // todo | ||||
| //            Debug.i(hardLineBreak); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Text text) { | ||||
| //            builder.append(text.getLiteral()); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(StrongEmphasis strongEmphasis) { | ||||
| //            final int length = builder.length(); | ||||
| //            visitChildren(strongEmphasis); | ||||
| //            builder.setSpan(new StrongEmphasisSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Emphasis emphasis) { | ||||
| //            final int length = builder.length(); | ||||
| //            visitChildren(emphasis); | ||||
| //            builder.setSpan(new EmphasisSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(IndentedCodeBlock indentedCodeBlock) { | ||||
| //            // todo | ||||
| //            Debug.i(indentedCodeBlock); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(BlockQuote blockQuote) { | ||||
| //            builder.append('\n'); | ||||
| //            final int length = builder.length(); | ||||
| //            blockQuoteIndent += 1; | ||||
| //            visitChildren(blockQuote); | ||||
| //            builder.setSpan(new BlockQuoteSpan(blockQuoteIndent), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //            blockQuoteIndent -= 1; | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Code code) { | ||||
| //            final int length = builder.length(); | ||||
| //            builder.append(code.getLiteral()); | ||||
| ////            builder.setSpan(new ForegroundColorSpan(0xff00ff00), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //            builder.setSpan(new CodeSpan(false, length, builder.length()), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(BulletList bulletList) { | ||||
| //            Debug.i(bulletList, bulletList.getBulletMarker()); | ||||
| //            visitChildren(bulletList); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(ListItem listItem) { | ||||
| //            Debug.i(listItem); | ||||
| ////            builder.append('\n'); | ||||
| //            if (builder.charAt(builder.length() - 1) != '\n') { | ||||
| //                builder.append('\n'); | ||||
| //            } | ||||
| //            final int length = builder.length(); | ||||
| //            blockQuoteIndent += 1; | ||||
| //            listLevel += 1; | ||||
| //            visitChildren(listItem); | ||||
| ////            builder.setSpan(new BulletSpan(4, 0xff0000ff), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //            builder.setSpan(new BulletListItemSpan(blockQuoteIndent, listLevel > 1, length), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //            blockQuoteIndent -= 1; | ||||
| //            listLevel -= 1; | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(ThematicBreak thematicBreak) { | ||||
| //            final int length = builder.length(); | ||||
| //            builder.append('\n') | ||||
| //                    .append(' '); // without space it won't render | ||||
| //            builder.setSpan(new ThematicBreakSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //            builder.append('\n'); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(OrderedList orderedList) { | ||||
| //            Debug.i(orderedList, orderedList.getDelimiter(), orderedList.getStartNumber()); | ||||
| //            // todo, ordering numbers | ||||
| //            super.visit(orderedList); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(SoftLineBreak softLineBreak) { | ||||
| //            Debug.i(softLineBreak); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Heading heading) { | ||||
| //            Debug.i(heading); | ||||
| //            if (builder.length() != 0 && builder.charAt(builder.length() - 1) != '\n') { | ||||
| //                builder.append('\n'); | ||||
| //            } | ||||
| //            final int length = builder.length(); | ||||
| //            visitChildren(heading); | ||||
| //            final int max = 120; | ||||
| //            final int one = 20; // total is 6 | ||||
| //            final int size = max - ((heading.getLevel() - 1) * one); | ||||
| //            builder.setSpan(new AbsoluteSizeSpan(size), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //            builder.append('\n'); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(FencedCodeBlock fencedCodeBlock) { | ||||
| //            builder.append('\n'); | ||||
| //            final int length = builder.length(); | ||||
| //            builder.append(fencedCodeBlock.getLiteral()); | ||||
| //            builder.setSpan(new CodeSpan(true, length, builder.length() - 1), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Paragraph paragraph) { | ||||
| //            Debug.i(paragraph); | ||||
| //            if (listLevel == 0 | ||||
| //                    && blockQuoteIndent == 0) { | ||||
| //                builder.append('\n') | ||||
| //                        .append('\n'); | ||||
| //            } | ||||
| //            visitChildren(paragraph); | ||||
| // | ||||
| //            if (listLevel == 0 | ||||
| //                    && blockQuoteIndent == 0) { | ||||
| //                builder.append('\n') | ||||
| //                        .append('\n'); | ||||
| //            } | ||||
| //        } | ||||
| // | ||||
| ////        private int htmlStart = -1; | ||||
| //        private final Deque<HtmlInlineItem> htmlStack = new ArrayDeque<>(); | ||||
| // | ||||
| //        private static class HtmlInlineItem { | ||||
| // | ||||
| //            final int start; | ||||
| //            final String tag; | ||||
| // | ||||
| //            private HtmlInlineItem(int start, String tag) { | ||||
| //                this.start = start; | ||||
| //                this.tag = tag; | ||||
| //            } | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(HtmlInline htmlInline) { | ||||
| // | ||||
| ////            Debug.i(htmlInline, htmlStart); | ||||
| ////            Debug.i(htmlInline.getLiteral(), htmlInline.toString()); | ||||
| // | ||||
| //            // okay, it's seems that we desperately need to understand if it's opening tag or closing | ||||
| // | ||||
| //            final HtmlTag tag = parseTag(htmlInline.getLiteral()); | ||||
| // | ||||
| //            Debug.i(htmlInline.getLiteral(), tag); | ||||
| // | ||||
| //            if (tag != null) { | ||||
| //                Debug.i("tag: %s, closing: %s", tag.tag, tag.closing); | ||||
| //                if (!tag.closing) { | ||||
| //                    htmlStack.push(new HtmlInlineItem(builder.length(), tag.tag)); | ||||
| //                    visitChildren(htmlInline); | ||||
| //                } else { | ||||
| //                    final HtmlInlineItem item = htmlStack.pop(); | ||||
| //                    final int start = item.start; | ||||
| //                    final int end = builder.length(); | ||||
| //                    // here, additionally, we can render some tags ourselves (sup/sub) | ||||
| //                    if ("sup".equals(item.tag)) { | ||||
| //                         builder.setSpan(new SuperScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                    } else if("sub".equals(item.tag)) { | ||||
| //                        builder.setSpan(new SubScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                    } else if("del".equals(item.tag)) { | ||||
| //                        // weird, but `Html` class does not return a spannable for `<del>o</del>` | ||||
| //                        // seems like a bug | ||||
| //                        builder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                    } else { | ||||
| //                        final String html = "<" + item.tag + ">" + (builder.subSequence(start, end).toString()) + "</" + item.tag + ">"; | ||||
| //                        final Spanned spanned = Html.fromHtml(html); | ||||
| //                        final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); | ||||
| // | ||||
| //                        Debug.i("html: %s, start: %d, end: %d, spans: %s", html, start, end, Arrays.toString(spans)); | ||||
| // | ||||
| //                        if (spans != null | ||||
| //                                && spans.length > 0) { | ||||
| //                            for (Object span: spans) { | ||||
| //                                Debug.i(span); | ||||
| //                                builder.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                            } | ||||
| //                        } | ||||
| //                    } | ||||
| //                } | ||||
| //            } else { | ||||
| //                super.visit(htmlInline); | ||||
| //            } | ||||
| //        } | ||||
| // | ||||
| //        private static class HtmlTag { | ||||
| //            final String tag; | ||||
| //            final boolean closing; | ||||
| //            HtmlTag(String tag, boolean closing) { | ||||
| //                this.tag = tag; | ||||
| //                this.closing = closing; | ||||
| //            } | ||||
| //            @Override | ||||
| //            public String toString() { | ||||
| //                return "HtmlTag{" + | ||||
| //                        "tag='" + tag + '\'' + | ||||
| //                        ", closing=" + closing + | ||||
| //                        '}'; | ||||
| //            } | ||||
| //        } | ||||
| // | ||||
| //        private static HtmlTag parseTag(String in) { | ||||
| // | ||||
| //            final HtmlTag out; | ||||
| // | ||||
| //            final int length = in != null | ||||
| //                    ? in.length() | ||||
| //                    : 0; | ||||
| // | ||||
| //            Debug.i(in, length); | ||||
| // | ||||
| //            if (length == 0 || length < 3) { | ||||
| //                out = null; | ||||
| //            } else { | ||||
| // | ||||
| //                final boolean closing = '<' == in.charAt(0) && '/' == in.charAt(1); | ||||
| //                final String tag = closing | ||||
| //                        ? in.substring(2, in.length() - 1) | ||||
| //                        : in.substring(1, in.length() - 1); | ||||
| //                out = new HtmlTag(tag, closing); | ||||
| //            } | ||||
| // | ||||
| //            return out; | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(HtmlBlock htmlBlock) { | ||||
| //            // interestring thing... what is it also? | ||||
| //            Debug.i(htmlBlock); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(CustomBlock customBlock) { | ||||
| //            // not supported, what is it anyway? | ||||
| //            Debug.i(customBlock); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Document document) { | ||||
| //            // the whole document, no need to do anything | ||||
| //            Debug.i(document); | ||||
| //            super.visit(document); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Link link) { | ||||
| //            Debug.i(link); | ||||
| //            final int length = builder.length(); | ||||
| //            visitChildren(link); | ||||
| //            builder.setSpan(new URLSpan(link.getDestination()), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(Image image) { | ||||
| //            // not supported... maybe for now? | ||||
| //            Debug.i(image); | ||||
| //            final int length = builder.length(); | ||||
| //            super.visit(image); | ||||
| // | ||||
| ////            final int length = builder.length(); | ||||
| //            final TestDrawable drawable = new TestDrawable(); | ||||
| //            final AsyncDrawableSpan span = new AsyncDrawableSpan(drawable); | ||||
| //            builder.append("  "); | ||||
| //            builder.setSpan(span, length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void visit(CustomNode customNode) { | ||||
| // | ||||
| //            Debug.i(customNode); | ||||
| // | ||||
| //            if (customNode instanceof Strikethrough) { | ||||
| //                final int length = builder.length(); | ||||
| //                visitChildren(customNode); | ||||
| //                builder.setSpan(new StrikethroughSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //            } else { | ||||
| //                super.visit(customNode); | ||||
| //            } | ||||
| //        } | ||||
| //    } | ||||
| // | ||||
| // | ||||
| //    private static class TestDrawable extends Drawable { | ||||
| // | ||||
| //        private final Handler handler = new Handler(Looper.getMainLooper()); | ||||
| //        private boolean called; | ||||
| // | ||||
| //        TestDrawable() { | ||||
| //            setBounds(0, 0, 50, 50); | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void draw(@NonNull final Canvas canvas) { | ||||
| //            canvas.clipRect(getBounds()); | ||||
| //            if (!called) { | ||||
| //                canvas.drawColor(0xFF00ff00); | ||||
| //                handler.removeCallbacksAndMessages(null); | ||||
| //                handler.postDelayed(new Runnable() { | ||||
| //                    @Override | ||||
| //                    public void run() { | ||||
| //                        called = true; | ||||
| //                        setBounds(0, 0, 400, 400); | ||||
| //                        invalidateSelf(); | ||||
| //                    } | ||||
| //                }, 2000L); | ||||
| //            } else { | ||||
| //                canvas.drawColor(0xFFff0000); | ||||
| //            } | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { | ||||
| // | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public void setColorFilter(@Nullable ColorFilter colorFilter) { | ||||
| // | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public int getOpacity() { | ||||
| //            return 0; | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public int getIntrinsicWidth() { | ||||
| //            return called ? 400 : 50; | ||||
| //        } | ||||
| // | ||||
| //        @Override | ||||
| //        public int getIntrinsicHeight() { | ||||
| //            return called ? 400 : 50; | ||||
| //        } | ||||
| //    } | ||||
| //} | ||||
							
								
								
									
										74
									
								
								app/src/main/java/ru/noties/markwon/TextViewTarget.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								app/src/main/java/ru/noties/markwon/TextViewTarget.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| package ru.noties.markwon; | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.drawable.BitmapDrawable; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import com.squareup.picasso.Picasso; | ||||
| import com.squareup.picasso.Target; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import ru.noties.markwon.spans.AsyncDrawable; | ||||
| 
 | ||||
| public class TextViewTarget implements Target { | ||||
| 
 | ||||
|     private final TextView view; | ||||
|     private final AsyncDrawable asyncDrawable; | ||||
| 
 | ||||
|     public TextViewTarget(TextView view, AsyncDrawable asyncDrawable) { | ||||
|         this.view = view; | ||||
|         this.asyncDrawable = asyncDrawable; | ||||
| 
 | ||||
|         attach(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { | ||||
|         if (bitmap != null) { | ||||
|             final Drawable drawable = new BitmapDrawable(view.getResources(), bitmap); | ||||
|             drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); | ||||
|             asyncDrawable.setResult(drawable); | ||||
|         } | ||||
|         detach(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBitmapFailed(Drawable errorDrawable) { | ||||
|         if (errorDrawable != null) { | ||||
|             asyncDrawable.setResult(errorDrawable); | ||||
|         } | ||||
|         detach(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onPrepareLoad(Drawable placeHolderDrawable) { | ||||
|         if (placeHolderDrawable != null) { | ||||
|             asyncDrawable.setResult(placeHolderDrawable); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void attach() { | ||||
| 
 | ||||
|         // amazing stuff here, in order to keep this target alive (picasso stores target in a WeakReference) | ||||
|         // we need to do this | ||||
| 
 | ||||
|         //noinspection unchecked | ||||
|         List<TextViewTarget> list = (List<TextViewTarget>) view.getTag(R.id.amazing); | ||||
|         if (list == null) { | ||||
|             list = new ArrayList<>(2); | ||||
|             view.setTag(R.id.amazing, list); | ||||
|         } | ||||
|         list.add(this); | ||||
|     } | ||||
| 
 | ||||
|     private void detach() { | ||||
|         //noinspection unchecked | ||||
|         final List<TextViewTarget> list = (List<TextViewTarget>) view.getTag(R.id.amazing); | ||||
|         if (list != null) { | ||||
|             list.remove(this); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,45 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.RectF; | ||||
| import android.text.Layout; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| import ru.noties.debug.Debug; | ||||
| 
 | ||||
| public class BlockQuoteSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private final int indent; | ||||
| 
 | ||||
|     public BlockQuoteSpan(int indent) { | ||||
|         this.indent = indent; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return 24; | ||||
|     } | ||||
| 
 | ||||
|     @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) { | ||||
| //        Debug.i("x: %d, dir: %d, top: %d, baseline: %d, bottom: %d, first: %s", | ||||
| //                x, dir, top, baseline, bottom, first | ||||
| //        ); | ||||
| 
 | ||||
|         final int save = c.save(); | ||||
|         try { | ||||
|             final int left = 24 * (indent - 1); | ||||
| //            final RectF rectF = new RectF(0, 0, 16, 16); | ||||
|             final Rect rect = new Rect(left, top, left + 8, bottom); | ||||
|             final Paint paint = new Paint(); | ||||
|             paint.setStyle(Paint.Style.FILL); | ||||
|             paint.setColor(0xFFf0f0f0); | ||||
|             c.drawRect(rect, paint); | ||||
| //            c.translate(x, .0F); | ||||
| //            c.drawOval(rectF, paint); | ||||
|         } finally { | ||||
|             c.restoreToCount(save); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,270 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.text.style.ReplacementSpan; | ||||
| 
 | ||||
| import ru.noties.debug.Debug; | ||||
| 
 | ||||
| // we will use Replacement span because code blocks cannot contain other markdown | ||||
| // so we will render the string (not a charSequence with possible metric affecting spans) | ||||
| public class CodeSpan extends ReplacementSpan/* implements LeadingMarginSpan*/ { | ||||
| 
 | ||||
|     private final boolean multiline; | ||||
|     private final int start; | ||||
|     private final int end; | ||||
| 
 | ||||
|     private final Rect rect = new Rect(); | ||||
|     private final Rect borderRect = new Rect(); | ||||
|     private final Paint paint = new Paint(); | ||||
| 
 | ||||
|     public CodeSpan(boolean multiline, int start, int end) { | ||||
|         this.multiline = multiline; | ||||
|         this.start = start; | ||||
|         this.end = end; | ||||
| 
 | ||||
|         paint.setStyle(Paint.Style.FILL); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getSize( | ||||
|             @NonNull Paint paint, | ||||
|             CharSequence text, | ||||
|             @IntRange(from = 0) int start, | ||||
|             @IntRange(from = 0) int end, | ||||
|             @Nullable Paint.FontMetricsInt fm | ||||
|     ) { | ||||
| 
 | ||||
|         final CharSequence cs = text.subSequence(start, end); | ||||
|         final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F); | ||||
| 
 | ||||
| //        final StaticLayout layout = new StaticLayout(cs, new TextPaint(paint), 10000, Layout.Alignment.ALIGN_NORMAL, 1.F, .0F, false); | ||||
| //        final float width = layout.getLineWidth(0); | ||||
| //        final int out = 32 + (int) (width + .5F); | ||||
| 
 | ||||
| //        Debug.i("text: %s, width: %s", cs, width); | ||||
| 
 | ||||
|         if (fm != null) { | ||||
|             // we add a padding top & bottom | ||||
|             Debug.i("a: %s, d: %s, t: %s, b: %s", fm.ascent, fm.descent, fm.top, fm.bottom); | ||||
|             final float ratio = .62F; // golden ratio | ||||
|             fm.ascent = fm.ascent - 8; | ||||
|             fm.descent = (int) (-fm.ascent * ratio); | ||||
|             fm.top = fm.ascent; | ||||
|             fm.bottom = fm.descent; | ||||
|         } | ||||
| 
 | ||||
|         return width; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void draw( | ||||
|             @NonNull Canvas canvas, | ||||
|             CharSequence text, | ||||
|             @IntRange(from = 0) int start, | ||||
|             @IntRange(from = 0) int end, | ||||
|             float x, | ||||
|             int top, | ||||
|             int y, | ||||
|             int bottom, | ||||
|             @NonNull Paint paint | ||||
|     ) { | ||||
| 
 | ||||
|         final int left = (int) (x + .5F); | ||||
| 
 | ||||
|         final int right; | ||||
|         if (multiline) { | ||||
|             right = canvas.getWidth(); | ||||
|         } else { | ||||
|             final int width = (16 * 2) + (int) (paint.measureText(text, start, end) + .5F); | ||||
|             right = left + width; | ||||
|         } | ||||
| 
 | ||||
|         rect.set(left, top, right, bottom); | ||||
| 
 | ||||
| 
 | ||||
|         // okay, draw background first | ||||
|         drawBackground(canvas); | ||||
| 
 | ||||
|         // then, if any, draw borders | ||||
|         drawBorders(canvas, this.start == start, this.end == end); | ||||
| 
 | ||||
|         // draw text | ||||
|         // y center position | ||||
|         final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2); | ||||
| //        if (config.textColor != 0) { | ||||
| //            // we will use Paint object that is used to draw all the text (textSize, textColor, typeface, etc) | ||||
| //            paint.setColor(config.textColor); | ||||
| //        } | ||||
|         canvas.drawText(text, start, end, x + 16, b, paint); | ||||
| 
 | ||||
| 
 | ||||
| //        Debug.i("text: %s, x: %s, top: %s, y: %s, bottom: %s", text.subSequence(start, end), x, top, y, bottom); | ||||
| // | ||||
| //        final CharSequence cs = text.subSequence(start, end); | ||||
| // | ||||
| //        final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F); | ||||
| // | ||||
| //        final int left = (int) (x + .5F); | ||||
| //        final int right = multiline | ||||
| //                ? canvas.getWidth() | ||||
| //                : left + width; | ||||
| // | ||||
| //        final Rect rect = new Rect( | ||||
| //                left, | ||||
| //                top, | ||||
| //                right, | ||||
| //                bottom | ||||
| //        ); | ||||
| // | ||||
| //        final Paint p = new Paint(); | ||||
| //        p.setStyle(Paint.Style.FILL); | ||||
| //        p.setColor(0x80ff0000); | ||||
| //        canvas.drawRect(rect, p); | ||||
| // | ||||
| //        // y center position | ||||
| //        final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2); | ||||
| //        p.setColor(0xFF000000); | ||||
| //        canvas.drawText(cs, 0, cs.length(), x + 16, b, paint); | ||||
|     } | ||||
| 
 | ||||
|     private void drawBackground(Canvas canvas) { | ||||
| //        final int color = config.backgroundColor; | ||||
| //        if (color != 0) { | ||||
|             paint.setColor(0x40ff0000); | ||||
|             canvas.drawRect(rect, paint); | ||||
| //        } | ||||
|     } | ||||
| 
 | ||||
|     private void drawBorders(Canvas canvas, boolean top, boolean bottom) { | ||||
| 
 | ||||
|         final int color = 0xFFff0000; | ||||
|         final int width = 4; | ||||
| //        if (color == 0 | ||||
| //                || width == 0) { | ||||
| //            return; | ||||
| //        } | ||||
| 
 | ||||
|         paint.setColor(color); | ||||
| 
 | ||||
|         // left and right are always drawn | ||||
| 
 | ||||
|         // LEFT | ||||
|         borderRect.set(rect.left, rect.top, rect.left + width, rect.bottom); | ||||
|         canvas.drawRect(borderRect, paint); | ||||
| 
 | ||||
|         // RIGHT | ||||
|         borderRect.set(rect.right - width, rect.top, rect.right, rect.bottom); | ||||
|         canvas.drawRect(borderRect, paint); | ||||
| 
 | ||||
|         // TOP | ||||
|         if (top) { | ||||
|             borderRect.set(rect.left, rect.top, rect.right, rect.top + width); | ||||
|             canvas.drawRect(borderRect, paint); | ||||
|         } | ||||
| 
 | ||||
|         // BOTTOM | ||||
|         if (bottom) { | ||||
|             borderRect.set(rect.left, rect.bottom - width, rect.right, rect.bottom); | ||||
|             canvas.drawRect(borderRect, paint); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| //    @Override | ||||
| //    public int getLeadingMargin(boolean first) { | ||||
| //        return 1; | ||||
| //    } | ||||
| // | ||||
| //    @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) { | ||||
| ////        Debug.i("x: %d, top: %d, bottom: %d", x, top, bottom); | ||||
| // | ||||
| ////        Debug.i("this: [%d, %d], came: [%d, %d]", this.start, this.end, start, end); | ||||
| //        Debug.i("x: %d, canvas: [%d-%d], text: %s", x, c.getWidth(), c.getHeight(), (text.subSequence(start, end))); | ||||
| // | ||||
| //        // the thing is... if we do not draw, then text won't be drawn also | ||||
| //        final Rect rect = new Rect(); | ||||
| // | ||||
| //        final Paint paint = new Paint(); | ||||
| //        paint.setStyle(Paint.Style.FILL); | ||||
| //        paint.setColor(0xffcccccc); | ||||
| // | ||||
| //        rect.set(x, top, c.getWidth(), bottom); | ||||
| //        c.drawRect(rect, paint); | ||||
| // | ||||
| //        if (this.start == start) { | ||||
| //            this.top = top; | ||||
| // | ||||
| ////            final int save = c.save(); | ||||
| ////            try { | ||||
| ////                c.drawColor(0x00ffffff); | ||||
| ////            } finally { | ||||
| ////                c.restoreToCount(save); | ||||
| ////            } | ||||
| // | ||||
| ////            c.drawColor(0x00ffffff); | ||||
| //        } | ||||
| // | ||||
| //        if (this.end == end) { | ||||
| //            // draw borders | ||||
| //            final Rect r = new Rect(x + 1, this.top, c.getWidth() - x, bottom); | ||||
| //            final Paint pa = new Paint(); | ||||
| //            pa.setStyle(Paint.Style.STROKE); | ||||
| //            pa.setColor(0xff999999); | ||||
| //            c.drawRect(r, pa); | ||||
| //        } | ||||
| ////        rect.inset((int) paint.getStrokeWidth(), (int) paint.getStrokeWidth()); | ||||
| ////        paint.setStyle(Paint.Style.STROKE); | ||||
| ////        paint.setColor(0xff333333); | ||||
| ////        c.drawRect(rect, paint); | ||||
| //    } | ||||
| 
 | ||||
| //    @Override | ||||
| //    public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { | ||||
| ////        int ht = mDrawable.getIntrinsicHeight(); | ||||
| //// | ||||
| ////        int need = ht - (v + fm.descent - fm.ascent - istartv); | ||||
| ////        if (need > 0) | ||||
| ////            fm.descent += need; | ||||
| //// | ||||
| ////        need = ht - (v + fm.bottom - fm.top - istartv); | ||||
| ////        if (need > 0) | ||||
| ////            fm.bottom += need; | ||||
| //// | ||||
| // | ||||
| ////        final int lineOffset = v - spanstartv; | ||||
| ////        final int desired = 128; | ||||
| ////        final int currentLineHeight = -fm.ascent + fm.descent; | ||||
| ////        final float ratio = (float) desired / currentLineHeight; | ||||
| //// | ||||
| ////        Debug.i("fm, came: %s", fm); | ||||
| ////        Debug.i("lineOffset: %d, current: %d, ratio: %s", lineOffset, currentLineHeight, ratio); | ||||
| //// | ||||
| ////        fm.ascent = (int) (ratio * fm.ascent + .5F); | ||||
| ////        fm.descent = (int) (ratio * fm.descent + .5F); | ||||
| //// | ||||
| ////        Debug.i("fm, out: %s", fm); | ||||
| // | ||||
| ////        Debug.i("top: %d, bottom: %d, ascent: %d, descent: %d", fm.top, fm.bottom, fm.ascent, fm.descent); | ||||
| ////        Debug.i("lineHeight: %d, v: %d, spanstartv: %d", lineOffset, v, spanstartv); | ||||
| //// | ||||
| ////        final int h = 128; | ||||
| ////        final int descentNeed = h - (v + fm.descent - fm.ascent - spanstartv); | ||||
| ////        if (descentNeed > 0) { | ||||
| ////            fm.ascent -= descentNeed / 2; | ||||
| ////            fm.descent += descentNeed / 2; | ||||
| ////        } | ||||
| ////        final int bottomNeed = h - (v + fm.bottom - fm.top - spanstartv); | ||||
| ////        if (bottomNeed > 0) { | ||||
| ////            fm.top -= bottomNeed; | ||||
| ////            fm.bottom += bottomNeed; | ||||
| ////        } | ||||
| //// | ||||
| ////        Debug.i("out, ascent: %d, descent: %d, bottom: %d", fm.ascent, fm.descent, fm.bottom); | ||||
| //    } | ||||
| } | ||||
| @ -1,97 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.support.annotation.IntDef; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.text.style.ReplacementSpan; | ||||
| 
 | ||||
| public class DrawableSpan extends ReplacementSpan { | ||||
| 
 | ||||
|     @IntDef({ ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER }) | ||||
|     @interface Alignment {} | ||||
| 
 | ||||
|     public static final int ALIGN_BOTTOM = 0; | ||||
|     public static final int ALIGN_BASELINE = 1; | ||||
|     public static final int ALIGN_CENTER = 2; | ||||
| 
 | ||||
|     private final Drawable drawable; | ||||
|     private final int alignment; | ||||
| 
 | ||||
|     public DrawableSpan(@NonNull Drawable drawable) { | ||||
|         this(drawable, ALIGN_BOTTOM); | ||||
|     } | ||||
| 
 | ||||
|     public DrawableSpan(@NonNull Drawable drawable, @Alignment int alignment) { | ||||
|         this.drawable = drawable; | ||||
|         this.alignment = alignment; | ||||
| 
 | ||||
|         // additionally set intrinsic bounds if empty | ||||
|         final Rect rect = drawable.getBounds(); | ||||
|         if (rect.isEmpty()) { | ||||
|             drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getSize( | ||||
|             @NonNull Paint paint, | ||||
|             CharSequence text, | ||||
|             @IntRange(from = 0) int start, | ||||
|             @IntRange(from = 0) int end, | ||||
|             @Nullable Paint.FontMetricsInt fm) { | ||||
| 
 | ||||
|         final Rect rect = drawable.getBounds(); | ||||
| 
 | ||||
|         if (fm != null) { | ||||
|             fm.ascent = -rect.bottom; | ||||
|             fm.descent = 0; | ||||
| 
 | ||||
|             fm.top = fm.ascent; | ||||
|             fm.bottom = 0; | ||||
|         } | ||||
| 
 | ||||
|         return rect.right; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void draw( | ||||
|             @NonNull Canvas canvas, | ||||
|             CharSequence text, | ||||
|             @IntRange(from = 0) int start, | ||||
|             @IntRange(from = 0) int end, | ||||
|             float x, | ||||
|             int top, | ||||
|             int y, | ||||
|             int bottom, | ||||
|             @NonNull Paint paint) { | ||||
| 
 | ||||
|         final Drawable drawable = this.drawable; | ||||
| 
 | ||||
|         final int b = bottom - drawable.getBounds().bottom; | ||||
| 
 | ||||
|         final int save = canvas.save(); | ||||
|         try { | ||||
|             final int translationY; | ||||
|             if (ALIGN_CENTER == alignment) { | ||||
|                 translationY = (int) (b / 2.F + .5F); | ||||
|             } else if (ALIGN_BASELINE == alignment) { | ||||
|                 translationY = b - paint.getFontMetricsInt().descent; | ||||
|             } else { | ||||
|                 translationY = b; | ||||
|             } | ||||
|             canvas.translate(x, translationY); | ||||
|             drawable.draw(canvas); | ||||
|         } finally { | ||||
|             canvas.restoreToCount(save); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public Drawable getDrawable() { | ||||
|         return drawable; | ||||
|     } | ||||
| } | ||||
| @ -1,116 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.os.SystemClock; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Spanned; | ||||
| import android.view.View; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import ru.noties.debug.Debug; | ||||
| 
 | ||||
| public class DrawableSpanUtils { | ||||
| 
 | ||||
|     // this method is not completely valid because DynamicDrawableSpan stores | ||||
|     // a drawable in weakReference & it could easily be freed, thus we might need | ||||
|     // to re-schedule a new one, but we have no means to do it | ||||
|     public static void scheduleDrawables(@NonNull final TextView textView) { | ||||
| 
 | ||||
|         final CharSequence cs = textView.getText(); | ||||
|         final int length = cs != null | ||||
|                 ? cs.length() | ||||
|                 : 0; | ||||
|         if (length == 0 || !(cs instanceof Spanned)) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final Object[] spans = ((Spanned) cs).getSpans(0, length, Object.class); | ||||
|         if (spans != null | ||||
|                 && spans.length > 0) { | ||||
| 
 | ||||
|             final List<Drawable> list = new ArrayList<>(2); | ||||
| 
 | ||||
|             for (Object span: spans) { | ||||
|                 if (span instanceof DrawableSpan) { | ||||
|                     list.add(((DrawableSpan) span).getDrawable()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (list.size() > 0) { | ||||
|                 textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { | ||||
|                     @Override | ||||
|                     public void onViewAttachedToWindow(View v) { | ||||
|                         // can it happen that the same view first detached & them attached with all previous content? hm.. | ||||
|                         // no op for now | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onViewDetachedFromWindow(View v) { | ||||
|                         // remove callbacks... | ||||
|                         textView.removeOnAttachStateChangeListener(this); | ||||
|                         for (Drawable drawable: list) { | ||||
|                             drawable.setCallback(null); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|                 for (Drawable drawable: list) { | ||||
|                     drawable.setCallback(new DrawableCallbackImpl(textView, drawable.getBounds())); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private DrawableSpanUtils() {} | ||||
| 
 | ||||
|     private static class DrawableCallbackImpl implements Drawable.Callback { | ||||
| 
 | ||||
|         private final TextView view; | ||||
|         private Rect previousBounds; | ||||
| 
 | ||||
|         DrawableCallbackImpl(TextView view, Rect initialBounds) { | ||||
|             this.view = view; | ||||
|             this.previousBounds = new Rect(initialBounds); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void invalidateDrawable(@NonNull Drawable who) { | ||||
| 
 | ||||
|             // okay... teh thing is IF we do not change bounds size, normal invalidate would do | ||||
|             // but if the size has changed, then we need to update the whole layout... | ||||
| 
 | ||||
|             final Rect rect = who.getBounds(); | ||||
| 
 | ||||
|             if (!previousBounds.equals(rect)) { | ||||
|                 // the only method that seems to work when bounds have changed | ||||
|                 view.setText(view.getText()); | ||||
|                 previousBounds = new Rect(rect); | ||||
|             } else { | ||||
|                 // if bounds are the same then simple invalidate would do | ||||
|                 final int scrollX = view.getScrollX(); | ||||
|                 final int scrollY = view.getScrollY(); | ||||
|                 view.postInvalidate( | ||||
|                         scrollX + rect.left, | ||||
|                         scrollY + rect.top, | ||||
|                         scrollX + rect.right, | ||||
|                         scrollY + rect.bottom | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { | ||||
|             final long delay = when - SystemClock.uptimeMillis(); | ||||
|             view.postDelayed(what, delay); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { | ||||
|             view.removeCallbacks(what); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,17 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class EmphasisSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint p) { | ||||
|         p.setTextSkewX(-0.25f); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         tp.setTextSkewX(-0.25f); | ||||
|     } | ||||
| } | ||||
| @ -1,50 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.RectF; | ||||
| import android.text.Layout; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| import ru.noties.debug.Debug; | ||||
| 
 | ||||
| public class ListItemSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private final int blockIndent; | ||||
|     private final boolean nested; | ||||
|     private final int start; | ||||
| 
 | ||||
|     public ListItemSpan(int blockIndent, boolean nested, int start) { | ||||
|         this.blockIndent = blockIndent; | ||||
|         this.nested = nested; | ||||
|         this.start = start; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return 36; | ||||
|     } | ||||
| 
 | ||||
|     @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) { | ||||
| //        Debug.i("x: %d, dir: %d, top: %d, baseline: %d, bottom: %d, first: %s", | ||||
| //                x, dir, top, baseline, bottom, first | ||||
| //        ); | ||||
| 
 | ||||
|         // if there was a line break, we don't need to draw it | ||||
|         if (this.start != start) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final int save = c.save(); | ||||
|         try { | ||||
|             final int left = 24 * (blockIndent - 1) + (first ? 12 : 0); | ||||
|             final RectF rectF = new RectF(left, top, left + 16, bottom); | ||||
|             final Paint paint = new Paint(); | ||||
|             paint.setStyle(nested ? Paint.Style.STROKE : Paint.Style.FILL); | ||||
|             paint.setColor(0xFFff0000); | ||||
|             c.drawOval(rectF, paint); | ||||
|         } finally { | ||||
|             c.restoreToCount(save); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,17 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class StrongEmphasisSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint p) { | ||||
|         p.setFakeBoldText(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         tp.setFakeBoldText(true); | ||||
|     } | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class SubSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift -= (int) (tp.ascent() / 2); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift -= (int) (tp.ascent() / 2); | ||||
|     } | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class SupSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift += (int) (tp.ascent() / 2); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift += (int) (tp.ascent() / 2); | ||||
|     } | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| package ru.noties.markwon.spans2; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.text.Layout; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| 
 | ||||
| public class ThematicBreakSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     @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) { | ||||
|         final int middle = (bottom - top) / 2; | ||||
|         final Rect rect = new Rect(0, top + middle - 2, c.getWidth(), top + middle + 2); | ||||
|         final Paint paint = new Paint(); | ||||
|         paint.setStyle(Paint.Style.FILL); | ||||
|         paint.setColor(0x80000000); | ||||
|         c.drawRect(rect, paint); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										4
									
								
								app/src/main/res/values/ids.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/src/main/res/values/ids.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <item name="amazing" type="id" /> | ||||
| </resources> | ||||
| @ -1,17 +0,0 @@ | ||||
| package ru.noties.markwon; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Example local unit test, which will execute on the development machine (host). | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| public class ExampleUnitTest { | ||||
|     @Test | ||||
|     public void addition_isCorrect() throws Exception { | ||||
|         assertEquals(4, 2 + 2); | ||||
|     } | ||||
| } | ||||
| @ -2,6 +2,7 @@ package ru.noties.markwon; | ||||
| 
 | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.os.Looper; | ||||
| import android.os.SystemClock; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Spanned; | ||||
| @ -13,6 +14,7 @@ import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import ru.noties.debug.Debug; | ||||
| import ru.noties.markwon.spans.AsyncDrawable; | ||||
| import ru.noties.markwon.spans.AsyncDrawableSpan; | ||||
| 
 | ||||
| @ -21,6 +23,7 @@ abstract class DrawablesScheduler { | ||||
|     static void schedule(@NonNull final TextView textView) { | ||||
| 
 | ||||
|         final List<AsyncDrawable> list = extract(textView); | ||||
|         Debug.i(list); | ||||
|         if (list.size() > 0) { | ||||
|             textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { | ||||
|                 @Override | ||||
| @ -36,13 +39,15 @@ abstract class DrawablesScheduler { | ||||
|             }); | ||||
| 
 | ||||
|             for (AsyncDrawable d : list) { | ||||
|                 d.setCallback2(new DrawableCallbackImpl(textView, d.getBounds())); | ||||
|                 Debug.i(d); | ||||
|                 d.setCallback2(new DrawableCallbackImpl(textView, null, d.getBounds())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // must be called when text manually changed in TextView | ||||
|     static void unschedule(@NonNull TextView view) { | ||||
|         Debug.i(); | ||||
|         for (AsyncDrawable d : extract(view)) { | ||||
|             d.setCallback2(null); | ||||
|         } | ||||
| @ -93,38 +98,61 @@ abstract class DrawablesScheduler { | ||||
|     private DrawablesScheduler() { | ||||
|     } | ||||
| 
 | ||||
|     private interface CoordinatesProvider { | ||||
|         int getX(); | ||||
|         int getY(); | ||||
|     } | ||||
| 
 | ||||
|     private static class DrawableCallbackImpl implements Drawable.Callback { | ||||
| 
 | ||||
|         private final TextView view; | ||||
|         private final CoordinatesProvider coordinatesProvider; | ||||
|         private Rect previousBounds; | ||||
| 
 | ||||
|         DrawableCallbackImpl(TextView view, Rect initialBounds) { | ||||
|         DrawableCallbackImpl(TextView view, CoordinatesProvider provider, Rect initialBounds) { | ||||
|             this.view = view; | ||||
|             this.coordinatesProvider = provider; | ||||
|             this.previousBounds = new Rect(initialBounds); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void invalidateDrawable(@NonNull Drawable who) { | ||||
|         public void invalidateDrawable(@NonNull final Drawable who) { | ||||
| 
 | ||||
|             if (Looper.myLooper() != Looper.getMainLooper()) { | ||||
|                 view.post(new Runnable() { | ||||
|                     @Override | ||||
|                     public void run() { | ||||
|                         invalidateDrawable(who); | ||||
|                     } | ||||
|                 }); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             final Rect rect = who.getBounds(); | ||||
| 
 | ||||
|             // okay... teh thing is IF we do not change bounds size, normal invalidate would do | ||||
|             // but if the size has changed, then we need to update the whole layout... | ||||
| 
 | ||||
|             final Rect rect = who.getBounds(); | ||||
| 
 | ||||
|             if (!previousBounds.equals(rect)) { | ||||
|                 // the only method that seems to work when bounds have changed | ||||
|                 view.setText(view.getText()); | ||||
|                 previousBounds = new Rect(rect); | ||||
|             } else { | ||||
|                 // if bounds are the same then simple invalidate would do | ||||
|                 final int scrollX = view.getScrollX(); | ||||
|                 final int scrollY = view.getScrollY(); | ||||
|                 view.postInvalidate( | ||||
|                         scrollX + rect.left, | ||||
|                         scrollY + rect.top, | ||||
|                         scrollX + rect.right, | ||||
|                         scrollY + rect.bottom | ||||
|                 ); | ||||
| //                // if bounds are the same then simple invalidate would do | ||||
| //                if (coordinatesProvider != null) { | ||||
| //                    final int x = coordinatesProvider.getX(); | ||||
| //                    final int y = coordinatesProvider.getY(); | ||||
| //                    view.postInvalidate( | ||||
| //                            x + rect.left, | ||||
| //                            y + rect.top, | ||||
| //                            x + rect.right, | ||||
| //                            y + rect.bottom | ||||
| //                    ); | ||||
| //                } else { | ||||
| //                    // else all we can do is request full re-draw... maybe system is smart enough not re-draw what is not on screen? | ||||
| //                    view.postInvalidate(); | ||||
| //                } | ||||
|                 view.postInvalidate(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -139,4 +167,23 @@ abstract class DrawablesScheduler { | ||||
|             view.removeCallbacks(what); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static class AsyncDrawableCoordinatesProvider implements CoordinatesProvider { | ||||
| 
 | ||||
|         private final AsyncDrawableSpan span; | ||||
| 
 | ||||
|         private AsyncDrawableCoordinatesProvider(AsyncDrawableSpan span) { | ||||
|             this.span = span; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int getX() { | ||||
|             return span.lastKnownDrawX(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int getY() { | ||||
|             return span.lastKnownDrawY(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -20,6 +20,7 @@ public class AsyncDrawable extends Drawable { | ||||
|     private final Loader loader; | ||||
| 
 | ||||
|     private Drawable result; | ||||
|     private Callback callback; | ||||
| 
 | ||||
|     public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) { | ||||
|         this.destination = destination; | ||||
| @ -45,6 +46,7 @@ public class AsyncDrawable extends Drawable { | ||||
|     // yeah | ||||
|     public void setCallback2(@Nullable Callback callback) { | ||||
| 
 | ||||
|         this.callback = callback; | ||||
|         super.setCallback(callback); | ||||
| 
 | ||||
|         // if not null -> means we are attached | ||||
| @ -58,7 +60,7 @@ public class AsyncDrawable extends Drawable { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setResult(Drawable result) { | ||||
|     public void setResult(@NonNull Drawable result) { | ||||
| 
 | ||||
|         // if we have previous one, detach it | ||||
|         if (this.result != null) { | ||||
| @ -66,7 +68,7 @@ public class AsyncDrawable extends Drawable { | ||||
|         } | ||||
| 
 | ||||
|         this.result = result; | ||||
|         this.result.setCallback(getCallback()); | ||||
|         this.result.setCallback(callback); | ||||
| 
 | ||||
|         // should we copy the data here? like bounds etc? | ||||
|         // if we are async and we load some image from some source | ||||
|  | ||||
| @ -25,6 +25,9 @@ public class AsyncDrawableSpan extends ReplacementSpan { | ||||
|     private final int alignment; | ||||
|     private final boolean replacementTextIsLink; | ||||
| 
 | ||||
|     private int lastKnownDrawX; | ||||
|     private int lastKnownDrawY; | ||||
| 
 | ||||
|     public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) { | ||||
|         this(theme, drawable, ALIGN_BOTTOM); | ||||
|     } | ||||
| @ -105,6 +108,9 @@ public class AsyncDrawableSpan extends ReplacementSpan { | ||||
|             int bottom, | ||||
|             @NonNull Paint paint) { | ||||
| 
 | ||||
|         this.lastKnownDrawX = (int) (x + .5F); | ||||
|         this.lastKnownDrawY = y; | ||||
| 
 | ||||
|         final AsyncDrawable drawable = this.drawable; | ||||
| 
 | ||||
|         if (drawable.hasResult()) { | ||||
| @ -144,4 +150,12 @@ public class AsyncDrawableSpan extends ReplacementSpan { | ||||
|     public AsyncDrawable getDrawable() { | ||||
|         return drawable; | ||||
|     } | ||||
| 
 | ||||
|     public int lastKnownDrawX() { | ||||
|         return lastKnownDrawX; | ||||
|     } | ||||
| 
 | ||||
|     public int lastKnownDrawY() { | ||||
|         return lastKnownDrawY; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,19 +0,0 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| 
 | ||||
| android { | ||||
| 
 | ||||
|     compileSdkVersion TARGET_SDK | ||||
|     buildToolsVersion BUILD_TOOLS | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion MIN_SDK | ||||
|         targetSdkVersion TARGET_SDK | ||||
|         versionCode 1 | ||||
|         versionName version | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     compile project(':library-renderer') | ||||
|     compile SUPPORT_ANNOTATIONS | ||||
| } | ||||
| @ -1 +0,0 @@ | ||||
| <manifest package="ru.noties.markwon.view"/> | ||||
| @ -1,59 +0,0 @@ | ||||
| package ru.noties.markwon.view; | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| import android.os.Build; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.util.AttributeSet; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; | ||||
| import org.commonmark.node.Node; | ||||
| import org.commonmark.parser.Parser; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| 
 | ||||
| import ru.noties.markwon.SpannableConfiguration; | ||||
| import ru.noties.markwon.renderer.SpannableRenderer; | ||||
| 
 | ||||
| public class MarkdownTextView extends TextView { | ||||
| 
 | ||||
|     private Parser parser; | ||||
|     private SpannableRenderer renderer; | ||||
|     private SpannableConfiguration configuration; | ||||
| 
 | ||||
|     public MarkdownTextView(Context context) { | ||||
|         super(context); | ||||
|     } | ||||
| 
 | ||||
|     public MarkdownTextView(Context context, @Nullable AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|     } | ||||
| 
 | ||||
|     public MarkdownTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|     } | ||||
| 
 | ||||
|     @TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     public MarkdownTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setText(CharSequence text, BufferType type) { | ||||
|         if (parser == null) { | ||||
|             parser = Parser.builder() | ||||
|                     .extensions(Collections.singletonList(StrikethroughExtension.create())) | ||||
|                     .build(); | ||||
|         } | ||||
|         if (renderer == null) { | ||||
|             renderer = new SpannableRenderer(); | ||||
|         } | ||||
|         if (configuration == null) { | ||||
|             configuration = SpannableConfiguration.create(getContext()); | ||||
|         } | ||||
|         final Node node = parser.parse(text.toString()); | ||||
|         final CharSequence cs = renderer.render(configuration, node); | ||||
|         super.setText(cs, type); | ||||
|     } | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| include ':app', ':library-renderer', ':library-view' | ||||
| include ':app', ':library-renderer' | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov