From a298016ac2c795e3b6b9ba61285aa1a89106b13b Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Sun, 2 Feb 2020 17:46:18 +0300
Subject: [PATCH 1/8] Working with inline latex parsing

---
 markwon-ext-latex/build.gradle                |  2 +
 .../ext/latex/JLatexMathBlockParser.java      |  2 +
 .../markwon/ext/latex/JLatexMathInline.java   | 19 ++++
 .../markwon/ext/latex/JLatexMathPlugin.java   | 94 ++++++++++++++++++-
 sample/build.gradle                           |  1 +
 5 files changed, 114 insertions(+), 4 deletions(-)
 create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java

diff --git a/markwon-ext-latex/build.gradle b/markwon-ext-latex/build.gradle
index 6e684440..9d5f50b0 100644
--- a/markwon-ext-latex/build.gradle
+++ b/markwon-ext-latex/build.gradle
@@ -19,6 +19,8 @@ dependencies {
 
     api deps['jlatexmath-android']
 
+    debugImplementation project(':markwon-inline-parser')
+
     deps['test'].with {
         testImplementation it['junit']
         testImplementation it['robolectric']
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
index 8f54f245..ba941c35 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
@@ -60,6 +60,8 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
         @Override
         public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
 
+            // ^\s{0,3}\$\$\s*$ as a regex to star the block
+
             final CharSequence line = state.getLine();
             final int length = line != null
                     ? line.length()
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java
new file mode 100644
index 00000000..b5d6810d
--- /dev/null
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java
@@ -0,0 +1,19 @@
+package io.noties.markwon.ext.latex;
+
+import org.commonmark.node.CustomNode;
+
+/**
+ * @since 4.2.1-SNAPSHOT
+ */
+public class JLatexMathInline extends CustomNode {
+
+    private String latex;
+
+    public String latex() {
+        return latex;
+    }
+
+    public void latex(String latex) {
+        this.latex = latex;
+    }
+}
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
index 5d136ece..10c4865b 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
@@ -14,6 +14,8 @@ import androidx.annotation.Nullable;
 import androidx.annotation.Px;
 import androidx.annotation.VisibleForTesting;
 
+import org.commonmark.node.Node;
+import org.commonmark.parser.InlineParserFactory;
 import org.commonmark.parser.Parser;
 
 import java.util.HashMap;
@@ -30,6 +32,9 @@ import io.noties.markwon.image.AsyncDrawableLoader;
 import io.noties.markwon.image.AsyncDrawableScheduler;
 import io.noties.markwon.image.AsyncDrawableSpan;
 import io.noties.markwon.image.ImageSizeResolver;
+import io.noties.markwon.image.ImageSizeResolverDef;
+import io.noties.markwon.inlineparser.InlineProcessor;
+import io.noties.markwon.inlineparser.MarkwonInlineParser;
 import ru.noties.jlatexmath.JLatexMathDrawable;
 
 /**
@@ -120,7 +125,17 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
     @Override
     public void configureParser(@NonNull Parser.Builder builder) {
-        builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
+
+        // what we can do:
+        // [0-3] spaces before block start/end
+        // if it's $$\n -> block
+        // if it's $$\\dhdsfjh$$ -> inline
+
+//        builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
+        final InlineParserFactory factory = MarkwonInlineParser.factoryBuilderNoDefaults()
+                .addInlineProcessor(new LatexInlineProcessor())
+                .build();
+        builder.inlineParserFactory(factory);
     }
 
     @Override
@@ -145,9 +160,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
                         new AsyncDrawable(
                                 latex,
                                 jLatextAsyncDrawableLoader,
-                                jLatexImageSizeResolver,
+                                new ImageSizeResolverDef(),
                                 null),
-                        AsyncDrawableSpan.ALIGN_BOTTOM,
+                        AsyncDrawableSpan.ALIGN_CENTER,
                         false);
 
                 visitor.setSpans(length, span);
@@ -155,6 +170,77 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         });
     }
 
+    private static class LatexInlineProcessor extends InlineProcessor {
+
+        @Override
+        public char specialCharacter() {
+            return '$';
+        }
+
+        @Nullable
+        @Override
+        protected Node parse() {
+
+            final int start = index;
+
+            index += 1;
+            if (peek() != '$') {
+                index = start;
+                return null;
+            }
+
+            // must be not $
+            index += 1;
+            if (peek() == '$') {
+                return text("$");
+            }
+
+            // find next '$$', but not broken with 2(or more) new lines
+
+            boolean dollar = false;
+            boolean newLine = false;
+            boolean found = false;
+
+            index += 1;
+            final int length = input.length();
+
+            while (index < length) {
+                final char c = peek();
+                if (c == '\n') {
+                    if (newLine) {
+                        // second new line
+                        break;
+                    }
+                    newLine = true;
+                    dollar = false; // cannot be on another line
+                } else {
+                    newLine = false;
+                    if (c == '$') {
+                        if (dollar) {
+                            found = true;
+                            // advance
+                            index += 1;
+                            break;
+                        }
+                        dollar = true;
+                    } else {
+                        dollar = false;
+                    }
+                }
+                index += 1;
+            }
+
+            if (found) {
+                final JLatexMathBlock block = new JLatexMathBlock();
+                block.latex(input.substring(start + 2, index - 2));
+                index += 1;
+                return block;
+            }
+
+            return null;
+        }
+    }
+
     @Override
     public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
         AsyncDrawableScheduler.unschedule(textView);
@@ -182,7 +268,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         @JLatexMathDrawable.Align
         private int align = JLatexMathDrawable.ALIGN_CENTER;
 
-        private boolean fitCanvas = true;
+        private boolean fitCanvas = false;
 
         // @since 4.0.0
         private int paddingHorizontal;
diff --git a/sample/build.gradle b/sample/build.gradle
index d2a9e27f..595fd54b 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -49,6 +49,7 @@ dependencies {
     implementation project(':markwon-syntax-highlight')
 
     implementation project(':markwon-image-picasso')
+    implementation project(':markwon-image-glide')
 
     deps.with {
         implementation it['x-recycler-view']

From d78b278b866e1fbc17207e3fca5306fddaa2cced Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Mon, 10 Feb 2020 22:25:20 +0300
Subject: [PATCH 2/8] Latex inline parsing WIP

---
 .../ext/latex/JLatexMathBlockParser.java      |  33 +++-
 .../ext/latex/JLatexMathInlineProcessor.java  |  36 ++++
 ...texMathInline.java => JLatexMathNode.java} |   4 +-
 .../markwon/ext/latex/JLatexMathPlugin.java   | 175 ++++++++++--------
 .../markwon/sample/latex/LatexActivity.java   |  26 +--
 5 files changed, 174 insertions(+), 100 deletions(-)
 create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java
 rename markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/{JLatexMathInline.java => JLatexMathNode.java} (76%)

diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
index ba941c35..575692bb 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
@@ -1,5 +1,6 @@
 package io.noties.markwon.ext.latex;
 
+import org.commonmark.internal.util.Parsing;
 import org.commonmark.node.Block;
 import org.commonmark.parser.block.AbstractBlockParser;
 import org.commonmark.parser.block.AbstractBlockParserFactory;
@@ -60,18 +61,30 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
         @Override
         public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
 
-            // ^\s{0,3}\$\$\s*$ as a regex to star the block
+            final int indent = state.getIndent();
 
-            final CharSequence line = state.getLine();
-            final int length = line != null
-                    ? line.length()
-                    : 0;
+            // check if it's an indented code block
+            if (indent < Parsing.CODE_BLOCK_INDENT) {
+                final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
+                final CharSequence line = state.getLine();
+                final int length = line.length();
+                // we are looking for 2 `$$` subsequent signs
+                // and immediate new-line or arbitrary number of white spaces (we check for the first one)
+                // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s
+                final int diff = length - (nextNonSpaceIndex + 2);
+                if (diff >= 0) {
+                    // check for both `$`
+                    if (line.charAt(nextNonSpaceIndex) == '$'
+                            && line.charAt(nextNonSpaceIndex + 1) == '$') {
 
-            if (length > 1) {
-                if ('$' == line.charAt(0)
-                        && '$' == line.charAt(1)) {
-                    return BlockStart.of(new JLatexMathBlockParser())
-                            .atIndex(state.getIndex() + 2);
+                        if (diff > 0) {
+                            if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) {
+                                return BlockStart.none();
+                            }
+                            // consume all until new-line or first not-white-space char
+                        }
+
+                    }
                 }
             }
 
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java
new file mode 100644
index 00000000..84c26b4f
--- /dev/null
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java
@@ -0,0 +1,36 @@
+package io.noties.markwon.ext.latex;
+
+import androidx.annotation.Nullable;
+
+import org.commonmark.node.Node;
+
+import java.util.regex.Pattern;
+
+import io.noties.markwon.inlineparser.InlineProcessor;
+
+/**
+ * @since 4.3.0-SNAPSHOT
+ */
+public class JLatexMathInlineProcessor extends InlineProcessor {
+
+    private static final Pattern RE = Pattern.compile("(\\${2})([\\s\\S]+?)\\1");
+
+    @Override
+    public char specialCharacter() {
+        return '$';
+    }
+
+    @Nullable
+    @Override
+    protected Node parse() {
+
+        final String latex = match(RE);
+        if (latex == null) {
+            return null;
+        }
+
+        final JLatexMathNode node = new JLatexMathNode();
+        node.latex(latex.substring(2, latex.length() - 2));
+        return node;
+    }
+}
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java
similarity index 76%
rename from markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java
rename to markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java
index b5d6810d..db7029a9 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java
@@ -3,9 +3,9 @@ package io.noties.markwon.ext.latex;
 import org.commonmark.node.CustomNode;
 
 /**
- * @since 4.2.1-SNAPSHOT
+ * @since 4.3.0-SNAPSHOT
  */
-public class JLatexMathInline extends CustomNode {
+public class JLatexMathNode extends CustomNode {
 
     private String latex;
 
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
index 10c4865b..57485623 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
@@ -14,7 +14,6 @@ import androidx.annotation.Nullable;
 import androidx.annotation.Px;
 import androidx.annotation.VisibleForTesting;
 
-import org.commonmark.node.Node;
 import org.commonmark.parser.InlineParserFactory;
 import org.commonmark.parser.Parser;
 
@@ -33,7 +32,6 @@ import io.noties.markwon.image.AsyncDrawableScheduler;
 import io.noties.markwon.image.AsyncDrawableSpan;
 import io.noties.markwon.image.ImageSizeResolver;
 import io.noties.markwon.image.ImageSizeResolverDef;
-import io.noties.markwon.inlineparser.InlineProcessor;
 import io.noties.markwon.inlineparser.MarkwonInlineParser;
 import ru.noties.jlatexmath.JLatexMathDrawable;
 
@@ -132,8 +130,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         // if it's $$\\dhdsfjh$$ -> inline
 
 //        builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
-        final InlineParserFactory factory = MarkwonInlineParser.factoryBuilderNoDefaults()
-                .addInlineProcessor(new LatexInlineProcessor())
+        final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
+                .addInlineProcessor(new JLatexMathInlineProcessor())
                 .build();
         builder.inlineParserFactory(factory);
     }
@@ -155,6 +153,33 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
                 final MarkwonConfiguration configuration = visitor.configuration();
 
+                final AsyncDrawableSpan span = new AsyncDrawableSpan(
+                        configuration.theme(),
+                        new AsyncDrawable(
+                                latex,
+                                jLatextAsyncDrawableLoader,
+                                jLatexImageSizeResolver,
+                                null),
+                        AsyncDrawableSpan.ALIGN_CENTER,
+                        false);
+
+                visitor.setSpans(length, span);
+            }
+        });
+        builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
+            @Override
+            public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) {
+                final String latex = jLatexMathNode.latex();
+
+                final int length = visitor.length();
+
+                // @since 4.0.2 we cannot append _raw_ latex as a placeholder-text,
+                // because Android will draw formula for each line of text, thus
+                // leading to formula duplicated (drawn on each line of text)
+                visitor.builder().append(prepareLatexTextPlaceholder(latex));
+
+                final MarkwonConfiguration configuration = visitor.configuration();
+
                 final AsyncDrawableSpan span = new AsyncDrawableSpan(
                         configuration.theme(),
                         new AsyncDrawable(
@@ -170,76 +195,76 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         });
     }
 
-    private static class LatexInlineProcessor extends InlineProcessor {
-
-        @Override
-        public char specialCharacter() {
-            return '$';
-        }
-
-        @Nullable
-        @Override
-        protected Node parse() {
-
-            final int start = index;
-
-            index += 1;
-            if (peek() != '$') {
-                index = start;
-                return null;
-            }
-
-            // must be not $
-            index += 1;
-            if (peek() == '$') {
-                return text("$");
-            }
-
-            // find next '$$', but not broken with 2(or more) new lines
-
-            boolean dollar = false;
-            boolean newLine = false;
-            boolean found = false;
-
-            index += 1;
-            final int length = input.length();
-
-            while (index < length) {
-                final char c = peek();
-                if (c == '\n') {
-                    if (newLine) {
-                        // second new line
-                        break;
-                    }
-                    newLine = true;
-                    dollar = false; // cannot be on another line
-                } else {
-                    newLine = false;
-                    if (c == '$') {
-                        if (dollar) {
-                            found = true;
-                            // advance
-                            index += 1;
-                            break;
-                        }
-                        dollar = true;
-                    } else {
-                        dollar = false;
-                    }
-                }
-                index += 1;
-            }
-
-            if (found) {
-                final JLatexMathBlock block = new JLatexMathBlock();
-                block.latex(input.substring(start + 2, index - 2));
-                index += 1;
-                return block;
-            }
-
-            return null;
-        }
-    }
+//    private static class LatexInlineProcessor extends InlineProcessor {
+//
+//        @Override
+//        public char specialCharacter() {
+//            return '$';
+//        }
+//
+//        @Nullable
+//        @Override
+//        protected Node parse() {
+//
+//            final int start = index;
+//
+//            index += 1;
+//            if (peek() != '$') {
+//                index = start;
+//                return null;
+//            }
+//
+//            // must be not $
+//            index += 1;
+//            if (peek() == '$') {
+//                return text("$");
+//            }
+//
+//            // find next '$$', but not broken with 2(or more) new lines
+//
+//            boolean dollar = false;
+//            boolean newLine = false;
+//            boolean found = false;
+//
+//            index += 1;
+//            final int length = input.length();
+//
+//            while (index < length) {
+//                final char c = peek();
+//                if (c == '\n') {
+//                    if (newLine) {
+//                        // second new line
+//                        break;
+//                    }
+//                    newLine = true;
+//                    dollar = false; // cannot be on another line
+//                } else {
+//                    newLine = false;
+//                    if (c == '$') {
+//                        if (dollar) {
+//                            found = true;
+//                            // advance
+//                            index += 1;
+//                            break;
+//                        }
+//                        dollar = true;
+//                    } else {
+//                        dollar = false;
+//                    }
+//                }
+//                index += 1;
+//            }
+//
+//            if (found) {
+//                final JLatexMathBlock block = new JLatexMathBlock();
+//                block.latex(input.substring(start + 2, index - 2));
+//                index += 1;
+//                return block;
+//            }
+//
+//            return null;
+//        }
+//    }
 
     @Override
     public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
@@ -383,7 +408,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
                                         .textSize(config.textSize)
                                         .background(backgroundProvider != null ? backgroundProvider.provide() : null)
                                         .align(config.align)
-                                        .fitCanvas(config.fitCanvas)
+                                        .fitCanvas(false /*config.fitCanvas*/)
                                         .padding(
                                                 config.paddingHorizontal,
                                                 config.paddingVertical,
diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
index 44d9aac3..8b81d119 100644
--- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
@@ -24,26 +24,26 @@ public class LatexActivity extends Activity {
 
         final TextView textView = findViewById(R.id.text_view);
 
-        String latex = "\\begin{array}{l}";
-        latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\";
-        latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\";
-        latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\";
-        latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\";
-        latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty  {\\sum\\limits_{m = 1}^{2^n  - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\";
-        latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\";
-        latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\";
-        latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\";
-        latex += "\\end{array}";
+//        String latex = "\\begin{array}{l}";
+//        latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\";
+//        latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\";
+//        latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\";
+//        latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\";
+//        latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty  {\\sum\\limits_{m = 1}^{2^n  - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\";
+//        latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\";
+//        latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\";
+//        latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\";
+//        latex += "\\end{array}";
 
-//        String latex = "\\text{A long division \\longdiv{12345}{13}";
-//                String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}";
+        String latex = "\\text{A long division \\longdiv{12345}{13}";
+//        String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}";
 
 //        String latex = "\\begin{array}{cc}";
 //        latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr";
 //        latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr";
 //        latex += "\\end{array}";
 
-        final String markdown = "# Example of LaTeX\n\n$$"
+        final String markdown = "# Example of LaTeX\n\nhello there: $$"
                 + latex + "$$\n\n something like **this**";
 
         final Markwon markwon = Markwon.builder(this)

From 7af0ead3a31a5805f2eb7e0cdc708a9a673f9048 Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Fri, 14 Feb 2020 18:35:44 +0300
Subject: [PATCH 3/8] Working with latex plugin

---
 README.md                                     |   2 +
 .../ext/latex/JLatexMathBlockParser.java      | 184 +++++++++++----
 .../markwon/ext/latex/JLatexMathPlugin.java   | 218 ++++++++++--------
 .../markwon/ext/latex/JLatexMathTheme.java    |  28 +++
 .../markwon/sample/latex/LatexActivity.java   |  49 ++--
 5 files changed, 332 insertions(+), 149 deletions(-)
 create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java

diff --git a/README.md b/README.md
index 594d9781..350479bf 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@
 
 [![Build](https://github.com/noties/Markwon/workflows/Build/badge.svg)](https://github.com/noties/Markwon/actions)
 
+![hey](http://img.xiaoyv.top/bbs/201603246-20aa1b8ad8bf27df3c906473619c2d84.jpg)
+
 **Markwon** is a markdown library for Android. It parses markdown
 following [commonmark-spec] with the help of amazing [commonmark-java]
 library and renders result as _Android-native_ Spannables. **No HTML**
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
index 575692bb..cb159aef 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
@@ -1,5 +1,9 @@
 package io.noties.markwon.ext.latex;
 
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
 import org.commonmark.internal.util.Parsing;
 import org.commonmark.node.Block;
 import org.commonmark.parser.block.AbstractBlockParser;
@@ -11,11 +15,21 @@ import org.commonmark.parser.block.ParserState;
 
 public class JLatexMathBlockParser extends AbstractBlockParser {
 
+    private static final char DOLLAR = '$';
+    private static final char SPACE = ' ';
+
     private final JLatexMathBlock block = new JLatexMathBlock();
 
     private final StringBuilder builder = new StringBuilder();
 
-    private boolean isClosed;
+//    private boolean isClosed;
+
+    private final int signs;
+
+    @SuppressWarnings("WeakerAccess")
+    JLatexMathBlockParser(int signs) {
+        this.signs = signs;
+    }
 
     @Override
     public Block getBlock() {
@@ -24,9 +38,22 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
 
     @Override
     public BlockContinue tryContinue(ParserState parserState) {
+        final int nextNonSpaceIndex = parserState.getNextNonSpaceIndex();
+        final CharSequence line = parserState.getLine();
+        final int length = line.length();
 
-        if (isClosed) {
-            return BlockContinue.finished();
+        // check for closing
+        if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) {
+            Log.e("LTX", String.format("signs: %d, skip dollar: %s", signs, Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length)));
+//            if (Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
+            if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
+                // okay, we have our number of signs
+                // let's consume spaces until the end
+                Log.e("LTX", String.format("length; %d, skip spaces: %s", length, Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length)));
+                if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) {
+                    return BlockContinue.finished();
+                }
+            }
         }
 
         return BlockContinue.atIndex(parserState.getIndex());
@@ -34,21 +61,24 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
 
     @Override
     public void addLine(CharSequence line) {
-
-        if (builder.length() > 0) {
-            builder.append('\n');
-        }
-
+//
+//        if (builder.length() > 0) {
+//            builder.append('\n');
+//        }
+//
+//        builder.append(line);
+//
+//        final int length = builder.length();
+//        if (length > 1) {
+//            isClosed = '$' == builder.charAt(length - 1)
+//                    && '$' == builder.charAt(length - 2);
+//            if (isClosed) {
+//                builder.replace(length - 2, length, "");
+//            }
+//        }
+        Log.e("LTX", "addLine: " + line);
         builder.append(line);
-
-        final int length = builder.length();
-        if (length > 1) {
-            isClosed = '$' == builder.charAt(length - 1)
-                    && '$' == builder.charAt(length - 2);
-            if (isClosed) {
-                builder.replace(length - 2, length, "");
-            }
-        }
+        builder.append('\n');
     }
 
     @Override
@@ -58,37 +88,111 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
 
     public static class Factory extends AbstractBlockParserFactory {
 
+//        private static final Pattern RE = Pattern.compile("(\\${2,}) *$");
+
         @Override
         public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
 
+            // let's define the spec:
+            //  * 0-3 spaces before are allowed (Parsing.CODE_BLOCK_INDENT = 4)
+            //  * 2+ subsequent `$` signs
+            //  * any optional amount of spaces
+            //  * new line
+            //  * block is closed when the same amount of opening signs is met
+
             final int indent = state.getIndent();
 
             // check if it's an indented code block
-            if (indent < Parsing.CODE_BLOCK_INDENT) {
-                final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
-                final CharSequence line = state.getLine();
-                final int length = line.length();
-                // we are looking for 2 `$$` subsequent signs
-                // and immediate new-line or arbitrary number of white spaces (we check for the first one)
-                // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s
-                final int diff = length - (nextNonSpaceIndex + 2);
-                if (diff >= 0) {
-                    // check for both `$`
-                    if (line.charAt(nextNonSpaceIndex) == '$'
-                            && line.charAt(nextNonSpaceIndex + 1) == '$') {
-
-                        if (diff > 0) {
-                            if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) {
-                                return BlockStart.none();
-                            }
-                            // consume all until new-line or first not-white-space char
-                        }
-
-                    }
-                }
+            if (indent >= Parsing.CODE_BLOCK_INDENT) {
+                return BlockStart.none();
             }
 
-            return BlockStart.none();
+            final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
+            final CharSequence line = state.getLine();
+            final int length = line.length();
+
+//            final int signs = Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) - 1;
+            final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length);
+
+            // 2 is minimum
+            if (signs < 2) {
+                return BlockStart.none();
+            }
+
+            // consume spaces until the end of the line, if any other content is found -> NONE
+            if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
+                return BlockStart.none();
+            }
+
+            Log.e("LTX", String.format("signs: %s, next: %d, length: %d, line: '%s'", signs, nextNonSpaceIndex, length, line));
+
+            return BlockStart.of(new JLatexMathBlockParser(signs))
+                    .atIndex(length + 1);
+
+
+//            // check if it's an indented code block
+//            if (indent < Parsing.CODE_BLOCK_INDENT) {
+//
+//                final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
+//                final CharSequence line = state.getLine();
+//                final int length = line.length();
+//
+//                final int signs = Parsing.skip('$', line, nextNonSpaceIndex, length);
+//
+//                // 2 is minimum
+//                if (signs < 2) {
+//                    return BlockStart.none();
+//                }
+//
+//                // consume spaces until the end of the line, if any other content is found -> NONE
+//                if (Parsing.skip(' ', line, nextNonSpaceIndex + signs, length) != length) {
+//                    return BlockStart.none();
+//                }
+//
+////                // consume spaces until the end of the line, if any other content is found -> NONE
+////                if ((nextNonSpaceIndex + signs) < length) {
+////                    // check if more content is available
+////                    if (Parsing.skip(' ',  line,nextNonSpaceIndex + signs, length) != length) {
+////                        return BlockStart.none();
+////                    }
+////                }
+//
+////                final Matcher matcher = RE.matcher(line);
+////                matcher.region(nextNonSpaceIndex, length);
+//
+////                Log.e("LATEX", String.format("nonSpace: %d, length: %s, line: '%s'", nextNonSpaceIndex, length, line));
+//
+//                // we are looking for 2 `$$` subsequent signs
+//                // and immediate new-line or arbitrary number of white spaces (we check for the first one)
+//                // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s
+//                final int diff = length - (nextNonSpaceIndex + 2);
+//                if (diff >= 0) {
+//                    // check for both `$`
+//                    if (line.charAt(nextNonSpaceIndex) == '$'
+//                            && line.charAt(nextNonSpaceIndex + 1) == '$') {
+//
+//                        if (diff > 0) {
+//                            if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) {
+//                                return BlockStart.none();
+//                            }
+//                            return BlockStart.of(new JLatexMathBlockParser()).atIndex(nextNonSpaceIndex + 3);
+//                        }
+//
+//                    }
+//                }
+//            }
+//
+//            return BlockStart.none();
         }
     }
+
+    private static int consume(char c, @NonNull CharSequence line, int start, int end) {
+        for (int i = start; i < end; i++) {
+            if (c != line.charAt(i)) {
+                return i - start;
+            }
+        }
+        // all consumed
+        return end - start;
+    }
 }
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
index 57485623..68308f6c 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
@@ -1,5 +1,6 @@
 package io.noties.markwon.ext.latex;
 
+import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -9,6 +10,7 @@ import android.text.Spanned;
 import android.util.Log;
 import android.widget.TextView;
 
+import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
@@ -26,10 +28,12 @@ import java.util.concurrent.Future;
 import io.noties.markwon.AbstractMarkwonPlugin;
 import io.noties.markwon.MarkwonConfiguration;
 import io.noties.markwon.MarkwonVisitor;
+import io.noties.markwon.core.MarkwonTheme;
 import io.noties.markwon.image.AsyncDrawable;
 import io.noties.markwon.image.AsyncDrawableLoader;
 import io.noties.markwon.image.AsyncDrawableScheduler;
 import io.noties.markwon.image.AsyncDrawableSpan;
+import io.noties.markwon.image.ImageSize;
 import io.noties.markwon.image.ImageSizeResolver;
 import io.noties.markwon.image.ImageSizeResolverDef;
 import io.noties.markwon.inlineparser.MarkwonInlineParser;
@@ -129,7 +133,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         // if it's $$\n -> block
         // if it's $$\\dhdsfjh$$ -> inline
 
-//        builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
+        builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
+
         final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
                 .addInlineProcessor(new JLatexMathInlineProcessor())
                 .build();
@@ -142,6 +147,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
             @Override
             public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) {
 
+                visitor.ensureNewLine();
+
                 final String latex = jLatexMathBlock.latex();
 
                 final int length = visitor.length();
@@ -155,15 +162,21 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
                 final AsyncDrawableSpan span = new AsyncDrawableSpan(
                         configuration.theme(),
-                        new AsyncDrawable(
+                        new JLatextAsyncDrawable(
                                 latex,
                                 jLatextAsyncDrawableLoader,
                                 jLatexImageSizeResolver,
-                                null),
+                                null,
+                                true),
                         AsyncDrawableSpan.ALIGN_CENTER,
                         false);
 
                 visitor.setSpans(length, span);
+
+                if (visitor.hasNext(jLatexMathBlock)) {
+                    visitor.ensureNewLine();
+                    visitor.forceNewLine();
+                }
             }
         });
         builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
@@ -180,13 +193,14 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
                 final MarkwonConfiguration configuration = visitor.configuration();
 
-                final AsyncDrawableSpan span = new AsyncDrawableSpan(
+                final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan(
                         configuration.theme(),
-                        new AsyncDrawable(
+                        new JLatextAsyncDrawable(
                                 latex,
                                 jLatextAsyncDrawableLoader,
                                 new ImageSizeResolverDef(),
-                                null),
+                                null,
+                                false),
                         AsyncDrawableSpan.ALIGN_CENTER,
                         false);
 
@@ -195,77 +209,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         });
     }
 
-//    private static class LatexInlineProcessor extends InlineProcessor {
-//
-//        @Override
-//        public char specialCharacter() {
-//            return '$';
-//        }
-//
-//        @Nullable
-//        @Override
-//        protected Node parse() {
-//
-//            final int start = index;
-//
-//            index += 1;
-//            if (peek() != '$') {
-//                index = start;
-//                return null;
-//            }
-//
-//            // must be not $
-//            index += 1;
-//            if (peek() == '$') {
-//                return text("$");
-//            }
-//
-//            // find next '$$', but not broken with 2(or more) new lines
-//
-//            boolean dollar = false;
-//            boolean newLine = false;
-//            boolean found = false;
-//
-//            index += 1;
-//            final int length = input.length();
-//
-//            while (index < length) {
-//                final char c = peek();
-//                if (c == '\n') {
-//                    if (newLine) {
-//                        // second new line
-//                        break;
-//                    }
-//                    newLine = true;
-//                    dollar = false; // cannot be on another line
-//                } else {
-//                    newLine = false;
-//                    if (c == '$') {
-//                        if (dollar) {
-//                            found = true;
-//                            // advance
-//                            index += 1;
-//                            break;
-//                        }
-//                        dollar = true;
-//                    } else {
-//                        dollar = false;
-//                    }
-//                }
-//                index += 1;
-//            }
-//
-//            if (found) {
-//                final JLatexMathBlock block = new JLatexMathBlock();
-//                block.latex(input.substring(start + 2, index - 2));
-//                index += 1;
-//                return block;
-//            }
-//
-//            return null;
-//        }
-//    }
-
     @Override
     public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
         AsyncDrawableScheduler.unschedule(textView);
@@ -401,20 +344,53 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
                         // @since 4.0.1 (background provider can be null)
                         final BackgroundProvider backgroundProvider = config.backgroundProvider;
 
+                        final JLatexMathDrawable jLatexMathDrawable;
+
+                        final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
+                        if (jLatextAsyncDrawable.isBlock) {
+                            // create JLatexMathDrawable
+                            //noinspection ConstantConditions
+                            jLatexMathDrawable =
+                                    JLatexMathDrawable.builder(drawable.getDestination())
+                                            .textSize(config.textSize)
+                                            .background(backgroundProvider != null ? backgroundProvider.provide() : null)
+                                            .align(config.align)
+                                            .fitCanvas(config.fitCanvas)
+                                            .padding(
+                                                    config.paddingHorizontal,
+                                                    config.paddingVertical,
+                                                    config.paddingHorizontal,
+                                                    config.paddingVertical)
+                                            .build();
+                        } else {
+                            jLatexMathDrawable =
+                                    JLatexMathDrawable.builder(drawable.getDestination())
+                                            .textSize(config.textSize)
+//                                            .background(backgroundProvider != null ? backgroundProvider.provide() : null)
+//                                            .align(config.align)
+//                                            .fitCanvas(config.fitCanvas)
+//                                            .padding(
+//                                                    config.paddingHorizontal,
+//                                                    config.paddingVertical,
+//                                                    config.paddingHorizontal,
+//                                                    config.paddingVertical)
+                                            .build();
+                        }
+
                         // create JLatexMathDrawable
-                        //noinspection ConstantConditions
-                        final JLatexMathDrawable jLatexMathDrawable =
-                                JLatexMathDrawable.builder(drawable.getDestination())
-                                        .textSize(config.textSize)
-                                        .background(backgroundProvider != null ? backgroundProvider.provide() : null)
-                                        .align(config.align)
-                                        .fitCanvas(false /*config.fitCanvas*/)
-                                        .padding(
-                                                config.paddingHorizontal,
-                                                config.paddingVertical,
-                                                config.paddingHorizontal,
-                                                config.paddingVertical)
-                                        .build();
+//                        //noinspection ConstantConditions
+//                        final JLatexMathDrawable jLatexMathDrawable =
+//                                JLatexMathDrawable.builder(drawable.getDestination())
+//                                        .textSize(config.textSize)
+//                                        .background(backgroundProvider != null ? backgroundProvider.provide() : null)
+//                                        .align(config.align)
+//                                        .fitCanvas(config.fitCanvas)
+//                                        .padding(
+//                                                config.paddingHorizontal,
+//                                                config.paddingVertical,
+//                                                config.paddingHorizontal,
+//                                                config.paddingVertical)
+//                                        .build();
 
                         // we must post to handler, but also have a way to identify the drawable
                         // for which we are posting (in case of cancellation)
@@ -496,4 +472,66 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
             return imageBounds;
         }
     }
+
+    private static class JLatextAsyncDrawable extends AsyncDrawable {
+
+        private final boolean isBlock;
+
+        public JLatextAsyncDrawable(
+                @NonNull String destination,
+                @NonNull AsyncDrawableLoader loader,
+                @NonNull ImageSizeResolver imageSizeResolver,
+                @Nullable ImageSize imageSize,
+                boolean isBlock
+        ) {
+            super(destination, loader, imageSizeResolver, imageSize);
+            this.isBlock = isBlock;
+        }
+    }
+
+    private static class JLatexAsyncDrawableSpan extends AsyncDrawableSpan {
+
+        private final AsyncDrawable drawable;
+
+        public JLatexAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) {
+            super(theme, drawable, alignment, replacementTextIsLink);
+            this.drawable = drawable;
+        }
+
+        @Override
+        public int getSize(
+                @NonNull Paint paint,
+                CharSequence text,
+                @IntRange(from = 0) int start,
+                @IntRange(from = 0) int end,
+                @Nullable Paint.FontMetricsInt fm) {
+
+            // if we have no async drawable result - we will just render text
+
+            final int size;
+
+            if (drawable.hasResult()) {
+
+                final Rect rect = drawable.getBounds();
+
+                if (fm != null) {
+                    final int half = rect.bottom / 2;
+                    fm.ascent = -half;
+                    fm.descent = half;
+
+                    fm.top = fm.ascent;
+                    fm.bottom = 0;
+                }
+
+                size = rect.right;
+
+            } else {
+
+                // NB, no specific text handling (no new lines, etc)
+                size = (int) (paint.measureText(text, start, end) + .5F);
+            }
+
+            return size;
+        }
+    }
 }
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
new file mode 100644
index 00000000..956eb20f
--- /dev/null
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
@@ -0,0 +1,28 @@
+package io.noties.markwon.ext.latex;
+
+import android.graphics.Rect;
+
+/**
+ * @since 4.3.0-SNAPSHOT
+ */
+public class JLatexMathTheme {
+
+    private float textSize;
+    private float inlineTextSize;
+    private float blockTextSize;
+
+    // TODO: move to a class
+    private JLatexMathPlugin.BackgroundProvider backgroundProvider;
+    private JLatexMathPlugin.BackgroundProvider inlineBackgroundProvider;
+    private JLatexMathPlugin.BackgroundProvider blockBackgroundProvider;
+
+    private boolean blockFitCanvas;
+    // horizontal alignment (when there is additional horizontal space)
+    private int blockAlign;
+
+    private Rect padding;
+    private Rect inlinePadding;
+    private Rect blockPadding;
+
+
+}
diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
index 8b81d119..f7c382b3 100644
--- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
@@ -4,14 +4,19 @@ import android.app.Activity;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.util.Log;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import org.commonmark.node.Node;
+
+import io.noties.markwon.AbstractMarkwonPlugin;
 import io.noties.markwon.Markwon;
 import io.noties.markwon.ext.latex.JLatexMathPlugin;
 import io.noties.markwon.sample.R;
+import io.noties.markwon.utils.DumpNodes;
 import ru.noties.jlatexmath.JLatexMathDrawable;
 
 public class LatexActivity extends Activity {
@@ -44,27 +49,33 @@ public class LatexActivity extends Activity {
 //        latex += "\\end{array}";
 
         final String markdown = "# Example of LaTeX\n\nhello there: $$"
-                + latex + "$$\n\n something like **this**";
+                + latex + "$$ so nice, really?\n\n $$  \n" + latex + "\n$$\n\n   $$     \n" + latex + "\n$$";
 
         final Markwon markwon = Markwon.builder(this)
-//                .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
-//                    @Override
-//                    public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
-//                        builder
-//                                .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() {
-//                                    @NonNull
-//                                    @Override
-//                                    public Drawable provide() {
-//                                        return new ColorDrawable(0x40ff0000);
-//                                    }
-//                                })
-//                                .fitCanvas(true)
-//                                .align(JLatexMathDrawable.ALIGN_LEFT)
-//                                .padding(48)
-//                        ;
-//                    }
-//                }))
-                .usePlugin(JLatexMathPlugin.create(textView.getTextSize()))
+                .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
+                    @Override
+                    public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
+                        builder
+                                .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() {
+                                    @NonNull
+                                    @Override
+                                    public Drawable provide() {
+                                        return new ColorDrawable(0x40ff0000);
+                                    }
+                                })
+                                .fitCanvas(true)
+                                .align(JLatexMathDrawable.ALIGN_CENTER)
+                                .padding(48)
+                        ;
+                    }
+                }))
+//                .usePlugin(JLatexMathPlugin.create(textView.getTextSize()))
+                .usePlugin(new AbstractMarkwonPlugin() {
+                    @Override
+                    public void beforeRender(@NonNull Node node) {
+                        Log.e("LTX", DumpNodes.dump(node));
+                    }
+                })
                 .build();
 //
 //        if (true) {

From 8d483fe49dc2211dfcb4e4c27860ce7c2714cdd6 Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Wed, 26 Feb 2020 09:51:33 +0300
Subject: [PATCH 4/8] Sample, add task list toggle

---
 .../markwon/ext/latex/JLatexMathPlugin.java   |  18 +--
 .../markwon/ext/latex/JLatexMathTheme.java    | 130 ++++++++++++++++--
 sample/src/main/AndroidManifest.xml           |   1 +
 .../noties/markwon/sample/MainActivity.java   |   5 +
 .../java/io/noties/markwon/sample/Sample.java |   4 +-
 .../markwon/sample/latex/LatexActivity.java   |   3 +-
 .../sample/tasklist/TaskListActivity.java     | 111 +++++++++++++++
 .../src/main/res/values/strings-samples.xml   |   2 +
 8 files changed, 245 insertions(+), 29 deletions(-)
 create mode 100644 sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java

diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
index 68308f6c..3f85b9f7 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
@@ -42,15 +42,7 @@ import ru.noties.jlatexmath.JLatexMathDrawable;
 /**
  * @since 3.0.0
  */
-public class JLatexMathPlugin extends AbstractMarkwonPlugin {
-
-    /**
-     * @since 4.0.0
-     */
-    public interface BackgroundProvider {
-        @NonNull
-        Drawable provide();
-    }
+public class  JLatexMathPlugin extends AbstractMarkwonPlugin {
 
     public interface BuilderConfigure {
         void configureBuilder(@NonNull Builder builder);
@@ -83,7 +75,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         private final float textSize;
 
         // @since 4.0.0
-        private final BackgroundProvider backgroundProvider;
+        private final JLatexMathTheme.BackgroundProvider backgroundProvider;
 
         @JLatexMathDrawable.Align
         private final int align;
@@ -231,7 +223,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         private final float textSize;
 
         // @since 4.0.0
-        private BackgroundProvider backgroundProvider;
+        private JLatexMathTheme.BackgroundProvider backgroundProvider;
 
         @JLatexMathDrawable.Align
         private int align = JLatexMathDrawable.ALIGN_CENTER;
@@ -252,7 +244,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         }
 
         @NonNull
-        public Builder backgroundProvider(@NonNull BackgroundProvider backgroundProvider) {
+        public Builder backgroundProvider(@NonNull JLatexMathTheme.BackgroundProvider backgroundProvider) {
             this.backgroundProvider = backgroundProvider;
             return this;
         }
@@ -342,7 +334,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
                     private void execute() {
 
                         // @since 4.0.1 (background provider can be null)
-                        final BackgroundProvider backgroundProvider = config.backgroundProvider;
+                        final JLatexMathTheme.BackgroundProvider backgroundProvider = config.backgroundProvider;
 
                         final JLatexMathDrawable jLatexMathDrawable;
 
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
index 956eb20f..04ea346c 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
@@ -1,28 +1,130 @@
 package io.noties.markwon.ext.latex;
 
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+
+import ru.noties.jlatexmath.JLatexMathDrawable;
 
 /**
  * @since 4.3.0-SNAPSHOT
  */
-public class JLatexMathTheme {
+public abstract class JLatexMathTheme {
 
-    private float textSize;
-    private float inlineTextSize;
-    private float blockTextSize;
+    @NonNull
+    public static JLatexMathTheme create(@Px float textSize) {
+        return null;
+    }
 
-    // TODO: move to a class
-    private JLatexMathPlugin.BackgroundProvider backgroundProvider;
-    private JLatexMathPlugin.BackgroundProvider inlineBackgroundProvider;
-    private JLatexMathPlugin.BackgroundProvider blockBackgroundProvider;
+    @NonNull
+    public static JLatexMathTheme builer() {
+        return null;
+    }
 
-    private boolean blockFitCanvas;
-    // horizontal alignment (when there is additional horizontal space)
-    private int blockAlign;
+    /**
+     * Moved from {@link JLatexMathPlugin} in {@code 4.3.0-SNAPSHOT} version
+     *
+     * @since 4.0.0
+     */
+    public interface BackgroundProvider {
+        @NonNull
+        Drawable provide();
+    }
 
-    private Rect padding;
-    private Rect inlinePadding;
-    private Rect blockPadding;
+    /**
+     * Special immutable class to hold padding information
+     */
+    public static class Padding {
+        public final int left;
+        public final int top;
+        public final int right;
+        public final int bottom;
+
+        public Padding(int left, int top, int right, int bottom) {
+            this.left = left;
+            this.top = top;
+            this.right = right;
+            this.bottom = bottom;
+        }
+
+        @Override
+        public String toString() {
+            return "Padding{" +
+                    "left=" + left +
+                    ", top=" + top +
+                    ", right=" + right +
+                    ", bottom=" + bottom +
+                    '}';
+        }
+
+        @NonNull
+        public static Padding all(int value) {
+            return new Padding(value, value, value, value);
+        }
+
+        @NonNull
+        public static Padding symmetric(int vertical, int horizontal) {
+            return new Padding(horizontal, vertical, horizontal, vertical);
+        }
+    }
+
+    /**
+     * @return text size in pixels for <strong>inline LaTeX</strong>
+     * @see #blockTexxtSize()
+     */
+    @Px
+    public abstract float inlineTextSize();
+
+    /**
+     * @return text size in pixels for <strong>block LaTeX</strong>
+     * @see #inlineTextSize()
+     */
+    @Px
+    public abstract float blockTexxtSize();
+
+    @Nullable
+    public abstract BackgroundProvider inlineBackgroundProvider();
+
+    @Nullable
+    public abstract BackgroundProvider blockBackgroundProvider();
+
+    /**
+     * @return boolean if <strong>block LaTeX</strong> must fit the width of canvas
+     */
+    public abstract boolean blockFitCanvas();
+
+    /**
+     * @return horizontal alignment of <strong>block LaTeX</strong> if {@link #blockFitCanvas()}
+     * is enabled (thus space for alignment is available)
+     */
+    @JLatexMathDrawable.Align
+    public abstract int blockHorizontalAlignment();
+
+    @Nullable
+    public abstract Padding inlinePadding();
+
+    @Nullable
+    public abstract Padding blockPadding();
 
 
+    public static class Builder {
+        private float textSize;
+        private float inlineTextSize;
+        private float blockTextSize;
+
+        private BackgroundProvider backgroundProvider;
+        private BackgroundProvider inlineBackgroundProvider;
+        private BackgroundProvider blockBackgroundProvider;
+
+        private boolean blockFitCanvas;
+        // horizontal alignment (when there is additional horizontal space)
+        private int blockAlign;
+
+        private Padding padding;
+        private Padding inlinePadding;
+        private Padding blockPadding;
+    }
 }
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 5e0ae714..0c02f47f 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -35,6 +35,7 @@
 
         <activity android:name=".inlineparser.InlineParserActivity" />
         <activity android:name=".htmldetails.HtmlDetailsActivity" />
+        <activity android:name=".tasklist.TaskListActivity" />
 
     </application>
 
diff --git a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
index 4cdd6d73..a14a8183 100644
--- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
@@ -30,6 +30,7 @@ import io.noties.markwon.sample.latex.LatexActivity;
 import io.noties.markwon.sample.precomputed.PrecomputedActivity;
 import io.noties.markwon.sample.recycler.RecyclerActivity;
 import io.noties.markwon.sample.simpleext.SimpleExtActivity;
+import io.noties.markwon.sample.tasklist.TaskListActivity;
 
 public class MainActivity extends Activity {
 
@@ -132,6 +133,10 @@ public class MainActivity extends Activity {
                 activity = HtmlDetailsActivity.class;
                 break;
 
+            case TASK_LIST:
+                activity = TaskListActivity.class;
+                break;
+
             default:
                 throw new IllegalStateException("No Activity is associated with sample-item: " + item);
         }
diff --git a/sample/src/main/java/io/noties/markwon/sample/Sample.java b/sample/src/main/java/io/noties/markwon/sample/Sample.java
index 36b13cd2..f243c0ec 100644
--- a/sample/src/main/java/io/noties/markwon/sample/Sample.java
+++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java
@@ -27,7 +27,9 @@ public enum Sample {
 
     INLINE_PARSER(R.string.sample_inline_parser),
 
-    HTML_DETAILS(R.string.sample_html_details);
+    HTML_DETAILS(R.string.sample_html_details),
+
+    TASK_LIST(R.string.sample_task_list);
 
     private final int textResId;
 
diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
index f7c382b3..3186c1cc 100644
--- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
@@ -15,6 +15,7 @@ import org.commonmark.node.Node;
 import io.noties.markwon.AbstractMarkwonPlugin;
 import io.noties.markwon.Markwon;
 import io.noties.markwon.ext.latex.JLatexMathPlugin;
+import io.noties.markwon.ext.latex.JLatexMathTheme;
 import io.noties.markwon.sample.R;
 import io.noties.markwon.utils.DumpNodes;
 import ru.noties.jlatexmath.JLatexMathDrawable;
@@ -56,7 +57,7 @@ public class LatexActivity extends Activity {
                     @Override
                     public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
                         builder
-                                .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() {
+                                .backgroundProvider(new JLatexMathTheme.BackgroundProvider() {
                                     @NonNull
                                     @Override
                                     public Drawable provide() {
diff --git a/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java
new file mode 100644
index 00000000..dfbf59af
--- /dev/null
+++ b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java
@@ -0,0 +1,111 @@
+package io.noties.markwon.sample.tasklist;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.ClickableSpan;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import io.noties.debug.Debug;
+import io.noties.markwon.AbstractMarkwonPlugin;
+import io.noties.markwon.Markwon;
+import io.noties.markwon.MarkwonSpansFactory;
+import io.noties.markwon.SpanFactory;
+import io.noties.markwon.ext.tasklist.TaskListItem;
+import io.noties.markwon.ext.tasklist.TaskListPlugin;
+import io.noties.markwon.ext.tasklist.TaskListSpan;
+import io.noties.markwon.sample.R;
+
+public class TaskListActivity extends Activity {
+
+    private TextView textView;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_text_view);
+
+        textView = findViewById(R.id.text_view);
+
+        mutate();
+    }
+
+    private void mutate() {
+
+        final Markwon markwon = Markwon.builder(this)
+                .usePlugin(TaskListPlugin.create(this))
+                .usePlugin(new AbstractMarkwonPlugin() {
+                    @Override
+                    public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
+                        // obtain origin task-list-factory
+                        final SpanFactory origin = builder.getFactory(TaskListItem.class);
+                        if (origin == null) {
+                            return;
+                        }
+
+                        builder.setFactory(TaskListItem.class, (configuration, props) -> {
+                            // maybe it's better to validate the actual type here also
+                            // and not force cast to task-list-span
+                            final TaskListSpan span = (TaskListSpan) origin.getSpans(configuration, props);
+                            if (span == null) {
+                                return null;
+                            }
+
+                            return new Object[]{
+                                    span,
+                                    new TaskListToggleSpan(span)
+                            };
+                        });
+                    }
+                })
+                .build();
+
+        final String md = "" +
+                "- [ ] Not done here!\n" +
+                "- [x] and done\n" +
+                "- [X] and again!\n" +
+                "* [ ] **and** syntax _included_ `code`";
+
+        markwon.setMarkdown(textView, md);
+    }
+
+    private static class TaskListToggleSpan extends ClickableSpan {
+
+        private final TaskListSpan span;
+
+        TaskListToggleSpan(@NonNull TaskListSpan span) {
+            this.span = span;
+        }
+
+        @Override
+        public void onClick(@NonNull View widget) {
+            // toggle span (this is a mere visual change)
+            span.setDone(!span.isDone());
+            // request visual update
+            widget.invalidate();
+
+            // it must be a TextView
+            final TextView textView = (TextView) widget;
+            // it must be spanned
+            final Spanned spanned = (Spanned) textView.getText();
+
+            // actual text of the span (this can be used along with the  `span`)
+            final CharSequence task = spanned.subSequence(
+                    spanned.getSpanStart(this),
+                    spanned.getSpanEnd(this)
+            );
+
+            Debug.i("task done: %s, '%s'", span.isDone(), task);
+        }
+
+        @Override
+        public void updateDrawState(@NonNull TextPaint ds) {
+            // no op, so text is not rendered as a link
+        }
+    }
+}
diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml
index d87585fd..f5a97644 100644
--- a/sample/src/main/res/values/strings-samples.xml
+++ b/sample/src/main/res/values/strings-samples.xml
@@ -31,4 +31,6 @@
 
     <string name="sample_html_details"># \# HTML &lt;details> tag\n\n&lt;details> tag parsed and rendered</string>
 
+    <string name="sample_task_list"># \# TaskList\n\nUsage of TaskListPlugin</string>
+
 </resources>
\ No newline at end of file

From c7494a922504acb922aba705a09cb073a6974078 Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Wed, 26 Feb 2020 13:39:37 +0300
Subject: [PATCH 5/8] Latex, introduce theme and render-mode

---
 .../io/noties/markwon/LinkResolverDef.java    |   2 +-
 .../ext/latex/JLatexMathBlockParser.java      |   1 +
 .../markwon/ext/latex/JLatexMathPlugin.java   |  28 ++-
 .../markwon/ext/latex/JLatexMathTheme.java    | 187 +++++++++++++++++-
 .../sample/ActivityWithMenuOptions.java       |  34 ++++
 .../io/noties/markwon/sample/MenuOptions.java |  46 +++++
 .../markwon/sample/editor/EditorActivity.java |  27 +--
 .../sample/editor/LinkEditHandler.java        |  34 ++--
 .../markwon/sample/latex/LatexActivity.java   | 125 ++++++------
 .../sample/tasklist/TaskListActivity.java     |  78 +++++++-
 .../main/res/drawable/custom_task_list.xml    |   5 +
 11 files changed, 462 insertions(+), 105 deletions(-)
 create mode 100644 sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java
 create mode 100644 sample/src/main/java/io/noties/markwon/sample/MenuOptions.java
 create mode 100644 sample/src/main/res/drawable/custom_task_list.xml

diff --git a/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java
index 999dcee5..f61ad21f 100644
--- a/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java
+++ b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java
@@ -20,7 +20,7 @@ public class LinkResolverDef implements LinkResolver {
         try {
             context.startActivity(intent);
         } catch (ActivityNotFoundException e) {
-            Log.w("LinkResolverDef", "Actvity was not found for intent, " + intent.toString());
+            Log.w("LinkResolverDef", "Actvity was not found for the link: '" + link + "'");
         }
     }
 }
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
index cb159aef..3ef61a24 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
@@ -120,6 +120,7 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
             }
 
             // consume spaces until the end of the line, if any other content is found -> NONE
+            // TODO: here we can check mode in which we operate (legacy or not)
             if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
                 return BlockStart.none();
             }
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
index 3f85b9f7..d758be38 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
@@ -42,8 +42,33 @@ import ru.noties.jlatexmath.JLatexMathDrawable;
 /**
  * @since 3.0.0
  */
-public class  JLatexMathPlugin extends AbstractMarkwonPlugin {
+public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
+    /**
+     * @since 4.3.0-SNAPSHOT
+     */
+    public enum RenderMode {
+        /**
+         * <em>LEGACY</em> mode mimics pre {@code 4.3.0-SNAPSHOT} behavior by rendering LaTeX blocks only.
+         * In this mode LaTeX is started by `$$` (that must be exactly at the start of a line) and
+         * ended at whatever line that is ended with `$$` characters exactly.
+         */
+        LEGACY,
+
+        /**
+         * Starting with {@code 4.3.0-SNAPSHOT} it is possible to have LaTeX inlines (which flows inside
+         * a text paragraph without breaking it). Inline LaTeX starts and ends with `$$` symbols. For example:
+         * {@code
+         * **bold $$\\begin{array}\\end{array}$$ bold-end**, and whatever more
+         * }
+         * LaTeX block starts on a new line started by 0-3 spaces and 2 (or more) {@code $} signs
+         * followed by a new-line (with any amount of space characters in-between). And ends on a new-line
+         * starting with 0-3 spaces followed by number of {@code $} signs that was used to <em>start the block</em>.
+         */
+        BLOCKS_AND_INLINES
+    }
+
+    // TODO: inlines are not moved to a new line when exceed available width.. (api 23, emulator)
     public interface BuilderConfigure {
         void configureBuilder(@NonNull Builder builder);
     }
@@ -338,6 +363,7 @@ public class  JLatexMathPlugin extends AbstractMarkwonPlugin {
 
                         final JLatexMathDrawable jLatexMathDrawable;
 
+                        // TODO: obtain real values from theme (for blocks and inlines)
                         final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
                         if (jLatextAsyncDrawable.isBlock) {
                             // create JLatexMathDrawable
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
index 04ea346c..30b9e04a 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
@@ -1,6 +1,5 @@
 package io.noties.markwon.ext.latex;
 
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 
 import androidx.annotation.NonNull;
@@ -16,12 +15,22 @@ public abstract class JLatexMathTheme {
 
     @NonNull
     public static JLatexMathTheme create(@Px float textSize) {
-        return null;
+        return builder(textSize).build();
     }
 
     @NonNull
-    public static JLatexMathTheme builer() {
-        return null;
+    public static JLatexMathTheme create(@Px float inlineTextSize, @Px float blockTextSize) {
+        return builder(inlineTextSize, blockTextSize).build();
+    }
+
+    @NonNull
+    public static JLatexMathTheme.Builder builder(@Px float textSize) {
+        return new JLatexMathTheme.Builder(textSize, 0F, 0F);
+    }
+
+    @NonNull
+    public static JLatexMathTheme.Builder builder(@Px float inlineTextSize, @Px float blockTextSize) {
+        return new Builder(0F, inlineTextSize, blockTextSize);
     }
 
     /**
@@ -73,7 +82,7 @@ public abstract class JLatexMathTheme {
 
     /**
      * @return text size in pixels for <strong>inline LaTeX</strong>
-     * @see #blockTexxtSize()
+     * @see #blockTextSize()
      */
     @Px
     public abstract float inlineTextSize();
@@ -83,7 +92,7 @@ public abstract class JLatexMathTheme {
      * @see #inlineTextSize()
      */
     @Px
-    public abstract float blockTexxtSize();
+    public abstract float blockTextSize();
 
     @Nullable
     public abstract BackgroundProvider inlineBackgroundProvider();
@@ -111,9 +120,9 @@ public abstract class JLatexMathTheme {
 
 
     public static class Builder {
-        private float textSize;
-        private float inlineTextSize;
-        private float blockTextSize;
+        private final float textSize;
+        private final float inlineTextSize;
+        private final float blockTextSize;
 
         private BackgroundProvider backgroundProvider;
         private BackgroundProvider inlineBackgroundProvider;
@@ -121,10 +130,168 @@ public abstract class JLatexMathTheme {
 
         private boolean blockFitCanvas;
         // horizontal alignment (when there is additional horizontal space)
-        private int blockAlign;
+        private int blockHorizontalAlignment;
 
         private Padding padding;
         private Padding inlinePadding;
         private Padding blockPadding;
+
+        Builder(float textSize, float inlineTextSize, float blockTextSize) {
+            this.textSize = textSize;
+            this.inlineTextSize = inlineTextSize;
+            this.blockTextSize = blockTextSize;
+        }
+
+        @NonNull
+        public Builder backgroundProvider(@Nullable BackgroundProvider backgroundProvider) {
+            this.backgroundProvider = backgroundProvider;
+            this.inlineBackgroundProvider = backgroundProvider;
+            this.blockBackgroundProvider = backgroundProvider;
+            return this;
+        }
+
+        @NonNull
+        public Builder inlineBackgroundProvider(@Nullable BackgroundProvider inlineBackgroundProvider) {
+            this.inlineBackgroundProvider = inlineBackgroundProvider;
+            return this;
+        }
+
+        @NonNull
+        public Builder blockBackgroundProvider(@Nullable BackgroundProvider blockBackgroundProvider) {
+            this.blockBackgroundProvider = blockBackgroundProvider;
+            return this;
+        }
+
+        @NonNull
+        public Builder blockFitCanvas(boolean blockFitCanvas) {
+            this.blockFitCanvas = blockFitCanvas;
+            return this;
+        }
+
+        @NonNull
+        public Builder blockHorizontalAlignment(@JLatexMathDrawable.Align int blockHorizontalAlignment) {
+            this.blockHorizontalAlignment = blockHorizontalAlignment;
+            return this;
+        }
+
+        @NonNull
+        public Builder padding(@Nullable Padding padding) {
+            this.padding = padding;
+            this.inlinePadding = padding;
+            this.blockPadding = padding;
+            return this;
+        }
+
+        @NonNull
+        public Builder inlinePadding(@Nullable Padding inlinePadding) {
+            this.inlinePadding = inlinePadding;
+            return this;
+        }
+
+        @NonNull
+        public Builder blockPadding(@Nullable Padding blockPadding) {
+            this.blockPadding = blockPadding;
+            return this;
+        }
+
+        @NonNull
+        public JLatexMathTheme build() {
+            return null;
+        }
+    }
+
+    static class Impl extends JLatexMathTheme {
+
+        private final float textSize;
+        private final float inlineTextSize;
+        private final float blockTextSize;
+
+        private final BackgroundProvider backgroundProvider;
+        private final BackgroundProvider inlineBackgroundProvider;
+        private final BackgroundProvider blockBackgroundProvider;
+
+        private final boolean blockFitCanvas;
+        // horizontal alignment (when there is additional horizontal space)
+        private int blockHorizontalAlignment;
+
+        private final Padding padding;
+        private final Padding inlinePadding;
+        private final Padding blockPadding;
+
+        Impl(@NonNull Builder builder) {
+            this.textSize = builder.textSize;
+            this.inlineTextSize = builder.inlineTextSize;
+            this.blockTextSize = builder.blockTextSize;
+            this.backgroundProvider = builder.backgroundProvider;
+            this.inlineBackgroundProvider = builder.inlineBackgroundProvider;
+            this.blockBackgroundProvider = builder.blockBackgroundProvider;
+            this.blockFitCanvas = builder.blockFitCanvas;
+            this.blockHorizontalAlignment = builder.blockHorizontalAlignment;
+            this.padding = builder.padding;
+            this.inlinePadding = builder.inlinePadding;
+            this.blockPadding = builder.blockPadding;
+        }
+
+        @Override
+        public float inlineTextSize() {
+            if (inlineTextSize > 0F) {
+                return inlineTextSize;
+            }
+            return textSize;
+        }
+
+        @Override
+        public float blockTextSize() {
+            if (blockTextSize > 0F) {
+                return blockTextSize;
+            }
+            return textSize;
+        }
+
+        @Nullable
+        @Override
+        public BackgroundProvider inlineBackgroundProvider() {
+            if (inlineBackgroundProvider != null) {
+                return inlineBackgroundProvider;
+            }
+            return backgroundProvider;
+        }
+
+        @Nullable
+        @Override
+        public BackgroundProvider blockBackgroundProvider() {
+            if (blockBackgroundProvider != null) {
+                return blockBackgroundProvider;
+            }
+            return backgroundProvider;
+        }
+
+        @Override
+        public boolean blockFitCanvas() {
+            return blockFitCanvas;
+        }
+
+        @Override
+        public int blockHorizontalAlignment() {
+            return blockHorizontalAlignment;
+        }
+
+        @Nullable
+        @Override
+        public Padding inlinePadding() {
+            if (inlinePadding != null) {
+                return inlinePadding;
+            }
+            return padding;
+        }
+
+        @Nullable
+        @Override
+        public Padding blockPadding() {
+            if (blockPadding != null) {
+                return blockPadding;
+            }
+            return padding;
+        }
     }
 }
diff --git a/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java
new file mode 100644
index 00000000..66690f7b
--- /dev/null
+++ b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java
@@ -0,0 +1,34 @@
+package io.noties.markwon.sample;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public abstract class ActivityWithMenuOptions extends Activity {
+
+    @NonNull
+    public abstract MenuOptions menuOptions();
+
+    private MenuOptions menuOptions;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        menuOptions = menuOptions();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        return menuOptions.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        return menuOptions.onOptionsItemSelected(item);
+    }
+}
diff --git a/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java
new file mode 100644
index 00000000..1c349bd8
--- /dev/null
+++ b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java
@@ -0,0 +1,46 @@
+package io.noties.markwon.sample;
+
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class MenuOptions {
+
+    @NonNull
+    public static MenuOptions create() {
+        return new MenuOptions();
+    }
+
+    // to preserve order use LinkedHashMap
+    private final Map<String, Runnable> actions = new LinkedHashMap<>();
+
+    @NonNull
+    public MenuOptions add(@NonNull String title, @NonNull Runnable action) {
+        actions.put(title, action);
+        return this;
+    }
+
+    boolean onCreateOptionsMenu(Menu menu) {
+        if (!actions.isEmpty()) {
+            for (String key : actions.keySet()) {
+                menu.add(key);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    boolean onOptionsItemSelected(MenuItem item) {
+        final String title = String.valueOf(item.getTitle());
+        final Runnable action = actions.get(title);
+        if (action != null) {
+            action.run();
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java
index 5553c9f8..0dc05ef9 100644
--- a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java
@@ -1,6 +1,5 @@
 package io.noties.markwon.sample.editor;
 
-import android.app.Activity;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.SpannableStringBuilder;
@@ -42,12 +41,26 @@ import io.noties.markwon.inlineparser.EntityInlineProcessor;
 import io.noties.markwon.inlineparser.HtmlInlineProcessor;
 import io.noties.markwon.inlineparser.MarkwonInlineParser;
 import io.noties.markwon.linkify.LinkifyPlugin;
+import io.noties.markwon.sample.ActivityWithMenuOptions;
+import io.noties.markwon.sample.MenuOptions;
 import io.noties.markwon.sample.R;
 
-public class EditorActivity extends Activity {
+public class EditorActivity extends ActivityWithMenuOptions {
 
     private EditText editText;
 
+    @NonNull
+    @Override
+    public MenuOptions menuOptions() {
+        return MenuOptions.create()
+                .add("simpleProcess", this::simple_process)
+                .add("simplePreRender", this::simple_pre_render)
+                .add("customPunctuationSpan", this::custom_punctuation_span)
+                .add("additionalEditSpan", this::additional_edit_span)
+                .add("additionalPlugins", this::additional_plugins)
+                .add("multipleEditSpans", this::multiple_edit_spans);
+    }
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -56,16 +69,6 @@ public class EditorActivity extends Activity {
         this.editText = findViewById(R.id.edit_text);
         initBottomBar();
 
-//        simple_process();
-
-//        simple_pre_render();
-
-//        custom_punctuation_span();
-
-//        additional_edit_span();
-
-//        additional_plugins();
-
         multiple_edit_spans();
     }
 
diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java b/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java
index 743428d0..3a6d60fd 100644
--- a/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java
+++ b/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java
@@ -40,24 +40,28 @@ class LinkEditHandler extends AbstractEditHandler<LinkSpan> {
         final EditLinkSpan editLinkSpan = persistedSpans.get(EditLinkSpan.class);
         editLinkSpan.link = span.getLink();
 
-        final int s;
-        final int e;
+        // First first __letter__ to find link content (scheme start in URL, receiver in email address)
+        // NB! do not use phone number auto-link (via LinkifyPlugin) as we cannot guarantee proper link
+        //  display. For example, we _could_ also look for a digit, but:
+        //  * if phone number start with special symbol, we won't have it (`+`, `(`)
+        //  * it might interfere with an ordered-list
+        int start = -1;
 
-        // markdown link vs. autolink
-        if ('[' == input.charAt(spanStart)) {
-            s = spanStart + 1;
-            e = spanStart + 1 + spanTextLength;
-        } else {
-            s = spanStart;
-            e = spanStart + spanTextLength;
+        for (int i = spanStart, length = input.length(); i < length; i++) {
+            if (Character.isLetter(input.charAt(i))) {
+                start = i;
+                break;
+            }
         }
 
-        editable.setSpan(
-                editLinkSpan,
-                s,
-                e,
-                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
-        );
+        if (start > -1) {
+            editable.setSpan(
+                    editLinkSpan,
+                    start,
+                    start + spanTextLength,
+                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+            );
+        }
     }
 
     @NonNull
diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
index 3186c1cc..c669cde0 100644
--- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
@@ -1,57 +1,100 @@
 package io.noties.markwon.sample.latex;
 
-import android.app.Activity;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.util.Log;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import org.commonmark.node.Node;
-
-import io.noties.markwon.AbstractMarkwonPlugin;
 import io.noties.markwon.Markwon;
 import io.noties.markwon.ext.latex.JLatexMathPlugin;
 import io.noties.markwon.ext.latex.JLatexMathTheme;
+import io.noties.markwon.sample.ActivityWithMenuOptions;
+import io.noties.markwon.sample.MenuOptions;
 import io.noties.markwon.sample.R;
-import io.noties.markwon.utils.DumpNodes;
 import ru.noties.jlatexmath.JLatexMathDrawable;
 
-public class LatexActivity extends Activity {
+public class LatexActivity extends ActivityWithMenuOptions {
+
+    private TextView textView;
+
+    @NonNull
+    @Override
+    public MenuOptions menuOptions() {
+        return MenuOptions.create()
+                .add("array", this::array)
+                .add("longDivision", this::longDivision)
+                .add("bangle", this::bangle)
+                .add("boxes", this::boxes)
+                .add("insideBlockQuote", this::insideBlockQuote);
+    }
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
         setContentView(R.layout.activity_text_view);
 
-        final TextView textView = findViewById(R.id.text_view);
+        textView = findViewById(R.id.text_view);
 
-//        String latex = "\\begin{array}{l}";
-//        latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\";
-//        latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\";
-//        latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\";
-//        latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\";
-//        latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty  {\\sum\\limits_{m = 1}^{2^n  - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\";
-//        latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\";
-//        latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\";
-//        latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\";
-//        latex += "\\end{array}";
+//        array();
+        longDivision();
+    }
 
+    private void array() {
+        String latex = "\\begin{array}{l}";
+        latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\";
+        latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\";
+        latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\";
+        latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\";
+        latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty  {\\sum\\limits_{m = 1}^{2^n  - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\";
+        latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\";
+        latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\";
+        latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\";
+        latex += "\\end{array}";
+
+        render(wrapLatexInSampleMarkdown(latex));
+    }
+
+    private void longDivision() {
         String latex = "\\text{A long division \\longdiv{12345}{13}";
-//        String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}";
+        render(wrapLatexInSampleMarkdown(latex));
+    }
 
-//        String latex = "\\begin{array}{cc}";
-//        latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr";
-//        latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr";
-//        latex += "\\end{array}";
+    private void bangle() {
+        String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}";
+        render(wrapLatexInSampleMarkdown(latex));
+    }
 
-        final String markdown = "# Example of LaTeX\n\nhello there: $$"
-                + latex + "$$ so nice, really?\n\n $$  \n" + latex + "\n$$\n\n   $$     \n" + latex + "\n$$";
+    private void boxes() {
+        String latex = "\\begin{array}{cc}";
+        latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr";
+        latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr";
+        latex += "\\end{array}";
+        render(wrapLatexInSampleMarkdown(latex));
+    }
 
+    private void insideBlockQuote() {
+        String latex = "W=W_1+W_2=F_1X_1-F_2X_2";
+        final String md = "" +
+                "# LaTeX inside a blockquote\n" +
+                "> $$" + latex + "$$\n";
+        render(md);
+    }
+
+    @NonNull
+    private static String wrapLatexInSampleMarkdown(@NonNull String latex) {
+        return "" +
+                "# Example of LaTeX\n\n" +
+                "(inline): $$" + latex + "$$ so nice, really? Now, (block):\n\n" +
+                "$$\n" +
+                "" + latex + "\n" +
+                "$$\n\n" +
+                "the end";
+    }
+
+    private void render(@NonNull String markdown) {
         final Markwon markwon = Markwon.builder(this)
                 .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
                     @Override
@@ -70,37 +113,7 @@ public class LatexActivity extends Activity {
                         ;
                     }
                 }))
-//                .usePlugin(JLatexMathPlugin.create(textView.getTextSize()))
-                .usePlugin(new AbstractMarkwonPlugin() {
-                    @Override
-                    public void beforeRender(@NonNull Node node) {
-                        Log.e("LTX", DumpNodes.dump(node));
-                    }
-                })
                 .build();
-//
-//        if (true) {
-////            final String l = "$$\n" +
-////                    "  P(X=r)=\\frac{\\lambda^r e^{-\\lambda}}{r!}\n" +
-////                    "$$\n" +
-////                    "\n" +
-////                    "$$\n" +
-////                    "  P(X<r)=P(X<r-1)\n" +
-////                    "$$\n" +
-////                    "\n" +
-////                    "$$\n" +
-////                    "  P(X>r)=1-P(X<r=1)\n" +
-////                    "$$\n" +
-////                    "\n" +
-////                    "$$\n" +
-////                    "  \\text{Variance} = \\lambda\n" +
-////                    "$$";
-//            final String l = "$$ \n" +
-//                    "    \\sigma_T^2 = \\frac{1-p}{p^2}\n" +
-//                    "$$";
-//            markwon.setMarkdown(textView, l);
-//            return;
-//        }
 
         markwon.setMarkdown(textView, markdown);
     }
diff --git a/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java
index dfbf59af..b5c421d6 100644
--- a/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java
@@ -1,6 +1,7 @@
 package io.noties.markwon.sample.tasklist;
 
-import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.Spanned;
 import android.text.TextPaint;
@@ -10,6 +11,9 @@ import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import java.util.Objects;
 
 import io.noties.debug.Debug;
 import io.noties.markwon.AbstractMarkwonPlugin;
@@ -19,12 +23,34 @@ import io.noties.markwon.SpanFactory;
 import io.noties.markwon.ext.tasklist.TaskListItem;
 import io.noties.markwon.ext.tasklist.TaskListPlugin;
 import io.noties.markwon.ext.tasklist.TaskListSpan;
+import io.noties.markwon.sample.ActivityWithMenuOptions;
+import io.noties.markwon.sample.MenuOptions;
 import io.noties.markwon.sample.R;
 
-public class TaskListActivity extends Activity {
+public class TaskListActivity extends ActivityWithMenuOptions {
+
+    private static final String MD = "" +
+            "- [ ] Not done here!\n" +
+            "- [x] and done\n" +
+            "- [X] and again!\n" +
+            "* [ ] **and** syntax _included_ `code`\n" +
+            "- [ ] [link](#)\n" +
+            "- [ ] [a check box](https://goog.le)\n" +
+            "- [x] [test]()\n" +
+            "- [List](https://goog.le) 3";
 
     private TextView textView;
 
+    @NonNull
+    @Override
+    public MenuOptions menuOptions() {
+        return MenuOptions.create()
+                .add("regular", this::regular)
+                .add("customColors", this::customColors)
+                .add("customDrawableResources", this::customDrawableResources)
+                .add("mutate", this::mutate);
+    }
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -32,7 +58,44 @@ public class TaskListActivity extends Activity {
 
         textView = findViewById(R.id.text_view);
 
-        mutate();
+//        mutate();
+        regular();
+    }
+
+    private void regular() {
+        // default theme
+
+        final Markwon markwon = Markwon.builder(this)
+                .usePlugin(TaskListPlugin.create(this))
+                .build();
+
+        markwon.setMarkdown(textView, MD);
+    }
+
+    private void customColors() {
+
+        final int checkedFillColor = Color.RED;
+        final int normalOutlineColor = Color.GREEN;
+        final int checkMarkColor = Color.BLUE;
+
+        final Markwon markwon = Markwon.builder(this)
+                .usePlugin(TaskListPlugin.create(checkedFillColor, normalOutlineColor, checkMarkColor))
+                .build();
+
+        markwon.setMarkdown(textView, MD);
+    }
+
+    private void customDrawableResources() {
+        // drawable **must** be stateful
+
+        final Drawable drawable = Objects.requireNonNull(
+                ContextCompat.getDrawable(this, R.drawable.custom_task_list));
+
+        final Markwon markwon = Markwon.builder(this)
+                .usePlugin(TaskListPlugin.create(drawable))
+                .build();
+
+        markwon.setMarkdown(textView, MD);
     }
 
     private void mutate() {
@@ -56,6 +119,7 @@ public class TaskListActivity extends Activity {
                                 return null;
                             }
 
+                            // NB, toggle click will intercept possible links inside task-list-item
                             return new Object[]{
                                     span,
                                     new TaskListToggleSpan(span)
@@ -65,13 +129,7 @@ public class TaskListActivity extends Activity {
                 })
                 .build();
 
-        final String md = "" +
-                "- [ ] Not done here!\n" +
-                "- [x] and done\n" +
-                "- [X] and again!\n" +
-                "* [ ] **and** syntax _included_ `code`";
-
-        markwon.setMarkdown(textView, md);
+        markwon.setMarkdown(textView, MD);
     }
 
     private static class TaskListToggleSpan extends ClickableSpan {
diff --git a/sample/src/main/res/drawable/custom_task_list.xml b/sample/src/main/res/drawable/custom_task_list.xml
new file mode 100644
index 00000000..43c2e2a8
--- /dev/null
+++ b/sample/src/main/res/drawable/custom_task_list.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:drawable="@drawable/ic_android_black_24dp" />
+    <item android:drawable="@drawable/ic_home_black_36dp" />
+</selector>
\ No newline at end of file

From a80ff09e15b55a78e49dcf1af610e2a3716f5cb6 Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Wed, 26 Feb 2020 15:08:00 +0300
Subject: [PATCH 6/8] Update sample configuration for latex block_and_inline
 renderMode

---
 .../latex/JLatexBlockImageSizeResolver.java   |  50 +++
 .../latex/JLatexInlineAsyncDrawableSpan.java  |  61 +++
 .../ext/latex/JLatexMathBlockParser.java      |  89 +---
 .../latex/JLatexMathBlockParserLegacy.java    |  82 ++++
 .../markwon/ext/latex/JLatexMathPlugin.java   | 418 +++++++-----------
 .../markwon/ext/latex/JLatexMathTheme.java    |   6 +-
 .../ext/latex/JLatextAsyncDrawable.java       |  32 ++
 .../markwon/sample/latex/LatexActivity.java   |  36 +-
 sample/src/main/res/values/dimens.xml         |   5 +
 9 files changed, 419 insertions(+), 360 deletions(-)
 create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java
 create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java
 create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java
 create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java
 create mode 100644 sample/src/main/res/values/dimens.xml

diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java
new file mode 100644
index 00000000..7e9ac95c
--- /dev/null
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java
@@ -0,0 +1,50 @@
+package io.noties.markwon.ext.latex;
+
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+
+import io.noties.markwon.image.AsyncDrawable;
+import io.noties.markwon.image.ImageSizeResolver;
+
+// we must make drawable fit canvas (if specified), but do not keep the ratio whilst scaling up
+// @since 4.0.0
+class JLatexBlockImageSizeResolver extends ImageSizeResolver {
+
+    private final boolean fitCanvas;
+
+    JLatexBlockImageSizeResolver(boolean fitCanvas) {
+        this.fitCanvas = fitCanvas;
+    }
+
+    @NonNull
+    @Override
+    public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
+
+        final Rect imageBounds = drawable.getResult().getBounds();
+        final int canvasWidth = drawable.getLastKnownCanvasWidth();
+
+        if (fitCanvas) {
+
+            // we modify bounds only if `fitCanvas` is true
+            final int w = imageBounds.width();
+
+            if (w < canvasWidth) {
+                // increase width and center formula (keep height as-is)
+                return new Rect(0, 0, canvasWidth, imageBounds.height());
+            }
+
+            // @since 4.0.2 we additionally scale down the resulting formula (keeping the ratio)
+            // the thing is - JLatexMathDrawable will do it anyway, but it will modify its own
+            // bounds (which AsyncDrawable won't catch), thus leading to an empty space after the formula
+            if (w > canvasWidth) {
+                // here we must scale it down (keeping the ratio)
+                final float ratio = (float) w / imageBounds.height();
+                final int h = (int) (canvasWidth / ratio + .5F);
+                return new Rect(0, 0, canvasWidth, h);
+            }
+        }
+
+        return imageBounds;
+    }
+}
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java
new file mode 100644
index 00000000..10b59837
--- /dev/null
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java
@@ -0,0 +1,61 @@
+package io.noties.markwon.ext.latex;
+
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import io.noties.markwon.core.MarkwonTheme;
+import io.noties.markwon.image.AsyncDrawable;
+import io.noties.markwon.image.AsyncDrawableSpan;
+
+/**
+ * @since 4.3.0-SNAPSHOT
+ */
+class JLatexInlineAsyncDrawableSpan extends AsyncDrawableSpan {
+
+    private final AsyncDrawable drawable;
+
+    JLatexInlineAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) {
+        super(theme, drawable, alignment, replacementTextIsLink);
+        this.drawable = drawable;
+    }
+
+    @Override
+    public int getSize(
+            @NonNull Paint paint,
+            CharSequence text,
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @Nullable Paint.FontMetricsInt fm) {
+
+        // if we have no async drawable result - we will just render text
+
+        final int size;
+
+        if (drawable.hasResult()) {
+
+            final Rect rect = drawable.getBounds();
+
+            if (fm != null) {
+                final int half = rect.bottom / 2;
+                fm.ascent = -half;
+                fm.descent = half;
+
+                fm.top = fm.ascent;
+                fm.bottom = 0;
+            }
+
+            size = rect.right;
+
+        } else {
+
+            // NB, no specific text handling (no new lines, etc)
+            size = (int) (paint.measureText(text, start, end) + .5F);
+        }
+
+        return size;
+    }
+}
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
index 3ef61a24..ef65bb15 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java
@@ -1,7 +1,5 @@
 package io.noties.markwon.ext.latex;
 
-import android.util.Log;
-
 import androidx.annotation.NonNull;
 
 import org.commonmark.internal.util.Parsing;
@@ -13,6 +11,10 @@ import org.commonmark.parser.block.BlockStart;
 import org.commonmark.parser.block.MatchedBlockParser;
 import org.commonmark.parser.block.ParserState;
 
+/**
+ * @since 4.3.0-SNAPSHOT (although there was a class with the same name,
+ * which is renamed now to {@link JLatexMathBlockParserLegacy})
+ */
 public class JLatexMathBlockParser extends AbstractBlockParser {
 
     private static final char DOLLAR = '$';
@@ -22,8 +24,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
 
     private final StringBuilder builder = new StringBuilder();
 
-//    private boolean isClosed;
-
     private final int signs;
 
     @SuppressWarnings("WeakerAccess")
@@ -44,12 +44,9 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
 
         // check for closing
         if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) {
-            Log.e("LTX", String.format("signs: %d, skip dollar: %s", signs, Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length)));
-//            if (Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
             if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
                 // okay, we have our number of signs
                 // let's consume spaces until the end
-                Log.e("LTX", String.format("length; %d, skip spaces: %s", length, Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length)));
                 if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) {
                     return BlockContinue.finished();
                 }
@@ -61,22 +58,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
 
     @Override
     public void addLine(CharSequence line) {
-//
-//        if (builder.length() > 0) {
-//            builder.append('\n');
-//        }
-//
-//        builder.append(line);
-//
-//        final int length = builder.length();
-//        if (length > 1) {
-//            isClosed = '$' == builder.charAt(length - 1)
-//                    && '$' == builder.charAt(length - 2);
-//            if (isClosed) {
-//                builder.replace(length - 2, length, "");
-//            }
-//        }
-        Log.e("LTX", "addLine: " + line);
         builder.append(line);
         builder.append('\n');
     }
@@ -88,8 +69,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
 
     public static class Factory extends AbstractBlockParserFactory {
 
-//        private static final Pattern RE = Pattern.compile("(\\${2,}) *$");
-
         @Override
         public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
 
@@ -111,7 +90,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
             final CharSequence line = state.getLine();
             final int length = line.length();
 
-//            final int signs = Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) - 1;
             final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length);
 
             // 2 is minimum
@@ -120,73 +98,16 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
             }
 
             // consume spaces until the end of the line, if any other content is found -> NONE
-            // TODO: here we can check mode in which we operate (legacy or not)
             if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
                 return BlockStart.none();
             }
 
-            Log.e("LTX", String.format("signs: %s, next: %d, length: %d, line: '%s'", signs, nextNonSpaceIndex, length, line));
-
             return BlockStart.of(new JLatexMathBlockParser(signs))
                     .atIndex(length + 1);
-
-
-//            // check if it's an indented code block
-//            if (indent < Parsing.CODE_BLOCK_INDENT) {
-//
-//                final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
-//                final CharSequence line = state.getLine();
-//                final int length = line.length();
-//
-//                final int signs = Parsing.skip('$', line, nextNonSpaceIndex, length);
-//
-//                // 2 is minimum
-//                if (signs < 2) {
-//                    return BlockStart.none();
-//                }
-//
-//                // consume spaces until the end of the line, if any other content is found -> NONE
-//                if (Parsing.skip(' ', line, nextNonSpaceIndex + signs, length) != length) {
-//                    return BlockStart.none();
-//                }
-//
-////                // consume spaces until the end of the line, if any other content is found -> NONE
-////                if ((nextNonSpaceIndex + signs) < length) {
-////                    // check if more content is available
-////                    if (Parsing.skip(' ',  line,nextNonSpaceIndex + signs, length) != length) {
-////                        return BlockStart.none();
-////                    }
-////                }
-//
-////                final Matcher matcher = RE.matcher(line);
-////                matcher.region(nextNonSpaceIndex, length);
-//
-////                Log.e("LATEX", String.format("nonSpace: %d, length: %s, line: '%s'", nextNonSpaceIndex, length, line));
-//
-//                // we are looking for 2 `$$` subsequent signs
-//                // and immediate new-line or arbitrary number of white spaces (we check for the first one)
-//                // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s
-//                final int diff = length - (nextNonSpaceIndex + 2);
-//                if (diff >= 0) {
-//                    // check for both `$`
-//                    if (line.charAt(nextNonSpaceIndex) == '$'
-//                            && line.charAt(nextNonSpaceIndex + 1) == '$') {
-//
-//                        if (diff > 0) {
-//                            if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) {
-//                                return BlockStart.none();
-//                            }
-//                            return BlockStart.of(new JLatexMathBlockParser()).atIndex(nextNonSpaceIndex + 3);
-//                        }
-//
-//                    }
-//                }
-//            }
-//
-//            return BlockStart.none();
         }
     }
 
+    @SuppressWarnings("SameParameterValue")
     private static int consume(char c, @NonNull CharSequence line, int start, int end) {
         for (int i = start; i < end; i++) {
             if (c != line.charAt(i)) {
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java
new file mode 100644
index 00000000..1a5ce282
--- /dev/null
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java
@@ -0,0 +1,82 @@
+package io.noties.markwon.ext.latex;
+
+import org.commonmark.node.Block;
+import org.commonmark.parser.block.AbstractBlockParser;
+import org.commonmark.parser.block.AbstractBlockParserFactory;
+import org.commonmark.parser.block.BlockContinue;
+import org.commonmark.parser.block.BlockStart;
+import org.commonmark.parser.block.MatchedBlockParser;
+import org.commonmark.parser.block.ParserState;
+
+/**
+ * @since 4.3.0-SNAPSHOT (although it is just renamed parser from previous versions)
+ */
+public class JLatexMathBlockParserLegacy extends AbstractBlockParser {
+
+    private final JLatexMathBlock block = new JLatexMathBlock();
+
+    private final StringBuilder builder = new StringBuilder();
+
+    private boolean isClosed;
+
+    @Override
+    public Block getBlock() {
+        return block;
+    }
+
+    @Override
+    public BlockContinue tryContinue(ParserState parserState) {
+
+        if (isClosed) {
+            return BlockContinue.finished();
+        }
+
+        return BlockContinue.atIndex(parserState.getIndex());
+    }
+
+    @Override
+    public void addLine(CharSequence line) {
+
+        if (builder.length() > 0) {
+            builder.append('\n');
+        }
+
+        builder.append(line);
+
+        final int length = builder.length();
+        if (length > 1) {
+            isClosed = '$' == builder.charAt(length - 1)
+                    && '$' == builder.charAt(length - 2);
+            if (isClosed) {
+                builder.replace(length - 2, length, "");
+            }
+        }
+    }
+
+    @Override
+    public void closeBlock() {
+        block.latex(builder.toString());
+    }
+
+    public static class Factory extends AbstractBlockParserFactory {
+
+        @Override
+        public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
+
+            final CharSequence line = state.getLine();
+            final int length = line != null
+                    ? line.length()
+                    : 0;
+
+            if (length > 1) {
+                if ('$' == line.charAt(0)
+                        && '$' == line.charAt(1)) {
+                    return BlockStart.of(new JLatexMathBlockParserLegacy())
+                            .atIndex(state.getIndex() + 2);
+                }
+            }
+
+            return BlockStart.none();
+        }
+    }
+}
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
index d758be38..5ecc0723 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
@@ -1,6 +1,5 @@
 package io.noties.markwon.ext.latex;
 
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -10,7 +9,6 @@ import android.text.Spanned;
 import android.util.Log;
 import android.widget.TextView;
 
-import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
@@ -28,14 +26,11 @@ import java.util.concurrent.Future;
 import io.noties.markwon.AbstractMarkwonPlugin;
 import io.noties.markwon.MarkwonConfiguration;
 import io.noties.markwon.MarkwonVisitor;
-import io.noties.markwon.core.MarkwonTheme;
 import io.noties.markwon.image.AsyncDrawable;
 import io.noties.markwon.image.AsyncDrawableLoader;
 import io.noties.markwon.image.AsyncDrawableScheduler;
 import io.noties.markwon.image.AsyncDrawableSpan;
-import io.noties.markwon.image.ImageSize;
 import io.noties.markwon.image.ImageSizeResolver;
-import io.noties.markwon.image.ImageSizeResolverDef;
 import io.noties.markwon.inlineparser.MarkwonInlineParser;
 import ru.noties.jlatexmath.JLatexMathDrawable;
 
@@ -68,7 +63,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         BLOCKS_AND_INLINES
     }
 
-    // TODO: inlines are not moved to a new line when exceed available width.. (api 23, emulator)
     public interface BuilderConfigure {
         void configureBuilder(@NonNull Builder builder);
     }
@@ -78,52 +72,65 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         return new JLatexMathPlugin(builder(textSize).build());
     }
 
+    /**
+     * @since 4.3.0-SNAPSHOT
+     */
+    @NonNull
+    public static JLatexMathPlugin create(@Px float inlineTextSize, @Px float blockTextSize) {
+        return new JLatexMathPlugin(builder(inlineTextSize, blockTextSize).build());
+    }
+
     @NonNull
     public static JLatexMathPlugin create(@NonNull Config config) {
         return new JLatexMathPlugin(config);
     }
 
     @NonNull
-    public static JLatexMathPlugin create(float textSize, @NonNull BuilderConfigure builderConfigure) {
-        final Builder builder = new Builder(textSize);
+    public static JLatexMathPlugin create(@Px float textSize, @NonNull BuilderConfigure builderConfigure) {
+        final Builder builder = builder(textSize);
+        builderConfigure.configureBuilder(builder);
+        return new JLatexMathPlugin(builder.build());
+    }
+
+    /**
+     * @since 4.3.0-SNAPSHOT
+     */
+    @NonNull
+    public static JLatexMathPlugin create(
+            @Px float inlineTextSize,
+            @Px float blockTextSize,
+            @NonNull BuilderConfigure builderConfigure) {
+        final Builder builder = builder(inlineTextSize, blockTextSize);
         builderConfigure.configureBuilder(builder);
         return new JLatexMathPlugin(builder.build());
     }
 
     @NonNull
-    public static JLatexMathPlugin.Builder builder(float textSize) {
-        return new Builder(textSize);
+    public static JLatexMathPlugin.Builder builder(@Px float textSize) {
+        return new Builder(JLatexMathTheme.builder(textSize));
+    }
+
+    /**
+     * @since 4.3.0-SNAPSHOT
+     */
+    @NonNull
+    public static JLatexMathPlugin.Builder builder(@Px float inlineTextSize, @Px float blockTextSize) {
+        return new Builder(JLatexMathTheme.builder(inlineTextSize, blockTextSize));
     }
 
     public static class Config {
 
-        private final float textSize;
+        // @since 4.3.0-SNAPSHOT
+        private final JLatexMathTheme theme;
 
-        // @since 4.0.0
-        private final JLatexMathTheme.BackgroundProvider backgroundProvider;
+        // @since 4.3.0-SNAPSHOT
+        private final RenderMode renderMode;
 
-        @JLatexMathDrawable.Align
-        private final int align;
-
-        private final boolean fitCanvas;
-
-        // @since 4.0.0
-        private final int paddingHorizontal;
-
-        // @since 4.0.0
-        private final int paddingVertical;
-
-        // @since 4.0.0
         private final ExecutorService executorService;
 
         Config(@NonNull Builder builder) {
-            this.textSize = builder.textSize;
-            this.backgroundProvider = builder.backgroundProvider;
-            this.align = builder.align;
-            this.fitCanvas = builder.fitCanvas;
-            this.paddingHorizontal = builder.paddingHorizontal;
-            this.paddingVertical = builder.paddingVertical;
-
+            this.theme = builder.theme.build();
+            this.renderMode = builder.renderMode;
             // @since 4.0.0
             ExecutorService executorService = builder.executorService;
             if (executorService == null) {
@@ -133,29 +140,46 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         }
     }
 
+    private final Config config;
     private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader;
-    private final JLatexImageSizeResolver jLatexImageSizeResolver;
+    private final JLatexBlockImageSizeResolver jLatexBlockImageSizeResolver;
+    private final ImageSizeResolver inlineImageSizeResolver;
 
     @SuppressWarnings("WeakerAccess")
     JLatexMathPlugin(@NonNull Config config) {
+        this.config = config;
         this.jLatextAsyncDrawableLoader = new JLatextAsyncDrawableLoader(config);
-        this.jLatexImageSizeResolver = new JLatexImageSizeResolver(config.fitCanvas);
+        this.jLatexBlockImageSizeResolver = new JLatexBlockImageSizeResolver(config.theme.blockFitCanvas());
+        this.inlineImageSizeResolver = new InlineImageSizeResolver();
     }
 
     @Override
     public void configureParser(@NonNull Parser.Builder builder) {
 
-        // what we can do:
-        // [0-3] spaces before block start/end
-        // if it's $$\n -> block
-        // if it's $$\\dhdsfjh$$ -> inline
+        // TODO: depending on renderMode we should register our parsing here
+        // * for LEGACY -> just add custom block parser
+        // * for INLINE.. -> require InlinePlugin, add inline processor + add block parser
 
-        builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
+        switch (config.renderMode) {
 
-        final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
-                .addInlineProcessor(new JLatexMathInlineProcessor())
-                .build();
-        builder.inlineParserFactory(factory);
+            case LEGACY: {
+                builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory());
+            }
+            break;
+
+            case BLOCKS_AND_INLINES: {
+                builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
+
+                final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
+                        .addInlineProcessor(new JLatexMathInlineProcessor())
+                        .build();
+                builder.inlineParserFactory(factory);
+            }
+            break;
+
+            default:
+                throw new RuntimeException("Unexpected `renderMode`: " + config.renderMode);
+        }
     }
 
     @Override
@@ -182,7 +206,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
                         new JLatextAsyncDrawable(
                                 latex,
                                 jLatextAsyncDrawableLoader,
-                                jLatexImageSizeResolver,
+                                jLatexBlockImageSizeResolver,
                                 null,
                                 true),
                         AsyncDrawableSpan.ALIGN_CENTER,
@@ -196,34 +220,39 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
                 }
             }
         });
-        builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
-            @Override
-            public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) {
-                final String latex = jLatexMathNode.latex();
 
-                final int length = visitor.length();
 
-                // @since 4.0.2 we cannot append _raw_ latex as a placeholder-text,
-                // because Android will draw formula for each line of text, thus
-                // leading to formula duplicated (drawn on each line of text)
-                visitor.builder().append(prepareLatexTextPlaceholder(latex));
+        if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) {
 
-                final MarkwonConfiguration configuration = visitor.configuration();
+            builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
+                @Override
+                public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) {
+                    final String latex = jLatexMathNode.latex();
 
-                final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan(
-                        configuration.theme(),
-                        new JLatextAsyncDrawable(
-                                latex,
-                                jLatextAsyncDrawableLoader,
-                                new ImageSizeResolverDef(),
-                                null,
-                                false),
-                        AsyncDrawableSpan.ALIGN_CENTER,
-                        false);
+                    final int length = visitor.length();
 
-                visitor.setSpans(length, span);
-            }
-        });
+                    // @since 4.0.2 we cannot append _raw_ latex as a placeholder-text,
+                    // because Android will draw formula for each line of text, thus
+                    // leading to formula duplicated (drawn on each line of text)
+                    visitor.builder().append(prepareLatexTextPlaceholder(latex));
+
+                    final MarkwonConfiguration configuration = visitor.configuration();
+
+                    final AsyncDrawableSpan span = new JLatexInlineAsyncDrawableSpan(
+                            configuration.theme(),
+                            new JLatextAsyncDrawable(
+                                    latex,
+                                    jLatextAsyncDrawableLoader,
+                                    inlineImageSizeResolver,
+                                    null,
+                                    false),
+                            AsyncDrawableSpan.ALIGN_CENTER,
+                            false);
+
+                    visitor.setSpans(length, span);
+                }
+            });
+        }
     }
 
     @Override
@@ -245,61 +274,30 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
     public static class Builder {
 
-        private final float textSize;
+        // @since 4.3.0-SNAPSHOT
+        private final JLatexMathTheme.Builder theme;
 
-        // @since 4.0.0
-        private JLatexMathTheme.BackgroundProvider backgroundProvider;
-
-        @JLatexMathDrawable.Align
-        private int align = JLatexMathDrawable.ALIGN_CENTER;
-
-        private boolean fitCanvas = false;
-
-        // @since 4.0.0
-        private int paddingHorizontal;
-
-        // @since 4.0.0
-        private int paddingVertical;
+        // @since 4.3.0-SNAPSHOT
+        private RenderMode renderMode = RenderMode.BLOCKS_AND_INLINES;
 
         // @since 4.0.0
         private ExecutorService executorService;
 
-        Builder(float textSize) {
-            this.textSize = textSize;
+        Builder(@NonNull JLatexMathTheme.Builder builder) {
+            this.theme = builder;
         }
 
         @NonNull
-        public Builder backgroundProvider(@NonNull JLatexMathTheme.BackgroundProvider backgroundProvider) {
-            this.backgroundProvider = backgroundProvider;
-            return this;
-        }
-
-        @NonNull
-        public Builder align(@JLatexMathDrawable.Align int align) {
-            this.align = align;
-            return this;
-        }
-
-        @NonNull
-        public Builder fitCanvas(boolean fitCanvas) {
-            this.fitCanvas = fitCanvas;
-            return this;
-        }
-
-        @NonNull
-        public Builder padding(@Px int padding) {
-            this.paddingHorizontal = padding;
-            this.paddingVertical = padding;
-            return this;
+        public JLatexMathTheme.Builder theme() {
+            return theme;
         }
 
         /**
-         * @since 4.0.0
+         * @since 4.3.0-SNAPSHOT
          */
         @NonNull
-        public Builder builder(@Px int paddingHorizontal, @Px int paddingVertical) {
-            this.paddingHorizontal = paddingHorizontal;
-            this.paddingVertical = paddingVertical;
+        public Builder renderMode(@NonNull RenderMode renderMode) {
+            this.renderMode = renderMode;
             return this;
         }
 
@@ -319,7 +317,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
     }
 
     // @since 4.0.0
-    private static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader {
+    static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader {
 
         private final Config config;
         private final Handler handler = new Handler(Looper.getMainLooper());
@@ -358,57 +356,15 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
                     private void execute() {
 
-                        // @since 4.0.1 (background provider can be null)
-                        final JLatexMathTheme.BackgroundProvider backgroundProvider = config.backgroundProvider;
-
                         final JLatexMathDrawable jLatexMathDrawable;
 
-                        // TODO: obtain real values from theme (for blocks and inlines)
                         final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
-                        if (jLatextAsyncDrawable.isBlock) {
-                            // create JLatexMathDrawable
-                            //noinspection ConstantConditions
-                            jLatexMathDrawable =
-                                    JLatexMathDrawable.builder(drawable.getDestination())
-                                            .textSize(config.textSize)
-                                            .background(backgroundProvider != null ? backgroundProvider.provide() : null)
-                                            .align(config.align)
-                                            .fitCanvas(config.fitCanvas)
-                                            .padding(
-                                                    config.paddingHorizontal,
-                                                    config.paddingVertical,
-                                                    config.paddingHorizontal,
-                                                    config.paddingVertical)
-                                            .build();
-                        } else {
-                            jLatexMathDrawable =
-                                    JLatexMathDrawable.builder(drawable.getDestination())
-                                            .textSize(config.textSize)
-//                                            .background(backgroundProvider != null ? backgroundProvider.provide() : null)
-//                                            .align(config.align)
-//                                            .fitCanvas(config.fitCanvas)
-//                                            .padding(
-//                                                    config.paddingHorizontal,
-//                                                    config.paddingVertical,
-//                                                    config.paddingHorizontal,
-//                                                    config.paddingVertical)
-                                            .build();
-                        }
 
-                        // create JLatexMathDrawable
-//                        //noinspection ConstantConditions
-//                        final JLatexMathDrawable jLatexMathDrawable =
-//                                JLatexMathDrawable.builder(drawable.getDestination())
-//                                        .textSize(config.textSize)
-//                                        .background(backgroundProvider != null ? backgroundProvider.provide() : null)
-//                                        .align(config.align)
-//                                        .fitCanvas(config.fitCanvas)
-//                                        .padding(
-//                                                config.paddingHorizontal,
-//                                                config.paddingVertical,
-//                                                config.paddingHorizontal,
-//                                                config.paddingVertical)
-//                                        .build();
+                        if (jLatextAsyncDrawable.isBlock()) {
+                            jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable.getDestination());
+                        } else {
+                            jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable.getDestination());
+                        }
 
                         // we must post to handler, but also have a way to identify the drawable
                         // for which we are posting (in case of cancellation)
@@ -447,109 +403,63 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         public Drawable placeholder(@NonNull AsyncDrawable drawable) {
             return null;
         }
+
+        // @since 4.3.0-SNAPSHOT
+        @NonNull
+        private JLatexMathDrawable createBlockDrawable(@NonNull String latex) {
+
+            final JLatexMathTheme theme = config.theme;
+
+            final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.blockBackgroundProvider();
+            final JLatexMathTheme.Padding padding = theme.blockPadding();
+
+            final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex)
+                    .textSize(theme.blockTextSize())
+                    .align(theme.blockHorizontalAlignment())
+                    .fitCanvas(theme.blockFitCanvas());
+
+            if (backgroundProvider != null) {
+                builder.background(backgroundProvider.provide());
+            }
+
+            if (padding != null) {
+                builder.padding(padding.left, padding.top, padding.right, padding.bottom);
+            }
+
+            return builder.build();
+        }
+
+        // @since 4.3.0-SNAPSHOT
+        @NonNull
+        private JLatexMathDrawable createInlineDrawable(@NonNull String latex) {
+
+            final JLatexMathTheme theme = config.theme;
+
+            final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.inlineBackgroundProvider();
+            final JLatexMathTheme.Padding padding = theme.inlinePadding();
+
+            final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex)
+                    .textSize(theme.inlineTextSize())
+                    .fitCanvas(false);
+
+            if (backgroundProvider != null) {
+                builder.background(backgroundProvider.provide());
+            }
+
+            if (padding != null) {
+                builder.padding(padding.left, padding.top, padding.right, padding.bottom);
+            }
+
+            return builder.build();
+        }
     }
 
-    // we must make drawable fit canvas (if specified), but do not keep the ratio whilst scaling up
-    // @since 4.0.0
-    private static class JLatexImageSizeResolver extends ImageSizeResolver {
-
-        private final boolean fitCanvas;
-
-        JLatexImageSizeResolver(boolean fitCanvas) {
-            this.fitCanvas = fitCanvas;
-        }
+    private static class InlineImageSizeResolver extends ImageSizeResolver {
 
         @NonNull
         @Override
         public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
-
-            final Rect imageBounds = drawable.getResult().getBounds();
-            final int canvasWidth = drawable.getLastKnownCanvasWidth();
-
-            if (fitCanvas) {
-
-                // we modify bounds only if `fitCanvas` is true
-                final int w = imageBounds.width();
-
-                if (w < canvasWidth) {
-                    // increase width and center formula (keep height as-is)
-                    return new Rect(0, 0, canvasWidth, imageBounds.height());
-                }
-
-                // @since 4.0.2 we additionally scale down the resulting formula (keeping the ratio)
-                // the thing is - JLatexMathDrawable will do it anyway, but it will modify its own
-                // bounds (which AsyncDrawable won't catch), thus leading to an empty space after the formula
-                if (w > canvasWidth) {
-                    // here we must scale it down (keeping the ratio)
-                    final float ratio = (float) w / imageBounds.height();
-                    final int h = (int) (canvasWidth / ratio + .5F);
-                    return new Rect(0, 0, canvasWidth, h);
-                }
-            }
-
-            return imageBounds;
-        }
-    }
-
-    private static class JLatextAsyncDrawable extends AsyncDrawable {
-
-        private final boolean isBlock;
-
-        public JLatextAsyncDrawable(
-                @NonNull String destination,
-                @NonNull AsyncDrawableLoader loader,
-                @NonNull ImageSizeResolver imageSizeResolver,
-                @Nullable ImageSize imageSize,
-                boolean isBlock
-        ) {
-            super(destination, loader, imageSizeResolver, imageSize);
-            this.isBlock = isBlock;
-        }
-    }
-
-    private static class JLatexAsyncDrawableSpan extends AsyncDrawableSpan {
-
-        private final AsyncDrawable drawable;
-
-        public JLatexAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) {
-            super(theme, drawable, alignment, replacementTextIsLink);
-            this.drawable = drawable;
-        }
-
-        @Override
-        public int getSize(
-                @NonNull Paint paint,
-                CharSequence text,
-                @IntRange(from = 0) int start,
-                @IntRange(from = 0) int end,
-                @Nullable Paint.FontMetricsInt fm) {
-
-            // if we have no async drawable result - we will just render text
-
-            final int size;
-
-            if (drawable.hasResult()) {
-
-                final Rect rect = drawable.getBounds();
-
-                if (fm != null) {
-                    final int half = rect.bottom / 2;
-                    fm.ascent = -half;
-                    fm.descent = half;
-
-                    fm.top = fm.ascent;
-                    fm.bottom = 0;
-                }
-
-                size = rect.right;
-
-            } else {
-
-                // NB, no specific text handling (no new lines, etc)
-                size = (int) (paint.measureText(text, start, end) + .5F);
-            }
-
-            return size;
+            return drawable.getResult().getBounds();
         }
     }
 }
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
index 30b9e04a..8a1d8801 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java
@@ -128,9 +128,9 @@ public abstract class JLatexMathTheme {
         private BackgroundProvider inlineBackgroundProvider;
         private BackgroundProvider blockBackgroundProvider;
 
-        private boolean blockFitCanvas;
+        private boolean blockFitCanvas = true;
         // horizontal alignment (when there is additional horizontal space)
-        private int blockHorizontalAlignment;
+        private int blockHorizontalAlignment = JLatexMathDrawable.ALIGN_CENTER;
 
         private Padding padding;
         private Padding inlinePadding;
@@ -196,7 +196,7 @@ public abstract class JLatexMathTheme {
 
         @NonNull
         public JLatexMathTheme build() {
-            return null;
+            return new Impl(this);
         }
     }
 
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java
new file mode 100644
index 00000000..4376d636
--- /dev/null
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java
@@ -0,0 +1,32 @@
+package io.noties.markwon.ext.latex;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import io.noties.markwon.image.AsyncDrawable;
+import io.noties.markwon.image.AsyncDrawableLoader;
+import io.noties.markwon.image.ImageSize;
+import io.noties.markwon.image.ImageSizeResolver;
+
+/**
+ * @since 4.3.0-SNAPSHOT
+ */
+class JLatextAsyncDrawable extends AsyncDrawable {
+
+    private final boolean isBlock;
+
+    JLatextAsyncDrawable(
+            @NonNull String destination,
+            @NonNull AsyncDrawableLoader loader,
+            @NonNull ImageSizeResolver imageSizeResolver,
+            @Nullable ImageSize imageSize,
+            boolean isBlock
+    ) {
+        super(destination, loader, imageSizeResolver, imageSize);
+        this.isBlock = isBlock;
+    }
+
+    public boolean isBlock() {
+        return isBlock;
+    }
+}
diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
index c669cde0..075c2ca4 100644
--- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
@@ -1,7 +1,7 @@
 package io.noties.markwon.sample.latex;
 
+import android.content.res.Resources;
 import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.widget.TextView;
 
@@ -14,7 +14,6 @@ import io.noties.markwon.ext.latex.JLatexMathTheme;
 import io.noties.markwon.sample.ActivityWithMenuOptions;
 import io.noties.markwon.sample.MenuOptions;
 import io.noties.markwon.sample.R;
-import ru.noties.jlatexmath.JLatexMathDrawable;
 
 public class LatexActivity extends ActivityWithMenuOptions {
 
@@ -87,7 +86,7 @@ public class LatexActivity extends ActivityWithMenuOptions {
     private static String wrapLatexInSampleMarkdown(@NonNull String latex) {
         return "" +
                 "# Example of LaTeX\n\n" +
-                "(inline): $$" + latex + "$$ so nice, really? Now, (block):\n\n" +
+                "(inline): $$" + latex + "$$ so nice, really-really really-really really-really? Now, (block):\n\n" +
                 "$$\n" +
                 "" + latex + "\n" +
                 "$$\n\n" +
@@ -95,23 +94,22 @@ public class LatexActivity extends ActivityWithMenuOptions {
     }
 
     private void render(@NonNull String markdown) {
+
+        final float textSize = textView.getTextSize();
+        final Resources r = getResources();
+
         final Markwon markwon = Markwon.builder(this)
-                .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
-                    @Override
-                    public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
-                        builder
-                                .backgroundProvider(new JLatexMathTheme.BackgroundProvider() {
-                                    @NonNull
-                                    @Override
-                                    public Drawable provide() {
-                                        return new ColorDrawable(0x40ff0000);
-                                    }
-                                })
-                                .fitCanvas(true)
-                                .align(JLatexMathDrawable.ALIGN_CENTER)
-                                .padding(48)
-                        ;
-                    }
+                .usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> {
+                    builder.theme()
+                            .inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00))
+                            .blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000))
+                            .blockPadding(JLatexMathTheme.Padding.symmetric(
+                                    r.getDimensionPixelSize(R.dimen.latex_block_padding_vertical),
+                                    r.getDimensionPixelSize(R.dimen.latex_block_padding_horizontal)
+                            ));
+
+                    // explicitly request LEGACY rendering mode
+//                    builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY);
                 }))
                 .build();
 
diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..b88d4ed5
--- /dev/null
+++ b/sample/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="latex_block_padding_vertical">8dip</dimen>
+    <dimen name="latex_block_padding_horizontal">16dip</dimen>
+</resources>
\ No newline at end of file

From 74682ae605e5fbf470fad74206e8b18a2acbe3bf Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Wed, 26 Feb 2020 15:54:08 +0300
Subject: [PATCH 7/8] MarkwonInlineParserPlugin

---
 markwon-inline-parser/build.gradle            |   1 +
 .../MarkwonInlineParserPlugin.java            |  59 ++++++++++
 .../sample/ActivityWithMenuOptions.java       |  17 ++-
 .../io/noties/markwon/sample/MenuOptions.java |  19 ++-
 .../markwon/sample/editor/EditorActivity.java | 108 +++++++++++++++++-
 .../src/main/res/values/strings-samples.xml   |   2 +-
 6 files changed, 195 insertions(+), 11 deletions(-)
 create mode 100644 markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java

diff --git a/markwon-inline-parser/build.gradle b/markwon-inline-parser/build.gradle
index 703a18ff..32a45d7c 100644
--- a/markwon-inline-parser/build.gradle
+++ b/markwon-inline-parser/build.gradle
@@ -14,6 +14,7 @@ android {
 }
 
 dependencies {
+    api project(':markwon-core')
     api deps['x-annotations']
     api deps['commonmark']
 
diff --git a/markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java b/markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java
new file mode 100644
index 00000000..ce80501b
--- /dev/null
+++ b/markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java
@@ -0,0 +1,59 @@
+package io.noties.markwon.inlineparser;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.parser.Parser;
+
+import io.noties.markwon.AbstractMarkwonPlugin;
+
+/**
+ * @since 4.3.0-SNAPSHOT
+ */
+public class MarkwonInlineParserPlugin extends AbstractMarkwonPlugin {
+
+    public interface BuilderConfigure<B extends MarkwonInlineParser.FactoryBuilder> {
+        void configureBuilder(@NonNull B factoryBuilder);
+    }
+
+    @NonNull
+    public static MarkwonInlineParserPlugin create() {
+        return create(MarkwonInlineParser.factoryBuilder());
+    }
+
+    @NonNull
+    public static MarkwonInlineParserPlugin create(@NonNull BuilderConfigure<MarkwonInlineParser.FactoryBuilder> configure) {
+        final MarkwonInlineParser.FactoryBuilder factoryBuilder = MarkwonInlineParser.factoryBuilder();
+        configure.configureBuilder(factoryBuilder);
+        return new MarkwonInlineParserPlugin(factoryBuilder);
+    }
+
+    @NonNull
+    public static MarkwonInlineParserPlugin create(@NonNull MarkwonInlineParser.FactoryBuilder factoryBuilder) {
+        return new MarkwonInlineParserPlugin(factoryBuilder);
+    }
+
+    @NonNull
+    public static <B extends MarkwonInlineParser.FactoryBuilder> MarkwonInlineParserPlugin create(
+            @NonNull B factoryBuilder,
+            @NonNull BuilderConfigure<B> configure) {
+        configure.configureBuilder(factoryBuilder);
+        return new MarkwonInlineParserPlugin(factoryBuilder);
+    }
+
+    private final MarkwonInlineParser.FactoryBuilder factoryBuilder;
+
+    @SuppressWarnings("WeakerAccess")
+    MarkwonInlineParserPlugin(@NonNull MarkwonInlineParser.FactoryBuilder factoryBuilder) {
+        this.factoryBuilder = factoryBuilder;
+    }
+
+    @Override
+    public void configureParser(@NonNull Parser.Builder builder) {
+        builder.inlineParserFactory(factoryBuilder.build());
+    }
+
+    @NonNull
+    public MarkwonInlineParser.FactoryBuilder factoryBuilder() {
+        return factoryBuilder;
+    }
+}
diff --git a/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java
index 66690f7b..54f5342f 100644
--- a/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java
+++ b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java
@@ -13,6 +13,14 @@ public abstract class ActivityWithMenuOptions extends Activity {
     @NonNull
     public abstract MenuOptions menuOptions();
 
+    protected void beforeOptionSelected(@NonNull String option) {
+        // no op, override to customize
+    }
+
+    protected void afterOptionSelected(@NonNull String option) {
+        // no op, override to customize
+    }
+
     private MenuOptions menuOptions;
 
     @Override
@@ -29,6 +37,13 @@ public abstract class ActivityWithMenuOptions extends Activity {
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        return menuOptions.onOptionsItemSelected(item);
+        final MenuOptions.Option option = menuOptions.onOptionsItemSelected(item);
+        if (option != null) {
+            beforeOptionSelected(option.title);
+            option.action.run();
+            afterOptionSelected(option.title);
+            return true;
+        }
+        return false;
     }
 }
diff --git a/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java
index 1c349bd8..6fb5b310 100644
--- a/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java
+++ b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java
@@ -4,6 +4,7 @@ import android.view.Menu;
 import android.view.MenuItem;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -15,6 +16,16 @@ public class MenuOptions {
         return new MenuOptions();
     }
 
+    static class Option {
+        final String title;
+        final Runnable action;
+
+        Option(@NonNull String title, @NonNull Runnable action) {
+            this.title = title;
+            this.action = action;
+        }
+    }
+
     // to preserve order use LinkedHashMap
     private final Map<String, Runnable> actions = new LinkedHashMap<>();
 
@@ -34,13 +45,13 @@ public class MenuOptions {
         return false;
     }
 
-    boolean onOptionsItemSelected(MenuItem item) {
+    @Nullable
+    Option onOptionsItemSelected(MenuItem item) {
         final String title = String.valueOf(item.getTitle());
         final Runnable action = actions.get(title);
         if (action != null) {
-            action.run();
-            return true;
+            return new Option(title, action);
         }
-        return false;
+        return null;
     }
 }
diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java
index 0dc05ef9..e1181a7f 100644
--- a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java
@@ -5,6 +5,7 @@ import android.text.Editable;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.TextPaint;
+import android.text.TextUtils;
 import android.text.method.LinkMovementMethod;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.MetricAffectingSpan;
@@ -40,6 +41,7 @@ import io.noties.markwon.inlineparser.BangInlineProcessor;
 import io.noties.markwon.inlineparser.EntityInlineProcessor;
 import io.noties.markwon.inlineparser.HtmlInlineProcessor;
 import io.noties.markwon.inlineparser.MarkwonInlineParser;
+import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
 import io.noties.markwon.linkify.LinkifyPlugin;
 import io.noties.markwon.sample.ActivityWithMenuOptions;
 import io.noties.markwon.sample.MenuOptions;
@@ -48,6 +50,7 @@ import io.noties.markwon.sample.R;
 public class EditorActivity extends ActivityWithMenuOptions {
 
     private EditText editText;
+    private String pendingInput;
 
     @NonNull
     @Override
@@ -58,16 +61,41 @@ public class EditorActivity extends ActivityWithMenuOptions {
                 .add("customPunctuationSpan", this::custom_punctuation_span)
                 .add("additionalEditSpan", this::additional_edit_span)
                 .add("additionalPlugins", this::additional_plugins)
-                .add("multipleEditSpans", this::multiple_edit_spans);
+                .add("multipleEditSpans", this::multiple_edit_spans)
+                .add("multipleEditSpansPlugin", this::multiple_edit_spans_plugin)
+                .add("pluginRequire", this::plugin_require)
+                .add("pluginNoDefaults", this::plugin_no_defaults);
+    }
+
+    @Override
+    protected void beforeOptionSelected(@NonNull String option) {
+        // we cannot _clear_ editText of text-watchers without keeping a reference to them...
+        pendingInput = editText != null
+                ? editText.getText().toString()
+                : null;
+
+        createView();
+    }
+
+    @Override
+    protected void afterOptionSelected(@NonNull String option) {
+        if (!TextUtils.isEmpty(pendingInput)) {
+            editText.setText(pendingInput);
+        }
+    }
+
+    private void createView() {
+        setContentView(R.layout.activity_editor);
+
+        this.editText = findViewById(R.id.edit_text);
+
+        initBottomBar();
     }
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_editor);
-
-        this.editText = findViewById(R.id.edit_text);
-        initBottomBar();
+        createView();
 
         multiple_edit_spans();
     }
@@ -219,6 +247,76 @@ public class EditorActivity extends ActivityWithMenuOptions {
                 editor, Executors.newSingleThreadExecutor(), editText));
     }
 
+    private void multiple_edit_spans_plugin() {
+        // inline parsing is configured via MarkwonInlineParserPlugin
+
+        // for links to be clickable
+        editText.setMovementMethod(LinkMovementMethod.getInstance());
+
+        final Markwon markwon = Markwon.builder(this)
+                .usePlugin(StrikethroughPlugin.create())
+                .usePlugin(LinkifyPlugin.create())
+                .usePlugin(MarkwonInlineParserPlugin.create(builder -> {
+                    builder
+                            .excludeInlineProcessor(BangInlineProcessor.class)
+                            .excludeInlineProcessor(HtmlInlineProcessor.class)
+                            .excludeInlineProcessor(EntityInlineProcessor.class);
+                }))
+                .build();
+
+        final LinkEditHandler.OnClick onClick = (widget, link) -> markwon.configuration().linkResolver().resolve(widget, link);
+
+        final MarkwonEditor editor = MarkwonEditor.builder(markwon)
+                .useEditHandler(new EmphasisEditHandler())
+                .useEditHandler(new StrongEmphasisEditHandler())
+                .useEditHandler(new StrikethroughEditHandler())
+                .useEditHandler(new CodeEditHandler())
+                .useEditHandler(new BlockQuoteEditHandler())
+                .useEditHandler(new LinkEditHandler(onClick))
+                .build();
+
+        editText.addTextChangedListener(MarkwonEditorTextWatcher.withPreRender(
+                editor, Executors.newSingleThreadExecutor(), editText));
+    }
+
+    private void plugin_require() {
+        // usage of plugin from other plugins
+
+        final Markwon markwon = Markwon.builder(this)
+                .usePlugin(MarkwonInlineParserPlugin.create())
+                .usePlugin(new AbstractMarkwonPlugin() {
+                    @Override
+                    public void configure(@NonNull Registry registry) {
+                        registry.require(MarkwonInlineParserPlugin.class)
+                                .factoryBuilder()
+                                .excludeInlineProcessor(HtmlInlineProcessor.class);
+                    }
+                })
+                .build();
+
+        final MarkwonEditor editor = MarkwonEditor.create(markwon);
+
+        editText.addTextChangedListener(MarkwonEditorTextWatcher.withPreRender(
+                editor, Executors.newSingleThreadExecutor(), editText));
+    }
+
+    private void plugin_no_defaults() {
+        // a plugin with no defaults registered
+
+        final Markwon markwon = Markwon.builder(this)
+                .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults()))
+//                .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults(), factoryBuilder -> {
+//                    // if anything, they can be included here
+////                    factoryBuilder.includeDefaults()
+//                }))
+                .build();
+
+        final MarkwonEditor editor = MarkwonEditor.create(markwon);
+
+        editText.addTextChangedListener(MarkwonEditorTextWatcher.withPreRender(
+                editor, Executors.newSingleThreadExecutor(), editText));
+    }
+
     private void initBottomBar() {
         // all except block-quote wraps if have selection, or inserts at current cursor position
 
diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml
index f5a97644..d7f11e1a 100644
--- a/sample/src/main/res/values/strings-samples.xml
+++ b/sample/src/main/res/values/strings-samples.xml
@@ -29,7 +29,7 @@
 
     <string name="sample_inline_parser"># \# Inline Parser\n\nUsage of custom inline parser</string>
 
-    <string name="sample_html_details"># \# HTML &lt;details> tag\n\n&lt;details> tag parsed and rendered</string>
+    <string name="sample_html_details"># \# HTML\n\n`details` tag parsed and rendered</string>
 
     <string name="sample_task_list"># \# TaskList\n\nUsage of TaskListPlugin</string>
 

From 1c08e3f240ccc5fa341935483782e9e28d17294c Mon Sep 17 00:00:00 2001
From: Dimitry Ivanov <di@noties.io>
Date: Wed, 26 Feb 2020 16:10:20 +0300
Subject: [PATCH 8/8] LatexPlugin now depens on inline-parser plugin

---
 markwon-ext-latex/build.gradle                |  3 +-
 .../markwon/ext/latex/JLatexMathPlugin.java   | 20 +++--
 .../markwon/sample/latex/LatexActivity.java   | 75 +++++++++++++------
 3 files changed, 66 insertions(+), 32 deletions(-)

diff --git a/markwon-ext-latex/build.gradle b/markwon-ext-latex/build.gradle
index 9d5f50b0..b0d3fc92 100644
--- a/markwon-ext-latex/build.gradle
+++ b/markwon-ext-latex/build.gradle
@@ -16,11 +16,10 @@ android {
 dependencies {
 
     api project(':markwon-core')
+    api project(':markwon-inline-parser')
 
     api deps['jlatexmath-android']
 
-    debugImplementation project(':markwon-inline-parser')
-
     deps['test'].with {
         testImplementation it['junit']
         testImplementation it['robolectric']
diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
index 5ecc0723..5a3d70b9 100644
--- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
+++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java
@@ -14,7 +14,6 @@ import androidx.annotation.Nullable;
 import androidx.annotation.Px;
 import androidx.annotation.VisibleForTesting;
 
-import org.commonmark.parser.InlineParserFactory;
 import org.commonmark.parser.Parser;
 
 import java.util.HashMap;
@@ -31,7 +30,7 @@ import io.noties.markwon.image.AsyncDrawableLoader;
 import io.noties.markwon.image.AsyncDrawableScheduler;
 import io.noties.markwon.image.AsyncDrawableSpan;
 import io.noties.markwon.image.ImageSizeResolver;
-import io.noties.markwon.inlineparser.MarkwonInlineParser;
+import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
 import ru.noties.jlatexmath.JLatexMathDrawable;
 
 /**
@@ -153,10 +152,19 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
         this.inlineImageSizeResolver = new InlineImageSizeResolver();
     }
 
+    @Override
+    public void configure(@NonNull Registry registry) {
+        if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) {
+            registry.require(MarkwonInlineParserPlugin.class)
+                    .factoryBuilder()
+                    .addInlineProcessor(new JLatexMathInlineProcessor());
+        }
+    }
+
     @Override
     public void configureParser(@NonNull Parser.Builder builder) {
 
-        // TODO: depending on renderMode we should register our parsing here
+        // depending on renderMode we should register our parsing here
         // * for LEGACY -> just add custom block parser
         // * for INLINE.. -> require InlinePlugin, add inline processor + add block parser
 
@@ -169,11 +177,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 
             case BLOCKS_AND_INLINES: {
                 builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
-
-                final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
-                        .addInlineProcessor(new JLatexMathInlineProcessor())
-                        .build();
-                builder.inlineParserFactory(factory);
+                // inline processor is added through `registry`
             }
             break;
 
diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
index 075c2ca4..8dcdcd11 100644
--- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java
@@ -11,12 +11,41 @@ import androidx.annotation.Nullable;
 import io.noties.markwon.Markwon;
 import io.noties.markwon.ext.latex.JLatexMathPlugin;
 import io.noties.markwon.ext.latex.JLatexMathTheme;
+import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
 import io.noties.markwon.sample.ActivityWithMenuOptions;
 import io.noties.markwon.sample.MenuOptions;
 import io.noties.markwon.sample.R;
 
 public class LatexActivity extends ActivityWithMenuOptions {
 
+    private static final String LATEX_ARRAY;
+
+    static {
+        String latex = "\\begin{array}{l}";
+        latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\";
+        latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\";
+        latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\";
+        latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\";
+        latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty  {\\sum\\limits_{m = 1}^{2^n  - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\";
+        latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\";
+        latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\";
+        latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\";
+        latex += "\\end{array}";
+        LATEX_ARRAY = latex;
+    }
+
+    private static final String LATEX_LONG_DIVISION = "\\text{A long division \\longdiv{12345}{13}";
+    private static final String LATEX_BANGLE = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}";
+    private static final String LATEX_BOXES;
+
+    static {
+        String latex = "\\begin{array}{cc}";
+        latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr";
+        latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr";
+        latex += "\\end{array}";
+        LATEX_BOXES = latex;
+    }
+
     private TextView textView;
 
     @NonNull
@@ -27,7 +56,8 @@ public class LatexActivity extends ActivityWithMenuOptions {
                 .add("longDivision", this::longDivision)
                 .add("bangle", this::bangle)
                 .add("boxes", this::boxes)
-                .add("insideBlockQuote", this::insideBlockQuote);
+                .add("insideBlockQuote", this::insideBlockQuote)
+                .add("legacy", this::legacy);
     }
 
     @Override
@@ -42,36 +72,19 @@ public class LatexActivity extends ActivityWithMenuOptions {
     }
 
     private void array() {
-        String latex = "\\begin{array}{l}";
-        latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\";
-        latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\";
-        latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\";
-        latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\";
-        latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty  {\\sum\\limits_{m = 1}^{2^n  - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\";
-        latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\";
-        latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\";
-        latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\";
-        latex += "\\end{array}";
-
-        render(wrapLatexInSampleMarkdown(latex));
+        render(wrapLatexInSampleMarkdown(LATEX_ARRAY));
     }
 
     private void longDivision() {
-        String latex = "\\text{A long division \\longdiv{12345}{13}";
-        render(wrapLatexInSampleMarkdown(latex));
+        render(wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION));
     }
 
     private void bangle() {
-        String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}";
-        render(wrapLatexInSampleMarkdown(latex));
+        render(wrapLatexInSampleMarkdown(LATEX_BANGLE));
     }
 
     private void boxes() {
-        String latex = "\\begin{array}{cc}";
-        latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr";
-        latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr";
-        latex += "\\end{array}";
-        render(wrapLatexInSampleMarkdown(latex));
+        render(wrapLatexInSampleMarkdown(LATEX_BOXES));
     }
 
     private void insideBlockQuote() {
@@ -82,6 +95,22 @@ public class LatexActivity extends ActivityWithMenuOptions {
         render(md);
     }
 
+    private void legacy() {
+        final String md = wrapLatexInSampleMarkdown(LATEX_BANGLE);
+
+        final Markwon markwon = Markwon.builder(this)
+                // LEGACY does not require inline parser
+                .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
+                    builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY);
+                    builder.theme()
+                            .backgroundProvider(() -> new ColorDrawable(0x100000ff))
+                            .padding(JLatexMathTheme.Padding.all(48));
+                }))
+                .build();
+
+        markwon.setMarkdown(textView, md);
+    }
+
     @NonNull
     private static String wrapLatexInSampleMarkdown(@NonNull String latex) {
         return "" +
@@ -99,6 +128,8 @@ public class LatexActivity extends ActivityWithMenuOptions {
         final Resources r = getResources();
 
         final Markwon markwon = Markwon.builder(this)
+                // NB! `MarkwonInlineParserPlugin` is required in order to parse inlines
+                .usePlugin(MarkwonInlineParserPlugin.create())
                 .usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> {
                     builder.theme()
                             .inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00))