From a298016ac2c795e3b6b9ba61285aa1a89106b13b Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 2 Feb 2020 17:46:18 +0300 Subject: [PATCH 01/33] 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 Date: Mon, 10 Feb 2020 22:25:20 +0300 Subject: [PATCH 02/33] 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() { + @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 Date: Fri, 14 Feb 2020 18:35:44 +0300 Subject: [PATCH 03/33] 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() { @@ -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 Date: Wed, 26 Feb 2020 09:51:33 +0300 Subject: [PATCH 04/33] 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 inline LaTeX + * @see #blockTexxtSize() + */ + @Px + public abstract float inlineTextSize(); + + /** + * @return text size in pixels for block LaTeX + * @see #inlineTextSize() + */ + @Px + public abstract float blockTexxtSize(); + + @Nullable + public abstract BackgroundProvider inlineBackgroundProvider(); + + @Nullable + public abstract BackgroundProvider blockBackgroundProvider(); + + /** + * @return boolean if block LaTeX must fit the width of canvas + */ + public abstract boolean blockFitCanvas(); + + /** + * @return horizontal alignment of block LaTeX 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 @@ + 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 @@ # \# HTML <details> tag\n\n<details> tag parsed and rendered + # \# TaskList\n\nUsage of TaskListPlugin + \ No newline at end of file From c7494a922504acb922aba705a09cb073a6974078 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 13:39:37 +0300 Subject: [PATCH 05/33] 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 { + /** + * LEGACY 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 start the block. + */ + 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 inline LaTeX - * @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 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 { 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(Xr)=1-P(X + + + + \ No newline at end of file From a80ff09e15b55a78e49dcf1af610e2a3716f5cb6 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 15:08:00 +0300 Subject: [PATCH 06/33] 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() { - @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() { + @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 @@ + + + 8dip + 16dip + \ No newline at end of file From 74682ae605e5fbf470fad74206e8b18a2acbe3bf Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 15:54:08 +0300 Subject: [PATCH 07/33] 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 { + void configureBuilder(@NonNull B factoryBuilder); + } + + @NonNull + public static MarkwonInlineParserPlugin create() { + return create(MarkwonInlineParser.factoryBuilder()); + } + + @NonNull + public static MarkwonInlineParserPlugin create(@NonNull BuilderConfigure 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 MarkwonInlineParserPlugin create( + @NonNull B factoryBuilder, + @NonNull BuilderConfigure 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 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 @@ # \# Inline Parser\n\nUsage of custom inline parser - # \# HTML <details> tag\n\n<details> tag parsed and rendered + # \# HTML\n\n`details` tag parsed and rendered # \# TaskList\n\nUsage of TaskListPlugin From 1c08e3f240ccc5fa341935483782e9e28d17294c Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 16:10:20 +0300 Subject: [PATCH 08/33] 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)) From 8da8a37178a002543e46badac28519c818f0a657 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 17:18:42 +0300 Subject: [PATCH 09/33] Update sample application --- CHANGELOG.md | 3 + gradle.properties | 2 +- .../basicplugins/BasicPluginsActivity.java | 111 +++++++++--------- .../markwon/sample/core/CoreActivity.java | 36 ++++-- .../CustomExtensionActivity2.java | 40 ++++--- .../inlineparser/InlineParserActivity.java | 15 ++- 6 files changed, 119 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b280a34..4982ffd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +# 4.3.0-SNAPSHOT + + # 4.2.2 * Fixed `AsyncDrawable` display when it has placeholder with empty bounds ([#189]) * Fixed `syntax-highlight` where code input is empty string ([#192]) diff --git a/gradle.properties b/gradle.properties index 5b0fd422..a1dd08b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ android.enableJetifier=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=4.2.2 +VERSION_NAME=4.3.0-SNAPSHOT GROUP=io.noties.markwon POM_DESCRIPTION=Markwon markdown for Android diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index df22cf06..7fe69cd3 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -1,12 +1,9 @@ package io.noties.markwon.sample.basicplugins; -import android.app.Activity; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; -import android.text.Layout; import android.text.TextUtils; -import android.text.style.AlignmentSpan; import android.text.style.ForegroundColorSpan; import android.widget.TextView; @@ -22,56 +19,56 @@ import java.util.Collections; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; -import io.noties.markwon.MarkwonPlugin; import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; -import io.noties.markwon.RenderProps; import io.noties.markwon.core.MarkwonTheme; -import io.noties.markwon.html.HtmlPlugin; -import io.noties.markwon.html.HtmlTag; -import io.noties.markwon.html.tag.SimpleTagHandler; import io.noties.markwon.image.ImageItem; import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.image.SchemeHandler; import io.noties.markwon.image.network.NetworkSchemeHandler; import io.noties.markwon.movement.MovementMethodPlugin; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; +import io.noties.markwon.sample.R; -public class BasicPluginsActivity extends Activity { +public class BasicPluginsActivity extends ActivityWithMenuOptions { private TextView textView; + + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("paragraphSpan", this::paragraphSpan) + .add("disableNode", this::disableNode) + .add("linkWithMovementMethod", this::linkWithMovementMethod) + .add("imagesPlugin", this::imagesPlugin); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_text_view); - textView = new TextView(this); - setContentView(textView); + textView = findViewById(R.id.text_view); - step_1(); - - step_2(); - - step_3(); - - step_4(); - - step_5(); - - step_6(); + paragraphSpan(); +// +// disableNode(); +// +// customizeTheme(); +// +// linkWithMovementMethod(); +// +// imagesPlugin(); } /** * In order to apply paragraph spans a custom plugin should be created (CorePlugin will take care * of everything else). - *

- * Please note that when a plugin is registered and it depends on CorePlugin, there is no - * need to explicitly specify it. By default all plugins that extend AbstractMarkwonPlugin do declare - * it\'s dependency on CorePlugin ({@link MarkwonPlugin#priority()}). - *

- * Order in which plugins are specified to the builder is of little importance as long as each - * plugin clearly states what dependencies it has */ - private void step_1() { + private void paragraphSpan() { final String markdown = "# Hello!\n\nA paragraph?\n\nIt should be!"; @@ -91,7 +88,7 @@ public class BasicPluginsActivity extends Activity { /** * To disable some nodes from rendering another custom plugin can be used */ - private void step_2() { + private void disableNode() { final String markdown = "# Heading 1\n\n## Heading 2\n\n**other** content [here](#)"; @@ -116,7 +113,7 @@ public class BasicPluginsActivity extends Activity { /** * To customize core theme plugin can be used again */ - private void step_3() { + private void customizeTheme() { final String markdown = "`A code` that is rendered differently\n\n```\nHello!\n```"; @@ -145,7 +142,7 @@ public class BasicPluginsActivity extends Activity { *

* In order to customize them a custom plugin should be used */ - private void step_4() { + private void linkWithMovementMethod() { final String markdown = "[a link without scheme](github.com)"; @@ -178,7 +175,7 @@ public class BasicPluginsActivity extends Activity { * images handling (parsing markdown containing images, obtain an image from network * file system or assets). Please note that */ - private void step_5() { + private void imagesPlugin() { final String markdown = "![image](myownscheme://en.wikipedia.org/static/images/project-logos/enwiki-2x.png)"; @@ -220,29 +217,29 @@ public class BasicPluginsActivity extends Activity { markwon.setMarkdown(textView, markdown); } - public void step_6() { - - final Markwon markwon = Markwon.builder(this) - .usePlugin(HtmlPlugin.create()) - .usePlugin(new AbstractMarkwonPlugin() { - @Override - public void configure(@NonNull Registry registry) { - registry.require(HtmlPlugin.class, plugin -> plugin.addHandler(new SimpleTagHandler() { - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) { - return new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER); - } - - @NonNull - @Override - public Collection supportedTags() { - return Collections.singleton("center"); - } - })); - } - }) - .build(); - } +// public void step_6() { +// +// final Markwon markwon = Markwon.builder(this) +// .usePlugin(HtmlPlugin.create()) +// .usePlugin(new AbstractMarkwonPlugin() { +// @Override +// public void configure(@NonNull Registry registry) { +// registry.require(HtmlPlugin.class, plugin -> plugin.addHandler(new SimpleTagHandler() { +// @Override +// public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) { +// return new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER); +// } +// +// @NonNull +// @Override +// public Collection supportedTags() { +// return Collections.singleton("center"); +// } +// })); +// } +// }) +// .build(); +// } // text lifecycle (after/before) // rendering lifecycle (before/after) diff --git a/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java b/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java index f1a67f50..19c6d3dd 100644 --- a/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java @@ -1,36 +1,48 @@ package io.noties.markwon.sample.core; -import android.app.Activity; import android.os.Bundle; import android.text.Spanned; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.commonmark.node.Node; import io.noties.markwon.Markwon; import io.noties.markwon.core.CorePlugin; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; +import io.noties.markwon.sample.R; -public class CoreActivity extends Activity { +public class CoreActivity extends ActivityWithMenuOptions { private TextView textView; + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("simple", this::simple) + .add("toast", this::toast) + .add("alreadyParsed", this::alreadyParsed); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_text_view); - textView = new TextView(this); - setContentView(textView); + textView = findViewById(R.id.text_view); - step_1(); +// step_1(); - step_2(); + simple(); - step_3(); - - step_4(); +// toast(); +// +// alreadyParsed(); } /** @@ -70,7 +82,7 @@ public class CoreActivity extends Activity { /** * To simply apply raw (non-parsed) markdown call {@link Markwon#setMarkdown(TextView, String)} */ - private void step_2() { + private void simple() { // this is raw markdown final String markdown = "Hello **markdown**!"; @@ -91,7 +103,7 @@ public class CoreActivity extends Activity { * of invalidation. But if a Toast for example is created with a custom view * ({@code new Toast(this).setView(...) }) and has access to a TextView everything should work. */ - private void step_3() { + private void toast() { final String markdown = "*Toast* __here__!\n\n> And a quote!"; @@ -105,7 +117,7 @@ public class CoreActivity extends Activity { /** * To apply already parsed markdown use {@link Markwon#setParsedMarkdown(TextView, Spanned)} */ - private void step_4() { + private void alreadyParsed() { final String markdown = "This **is** pre-parsed [markdown](#)"; diff --git a/sample/src/main/java/io/noties/markwon/sample/customextension2/CustomExtensionActivity2.java b/sample/src/main/java/io/noties/markwon/sample/customextension2/CustomExtensionActivity2.java index cb4f178f..cd286198 100644 --- a/sample/src/main/java/io/noties/markwon/sample/customextension2/CustomExtensionActivity2.java +++ b/sample/src/main/java/io/noties/markwon/sample/customextension2/CustomExtensionActivity2.java @@ -1,6 +1,5 @@ package io.noties.markwon.sample.customextension2; -import android.app.Activity; import android.os.Bundle; import android.widget.TextView; @@ -25,34 +24,45 @@ import io.noties.markwon.core.CorePlugin; import io.noties.markwon.core.CoreProps; import io.noties.markwon.inlineparser.InlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParser; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; -public class CustomExtensionActivity2 extends Activity { +public class CustomExtensionActivity2 extends ActivityWithMenuOptions { + + private static final String MD = "" + + "# Custom Extension 2\n" + + "\n" + + "This is an issue #1\n" + + "Done by @noties"; + + private TextView textView; + + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("text_added", this::text_added) + .add("inline_parsing", this::inline_parsing); + } @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); // let's look for github special links: // * `#1` - an issue or a pull request // * `@user` link to a user - - final String md = "# Custom Extension 2\n" + - "\n" + - "This is an issue #1\n" + - "Done by @noties"; - - // inline_parsing(textView, md); - text_added(textView, md); + text_added(); } - private void text_added(@NonNull TextView textView, @NonNull String md) { + private void text_added() { final Markwon markwon = Markwon.builder(this) .usePlugin(new AbstractMarkwonPlugin() { @@ -64,10 +74,10 @@ public class CustomExtensionActivity2 extends Activity { }) .build(); - markwon.setMarkdown(textView, md); + markwon.setMarkdown(textView, MD); } - private void inline_parsing(@NonNull TextView textView, @NonNull String md) { + private void inline_parsing() { final InlineParserFactory inlineParserFactory = MarkwonInlineParser.factoryBuilder() // include all current defaults (otherwise will be empty - contain only our inline-processors) @@ -86,7 +96,7 @@ public class CustomExtensionActivity2 extends Activity { }) .build(); - markwon.setMarkdown(textView, md); + markwon.setMarkdown(textView, MD); } private static class IssueInlineProcessor extends InlineProcessor { diff --git a/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java b/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java index 27d069eb..833a63b1 100644 --- a/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java @@ -1,6 +1,5 @@ package io.noties.markwon.sample.inlineparser; -import android.app.Activity; import android.os.Bundle; import android.widget.TextView; @@ -26,18 +25,28 @@ import io.noties.markwon.inlineparser.BackticksInlineProcessor; import io.noties.markwon.inlineparser.CloseBracketInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParser; import io.noties.markwon.inlineparser.OpenBracketInlineProcessor; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; -public class InlineParserActivity extends Activity { +public class InlineParserActivity extends ActivityWithMenuOptions { private TextView textView; + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("links_only", this::links_only) + .add("disable_code", this::disable_code); + } + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_text_view); - this.textView = findViewById(R.id.text_view); + textView = findViewById(R.id.text_view); // links_only(); From cc35c355813b65b7dfcbc973a70982f6ee6900b2 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 17:29:49 +0300 Subject: [PATCH 10/33] Update CHANGELOG for upcoming 4.3.0 version --- CHANGELOG.md | 11 ++++++++ .../ext/latex/JLatexMathBlockParser.java | 2 +- .../latex/JLatexMathBlockParserLegacy.java | 2 +- .../ext/latex/JLatexMathInlineProcessor.java | 2 +- .../markwon/sample/latex/LatexActivity.java | 28 +++++++++---------- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4982ffd4..95c04c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,18 @@ # Changelog # 4.3.0-SNAPSHOT +* add `MarkwonInlineParserPlugin` in `inline-parser` module +* `JLatexMathPlugin` now supports both inline and block structures + this comes with a breaking change: `JLatexMathPlugin` now depends on `inline-parser` module and `MarkwonInlineParserPlugin` must be explicitly added to a `Markwon` instance: + ```java + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textSize)) + .build(); + ``` +* `JLatexMathPlugin`: add `theme` (to customize both inlines and blocks) +* `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering # 4.2.2 * Fixed `AsyncDrawable` display when it has placeholder with empty bounds ([#189]) 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 ef65bb15..8d4b4d37 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 @@ -15,7 +15,7 @@ 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 { +class JLatexMathBlockParser extends AbstractBlockParser { private static final char DOLLAR = '$'; private static final char SPACE = ' '; 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 index 1a5ce282..2506ca21 100644 --- 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 @@ -11,7 +11,7 @@ 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 { +class JLatexMathBlockParserLegacy extends AbstractBlockParser { private final JLatexMathBlock block = new JLatexMathBlock(); 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 index 84c26b4f..364e8d54 100644 --- 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 @@ -11,7 +11,7 @@ import io.noties.markwon.inlineparser.InlineProcessor; /** * @since 4.3.0-SNAPSHOT */ -public class JLatexMathInlineProcessor extends InlineProcessor { +class JLatexMathInlineProcessor extends InlineProcessor { private static final Pattern RE = Pattern.compile("(\\${2})([\\s\\S]+?)\\1"); 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 8dcdcd11..48f052c2 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 @@ -127,22 +127,22 @@ public class LatexActivity extends ActivityWithMenuOptions { final float textSize = textView.getTextSize(); 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)) - .blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000)) - .blockPadding(JLatexMathTheme.Padding.symmetric( - r.getDimensionPixelSize(R.dimen.latex_block_padding_vertical), - r.getDimensionPixelSize(R.dimen.latex_block_padding_horizontal) - )); +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)) + .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 + // explicitly request LEGACY rendering mode // builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY); - })) - .build(); + })) + .build(); markwon.setMarkdown(textView, markdown); } From 9532d32e8db9a81b2d86ea51a0cc4d70fc93cf4d Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 17:35:58 +0300 Subject: [PATCH 11/33] Add SoftBreakAddsNewLinePlugin plugin in core module --- CHANGELOG.md | 2 ++ .../markwon/SoftBreakAddsNewLinePlugin.java | 26 +++++++++++++++++ .../basicplugins/BasicPluginsActivity.java | 29 +++++++++++++++++-- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c04c1f..047ae4d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ ``` * `JLatexMathPlugin`: add `theme` (to customize both inlines and blocks) * `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering +* add `SoftBreakAddsNewLinePlugin` plugin (`core` module) + # 4.2.2 * Fixed `AsyncDrawable` display when it has placeholder with empty bounds ([#189]) diff --git a/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java new file mode 100644 index 00000000..6f49ac68 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java @@ -0,0 +1,26 @@ +package io.noties.markwon; + +import androidx.annotation.NonNull; + +import org.commonmark.node.SoftLineBreak; + +/** + * @since 4.3.0-SNAPSHOT + */ +public class SoftBreakAddsNewLinePlugin extends AbstractMarkwonPlugin { + + @NonNull + public static SoftBreakAddsNewLinePlugin create() { + return new SoftBreakAddsNewLinePlugin(); + } + + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { + visitor.forceNewLine(); + } + }); + } +} diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index 7fe69cd3..49703e72 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -21,6 +21,7 @@ import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.SoftBreakAddsNewLinePlugin; import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.image.ImageItem; import io.noties.markwon.image.ImagesPlugin; @@ -35,15 +36,17 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { private TextView textView; - @NonNull @Override public MenuOptions menuOptions() { return MenuOptions.create() .add("paragraphSpan", this::paragraphSpan) .add("disableNode", this::disableNode) + .add("customizeTheme", this::customizeTheme) .add("linkWithMovementMethod", this::linkWithMovementMethod) - .add("imagesPlugin", this::imagesPlugin); + .add("imagesPlugin", this::imagesPlugin) + .add("softBreakAddsSpace", this::softBreakAddsSpace) + .add("softBreakAddsNewLine", this::softBreakAddsNewLine); } @Override @@ -217,6 +220,28 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, markdown); } + private void softBreakAddsSpace() { + // default behavior + + final String md = "" + + "Hello there ->(line)\n(break)<- going on and on"; + + Markwon.create(this).setMarkdown(textView, md); + } + + private void softBreakAddsNewLine() { + // insert a new line when markdown has a soft break + + final Markwon markwon = Markwon.builder(this) + .usePlugin(SoftBreakAddsNewLinePlugin.create()) + .build(); + + final String md = "" + + "Hello there ->(line)\n(break)<- going on and on"; + + markwon.setMarkdown(textView, md); + } + // public void step_6() { // // final Markwon markwon = Markwon.builder(this) From 823c26448a0f277386772bbb9a7c7b3ebf1d7743 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Feb 2020 17:37:33 +0300 Subject: [PATCH 12/33] Clean up README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 350479bf..594d9781 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ [![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** From d7f52607ab711e653c79f0ea89dd26cec4597aad Mon Sep 17 00:00:00 2001 From: Drakeet Date: Sat, 29 Feb 2020 20:50:30 +0800 Subject: [PATCH 13/33] Allow LinkifyPlugin to use LinkifyCompat through config --- markwon-linkify/build.gradle | 8 ++++- .../noties/markwon/linkify/LinkifyPlugin.java | 36 +++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/markwon-linkify/build.gradle b/markwon-linkify/build.gradle index 764113ab..39f8eecf 100644 --- a/markwon-linkify/build.gradle +++ b/markwon-linkify/build.gradle @@ -14,7 +14,13 @@ android { } dependencies { + deps.with { + // To use LinkifyCompat + // note that this dependency must be added on a client side explicitly + compileOnly it['x-core'] + } + api project(':markwon-core') } -registerArtifact(this) \ No newline at end of file +registerArtifact(this) diff --git a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java index cb5c889e..b1bf826a 100644 --- a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java +++ b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java @@ -1,11 +1,13 @@ package io.noties.markwon.linkify; +import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.URLSpan; import android.text.util.Linkify; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.core.text.util.LinkifyCompat; import org.commonmark.node.Link; @@ -33,19 +35,31 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { @NonNull public static LinkifyPlugin create() { - return create(Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS); + return create(false); + } + + @NonNull + public static LinkifyPlugin create(boolean useCompat) { + return create(Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS, useCompat); } @NonNull public static LinkifyPlugin create(@LinkifyMask int mask) { - return new LinkifyPlugin(mask); + return new LinkifyPlugin(mask, false); + } + + @NonNull + public static LinkifyPlugin create(@LinkifyMask int mask, boolean useCompat) { + return new LinkifyPlugin(mask, useCompat); } private final int mask; + private final boolean useCompat; @SuppressWarnings("WeakerAccess") - LinkifyPlugin(@LinkifyMask int mask) { + LinkifyPlugin(@LinkifyMask int mask, boolean useCompat) { this.mask = mask; + this.useCompat = useCompat; } @Override @@ -53,7 +67,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { registry.require(CorePlugin.class, new Action() { @Override public void apply(@NonNull CorePlugin corePlugin) { - corePlugin.addOnTextAddedListener(new LinkifyTextAddedListener(mask)); + corePlugin.addOnTextAddedListener(new LinkifyTextAddedListener(mask, useCompat)); } }); } @@ -61,9 +75,11 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { private static class LinkifyTextAddedListener implements CorePlugin.OnTextAddedListener { private final int mask; + private final boolean useCompat; - LinkifyTextAddedListener(int mask) { + LinkifyTextAddedListener(int mask, boolean useCompat) { this.mask = mask; + this.useCompat = useCompat; } @Override @@ -80,7 +96,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { // render calls from different threads and ... better performance) final SpannableStringBuilder builder = new SpannableStringBuilder(text); - if (Linkify.addLinks(builder, mask)) { + if (addLinks(builder, mask)) { // target URL span specifically final URLSpan[] spans = builder.getSpans(0, builder.length(), URLSpan.class); if (spans != null @@ -101,5 +117,13 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { } } } + + private boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) { + if (useCompat) { + return LinkifyCompat.addLinks(text, mask); + } else { + return Linkify.addLinks(text, mask); + } + } } } From f887cb132bd6ee3be43e1a7ebfee52c3ac113f3a Mon Sep 17 00:00:00 2001 From: Drakeet Date: Sat, 29 Feb 2020 21:05:42 +0800 Subject: [PATCH 14/33] Add java doc notes for the useCompat --- .../java/io/noties/markwon/linkify/LinkifyPlugin.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java index b1bf826a..47347ed3 100644 --- a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java +++ b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java @@ -38,6 +38,11 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { return create(false); } + /** + * @param useCompat If true, use {@link LinkifyCompat} to handle links. + * Note that the {@link LinkifyCompat} depends on androidx.core:core, + * the dependency must be added on a client side explicitly. + */ @NonNull public static LinkifyPlugin create(boolean useCompat) { return create(Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS, useCompat); @@ -48,6 +53,11 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { return new LinkifyPlugin(mask, false); } + /** + * @param useCompat If true, use {@link LinkifyCompat} to handle links. + * Note that the {@link LinkifyCompat} depends on androidx.core:core, + * the dependency must be added on a client side explicitly. + */ @NonNull public static LinkifyPlugin create(@LinkifyMask int mask, boolean useCompat) { return new LinkifyPlugin(mask, useCompat); From 3068cb6987d5f022778565a8c9e714cbc936f646 Mon Sep 17 00:00:00 2001 From: Drakeet Date: Sat, 29 Feb 2020 21:21:15 +0800 Subject: [PATCH 15/33] Add Pure Writer to list of awesome section --- .../.vuepress/public/assets/apps/purewriter.png | Bin 0 -> 40606 bytes docs/README.md | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/.vuepress/public/assets/apps/purewriter.png diff --git a/docs/.vuepress/public/assets/apps/purewriter.png b/docs/.vuepress/public/assets/apps/purewriter.png new file mode 100644 index 0000000000000000000000000000000000000000..aab5623dbd8b157351aad0dd78d436c6919c42c5 GIT binary patch literal 40606 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 literal 0 HcmV?d00001 diff --git a/docs/README.md b/docs/README.md index 82143911..3b80abb4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -109,7 +109,8 @@ and 2 themes included: Light & Dark. It can be downloaded from [releases](ht From 3ac21a7ab3aa6b04526af643bafffee634bd3295 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 2 Mar 2020 09:26:44 +0300 Subject: [PATCH 16/33] Clarify CHANGELOG for LinkResolver rename --- CHANGELOG.md | 2 +- docs/docs/v4/core/core-plugin.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047ae4d0..ca948871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,7 +100,7 @@ use `Markwon#builderNoCore()` to obtain a builder without `CorePlugin` * Added `MarkwonPlugin.Registry` and `MarkwonPlugin#configure(Registry)` method * `CorePlugin#addOnTextAddedListener` (process raw text added) * `ImageSizeResolver` signature change (accept `AsyncDrawable`) -* `LinkResolver` is now an independent entity (previously part of `LinkSpan`) +* `LinkResolver` is now an independent entity (previously part of the `LinkSpan`), `LinkSpan.Resolver` -> `LinkResolver` * `AsyncDrawableScheduler` can now be called multiple times without performance penalty * `AsyncDrawable` now exposes its destination, image-size, last known dimensions (canvas, text-size) * `AsyncDrawableLoader` signature change (accept `AsyncDrawable`) diff --git a/docs/docs/v4/core/core-plugin.md b/docs/docs/v4/core/core-plugin.md index a5002ba8..7538ad1e 100644 --- a/docs/docs/v4/core/core-plugin.md +++ b/docs/docs/v4/core/core-plugin.md @@ -81,10 +81,15 @@ More information about props can be found [here](/docs/v4/core/render-props.md) --- :::tip Soft line break -Since Markwon core does not give an option to -insert a new line when there is a soft line break in markdown. Instead a -custom plugin can be used: +Since there is a dedicated plugin to insert a new line for +markdown soft breaks - `SoftBreakAddsNewLinePlugin`: +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(SoftBreakAddsNewLinePlugin.create()) + .build(); +``` +It is still possible to do it manually with a custom visitor: ```java final Markwon markwon = Markwon.builder(this) .usePlugin(new AbstractMarkwonPlugin() { From c98f4567440e365aac9721620edac1612eb15def Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 2 Mar 2020 09:54:58 +0300 Subject: [PATCH 17/33] LinkResolverDef defaults to https when no scheme is available --- CHANGELOG.md | 1 + .../io/noties/markwon/LinkResolverDef.java | 21 ++++- .../noties/markwon/LinkResolverDefTest.java | 79 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 markwon-core/src/test/java/io/noties/markwon/LinkResolverDefTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ca948871..64de0ca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * `JLatexMathPlugin`: add `theme` (to customize both inlines and blocks) * `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering * add `SoftBreakAddsNewLinePlugin` plugin (`core` module) +* `LinkResolverDef` defaults to `https` when a link does not have scheme information # 4.2.2 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 f61ad21f..44c5da9c 100644 --- a/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java +++ b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java @@ -5,15 +5,20 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.provider.Browser; +import android.text.TextUtils; import android.util.Log; import android.view.View; import androidx.annotation.NonNull; public class LinkResolverDef implements LinkResolver { + + // @since 4.3.0-SNAPSHOT + private static final String DEFAULT_SCHEME = "https"; + @Override public void resolve(@NonNull View view, @NonNull String link) { - final Uri uri = Uri.parse(link); + final Uri uri = parseLink(link); final Context context = view.getContext(); final Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); @@ -23,4 +28,18 @@ public class LinkResolverDef implements LinkResolver { Log.w("LinkResolverDef", "Actvity was not found for the link: '" + link + "'"); } } + + /** + * @since 4.3.0-SNAPSHOT + */ + @NonNull + private static Uri parseLink(@NonNull String link) { + final Uri uri = Uri.parse(link); + if (TextUtils.isEmpty(uri.getScheme())) { + return uri.buildUpon() + .scheme(DEFAULT_SCHEME) + .build(); + } + return uri; + } } diff --git a/markwon-core/src/test/java/io/noties/markwon/LinkResolverDefTest.java b/markwon-core/src/test/java/io/noties/markwon/LinkResolverDefTest.java new file mode 100644 index 00000000..eb19d933 --- /dev/null +++ b/markwon-core/src/test/java/io/noties/markwon/LinkResolverDefTest.java @@ -0,0 +1,79 @@ +package io.noties.markwon; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.view.View; + +import androidx.annotation.NonNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class LinkResolverDefTest { + + @Test + public void no_scheme_https() { + // when supplied url doesn't have scheme fallback to `https` + + // must be => `https://www.markw.on + final String link = "www.markw.on"; + + final Uri uri = resolve(link); + + final String scheme = uri.getScheme(); + assertNotNull(uri.toString(), scheme); + + assertEquals(uri.toString(), "https", scheme); + } + + @Test + public void scheme_present() { + // when scheme is present, it won't be touched + + final String link = "whatnot://hey/ho"; + + final Uri uri = resolve(link); + + final String scheme = uri.getScheme(); + assertEquals(uri.toString(), "whatnot", scheme); + + assertEquals(Uri.parse(link), uri); + } + + // we could call `parseLink` directly, but this doesn't mean LinkResolverDef uses it + @NonNull + private Uri resolve(@NonNull String link) { + final View view = mock(View.class); + final Context context = mock(Context.class); + when(view.getContext()).thenReturn(context); + + final LinkResolverDef def = new LinkResolverDef(); + def.resolve(view, link); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + + verify(context, times(1)) + .startActivity(captor.capture()); + + final Intent intent = captor.getValue(); + assertNotNull(intent); + + final Uri uri = intent.getData(); + assertNotNull(uri); + + return uri; + } +} \ No newline at end of file From a1f12641c3b93c141c13dd72a9816097b077d26d Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 2 Mar 2020 10:22:04 +0300 Subject: [PATCH 18/33] JLatexMathPlugin add error handling --- CHANGELOG.md | 6 +- .../markwon/ext/latex/JLatexMathPlugin.java | 83 +++++++++++++++---- .../markwon/sample/latex/LatexActivity.java | 53 ++++++++---- 3 files changed, 110 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64de0ca5..b6983a78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,12 @@ ``` * `JLatexMathPlugin`: add `theme` (to customize both inlines and blocks) * `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering +* add `JLatexMathPlugin.ErrorHandler` to catch latex rendering errors and (optionally) display error drawable ([#204]) * add `SoftBreakAddsNewLinePlugin` plugin (`core` module) -* `LinkResolverDef` defaults to `https` when a link does not have scheme information +* `LinkResolverDef` defaults to `https` when a link does not have scheme information ([#75]) + +[#75]: https://github.com/noties/Markwon/issues/75 +[#204]: https://github.com/noties/Markwon/issues/204 # 4.2.2 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 5a3d70b9..e83f3bf6 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 @@ -29,6 +29,7 @@ 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.DrawableUtils; import io.noties.markwon.image.ImageSizeResolver; import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import ru.noties.jlatexmath.JLatexMathDrawable; @@ -62,6 +63,22 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { BLOCKS_AND_INLINES } + /** + * @since 4.3.0-SNAPSHOT + */ + public interface ErrorHandler { + + /** + * @param latex that caused the error or null if operated `AsyncDrawable` + * is not an instance of `JLatexAsyncDrawable` + * @param error occurred + * @return (optional) error drawable that will be used instead (if drawable will have bounds + * it will be used, if not intrinsic bounds will be set) + */ + @Nullable + Drawable handleError(@Nullable String latex, @NonNull Throwable error); + } + public interface BuilderConfigure { void configureBuilder(@NonNull Builder builder); } @@ -125,11 +142,15 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // @since 4.3.0-SNAPSHOT private final RenderMode renderMode; + // @since 4.3.0-SNAPSHOT + private final ErrorHandler errorHandler; + private final ExecutorService executorService; Config(@NonNull Builder builder) { this.theme = builder.theme.build(); this.renderMode = builder.renderMode; + this.errorHandler = builder.errorHandler; // @since 4.0.0 ExecutorService executorService = builder.executorService; if (executorService == null) { @@ -284,6 +305,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // @since 4.3.0-SNAPSHOT private RenderMode renderMode = RenderMode.BLOCKS_AND_INLINES; + // @since 4.3.0-SNAPSHOT + private ErrorHandler errorHandler; + // @since 4.0.0 private ExecutorService executorService; @@ -305,6 +329,12 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return this; } + @NonNull + public Builder errorHandler(@Nullable ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + return this; + } + /** * @since 4.0.0 */ @@ -351,10 +381,24 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { try { execute(); } catch (Throwable t) { - Log.e( - "JLatexMathPlugin", - "Error displaying latex: `" + drawable.getDestination() + "`", - t); + // @since 4.3.0-SNAPSHOT add error handling + final ErrorHandler errorHandler = config.errorHandler; + if (errorHandler == null) { + // as before + Log.e( + "JLatexMathPlugin", + "Error displaying latex: `" + drawable.getDestination() + "`", + t); + } else { + final Drawable errorDrawable = errorHandler.handleError( + drawable.getDestination(), + t + ); + if (errorDrawable != null) { + DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable); + setResult(drawable, errorDrawable); + } + } } } @@ -370,19 +414,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { 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) - handler.postAtTime(new Runnable() { - @Override - public void run() { - // remove entry from cache (it will be present if task is not cancelled) - if (cache.remove(drawable) != null - && drawable.isAttached()) { - drawable.setResult(jLatexMathDrawable); - } - - } - }, drawable, SystemClock.uptimeMillis()); + setResult(drawable, jLatexMathDrawable); } })); } @@ -456,6 +488,23 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return builder.build(); } + + // @since 4.3.0-SNAPSHOT + private void setResult(@NonNull final AsyncDrawable drawable, @NonNull final Drawable result) { + // we must post to handler, but also have a way to identify the drawable + // for which we are posting (in case of cancellation) + handler.postAtTime(new Runnable() { + @Override + public void run() { + // remove entry from cache (it will be present if task is not cancelled) + if (cache.remove(drawable) != null + && drawable.isAttached()) { + drawable.setResult(result); + } + + } + }, drawable, SystemClock.uptimeMillis()); + } } private static class InlineImageSizeResolver extends ImageSizeResolver { 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 48f052c2..24d87da3 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 @@ -2,12 +2,15 @@ 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; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import io.noties.debug.Debug; import io.noties.markwon.Markwon; import io.noties.markwon.ext.latex.JLatexMathPlugin; import io.noties.markwon.ext.latex.JLatexMathTheme; @@ -57,6 +60,7 @@ public class LatexActivity extends ActivityWithMenuOptions { .add("bangle", this::bangle) .add("boxes", this::boxes) .add("insideBlockQuote", this::insideBlockQuote) + .add("error", this::error) .add("legacy", this::legacy); } @@ -95,6 +99,27 @@ public class LatexActivity extends ActivityWithMenuOptions { render(md); } + private void error() { + final String md = wrapLatexInSampleMarkdown("\\sum_{i=0}^\\infty x \\cdot 0 \\rightarrow \\iMightNotExist{0}"); + + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + //noinspection Convert2Lambda + builder.errorHandler(new JLatexMathPlugin.ErrorHandler() { + @Nullable + @Override + public Drawable handleError(@Nullable String latex, @NonNull Throwable error) { + Debug.e(error, latex); + return ContextCompat.getDrawable(LatexActivity.this, R.drawable.ic_android_black_24dp); + } + }); + })) + .build(); + + markwon.setMarkdown(textView, md); + } + private void legacy() { final String md = wrapLatexInSampleMarkdown(LATEX_BANGLE); @@ -127,22 +152,22 @@ public class LatexActivity extends ActivityWithMenuOptions { final float textSize = textView.getTextSize(); 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)) - .blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000)) - .blockPadding(JLatexMathTheme.Padding.symmetric( - r.getDimensionPixelSize(R.dimen.latex_block_padding_vertical), - r.getDimensionPixelSize(R.dimen.latex_block_padding_horizontal) - )); + 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)) + .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 + // explicitly request LEGACY rendering mode // builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY); - })) - .build(); + })) + .build(); markwon.setMarkdown(textView, markdown); } From 5c3763a9a1cc5cdb9b80994258af850eb3f51a8b Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 2 Mar 2020 10:46:18 +0300 Subject: [PATCH 19/33] JLatexMathPlugin error handler non-null latex argument --- .../io/noties/markwon/ext/latex/JLatexMathPlugin.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 e83f3bf6..11207041 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 @@ -69,14 +69,13 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { public interface ErrorHandler { /** - * @param latex that caused the error or null if operated `AsyncDrawable` - * is not an instance of `JLatexAsyncDrawable` + * @param latex that caused the error * @param error occurred * @return (optional) error drawable that will be used instead (if drawable will have bounds * it will be used, if not intrinsic bounds will be set) */ @Nullable - Drawable handleError(@Nullable String latex, @NonNull Throwable error); + Drawable handleError(@NonNull String latex, @NonNull Throwable error); } public interface BuilderConfigure { @@ -323,12 +322,14 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { /** * @since 4.3.0-SNAPSHOT */ + @SuppressWarnings("UnusedReturnValue") @NonNull public Builder renderMode(@NonNull RenderMode renderMode) { this.renderMode = renderMode; return this; } + @SuppressWarnings("UnusedReturnValue") @NonNull public Builder errorHandler(@Nullable ErrorHandler errorHandler) { this.errorHandler = errorHandler; @@ -338,6 +339,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { /** * @since 4.0.0 */ + @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"}) @NonNull public Builder executorService(@NonNull ExecutorService executorService) { this.executorService = executorService; @@ -390,6 +392,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { "Error displaying latex: `" + drawable.getDestination() + "`", t); } else { + // just call `getDestination` without casts and checks final Drawable errorDrawable = errorHandler.handleError( drawable.getDestination(), t From b047f8131b95e9912761e7d39dadc87ce2a36a17 Mon Sep 17 00:00:00 2001 From: Drakeet Date: Wed, 4 Mar 2020 23:05:40 +0800 Subject: [PATCH 20/33] LinkifyCompatTextAddedListener --- .../noties/markwon/linkify/LinkifyPlugin.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java index 47347ed3..07fdf4db 100644 --- a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java +++ b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java @@ -77,7 +77,13 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { registry.require(CorePlugin.class, new Action() { @Override public void apply(@NonNull CorePlugin corePlugin) { - corePlugin.addOnTextAddedListener(new LinkifyTextAddedListener(mask, useCompat)); + final LinkifyTextAddedListener listener; + if (useCompat) { + listener = new LinkifyCompatTextAddedListener(mask); + } else { + listener = new LinkifyTextAddedListener(mask); + } + corePlugin.addOnTextAddedListener(listener); } }); } @@ -85,11 +91,9 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { private static class LinkifyTextAddedListener implements CorePlugin.OnTextAddedListener { private final int mask; - private final boolean useCompat; - LinkifyTextAddedListener(int mask, boolean useCompat) { + LinkifyTextAddedListener(int mask) { this.mask = mask; - this.useCompat = useCompat; } @Override @@ -128,12 +132,20 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { } } - private boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) { - if (useCompat) { - return LinkifyCompat.addLinks(text, mask); - } else { - return Linkify.addLinks(text, mask); - } + protected boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) { + return Linkify.addLinks(text, mask); + } + } + + private static class LinkifyCompatTextAddedListener extends LinkifyTextAddedListener { + + LinkifyCompatTextAddedListener(int mask) { + super(mask); + } + + @Override + protected boolean addLinks(@NonNull Spannable text, int mask) { + return LinkifyCompat.addLinks(text, mask); } } } From 8c04748597a7040167be8e77bfa161634b06eab4 Mon Sep 17 00:00:00 2001 From: Drakeet Date: Wed, 4 Mar 2020 23:10:31 +0800 Subject: [PATCH 21/33] Small change: add @LinkifyMask to LinkifyCompatTextAddedListener --- .../src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java index 07fdf4db..a910d970 100644 --- a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java +++ b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java @@ -144,7 +144,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { } @Override - protected boolean addLinks(@NonNull Spannable text, int mask) { + protected boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) { return LinkifyCompat.addLinks(text, mask); } } From db660d2a4026b963f056c5cea9df4ac695b1d6bc Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sat, 7 Mar 2020 14:14:10 +0300 Subject: [PATCH 22/33] latex block parsing tests --- CHANGELOG.md | 1 + .../markwon/ext/latex/JLatexMathPlugin.java | 15 +- .../ext/latex/JLatexMathBlockParserTest.java | 173 ++++++++++++++++++ .../ext/latex/JLatexMathPluginTest.java | 109 +++++++++++ .../basicplugins/BasicPluginsActivity.java | 72 +++++++- 5 files changed, 362 insertions(+), 8 deletions(-) create mode 100644 markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathBlockParserTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b6983a78..f7410eca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * add `JLatexMathPlugin.ErrorHandler` to catch latex rendering errors and (optionally) display error drawable ([#204]) * add `SoftBreakAddsNewLinePlugin` plugin (`core` module) * `LinkResolverDef` defaults to `https` when a link does not have scheme information ([#75]) +* add `option` abstraction for `sample` module allowing switching of multiple cases in runtime via menu [#75]: https://github.com/noties/Markwon/issues/75 [#204]: https://github.com/noties/Markwon/issues/204 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 11207041..02bb618e 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 @@ -133,18 +133,19 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return new Builder(JLatexMathTheme.builder(inlineTextSize, blockTextSize)); } - public static class Config { + @VisibleForTesting + static class Config { // @since 4.3.0-SNAPSHOT - private final JLatexMathTheme theme; + final JLatexMathTheme theme; // @since 4.3.0-SNAPSHOT - private final RenderMode renderMode; + final RenderMode renderMode; // @since 4.3.0-SNAPSHOT - private final ErrorHandler errorHandler; + final ErrorHandler errorHandler; - private final ExecutorService executorService; + final ExecutorService executorService; Config(@NonNull Builder builder) { this.theme = builder.theme.build(); @@ -159,7 +160,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } } - private final Config config; + @VisibleForTesting + final Config config; + private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader; private final JLatexBlockImageSizeResolver jLatexBlockImageSizeResolver; private final ImageSizeResolver inlineImageSizeResolver; diff --git a/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathBlockParserTest.java b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathBlockParserTest.java new file mode 100644 index 00000000..b1587e1d --- /dev/null +++ b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathBlockParserTest.java @@ -0,0 +1,173 @@ +package io.noties.markwon.ext.latex; + +import androidx.annotation.NonNull; + +import org.commonmark.internal.BlockContinueImpl; +import org.commonmark.internal.BlockStartImpl; +import org.commonmark.internal.util.Parsing; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.ParserState; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class JLatexMathBlockParserTest { + + private static final String[] NO_MATCH = { + " ", + " ", + " ", + "$ ", + " $ $", + "-$$", + " -$$", + "$$-", + " $$ -", + " $$ -", + "$$$ -" + }; + + private static final String[] MATCH = { + "$$", + " $$", + " $$", + " $$", + "$$ ", + " $$ ", + " $$ ", + " $$ ", + "$$$", + " $$$", + " $$$", + "$$$$", + " $$$$", + "$$$$$$$$$$$$$$$$$$$$$", + " $$$$$$$$$$$$$$$$$$$$$", + " $$$$$$$$$$$$$$$$$$$$$", + " $$$$$$$$$$$$$$$$$$$$$" + }; + + private JLatexMathBlockParser.Factory factory; + + @Before + public void before() { + factory = new JLatexMathBlockParser.Factory(); + } + + @Test + public void factory_indentBlock() { + // when state indent is greater than block -> nono + + final ParserState state = mock(ParserState.class); + when(state.getIndent()).thenReturn(Parsing.CODE_BLOCK_INDENT); + + // hm, interesting, `BlockStart.none()` actually returns null + final BlockStart start = factory.tryStart(state, null); + assertNull(start); + } + + @Test + public void factory_noMatch() { + + for (String line : NO_MATCH) { + final ParserState state = createState(line); + + assertNull(factory.tryStart(state, null)); + } + } + + @Test + public void factory_match() { + + for (String line : MATCH) { + final ParserState state = createState(line); + + final BlockStart start = factory.tryStart(state, null); + assertNotNull(start); + + // hm... + final BlockStartImpl impl = (BlockStartImpl) start; + assertEquals(quote(line), line.length() + 1, impl.getNewIndex()); + } + } + + @Test + public void finish() { + + for (String line : MATCH) { + final ParserState state = createState(line); + + // we will have 2 checks here: + // * must pass for correct length + // * must fail for incorrect + + final int count = countDollarSigns(line); + + // pass + { + final JLatexMathBlockParser parser = new JLatexMathBlockParser(count); + final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state); + assertTrue(quote(line), impl.isFinalize()); + } + + // fail (in terms of closing, not failing test) + { + final JLatexMathBlockParser parser = new JLatexMathBlockParser(count + 1); + final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state); + assertFalse(quote(line), impl.isFinalize()); + } + } + } + + @Test + public void finish_noMatch() { + for (String line : NO_MATCH) { + final ParserState state = createState(line); + // doesn't matter + final int count = 2; + final JLatexMathBlockParser parser = new JLatexMathBlockParser(count); + final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state); + assertFalse(quote(line), impl.isFinalize()); + } + } + + @NonNull + private static ParserState createState(@NonNull String line) { + + final ParserState state = mock(ParserState.class); + + int i = 0; + for (int length = line.length(); i < length; i++) { + if (' ' != line.charAt(i)) { + // previous is the last space + break; + } + } + + when(state.getIndent()).thenReturn(i); + when(state.getNextNonSpaceIndex()).thenReturn(i); + when(state.getLine()).thenReturn(line); + + return state; + } + + private static int countDollarSigns(@NonNull String line) { + int count = 0; + for (int i = 0, length = line.length(); i < length; i++) { + if ('$' == line.charAt(i)) count += 1; + } + return count; + } + + @NonNull + private static String quote(@NonNull String s) { + return '\'' + s + '\''; + } +} \ No newline at end of file diff --git a/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java index 8190a470..57584168 100644 --- a/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java +++ b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java @@ -10,17 +10,24 @@ import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.List; import java.util.concurrent.ExecutorService; import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonPlugin; import io.noties.markwon.MarkwonVisitor; import io.noties.markwon.SpannableBuilder; +import io.noties.markwon.inlineparser.InlineProcessor; +import io.noties.markwon.inlineparser.MarkwonInlineParser; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -110,4 +117,106 @@ public class JLatexMathPluginTest { verify(visitor, times(1)).setSpans(eq(0), any()); } + + @Test + public void legacy() { + // if render mode is legacy: + // - no inline plugin is required, + // - parser has legacy block parser factory + // - no inline node is registered (node) + + final JLatexMathPlugin plugin = JLatexMathPlugin.create(1, new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY); + } + }); + + // registry + { + final MarkwonPlugin.Registry registry = mock(MarkwonPlugin.Registry.class); + plugin.configure(registry); + verify(registry, never()).require(any(Class.class)); + } + + // parser + { + final Parser.Builder builder = mock(Parser.Builder.class); + plugin.configureParser(builder); + + final ArgumentCaptor captor = + ArgumentCaptor.forClass(BlockParserFactory.class); + verify(builder, times(1)).customBlockParserFactory(captor.capture()); + final BlockParserFactory factory = captor.getValue(); + assertTrue(factory.getClass().getName(), factory instanceof JLatexMathBlockParserLegacy.Factory); + } + + // visitor + { + final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class); + plugin.configureVisitor(builder); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + verify(builder, times(1)).on(captor.capture(), any(MarkwonVisitor.NodeVisitor.class)); + + assertEquals(JLatexMathBlock.class, captor.getValue()); + } + } + + @Test + public void blocks_inlines_implicit() { + final JLatexMathPlugin plugin = JLatexMathPlugin.create(1); + final JLatexMathPlugin.Config config = plugin.config; + assertEquals(JLatexMathPlugin.RenderMode.BLOCKS_AND_INLINES, config.renderMode); + } + + @Test + public void blocks_inlines() { + final JLatexMathPlugin plugin = JLatexMathPlugin.create(12); + + // registry + { + final MarkwonInlineParser.FactoryBuilder factoryBuilder = mock(MarkwonInlineParser.FactoryBuilder.class); + final MarkwonInlineParserPlugin inlineParserPlugin = mock(MarkwonInlineParserPlugin.class); + final MarkwonPlugin.Registry registry = mock(MarkwonPlugin.Registry.class); + when(inlineParserPlugin.factoryBuilder()).thenReturn(factoryBuilder); + when(registry.require(eq(MarkwonInlineParserPlugin.class))).thenReturn(inlineParserPlugin); + plugin.configure(registry); + + verify(registry, times(1)).require(eq(MarkwonInlineParserPlugin.class)); + verify(inlineParserPlugin, times(1)).factoryBuilder(); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(InlineProcessor.class); + verify(factoryBuilder, times(1)).addInlineProcessor(captor.capture()); + + final InlineProcessor inlineProcessor = captor.getValue(); + assertTrue(inlineParserPlugin.getClass().getName(), inlineProcessor instanceof JLatexMathInlineProcessor); + } + + // parser + { + final Parser.Builder builder = mock(Parser.Builder.class); + plugin.configureParser(builder); + + final ArgumentCaptor captor = + ArgumentCaptor.forClass(BlockParserFactory.class); + verify(builder, times(1)).customBlockParserFactory(captor.capture()); + final BlockParserFactory factory = captor.getValue(); + assertTrue(factory.getClass().getName(), factory instanceof JLatexMathBlockParser.Factory); + } + + // visitor + { + final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class); + plugin.configureVisitor(builder); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + verify(builder, times(2)).on(captor.capture(), any(MarkwonVisitor.NodeVisitor.class)); + + final List nodes = captor.getAllValues(); + assertEquals(2, nodes.size()); + assertTrue(nodes.toString(), nodes.contains(JLatexMathNode.class)); + assertTrue(nodes.toString(), nodes.contains(JLatexMathBlock.class)); + } + } } \ No newline at end of file diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index 49703e72..02878480 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -21,8 +21,12 @@ import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.RenderProps; import io.noties.markwon.SoftBreakAddsNewLinePlugin; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.CoreProps; import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.core.spans.LastLineSpacingSpan; import io.noties.markwon.image.ImageItem; import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.image.SchemeHandler; @@ -46,7 +50,9 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { .add("linkWithMovementMethod", this::linkWithMovementMethod) .add("imagesPlugin", this::imagesPlugin) .add("softBreakAddsSpace", this::softBreakAddsSpace) - .add("softBreakAddsNewLine", this::softBreakAddsNewLine); + .add("softBreakAddsNewLine", this::softBreakAddsNewLine) + .add("additionalSpacing", this::additionalSpacing) + .add("headingNoSpace", this::headingNoSpace); } @Override @@ -242,6 +248,69 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + private void additionalSpacing() { + + // please note that bottom line (after 1 & 2 levels) will be drawn _AFTER_ padding + final int spacing = (int) (128 * getResources().getDisplayMetrics().density + .5F); + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureTheme(@NonNull MarkwonTheme.Builder builder) { + builder.headingBreakHeight(0); + } + + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.appendFactory( + Heading.class, + (configuration, props) -> new LastLineSpacingSpan(spacing)); + } + }) + .build(); + + final String md = "" + + "# Title title title title title title title title title title \n\ntext text text text"; + + markwon.setMarkdown(textView, md); + } + + private void headingNoSpace() { + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureTheme(@NonNull MarkwonTheme.Builder builder) { + builder.headingBreakHeight(0); + } + + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Heading.class, (visitor, heading) -> { + + visitor.ensureNewLine(); + + final int length = visitor.length(); + visitor.visitChildren(heading); + + CoreProps.HEADING_LEVEL.set(visitor.renderProps(), heading.getLevel()); + + visitor.setSpansForNodeOptional(heading, length); + + if (visitor.hasNext(heading)) { + visitor.ensureNewLine(); +// visitor.forceNewLine(); + } + }); + } + }) + .build(); + + final String md = "" + + "# Title title title title title title title title title title \n\ntext text text text"; + + markwon.setMarkdown(textView, md); + } + // public void step_6() { // // final Markwon markwon = Markwon.builder(this) @@ -270,5 +339,4 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { // rendering lifecycle (before/after) // renderProps // process - // priority } From 12c7c8909bad20830c3841314ab22275b78b2b98 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 8 Mar 2020 12:45:31 +0300 Subject: [PATCH 23/33] Non empty bounds for AsyncDrawable when no dimensions available --- CHANGELOG.md | 2 + .../noties/markwon/image/AsyncDrawable.java | 23 +++++ sample/src/main/AndroidManifest.xml | 1 + .../noties/markwon/sample/MainActivity.java | 5 ++ .../java/io/noties/markwon/sample/Sample.java | 4 +- .../markwon/sample/images/ImagesActivity.java | 85 +++++++++++++++++++ .../markwon/sample/latex/LatexActivity.java | 17 +++- .../src/main/res/values/strings-samples.xml | 2 + 8 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f7410eca..b86d8953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ * add `SoftBreakAddsNewLinePlugin` plugin (`core` module) * `LinkResolverDef` defaults to `https` when a link does not have scheme information ([#75]) * add `option` abstraction for `sample` module allowing switching of multiple cases in runtime via menu +* non-empty bounds for `AsyncDrawable` when no dimensions are not yet available ([#189]) +[#189]: https://github.com/noties/Markwon/issues/189 [#75]: https://github.com/noties/Markwon/issues/75 [#204]: https://github.com/noties/Markwon/issues/204 diff --git a/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java index ad247238..935313d5 100644 --- a/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java @@ -223,6 +223,7 @@ public class AsyncDrawable extends Drawable { } this.result = result; +// this.result.setCallback(callback); initBounds(); } @@ -250,6 +251,10 @@ public class AsyncDrawable extends Drawable { if (canvasWidth == 0) { // we still have no bounds - wait for them waitingForDimensions = true; + + // we cannot have empty bounds - otherwise in case if text contains + // a single AsyncDrawableSpan, it won't be displayed + setBounds(noDimensionsBounds(result)); return; } @@ -268,6 +273,24 @@ public class AsyncDrawable extends Drawable { invalidateSelf(); } + /** + * @since 4.3.0-SNAPSHOT + */ + @NonNull + private static Rect noDimensionsBounds(@Nullable Drawable result) { + if (result != null) { + final Rect bounds = result.getBounds(); + if (!bounds.isEmpty()) { + return bounds; + } + final Rect intrinsicBounds = DrawableUtils.intrinsicBounds(result); + if (!intrinsicBounds.isEmpty()) { + return intrinsicBounds; + } + } + return new Rect(0, 0, 1, 1); + } + /** * @since 1.0.1 */ diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 0c02f47f..d3266265 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -36,6 +36,7 @@ + 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 a14a8183..a8391ce8 100644 --- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java @@ -25,6 +25,7 @@ import io.noties.markwon.sample.customextension2.CustomExtensionActivity2; import io.noties.markwon.sample.editor.EditorActivity; import io.noties.markwon.sample.html.HtmlActivity; import io.noties.markwon.sample.htmldetails.HtmlDetailsActivity; +import io.noties.markwon.sample.images.ImagesActivity; import io.noties.markwon.sample.inlineparser.InlineParserActivity; import io.noties.markwon.sample.latex.LatexActivity; import io.noties.markwon.sample.precomputed.PrecomputedActivity; @@ -137,6 +138,10 @@ public class MainActivity extends Activity { activity = TaskListActivity.class; break; + case IMAGES: + activity = ImagesActivity.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 f243c0ec..bf05297d 100644 --- a/sample/src/main/java/io/noties/markwon/sample/Sample.java +++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java @@ -29,7 +29,9 @@ public enum Sample { HTML_DETAILS(R.string.sample_html_details), - TASK_LIST(R.string.sample_task_list); + TASK_LIST(R.string.sample_task_list), + + IMAGES(R.string.sample_images); private final int textResId; diff --git a/sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java b/sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java new file mode 100644 index 00000000..dd4a44dc --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java @@ -0,0 +1,85 @@ +package io.noties.markwon.sample.images; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestBuilder; +import com.bumptech.glide.request.target.Target; + +import io.noties.markwon.Markwon; +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.glide.GlideImagesPlugin; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; +import io.noties.markwon.sample.R; + +public class ImagesActivity extends ActivityWithMenuOptions { + + private TextView textView; + + @NonNull + @Override + public MenuOptions menuOptions() { + // todo: same for other plugins + return MenuOptions.create() + .add("glide-singleImage", this::glideSingleImage) + .add("glide-singleImageWithPlaceholder", this::glideSingleImageWithPlaceholder); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_text_view); + textView = findViewById(R.id.text_view); + + glideSingleImageWithPlaceholder(); + } + + private void glideSingleImage() { + final String md = "[![undefined](https://img.youtube.com/vi/gs1I8_m4AOM/0.jpg)](https://www.youtube.com/watch?v=gs1I8_m4AOM)"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(GlideImagesPlugin.create(this)) + .build(); + + markwon.setMarkdown(textView, md); + } + + // can be checked when used first, otherwise works as expected... + private void glideSingleImageWithPlaceholder() { + final String md = "[![undefined](https://img.youtube.com/vi/gs1I8_m4AOM/0.jpg)](https://www.youtube.com/watch?v=gs1I8_m4AOM)"; + + final Context context = this; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(GlideImagesPlugin.create(new GlideImagesPlugin.GlideStore() { + @NonNull + @Override + public RequestBuilder load(@NonNull AsyncDrawable drawable) { +// final Drawable placeholder = ContextCompat.getDrawable(context, R.drawable.ic_home_black_36dp); +// placeholder.setBounds(0, 0, 100, 100); + return Glide.with(context) + .load(drawable.getDestination()) +// .placeholder(ContextCompat.getDrawable(context, R.drawable.ic_home_black_36dp)); +// .placeholder(placeholder); + .placeholder(R.drawable.ic_home_black_36dp); + } + + @Override + public void cancel(@NonNull Target target) { + Glide.with(context) + .clear(target); + } + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} 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 24d87da3..8a55a8ec 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 @@ -61,7 +61,8 @@ public class LatexActivity extends ActivityWithMenuOptions { .add("boxes", this::boxes) .add("insideBlockQuote", this::insideBlockQuote) .add("error", this::error) - .add("legacy", this::legacy); + .add("legacy", this::legacy) + .add("textColor", this::textColor); } @Override @@ -136,6 +137,20 @@ public class LatexActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + private void textColor() { + final String md = wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION); + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + + } + })) + .build(); + markwon.setMarkdown(textView, md); + } + @NonNull private static String wrapLatexInSampleMarkdown(@NonNull String latex) { return "" + diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml index d7f11e1a..d174e736 100644 --- a/sample/src/main/res/values/strings-samples.xml +++ b/sample/src/main/res/values/strings-samples.xml @@ -33,4 +33,6 @@ # \# TaskList\n\nUsage of TaskListPlugin + # \# Images\n\nUsage of different images plugins + \ No newline at end of file From a94090a7464b0f2fe33e8f85d92161f2bc50100d Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 8 Mar 2020 13:03:40 +0300 Subject: [PATCH 24/33] latex, expose text color customization --- CHANGELOG.md | 2 + .../markwon/ext/latex/JLatexMathPlugin.java | 10 ++++ .../markwon/ext/latex/JLatexMathTheme.java | 51 +++++++++++++++++++ .../markwon/sample/latex/LatexActivity.java | 12 ++--- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b86d8953..dc89ffae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,12 @@ * `LinkResolverDef` defaults to `https` when a link does not have scheme information ([#75]) * add `option` abstraction for `sample` module allowing switching of multiple cases in runtime via menu * non-empty bounds for `AsyncDrawable` when no dimensions are not yet available ([#189]) +* `JLatexMathPlugin` add text color customization ([#207]) [#189]: https://github.com/noties/Markwon/issues/189 [#75]: https://github.com/noties/Markwon/issues/75 [#204]: https://github.com/noties/Markwon/issues/204 +[#207]: https://github.com/noties/Markwon/issues/207 # 4.2.2 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 02bb618e..1403edb9 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 @@ -454,6 +454,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.blockBackgroundProvider(); final JLatexMathTheme.Padding padding = theme.blockPadding(); + final int color = theme.blockTextColor(); final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex) .textSize(theme.blockTextSize()) @@ -468,6 +469,10 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { builder.padding(padding.left, padding.top, padding.right, padding.bottom); } + if (color != 0) { + builder.color(color); + } + return builder.build(); } @@ -479,6 +484,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.inlineBackgroundProvider(); final JLatexMathTheme.Padding padding = theme.inlinePadding(); + final int color = theme.inlineTextColor(); final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex) .textSize(theme.inlineTextSize()) @@ -492,6 +498,10 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { builder.padding(padding.left, padding.top, padding.right, padding.bottom); } + if (color != 0) { + builder.color(color); + } + return builder.build(); } 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 8a1d8801..4f8ede7b 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 @@ -2,6 +2,7 @@ package io.noties.markwon.ext.latex; import android.graphics.drawable.Drawable; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; @@ -118,6 +119,11 @@ public abstract class JLatexMathTheme { @Nullable public abstract Padding blockPadding(); + @ColorInt + public abstract int inlineTextColor(); + + @ColorInt + public abstract int blockTextColor(); public static class Builder { private final float textSize; @@ -136,6 +142,10 @@ public abstract class JLatexMathTheme { private Padding inlinePadding; private Padding blockPadding; + private int textColor = 0xFF000000; + private int inlineTextColor; + private int blockTextColor; + Builder(float textSize, float inlineTextSize, float blockTextSize) { this.textSize = textSize; this.inlineTextSize = inlineTextSize; @@ -194,6 +204,24 @@ public abstract class JLatexMathTheme { return this; } + @NonNull + public Builder textColor(@ColorInt int textColor) { + this.textColor = textColor; + return this; + } + + @NonNull + public Builder inlineTextColor(@ColorInt int inlineTextColor) { + this.inlineTextColor = inlineTextColor; + return this; + } + + @NonNull + public Builder blockTextColor(@ColorInt int blockTextColor) { + this.blockTextColor = blockTextColor; + return this; + } + @NonNull public JLatexMathTheme build() { return new Impl(this); @@ -218,6 +246,10 @@ public abstract class JLatexMathTheme { private final Padding inlinePadding; private final Padding blockPadding; + private final int textColor; + private final int inlineTextColor; + private final int blockTextColor; + Impl(@NonNull Builder builder) { this.textSize = builder.textSize; this.inlineTextSize = builder.inlineTextSize; @@ -230,6 +262,9 @@ public abstract class JLatexMathTheme { this.padding = builder.padding; this.inlinePadding = builder.inlinePadding; this.blockPadding = builder.blockPadding; + this.textColor = builder.textColor; + this.inlineTextColor = builder.inlineTextColor; + this.blockTextColor = builder.blockTextColor; } @Override @@ -293,5 +328,21 @@ public abstract class JLatexMathTheme { } return padding; } + + @Override + public int inlineTextColor() { + if (inlineTextColor != 0) { + return inlineTextColor; + } + return textColor; + } + + @Override + public int blockTextColor() { + if (blockTextColor != 0) { + return blockTextColor; + } + return textColor; + } } } 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 8a55a8ec..9816b0a1 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,6 +1,7 @@ package io.noties.markwon.sample.latex; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -141,12 +142,11 @@ public class LatexActivity extends ActivityWithMenuOptions { final String md = wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION); final Markwon markwon = Markwon.builder(this) .usePlugin(MarkwonInlineParserPlugin.create()) - .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { - @Override - public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { - - } - })) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.theme() + .inlineTextColor(Color.RED) + .blockTextColor(Color.GREEN) + .inlineBackgroundProvider(() -> new ColorDrawable(Color.YELLOW)) + .blockBackgroundProvider(() -> new ColorDrawable(Color.GRAY)))) .build(); markwon.setMarkdown(textView, md); } From d31940a290b0975d1c7d8c023545caddeba5f6bd Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 8 Mar 2020 13:19:03 +0300 Subject: [PATCH 25/33] Pull from origin (linkify compat) --- CHANGELOG.md | 4 ++++ .../io/noties/markwon/linkify/LinkifyPlugin.java | 4 ++++ .../markwon/sample/images/ImagesActivity.java | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc89ffae..c2711cc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,15 @@ * add `option` abstraction for `sample` module allowing switching of multiple cases in runtime via menu * non-empty bounds for `AsyncDrawable` when no dimensions are not yet available ([#189]) * `JLatexMathPlugin` add text color customization ([#207]) +* `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201]) +
Thanks to [@drakeet] [#189]: https://github.com/noties/Markwon/issues/189 [#75]: https://github.com/noties/Markwon/issues/75 [#204]: https://github.com/noties/Markwon/issues/204 [#207]: https://github.com/noties/Markwon/issues/207 +[#201]: https://github.com/noties/Markwon/issues/201 +[@drakeet]: https://github.com/drakeet # 4.2.2 diff --git a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java index a910d970..ec087741 100644 --- a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java +++ b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java @@ -42,6 +42,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { * @param useCompat If true, use {@link LinkifyCompat} to handle links. * Note that the {@link LinkifyCompat} depends on androidx.core:core, * the dependency must be added on a client side explicitly. + * @since 4.3.0-SNAPSHOT `useCompat` argument */ @NonNull public static LinkifyPlugin create(boolean useCompat) { @@ -57,6 +58,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { * @param useCompat If true, use {@link LinkifyCompat} to handle links. * Note that the {@link LinkifyCompat} depends on androidx.core:core, * the dependency must be added on a client side explicitly. + * @since 4.3.0-SNAPSHOT `useCompat` argument */ @NonNull public static LinkifyPlugin create(@LinkifyMask int mask, boolean useCompat) { @@ -78,6 +80,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { @Override public void apply(@NonNull CorePlugin corePlugin) { final LinkifyTextAddedListener listener; + // @since 4.3.0-SNAPSHOT if (useCompat) { listener = new LinkifyCompatTextAddedListener(mask); } else { @@ -137,6 +140,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { } } + // @since 4.3.0-SNAPSHOT private static class LinkifyCompatTextAddedListener extends LinkifyTextAddedListener { LinkifyCompatTextAddedListener(int mask) { diff --git a/sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java b/sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java index dd4a44dc..6206a2c4 100644 --- a/sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/images/ImagesActivity.java @@ -3,6 +3,7 @@ package io.noties.markwon.sample.images; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.text.method.LinkMovementMethod; import android.widget.TextView; import androidx.annotation.NonNull; @@ -14,6 +15,7 @@ import com.bumptech.glide.request.target.Target; import io.noties.markwon.Markwon; import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.image.glide.GlideImagesPlugin; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; @@ -29,7 +31,8 @@ public class ImagesActivity extends ActivityWithMenuOptions { // todo: same for other plugins return MenuOptions.create() .add("glide-singleImage", this::glideSingleImage) - .add("glide-singleImageWithPlaceholder", this::glideSingleImageWithPlaceholder); + .add("glide-singleImageWithPlaceholder", this::glideSingleImageWithPlaceholder) + .add("click", this::click); } @Override @@ -82,4 +85,15 @@ public class ImagesActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + + private void click() { + + textView.setMovementMethod(LinkMovementMethod.getInstance()); + + final String md = "[![markdown](https://www.mdeditor.com/images/logos/markdown.png \"markdown\")](https://www.mdeditor.com/images/logos/markdown.png)"; + final Markwon markwon = Markwon.builder(this) + .usePlugin(ImagesPlugin.create()) + .build(); + markwon.setMarkdown(textView, md); + } } From 86d34cef6fdd4494c2288d885aab422a640e66ec Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 8 Mar 2020 14:48:41 +0300 Subject: [PATCH 26/33] Documentation, add changelog to install section --- docs/docs/v4/install.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/docs/v4/install.md b/docs/docs/v4/install.md index c6de0e5a..d0f4e8e7 100644 --- a/docs/docs/v4/install.md +++ b/docs/docs/v4/install.md @@ -5,8 +5,18 @@ next: /docs/v4/core/getting-started.md # Installation -![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable) -![snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/io.noties.markwon/core.svg?label=snapshot) + + + + + + + + + + + +
stablechangelog
snapshotchangelog
From c90675d67baf923377158a05927fe96e691493bf Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 9 Mar 2020 14:54:05 +0300 Subject: [PATCH 27/33] Latex, default text color if not specified explicitly --- CHANGELOG.md | 5 +- build.gradle | 3 +- docs/docs/v4/ext-latex/README.md | 11 +++- .../ext/latex/JLatexAsyncDrawableSpan.java | 62 +++++++++++++++++++ .../latex/JLatexInlineAsyncDrawableSpan.java | 8 +-- .../markwon/ext/latex/JLatexMathPlugin.java | 22 ++++--- .../markwon/ext/latex/JLatexMathTheme.java | 5 +- .../markwon/sample/latex/LatexActivity.java | 23 ++++++- 8 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c2711cc4..a1bf9443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,14 @@ .build(); ``` * `JLatexMathPlugin`: add `theme` (to customize both inlines and blocks) -* `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering +* `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering (`LEGACY` & `BLOCKS_AND_INLINES`) * add `JLatexMathPlugin.ErrorHandler` to catch latex rendering errors and (optionally) display error drawable ([#204]) +* `JLatexMathPlugin` add text color customization ([#207]) +* `JLatexMathPlugin` will use text color of widget in which it is displayed **if color is not set explicitly** * add `SoftBreakAddsNewLinePlugin` plugin (`core` module) * `LinkResolverDef` defaults to `https` when a link does not have scheme information ([#75]) * add `option` abstraction for `sample` module allowing switching of multiple cases in runtime via menu * non-empty bounds for `AsyncDrawable` when no dimensions are not yet available ([#189]) -* `JLatexMathPlugin` add text color customization ([#207]) * `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201])
Thanks to [@drakeet] diff --git a/build.gradle b/build.gradle index 41a8b70b..582e26ae 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ allprojects { } google() jcenter() +// maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } version = VERSION_NAME group = GROUP @@ -69,7 +70,7 @@ ext { 'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion", 'android-svg' : 'com.caverock:androidsvg:1.4', 'android-gif' : 'pl.droidsonroids.gif:android-gif-drawable:1.2.15', - 'jlatexmath-android' : 'ru.noties:jlatexmath-android:0.1.0', + 'jlatexmath-android' : 'ru.noties:jlatexmath-android:0.1.1', 'okhttp' : 'com.squareup.okhttp3:okhttp:3.9.0', 'prism4j' : 'io.noties:prism4j:2.0.0', 'debug' : 'io.noties:debug:5.0.0@jar', diff --git a/docs/docs/v4/ext-latex/README.md b/docs/docs/v4/ext-latex/README.md index 16fde085..d8f5f241 100644 --- a/docs/docs/v4/ext-latex/README.md +++ b/docs/docs/v4/ext-latex/README.md @@ -48,5 +48,14 @@ final Markwon markwon = Markwon.builder(context) :::tip -Since `JLatexMathPlugin` operates independently of `ImagesPlugin` +Sometimes it is enough to use rendered to an image LaTeX formula and +inline it directly in your markdown document. For this markdown references can be useful. For example: +```markdown + +![markdown-reference] of a solution... + + +[markdown-reference]: data:image/svg+xml;base64,base64encodeddata== +``` +For this to work an image loader that supports data uri and base64 must be used. Default `Markwon` [image-loader](../image/) supports it out of box (including SVG support) ::: \ No newline at end of file diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java new file mode 100644 index 00000000..799aaf37 --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java @@ -0,0 +1,62 @@ +package io.noties.markwon.ext.latex; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; + +import org.scilab.forge.jlatexmath.TeXIcon; + +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.image.AsyncDrawableSpan; +import ru.noties.jlatexmath.JLatexMathDrawable; +import ru.noties.jlatexmath.awt.Color; + +/** + * @since 4.3.0-SNAPSHOT + */ +public class JLatexAsyncDrawableSpan extends AsyncDrawableSpan { + + private final JLatextAsyncDrawable drawable; + private final int color; + private boolean appliedTextColor; + + public JLatexAsyncDrawableSpan( + @NonNull MarkwonTheme theme, + @NonNull JLatextAsyncDrawable drawable, + @ColorInt int color) { + super(theme, drawable, ALIGN_CENTER, false); + this.drawable = drawable; + this.color = color; + // if color is not 0 -> then no need to apply text color + this.appliedTextColor = color != 0; + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) { + if (!appliedTextColor && drawable.hasResult()) { + // it is important to check for type (in case of an error, or custom placeholder or whatever + // this result can be of other type) + final Drawable drawableResult = drawable.getResult(); + if (drawableResult instanceof JLatexMathDrawable) { + final JLatexMathDrawable result = (JLatexMathDrawable) drawableResult; + final TeXIcon icon = result.icon(); + icon.setForeground(new Color(paint.getColor())); + appliedTextColor = true; + } + } + super.draw(canvas, text, start, end, x, top, y, bottom, paint); + } + + @NonNull + public JLatextAsyncDrawable drawable() { + return drawable; + } + + @ColorInt + public int color() { + return color; + } +} 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 index 10b59837..09fdd553 100644 --- 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 @@ -3,23 +3,23 @@ package io.noties.markwon.ext.latex; import android.graphics.Paint; import android.graphics.Rect; +import androidx.annotation.ColorInt; 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 { +class JLatexInlineAsyncDrawableSpan extends JLatexAsyncDrawableSpan { private final AsyncDrawable drawable; - JLatexInlineAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) { - super(theme, drawable, alignment, replacementTextIsLink); + JLatexInlineAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull JLatextAsyncDrawable drawable, @ColorInt int color) { + super(theme, drawable, color); this.drawable = drawable; } 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 1403edb9..eb15af1d 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 @@ -228,7 +228,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final MarkwonConfiguration configuration = visitor.configuration(); - final AsyncDrawableSpan span = new AsyncDrawableSpan( + final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan( configuration.theme(), new JLatextAsyncDrawable( latex, @@ -236,8 +236,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { jLatexBlockImageSizeResolver, null, true), - AsyncDrawableSpan.ALIGN_CENTER, - false); + config.theme.blockTextColor() + ); visitor.setSpans(length, span); @@ -273,8 +273,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { inlineImageSizeResolver, null, false), - AsyncDrawableSpan.ALIGN_CENTER, - false); + config.theme.inlineTextColor() + ); visitor.setSpans(length, span); } @@ -415,9 +415,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable; if (jLatextAsyncDrawable.isBlock()) { - jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable.getDestination()); + jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable); } else { - jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable.getDestination()); + jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable); } setResult(drawable, jLatexMathDrawable); @@ -448,7 +448,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // @since 4.3.0-SNAPSHOT @NonNull - private JLatexMathDrawable createBlockDrawable(@NonNull String latex) { + private JLatexMathDrawable createBlockDrawable(@NonNull JLatextAsyncDrawable drawable) { + + final String latex = drawable.getDestination(); final JLatexMathTheme theme = config.theme; @@ -478,7 +480,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // @since 4.3.0-SNAPSHOT @NonNull - private JLatexMathDrawable createInlineDrawable(@NonNull String latex) { + private JLatexMathDrawable createInlineDrawable(@NonNull JLatextAsyncDrawable drawable) { + + final String latex = drawable.getDestination(); final JLatexMathTheme theme = config.theme; 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 4f8ede7b..e729060b 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 @@ -47,6 +47,7 @@ public abstract class JLatexMathTheme { /** * Special immutable class to hold padding information */ + @SuppressWarnings("WeakerAccess") public static class Padding { public final int left; public final int top; @@ -60,6 +61,7 @@ public abstract class JLatexMathTheme { this.bottom = bottom; } + @NonNull @Override public String toString() { return "Padding{" + @@ -125,6 +127,7 @@ public abstract class JLatexMathTheme { @ColorInt public abstract int blockTextColor(); + @SuppressWarnings({"unused", "UnusedReturnValue"}) public static class Builder { private final float textSize; private final float inlineTextSize; @@ -142,7 +145,7 @@ public abstract class JLatexMathTheme { private Padding inlinePadding; private Padding blockPadding; - private int textColor = 0xFF000000; + private int textColor; private int inlineTextColor; private int blockTextColor; 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 9816b0a1..f07ecc1d 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 @@ -63,7 +63,16 @@ public class LatexActivity extends ActivityWithMenuOptions { .add("insideBlockQuote", this::insideBlockQuote) .add("error", this::error) .add("legacy", this::legacy) - .add("textColor", this::textColor); + .add("textColor", this::textColor) + .add("defaultTextColor", this::defaultTextColor); + } + + @Override + protected void beforeOptionSelected(@NonNull String option) { + super.beforeOptionSelected(option); + + // reset text color + textView.setTextColor(0xFF000000); } @Override @@ -151,6 +160,18 @@ public class LatexActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + private void defaultTextColor() { + // @since 4.3.0-SNAPSHOT text color is automatically taken from textView + textView.setTextColor(0xFFff0000); + + final String md = wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION); + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .build(); + markwon.setMarkdown(textView, md); + } + @NonNull private static String wrapLatexInSampleMarkdown(@NonNull String latex) { return "" + From 69c2d1255c733f4fff161302dcbddbdd8235c1d6 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 9 Mar 2020 17:40:16 +0300 Subject: [PATCH 28/33] Stabilizing latex API --- CHANGELOG.md | 40 +++-- docs/docs/v4/ext-latex/README.md | 112 +++++++++--- .../ext/latex/JLatexMathBlockParser.java | 1 - .../markwon/ext/latex/JLatexMathPlugin.java | 163 +++++++++--------- .../ext/latex/JLatexMathPluginTest.java | 14 +- release-management.md | 19 +- .../markwon/sample/latex/LatexActivity.java | 64 +++++-- 7 files changed, 279 insertions(+), 134 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1bf9443..eb32a054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,9 @@ # 4.3.0-SNAPSHOT * add `MarkwonInlineParserPlugin` in `inline-parser` module -* `JLatexMathPlugin` now supports both inline and block structures - - this comes with a breaking change: `JLatexMathPlugin` now depends on `inline-parser` module and `MarkwonInlineParserPlugin` must be explicitly added to a `Markwon` instance: - ```java - final Markwon markwon = Markwon.builder(this) - .usePlugin(MarkwonInlineParserPlugin.create()) - .usePlugin(JLatexMathPlugin.create(textSize)) - .build(); - ``` +* `JLatexMathPlugin` now supports inline LaTeX structures via `MarkwonInlineParserPlugin` +dependency (must be explicitly added to `Markwon` whilst configuring) * `JLatexMathPlugin`: add `theme` (to customize both inlines and blocks) -* `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering (`LEGACY` & `BLOCKS_AND_INLINES`) * add `JLatexMathPlugin.ErrorHandler` to catch latex rendering errors and (optionally) display error drawable ([#204]) * `JLatexMathPlugin` add text color customization ([#207]) * `JLatexMathPlugin` will use text color of widget in which it is displayed **if color is not set explicitly** @@ -23,6 +15,34 @@ * `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201])
Thanks to [@drakeet] + +```java +// default usage: new blocks parser, no inlines +final Markwon markwon = Markwon.builder(this) + .usePlugin(JLatexMathPlugin.create(textSize)) + .build(); +``` + +```java +// legacy blocks (pre `4.3.0`) parsing, no inlines +final Markwon markwon = Markwon.builder(this) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.blocksLegacy(true))) + .build(); +``` + +```java +// new blocks parsing and inline parsing +final Markwon markwon = Markwon.builder(this) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + // blocksEnabled and blocksLegacy can be omitted + builder + .blocksEnabled(true) + .blocksLegacy(false) + .inlinesEnabled(true); + })) + .build(); +``` + [#189]: https://github.com/noties/Markwon/issues/189 [#75]: https://github.com/noties/Markwon/issues/75 [#204]: https://github.com/noties/Markwon/issues/204 diff --git a/docs/docs/v4/ext-latex/README.md b/docs/docs/v4/ext-latex/README.md index d8f5f241..6e19dc45 100644 --- a/docs/docs/v4/ext-latex/README.md +++ b/docs/docs/v4/ext-latex/README.md @@ -2,50 +2,120 @@ -This is an extension that will help you display LaTeX formulas in your markdown. -Syntax is pretty simple: pre-fix and post-fix your latex with `$$` (double dollar sign). -`$$` should be the first characters in a line. +This is an extension that will help you display LaTeX content in your markdown. +Since supports both blocks and inlines markdown structures (blocks only before `4.3.0`). +## Blocks +Start a line with 2 (or more) `$` symbols followed by a new line: ```markdown $$ \\text{A long division \\longdiv{12345}{13} $$ ``` +LaTeX block content will be considered ended when a starting sequence of `$` is found on +a new line. If block was started with `$$$` it must be ended with `$$$` symbols. +## Inline +Exactly `$$` before and after _inline_ LaTeX content: ```markdown $$\\text{A long division \\longdiv{12345}{13}$$ ``` +:::warning +By default inline nodes are disabled and must be enabled explicitly: ```java -Markwon.builder(context) - .use(JLatexMathPlugin.create(textSize)) - .build(); +final Markwon markwon = Markwon.builder(this) + // required plugin to support inline parsing + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + // ENABLE inlines + builder.inlinesEnabled(true); + } + })) + .build(); ``` +Please note that usage of inline nodes **require** [MarkwonInlineParserPlugin](../inline-parser/) +::: This extension uses [jlatexmath-android](https://github.com/noties/jlatexmath-android) artifact to create LaTeX drawable. ## Config ```java -final Markwon markwon = Markwon.builder(context) - .usePlugin(JLatexMathPlugin.create(textSize, new BuilderConfigure() { +// create default instance of plugin and use specified text size for both blocks and inlines +JLatexMathPlugin.create(textView.getTextSize()); + +// create default instance of plugin and use specified text sizes +JLatexMathPlugin.create(inlineTextSize, blockTextSize); + +JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + // enable inlines (require `MarkwonInlineParserPlugin`), by default `false` + builder.inlinesEnabled(true); + + // use pre-4.3.0 LaTeX block parsing (by default `false`) + builder.blocksLegacy(true); + + // by default true + builder.blocksEnabled(true); + + // @since 4.3.0 + builder.errorHandler(new JLatexMathPlugin.ErrorHandler() { + @Nullable @Override - public void configureBuilder(@NonNull Builder builder) { - builder - .align(JLatexMathDrawable.ALIGN_CENTER) - .fitCanvas(true) - .padding(paddingPx) - // @since 4.0.0 - horizontal and vertical padding - .padding(paddingHorizontalPx, paddingVerticalPx) - // @since 4.0.0 - change to provider - .backgroundProvider(() -> new MyDrawable())) - // @since 4.0.0 - optional, by default cached-thread-pool will be used - .executorService(Executors.newCachedThreadPool()); + public Drawable handleError(@NonNull String latex, @NonNull Throwable error) { + // Receive error and optionally return drawable to be displayed instead + return null; } - })) - .build(); + }); + + // executor on which parsing of LaTeX is done (by default `Executors.newCachedThreadPool()`) + builder.executorService(Executors.newCachedThreadPool()); + } +}); ``` +## Theme + +```java +JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + + // background provider for both inlines and blocks + // or more specific: `inlineBackgroundProvider` & `blockBackgroundProvider` + builder.theme().backgroundProvider(new JLatexMathTheme.BackgroundProvider() { + @NonNull + @Override + public Drawable provide() { + return new ColorDrawable(0xFFff0000); + } + }); + + // should block fit the whole canvas width, by default true + builder.theme().blockFitCanvas(true); + + // horizontal alignment for block, by default ALIGN_CENTER + builder.theme().blockHorizontalAlignment(JLatexMathDrawable.ALIGN_CENTER); + + // padding for both inlines and blocks + builder.theme().padding(JLatexMathTheme.Padding.all(8)); + + // padding for inlines + builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(16, 8)); + + // padding for blocks + builder.theme().blockPadding(new JLatexMathTheme.Padding(0, 1, 2, 3)); + + // text color of LaTeX content for both inlines and blocks + // or more specific: `inlineTextColor` & `blockTextColor` + builder.theme().textColor(Color.RED); + } +}); +``` :::tip Sometimes it is enough to use rendered to an image LaTeX formula and 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 8d4b4d37..0d1653c6 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 @@ -26,7 +26,6 @@ class JLatexMathBlockParser extends AbstractBlockParser { private final int signs; - @SuppressWarnings("WeakerAccess") JLatexMathBlockParser(int signs) { this.signs = signs; } 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 eb15af1d..d0886a14 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 @@ -39,30 +39,6 @@ import ru.noties.jlatexmath.JLatexMathDrawable; */ public class JLatexMathPlugin extends AbstractMarkwonPlugin { - /** - * @since 4.3.0-SNAPSHOT - */ - public enum RenderMode { - /** - * LEGACY 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 start the block. - */ - BLOCKS_AND_INLINES - } - /** * @since 4.3.0-SNAPSHOT */ @@ -140,7 +116,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final JLatexMathTheme theme; // @since 4.3.0-SNAPSHOT - final RenderMode renderMode; + final boolean blocksEnabled; + final boolean blocksLegacy; + final boolean inlinesEnabled; // @since 4.3.0-SNAPSHOT final ErrorHandler errorHandler; @@ -149,7 +127,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { Config(@NonNull Builder builder) { this.theme = builder.theme.build(); - this.renderMode = builder.renderMode; + this.blocksEnabled = builder.blocksEnabled; + this.blocksLegacy = builder.blocksLegacy; + this.inlinesEnabled = builder.inlinesEnabled; this.errorHandler = builder.errorHandler; // @since 4.0.0 ExecutorService executorService = builder.executorService; @@ -177,7 +157,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void configure(@NonNull Registry registry) { - if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) { + if (config.inlinesEnabled) { registry.require(MarkwonInlineParserPlugin.class) .factoryBuilder() .addInlineProcessor(new JLatexMathInlineProcessor()); @@ -186,31 +166,27 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void configureParser(@NonNull Parser.Builder builder) { - - // 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 - - switch (config.renderMode) { - - case LEGACY: { + // @since $nap; + if (config.blocksEnabled) { + if (config.blocksLegacy) { builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory()); - } - break; - - case BLOCKS_AND_INLINES: { + } else { builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); - // inline processor is added through `registry` } - break; - - default: - throw new RuntimeException("Unexpected `renderMode`: " + config.renderMode); } } @Override public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + addBlockVisitor(builder); + addInlineVisitor(builder); + } + + private void addBlockVisitor(@NonNull MarkwonVisitor.Builder builder) { + if (!config.blocksEnabled) { + return; + } + builder.on(JLatexMathBlock.class, new MarkwonVisitor.NodeVisitor() { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) { @@ -247,39 +223,42 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } } }); + } + private void addInlineVisitor(@NonNull MarkwonVisitor.Builder builder) { - if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) { - - builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor() { - @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 JLatexInlineAsyncDrawableSpan( - configuration.theme(), - new JLatextAsyncDrawable( - latex, - jLatextAsyncDrawableLoader, - inlineImageSizeResolver, - null, - false), - config.theme.inlineTextColor() - ); - - visitor.setSpans(length, span); - } - }); + if (!config.inlinesEnabled) { + return; } + + builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor() { + @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 JLatexInlineAsyncDrawableSpan( + configuration.theme(), + new JLatextAsyncDrawable( + latex, + jLatextAsyncDrawableLoader, + inlineImageSizeResolver, + null, + false), + config.theme.inlineTextColor() + ); + + visitor.setSpans(length, span); + } + }); } @Override @@ -299,13 +278,16 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return latex.replace('\n', ' ').trim(); } + @SuppressWarnings({"unused", "UnusedReturnValue"}) public static class Builder { // @since 4.3.0-SNAPSHOT private final JLatexMathTheme.Builder theme; // @since 4.3.0-SNAPSHOT - private RenderMode renderMode = RenderMode.BLOCKS_AND_INLINES; + private boolean blocksEnabled = true; + private boolean blocksLegacy; + private boolean inlinesEnabled; // @since 4.3.0-SNAPSHOT private ErrorHandler errorHandler; @@ -323,16 +305,35 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } /** - * @since 4.3.0-SNAPSHOT + * @since $nap; */ - @SuppressWarnings("UnusedReturnValue") @NonNull - public Builder renderMode(@NonNull RenderMode renderMode) { - this.renderMode = renderMode; + public Builder blocksEnabled(boolean blocksEnabled) { + this.blocksEnabled = blocksEnabled; + return this; + } + + /** + * @param blocksLegacy indicates if blocks should be handled in legacy mode ({@code pre 4.3.0}) + * @since 4.3.0-SNAPSHOT + */ + @NonNull + public Builder blocksLegacy(boolean blocksLegacy) { + this.blocksLegacy = blocksLegacy; + return this; + } + + /** + * @param inlinesEnabled indicates if inline parsing should be enabled. + * NB, this requires `MarkwonInlineParserPlugin` to be used when creating `MarkwonInstance` + * @since 4.3.0-SNAPSHOT + */ + @NonNull + public Builder inlinesEnabled(boolean inlinesEnabled) { + this.inlinesEnabled = inlinesEnabled; return this; } - @SuppressWarnings("UnusedReturnValue") @NonNull public Builder errorHandler(@Nullable ErrorHandler errorHandler) { this.errorHandler = errorHandler; @@ -342,7 +343,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { /** * @since 4.0.0 */ - @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"}) + @SuppressWarnings("WeakerAccess") @NonNull public Builder executorService(@NonNull ExecutorService executorService) { this.executorService = executorService; diff --git a/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java index 57584168..a2467ae3 100644 --- a/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java +++ b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java @@ -128,7 +128,8 @@ public class JLatexMathPluginTest { final JLatexMathPlugin plugin = JLatexMathPlugin.create(1, new JLatexMathPlugin.BuilderConfigure() { @Override public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { - builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY); + builder.blocksLegacy(true); + builder.inlinesEnabled(false); } }); @@ -167,12 +168,19 @@ public class JLatexMathPluginTest { public void blocks_inlines_implicit() { final JLatexMathPlugin plugin = JLatexMathPlugin.create(1); final JLatexMathPlugin.Config config = plugin.config; - assertEquals(JLatexMathPlugin.RenderMode.BLOCKS_AND_INLINES, config.renderMode); + assertTrue("blocksEnabled", config.blocksEnabled); + assertFalse("blocksLegacy", config.blocksLegacy); + assertFalse("inlinesEnabled", config.inlinesEnabled); } @Test public void blocks_inlines() { - final JLatexMathPlugin plugin = JLatexMathPlugin.create(12); + final JLatexMathPlugin plugin = JLatexMathPlugin.create(12, new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + builder.inlinesEnabled(true); + } + }); // registry { diff --git a/release-management.md b/release-management.md index 36efc91f..7338d9fc 100644 --- a/release-management.md +++ b/release-management.md @@ -24,4 +24,21 @@ The issuer branch (with version name) should be deleted. A new version must be pushed to MavenCentral and new git-tag with version name must be created in the repository. -Rinse and repeat. \ No newline at end of file +Rinse and repeat. + +## `@since` annotation + +Although it is not required it is a nice thing to do: add `@since $VERSION` comment to the code +whenever it is possible (at least for publicly accessible code - API). This would help +navigating the project without the need to checkout the full VCS history. As keeping track of +current and/or upcoming version can be error-prone it is better to insert a generic `@since code` +that can be properly substituted upon a release. + +For example, `@since $nap` seems like a good candidate. For this a live template can be created and used +whenever a new API method/field/functionality-change is introduced (`snc`): + +``` +@since $nap; +``` + +This live template would be possible to use in both inline comment and javadoc comment. \ No newline at end of file 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 f07ecc1d..7e885a06 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 @@ -64,7 +64,8 @@ public class LatexActivity extends ActivityWithMenuOptions { .add("error", this::error) .add("legacy", this::legacy) .add("textColor", this::textColor) - .add("defaultTextColor", this::defaultTextColor); + .add("defaultTextColor", this::defaultTextColor) + .add("inlineAndBlock", this::inlineAndBlock); } @Override @@ -87,19 +88,19 @@ public class LatexActivity extends ActivityWithMenuOptions { } private void array() { - render(wrapLatexInSampleMarkdown(LATEX_ARRAY)); + renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_ARRAY)); } private void longDivision() { - render(wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION)); + renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION)); } private void bangle() { - render(wrapLatexInSampleMarkdown(LATEX_BANGLE)); + renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_BANGLE)); } private void boxes() { - render(wrapLatexInSampleMarkdown(LATEX_BOXES)); + renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_BOXES)); } private void insideBlockQuote() { @@ -107,7 +108,7 @@ public class LatexActivity extends ActivityWithMenuOptions { final String md = "" + "# LaTeX inside a blockquote\n" + "> $$" + latex + "$$\n"; - render(md); + renderWithBlocksAndInlines(md); } private void error() { @@ -116,6 +117,7 @@ public class LatexActivity extends ActivityWithMenuOptions { final Markwon markwon = Markwon.builder(this) .usePlugin(MarkwonInlineParserPlugin.create()) .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.inlinesEnabled(true); //noinspection Convert2Lambda builder.errorHandler(new JLatexMathPlugin.ErrorHandler() { @Nullable @@ -137,7 +139,7 @@ public class LatexActivity extends ActivityWithMenuOptions { final Markwon markwon = Markwon.builder(this) // LEGACY does not require inline parser .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { - builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY); + builder.blocksLegacy(true); builder.theme() .backgroundProvider(() -> new ColorDrawable(0x100000ff)) .padding(JLatexMathTheme.Padding.all(48)); @@ -151,27 +153,56 @@ public class LatexActivity extends ActivityWithMenuOptions { final String md = wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION); final Markwon markwon = Markwon.builder(this) .usePlugin(MarkwonInlineParserPlugin.create()) - .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.theme() - .inlineTextColor(Color.RED) - .blockTextColor(Color.GREEN) - .inlineBackgroundProvider(() -> new ColorDrawable(Color.YELLOW)) - .blockBackgroundProvider(() -> new ColorDrawable(Color.GRAY)))) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.inlinesEnabled(true); + builder.theme() + .inlineTextColor(Color.RED) + .blockTextColor(Color.GREEN) + .inlineBackgroundProvider(() -> new ColorDrawable(Color.YELLOW)) + .blockBackgroundProvider(() -> new ColorDrawable(Color.GRAY)); + })) .build(); markwon.setMarkdown(textView, md); } private void defaultTextColor() { // @since 4.3.0-SNAPSHOT text color is automatically taken from textView + // (if it's not specified explicitly via configuration) textView.setTextColor(0xFFff0000); final String md = wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION); final Markwon markwon = Markwon.builder(this) .usePlugin(MarkwonInlineParserPlugin.create()) - .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + builder.inlinesEnabled(true); + // override default text color + builder.theme() + .inlineTextColor(0xFF00ffff); + } + })) .build(); + markwon.setMarkdown(textView, md); } + private void inlineAndBlock() { + final String md = "" + + "# Inline and block\n\n" + + "$$\\int_{a}^{b} f(x)dx = F(b) - F(a)$$\n\n" + + "this was **inline** _LaTeX_ $$\\int_{a}^{b} f(x)dx = F(b) - F(a)$$ and once again it was\n\n" + + "Now a block:\n\n" + + "$$\n" + + "\\int_{a}^{b} f(x)dx = F(b) - F(a)\n" + + "$$\n\n" + + "Not a block (content on delimited line), but inline instead:\n\n" + + "$$\\int_{a}^{b} f(x)dx = F(b) - F(a)$$" + + "\n\n" + + "that's it"; + renderWithBlocksAndInlines(md); + } + @NonNull private static String wrapLatexInSampleMarkdown(@NonNull String latex) { return "" + @@ -183,7 +214,7 @@ public class LatexActivity extends ActivityWithMenuOptions { "the end"; } - private void render(@NonNull String markdown) { + private void renderWithBlocksAndInlines(@NonNull String markdown) { final float textSize = textView.getTextSize(); final Resources r = getResources(); @@ -192,6 +223,8 @@ public class LatexActivity extends ActivityWithMenuOptions { // NB! `MarkwonInlineParserPlugin` is required in order to parse inlines .usePlugin(MarkwonInlineParserPlugin.create()) .usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> { + // Important thing to do is to enable inlines (by default disabled) + builder.inlinesEnabled(true); builder.theme() .inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00)) .blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000)) @@ -199,9 +232,6 @@ public class LatexActivity extends ActivityWithMenuOptions { 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(); From 0b813e43f7d16cfe62c17db26aaeb84bdb11c89b Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 11 Mar 2020 12:43:20 +0300 Subject: [PATCH 29/33] BlockHandler abstraction in core module --- CHANGELOG.md | 1 + .../io/noties/markwon/BlockHandlerDef.java | 23 ++++++ .../io/noties/markwon/MarkwonVisitor.java | 33 +++++++++ .../io/noties/markwon/MarkwonVisitorImpl.java | 34 ++++++++- .../markwon/SoftBreakAddsNewLinePlugin.java | 2 +- .../io/noties/markwon/core/CorePlugin.java | 35 +++------- .../markwon/core/SimpleBlockNodeVisitor.java | 9 +-- .../markwon/AbstractMarkwonVisitorImpl.java | 5 +- .../markwon/MarkwonVisitorImplTest.java | 24 ++++--- .../noties/markwon/core/CorePluginTest.java | 6 ++ .../markwon/syntax/SyntaxHighlightTest.java | 3 +- .../markwon/ext/latex/JLatexMathPlugin.java | 7 +- .../markwon/ext/tables/TablePlugin.java | 11 +-- .../basicplugins/BasicPluginsActivity.java | 70 ++++++++++++++++++- 14 files changed, 207 insertions(+), 56 deletions(-) create mode 100644 markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java diff --git a/CHANGELOG.md b/CHANGELOG.md index eb32a054..c2b1767d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ dependency (must be explicitly added to `Markwon` whilst configuring) * non-empty bounds for `AsyncDrawable` when no dimensions are not yet available ([#189]) * `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201])
Thanks to [@drakeet] +* `MarkwonVisitor.BlockHandler` and `BlockHandlerDef` implementation to control how blocks insert new lines after them ```java diff --git a/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java b/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java new file mode 100644 index 00000000..0c5b3b49 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java @@ -0,0 +1,23 @@ +package io.noties.markwon; + +import androidx.annotation.NonNull; + +import org.commonmark.node.Node; + +/** + * @since $nap; + */ +public class BlockHandlerDef implements MarkwonVisitor.BlockHandler { + @Override + public void blockStart(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + visitor.ensureNewLine(); + } + + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java index 9fd6ffc7..f9c4554c 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java @@ -23,6 +23,19 @@ public interface MarkwonVisitor extends Visitor { void visit(@NonNull MarkwonVisitor visitor, @NonNull N n); } + /** + * Primary purpose is to control the spacing applied before/after certain blocks, which + * visitors are created elsewhere + * + * @since $nap; + */ + interface BlockHandler { + + void blockStart(@NonNull MarkwonVisitor visitor, @NonNull Node node); + + void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node); + } + interface Builder { /** @@ -33,6 +46,16 @@ public interface MarkwonVisitor extends Visitor { @NonNull Builder on(@NonNull Class node, @Nullable NodeVisitor nodeVisitor); + /** + * @param blockHandler to handle block start/end + * @see BlockHandler + * @see BlockHandlerDef + * @since $nap; + */ + @SuppressWarnings("UnusedReturnValue") + @NonNull + Builder blockHandler(@NonNull BlockHandler blockHandler); + @NonNull MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps); } @@ -133,4 +156,14 @@ public interface MarkwonVisitor extends Visitor { */ @SuppressWarnings("unused") void setSpansForNodeOptional(@NonNull Class node, int start); + + /** + * @since $nap; + */ + void blockStart(@NonNull Node node); + + /** + * @since $nap; + */ + void blockEnd(@NonNull Node node); } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java index 659a6622..bd32352e 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java @@ -45,15 +45,20 @@ class MarkwonVisitorImpl implements MarkwonVisitor { private final Map, NodeVisitor> nodes; + // @since $nap; + private final BlockHandler blockHandler; + MarkwonVisitorImpl( @NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull SpannableBuilder builder, - @NonNull Map, NodeVisitor> nodes) { + @NonNull Map, NodeVisitor> nodes, + @NonNull BlockHandler blockHandler) { this.configuration = configuration; this.renderProps = renderProps; this.builder = builder; this.nodes = nodes; + this.blockHandler = blockHandler; } @Override @@ -268,9 +273,20 @@ class MarkwonVisitorImpl implements MarkwonVisitor { } } + @Override + public void blockStart(@NonNull Node node) { + blockHandler.blockStart(this, node); + } + + @Override + public void blockEnd(@NonNull Node node) { + blockHandler.blockEnd(this, node); + } + static class BuilderImpl implements Builder { private final Map, NodeVisitor> nodes = new HashMap<>(); + private BlockHandler blockHandler; @NonNull @Override @@ -290,14 +306,28 @@ class MarkwonVisitorImpl implements MarkwonVisitor { return this; } + @NonNull + @Override + public Builder blockHandler(@NonNull BlockHandler blockHandler) { + this.blockHandler = blockHandler; + return this; + } + @NonNull @Override public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps) { + // @since $nap; + BlockHandler blockHandler = this.blockHandler; + if (blockHandler == null) { + blockHandler = new BlockHandlerDef(); + } + return new MarkwonVisitorImpl( configuration, renderProps, new SpannableBuilder(), - Collections.unmodifiableMap(nodes)); + Collections.unmodifiableMap(nodes), + blockHandler); } } } diff --git a/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java index 6f49ac68..f1aff45d 100644 --- a/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java @@ -19,7 +19,7 @@ public class SoftBreakAddsNewLinePlugin extends AbstractMarkwonPlugin { builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor() { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { - visitor.forceNewLine(); + visitor.ensureNewLine(); } }); } diff --git a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java index dffa215d..29c63a2a 100644 --- a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java @@ -210,17 +210,14 @@ public class CorePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull BlockQuote blockQuote) { - visitor.ensureNewLine(); + visitor.blockStart(blockQuote); final int length = visitor.length(); visitor.visitChildren(blockQuote); visitor.setSpansForNodeOptional(blockQuote, length); - if (visitor.hasNext(blockQuote)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(blockQuote); } }); } @@ -316,7 +313,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { @NonNull String code, @NonNull Node node) { - visitor.ensureNewLine(); + visitor.blockStart(node); final int length = visitor.length(); @@ -333,10 +330,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { visitor.setSpansForNodeOptional(node, length); - if (visitor.hasNext(node)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(node); } private static void bulletList(@NonNull MarkwonVisitor.Builder builder) { @@ -402,7 +396,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull ThematicBreak thematicBreak) { - visitor.ensureNewLine(); + visitor.blockStart(thematicBreak); final int length = visitor.length(); @@ -411,10 +405,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { visitor.setSpansForNodeOptional(thematicBreak, length); - if (visitor.hasNext(thematicBreak)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(thematicBreak); } }); } @@ -424,7 +415,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) { - visitor.ensureNewLine(); + visitor.blockStart(heading); final int length = visitor.length(); visitor.visitChildren(heading); @@ -433,10 +424,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { visitor.setSpansForNodeOptional(heading, length); - if (visitor.hasNext(heading)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(heading); } }); } @@ -467,7 +455,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { final boolean inTightList = isInTightList(paragraph); if (!inTightList) { - visitor.ensureNewLine(); + visitor.blockStart(paragraph); } final int length = visitor.length(); @@ -478,9 +466,8 @@ public class CorePlugin extends AbstractMarkwonPlugin { // @since 1.1.1 apply paragraph span visitor.setSpansForNodeOptional(paragraph, length); - if (!inTightList && visitor.hasNext(paragraph)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); + if (!inTightList) { + visitor.blockEnd(paragraph); } } }); diff --git a/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java b/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java index 91742773..6a27599e 100644 --- a/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java @@ -17,19 +17,16 @@ public class SimpleBlockNodeVisitor implements MarkwonVisitor.NodeVisitor @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + visitor.blockStart(node); + // @since 3.0.1 we keep track of start in order to apply spans (optionally) final int length = visitor.length(); - visitor.ensureNewLine(); - visitor.visitChildren(node); // @since 3.0.1 we apply optional spans visitor.setSpansForNodeOptional(node, length); - if (visitor.hasNext(node)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(node); } } diff --git a/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java index ed4dae6b..5bae81f9 100644 --- a/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java +++ b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java @@ -12,7 +12,8 @@ public class AbstractMarkwonVisitorImpl extends MarkwonVisitorImpl { @NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull SpannableBuilder spannableBuilder, - @NonNull Map, NodeVisitor> nodes) { - super(configuration, renderProps, spannableBuilder, nodes); + @NonNull Map, NodeVisitor> nodes, + @NonNull BlockHandler blockHandler) { + super(configuration, renderProps, spannableBuilder, nodes, blockHandler); } } diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java index 45387afb..6c6c93ba 100644 --- a/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java @@ -43,7 +43,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), renderProps, spannableBuilder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); impl.clear(); @@ -61,7 +62,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); // at the start - won't add anything impl.ensureNewLine(); @@ -92,7 +94,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); assertEquals(0, builder.length()); @@ -144,7 +147,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), mock(SpannableBuilder.class), - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); final BlockQuote node = mock(BlockQuote.class); final Node child = mock(Node.class); @@ -163,7 +167,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), mock(SpannableBuilder.class), - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); final Node noNext = mock(Node.class); assertFalse(impl.hasNext(noNext)); @@ -195,7 +200,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); for (int i = 0; i < 13; i++) { builder.setLength(i); @@ -221,7 +227,8 @@ public class MarkwonVisitorImplTest { configuration, mock(RenderProps.class), mock(SpannableBuilder.class), - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); impl.setSpansForNode(Node.class, 0); @@ -252,7 +259,8 @@ public class MarkwonVisitorImplTest { configuration, mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); // append something builder.append("no-spans-test"); diff --git a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java index 9cb95028..56bc539c 100644 --- a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java @@ -107,6 +107,12 @@ public class CorePluginTest { return this; } + @NonNull + @Override + public MarkwonVisitor.Builder blockHandler(@NonNull MarkwonVisitor.BlockHandler blockHandler) { + throw new RuntimeException(); + } + @NonNull @Override public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps) { diff --git a/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java b/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java index fdde1888..3ff6df03 100644 --- a/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java @@ -91,7 +91,8 @@ public class SyntaxHighlightTest { configuration, mock(RenderProps.class), new SpannableBuilder(), - visitorMap); + visitorMap, + mock(MarkwonVisitor.BlockHandler.class)); final SpannableBuilder builder = visitor.builder(); 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 d0886a14..f39b53af 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 @@ -191,7 +191,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) { - visitor.ensureNewLine(); + visitor.blockStart(jLatexMathBlock); final String latex = jLatexMathBlock.latex(); @@ -217,10 +217,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { visitor.setSpans(length, span); - if (visitor.hasNext(jLatexMathBlock)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(jLatexMathBlock); } }); } diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java index 086afd75..0cbce8b8 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java @@ -121,12 +121,15 @@ public class TablePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableBlock tableBlock) { + visitor.blockStart(tableBlock); + visitor.visitChildren(tableBlock); - if (visitor.hasNext(tableBlock)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } +// if (visitor.hasNext(tableBlock)) { +// visitor.ensureNewLine(); +// visitor.forceNewLine(); +// } + visitor.blockEnd(tableBlock); } }) .on(TableBody.class, new MarkwonVisitor.NodeVisitor() { diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index 02878480..632c94ec 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -11,19 +11,19 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.commonmark.node.Heading; +import org.commonmark.node.Node; import org.commonmark.node.Paragraph; import java.util.Collection; import java.util.Collections; import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.BlockHandlerDef; import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; -import io.noties.markwon.RenderProps; import io.noties.markwon.SoftBreakAddsNewLinePlugin; -import io.noties.markwon.SpanFactory; import io.noties.markwon.core.CoreProps; import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.core.spans.LastLineSpacingSpan; @@ -52,7 +52,9 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { .add("softBreakAddsSpace", this::softBreakAddsSpace) .add("softBreakAddsNewLine", this::softBreakAddsNewLine) .add("additionalSpacing", this::additionalSpacing) - .add("headingNoSpace", this::headingNoSpace); + .add("headingNoSpace", this::headingNoSpace) + .add("headingNoSpaceBlockHandler", this::headingNoSpaceBlockHandler) + .add("allBlocksNoForcedLine", this::allBlocksNoForcedLine); } @Override @@ -311,6 +313,68 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + private void headingNoSpaceBlockHandler() { + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.blockHandler(new BlockHandlerDef() { + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (node instanceof Heading) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + // ensure new line but do not force insert one + } + } else { + super.blockEnd(visitor, node); + } + } + }); + } + }) + .build(); + + final String md = "" + + "# Title title title title title title title title title title \n\ntext text text text"; + + markwon.setMarkdown(textView, md); + } + + private void allBlocksNoForcedLine() { + final MarkwonVisitor.BlockHandler blockHandler = new BlockHandlerDef() { + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + } + } + }; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.blockHandler(blockHandler); + } + }) + .build(); + + final String md = "" + + "# Hello there!\n\n" + + "* a first\n" + + "* second\n" + + "- third\n" + + "* * nested one\n\n" + + "> block quote\n\n" + + "> > and nested one\n\n" + + "```java\n" + + "final int i = 0;\n" + + "```\n\n"; + + markwon.setMarkdown(textView, md); + } + // public void step_6() { // // final Markwon markwon = Markwon.builder(this) From c425773c84e8916291928fb2e0362317aa7fbf88 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 12 Mar 2020 10:25:18 +0300 Subject: [PATCH 30/33] Add remote views sample --- sample/src/main/AndroidManifest.xml | 1 + .../noties/markwon/sample/MainActivity.java | 5 + .../java/io/noties/markwon/sample/Sample.java | 4 +- .../notification/NotificationActivity.java | 254 ++++++++++++++++++ .../res/drawable-anydpi-v24/ic_stat_name.xml | 13 + .../main/res/drawable-hdpi/ic_stat_name.png | Bin 0 -> 513 bytes .../main/res/drawable-mdpi/ic_stat_name.png | Bin 0 -> 346 bytes .../main/res/drawable-xhdpi/ic_stat_name.png | Bin 0 -> 687 bytes .../main/res/drawable-xxhdpi/ic_stat_name.png | Bin 0 -> 1044 bytes .../src/main/res/values/strings-samples.xml | 2 + 10 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/notification/NotificationActivity.java create mode 100644 sample/src/main/res/drawable-anydpi-v24/ic_stat_name.xml create mode 100644 sample/src/main/res/drawable-hdpi/ic_stat_name.png create mode 100644 sample/src/main/res/drawable-mdpi/ic_stat_name.png create mode 100644 sample/src/main/res/drawable-xhdpi/ic_stat_name.png create mode 100644 sample/src/main/res/drawable-xxhdpi/ic_stat_name.png diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index d3266265..f85d8750 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -37,6 +37,7 @@ + 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 a8391ce8..2ac55d99 100644 --- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java @@ -28,6 +28,7 @@ import io.noties.markwon.sample.htmldetails.HtmlDetailsActivity; import io.noties.markwon.sample.images.ImagesActivity; import io.noties.markwon.sample.inlineparser.InlineParserActivity; import io.noties.markwon.sample.latex.LatexActivity; +import io.noties.markwon.sample.notification.NotificationActivity; import io.noties.markwon.sample.precomputed.PrecomputedActivity; import io.noties.markwon.sample.recycler.RecyclerActivity; import io.noties.markwon.sample.simpleext.SimpleExtActivity; @@ -142,6 +143,10 @@ public class MainActivity extends Activity { activity = ImagesActivity.class; break; + case REMOTE_VIEWS: + activity = NotificationActivity.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 bf05297d..f18ed25b 100644 --- a/sample/src/main/java/io/noties/markwon/sample/Sample.java +++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java @@ -31,7 +31,9 @@ public enum Sample { TASK_LIST(R.string.sample_task_list), - IMAGES(R.string.sample_images); + IMAGES(R.string.sample_images), + + REMOTE_VIEWS(R.string.sample_remote_views); private final int textResId; diff --git a/sample/src/main/java/io/noties/markwon/sample/notification/NotificationActivity.java b/sample/src/main/java/io/noties/markwon/sample/notification/NotificationActivity.java new file mode 100644 index 00000000..0012f41f --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/notification/NotificationActivity.java @@ -0,0 +1,254 @@ +package io.noties.markwon.sample.notification; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.os.Build; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.BulletSpan; +import android.text.style.DynamicDrawableSpan; +import android.text.style.ImageSpan; +import android.text.style.QuoteSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; + +import androidx.annotation.NonNull; + +import org.commonmark.ext.gfm.strikethrough.Strikethrough; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.Emphasis; +import org.commonmark.node.Heading; +import org.commonmark.node.ListItem; +import org.commonmark.node.StrongEmphasis; + +import io.noties.debug.Debug; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; +import io.noties.markwon.sample.R; + +public class NotificationActivity extends ActivityWithMenuOptions { + + private static final String CHANNEL_ID = "whatever"; + + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("bold-italic", this::bold_italic) + .add("heading", this::heading) + .add("lists", this::lists) + .add("image", this::image) + .add("link", this::link) + .add("blockquote", this::blockquote) + .add("strikethrough", this::strikethrough); + } + + private void bold_italic() { + // Unfortunately we cannot just use Markwon created CharSequence in a RemoteViews context + // because it requires for spans to be platform ones + + final String md = "Just a **bold** here and _italic_, but what if **it is bold _and italic_**?"; + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder + .setFactory(StrongEmphasis.class, (configuration, props) -> new StyleSpan(Typeface.BOLD)) + .setFactory(Emphasis.class, (configuration, props) -> new StyleSpan(Typeface.ITALIC)); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void heading() { + + // please note that heading doesn't seem to be working in remote views, + // tried both `RelativeSizeSpan` and `AbsoluteSizeSpan` with no effect + + final float base = 12; + + final float[] sizes = { + 2.F, 1.5F, 1.17F, 1.F, .83F, .67F, + }; + + final String md = "" + + "# H1\n" + + "## H2\n" + + "### H3\n" + + "#### H4\n" + + "##### H5\n" + + "###### H6\n\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Heading.class, (configuration, props) -> { + final Integer level = CoreProps.HEADING_LEVEL.get(props); + Debug.i(level); + if (level != null && level > 0 && level <= sizes.length) { +// return new RelativeSizeSpan(sizes[level - 1]); + final Object span = new AbsoluteSizeSpan((int) (base * sizes[level - 1] + .5F), true); + return new Object[]{ + span, + new StyleSpan(Typeface.BOLD) + }; + } + return null; + }); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void lists() { + final String md = "" + + "* bullet 1\n" + + "* bullet 2\n" + + "* * bullet 2 1\n" + + " * bullet 2 0 1\n" + + "1) order 1\n" + + "1) order 2\n" + + "1) order 3\n"; + + // ordered lists _could_ be translated to raw text representation (`1.`, `1)` etc) in resulting markdown + // or they could be _disabled_ all together... (can ordered lists be disabled in parser?) + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(ListItem.class, (configuration, props) -> { + final CoreProps.ListItemType type = CoreProps.LIST_ITEM_TYPE.get(props); + if (type != null) { + // bullet and ordered list share the same markdown node + return new BulletSpan(); + } + return null; + }); + } + }) + .build(); + + display(markwon.toMarkdown(md)); + } + + private void image() { + // please note that image _could_ be supported only if it would be available immediately + // debugging possibility + // + // doesn't seem to be working + + final Bitmap bitmap = Bitmap.createBitmap(128, 256, Bitmap.Config.ARGB_4444); + final Canvas canvas = new Canvas(bitmap); + canvas.drawColor(0xFFAD1457); + + final SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append("An image: "); + + final int length = builder.length(); + builder.append("[bitmap]"); + builder.setSpan( + new ImageSpan(this, bitmap, DynamicDrawableSpan.ALIGN_BOTTOM), + length, + builder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ); + + builder.append(" okay, and "); + + final int start = builder.length(); + builder.append("[resource]"); + builder.setSpan( + new ImageSpan(this, R.drawable.ic_memory_black_48dp), + start, + builder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ); + + display(builder); + } + + private void link() { + final String md = "" + + "[a link](https://isa.link/) is here, styling yes, clicking - no"; + display(Markwon.create(this).toMarkdown(md)); + } + + private void blockquote() { + final String md = "" + + "> This was once said by me\n" + + "> > And this one also\n\n" + + "Me"; + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(BlockQuote.class, (configuration, props) -> new QuoteSpan()); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void strikethrough() { + final String md = "~~strike that!~~"; + final Markwon markwon = Markwon.builder(this) + .usePlugin(new StrikethroughPlugin()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Strikethrough.class, (configuration, props) -> new StrikethroughSpan()); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void display(@NonNull CharSequence cs) { + final NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + if (manager == null) { + throw new IllegalStateException("No NotificationManager is available"); + } + + ensureChannel(manager); + + final Notification.Builder builder = new Notification.Builder(this) + .setSmallIcon(R.drawable.ic_stat_name) + .setContentTitle("Markwon") + .setContentText(cs) + .setStyle(new Notification.BigTextStyle().bigText(cs)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setChannelId(CHANNEL_ID); + } + + manager.notify(1, builder.build()); + } + + private void ensureChannel(@NonNull NotificationManager manager) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return; + } + + final NotificationChannel channel = manager.getNotificationChannel(CHANNEL_ID); + if (channel == null) { + manager.createNotificationChannel(new NotificationChannel( + CHANNEL_ID, + CHANNEL_ID, + NotificationManager.IMPORTANCE_DEFAULT)); + } + } +} diff --git a/sample/src/main/res/drawable-anydpi-v24/ic_stat_name.xml b/sample/src/main/res/drawable-anydpi-v24/ic_stat_name.xml new file mode 100644 index 00000000..fd7cefc2 --- /dev/null +++ b/sample/src/main/res/drawable-anydpi-v24/ic_stat_name.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/sample/src/main/res/drawable-hdpi/ic_stat_name.png b/sample/src/main/res/drawable-hdpi/ic_stat_name.png new file mode 100644 index 0000000000000000000000000000000000000000..19e7a26b3b180660ea0a6d9430f2a1324f9da287 GIT binary patch literal 513 zcmV+c0{;DpP)*o+g4g`bSX3yK8K6w9b8n~+O*LYhQ#z; zIKts@o$j3(Yl1oO1DW&3k2&|;dnb{IEw(=}*LAbt1Ps7Em=SlN4~{|RH-uEcE%NpJ?vz1D*AQH+XBvgbWk*Rk33mTa1G%r_Km%x=l&?HP)NzL~zIE?+08!-|cB zZ!Si;smX7On7Qzen6G0ZsLvAG!Z#6nQC^du7kkh|_}k3aHxV>t2?v^Xbg6Y!j~Z2q z>B_dKnymiRM9_?azd&jS$54uDE1UgGL0elEk|67rc`fP|IJBe{bWwYaz2oEh;RGFf zuZ^(?-Yvwa*kYiK(d+0SUjw`uh*2>Ok!yc+m>jO^V5Ix=S|emlzH(@XtLgQFgr9;- zLjA6Ue*K8#1ub${*hpUTBl#H=$q&WIeoDs{o5sEY5t|;T!-c>b00000NkvXXu0mjf Dp2_dd literal 0 HcmV?d00001 diff --git a/sample/src/main/res/drawable-mdpi/ic_stat_name.png b/sample/src/main/res/drawable-mdpi/ic_stat_name.png new file mode 100644 index 0000000000000000000000000000000000000000..0525d8746741507a21e5dcae150b947eeb7aa447 GIT binary patch literal 346 zcmV-g0j2(lP)ifEhVxe$h;-248|ZxPcUe z22IGm8?}onGrx0YV+Z}1S{XAf69*JuU4j>q$e5G0CM>8}%61oQ!Q7yov#-l`%CU(p z!I4W1*-umAu6 literal 0 HcmV?d00001 diff --git a/sample/src/main/res/drawable-xhdpi/ic_stat_name.png b/sample/src/main/res/drawable-xhdpi/ic_stat_name.png new file mode 100644 index 0000000000000000000000000000000000000000..c5f2f07610c1453770c4cc984d2ae2401320f764 GIT binary patch literal 687 zcmV;g0#N;lP)jhG9Hq?0mgaieLfF|NF z|4LI>qp`DVuL&5B^huFD^Y3iDo;_SslUQPjCFVBAahkvYm;?)8o__a%#^0Ls5ZDEm zj=z0@bFd8te?#Kq;KB)Rr(mQSiT8tJ$DSR4-k(Z-8a#O<@(zk%4-~*GQJ`x@@{#NT zWMtf+&u|bd3GO9U|HG^jh`(dTgY;_{jSxWfq(oAh#(6zFZ&{@o-g#Chv9r5|Y z+G5UNIbAR8rgTij)JL|QHrdeNS}oWBFB*xzkZ*0|8yeFsC9b!FO*&#MyJE_8P2Fq% zu`OlTO+9=%6jj~S|M_E;SR8D{;%G4zhr6*jUXR5Gj@W!+@>z=it&e<(C6-wA>>Gl< VCxPrB2><{9002ovPDHLkV1j9XJI(+A literal 0 HcmV?d00001 diff --git a/sample/src/main/res/drawable-xxhdpi/ic_stat_name.png b/sample/src/main/res/drawable-xxhdpi/ic_stat_name.png new file mode 100644 index 0000000000000000000000000000000000000000..993df1f0d759f41fe0247a987270744b9ce64319 GIT binary patch literal 1044 zcmV+v1nc{WP)6 zd6$6$z(>G&{{0O25O@pN@jnQX1>Oa|1%3wp@zik!+y%Y@4*qMxTmvowzj@}c z4}1>1a*>58;Bfiv*~2k#$^Q~Ak}#KmPk}!?cQ^z-2IkT(&Q0K6j0gRRjEXw2H?874 z03Inm$N_KzI0d{7%!l~P<7ZR&zXyuGzXR{3O`KVURp|t{4lKopQ$imn3aj2sTEuxN zN%j;c>w<*^;8YTOJ`KMd5k3Kn7ULH&h6poBiE|%+%3<(_Th(9D9IcvF!4N-lJ}2R7 zbto88V+nj=V}c>)Puw?L);V7U;tVA?jr8tBva-7fh5Wg_PnHkw`g#P*>;*yMmaM1tiAIg|-sJJjlgbLI)r zPs*AUs6-^UCa{^7?8|yI;`d1QU(*8HHNs}lq(EhyFObcI+peN7Kk@ z2_DUaVM1cpDQMKMM#y88@a4zol-N!XwgV;v8X=tDQLya=)&e#Sta95Ta*|l?4-NxkI@ZxMu5KTQ+KNIGZTb{rBy;qP!HryAK(YO^u8+js#v}Z!taCdp z(aQ^gpRe)jFJRY&=U+a%BKk=F`U@t)7pN2x;wyLx$?+x57zHF>13vdl;)~>xHvG_;o0aiIiWcQ|JQlE18N-H-4#CLoj|VoWWo)7z_p@#rO+5y9eqhf~vXz O0000M&r-` literal 0 HcmV?d00001 diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml index d174e736..0305b471 100644 --- a/sample/src/main/res/values/strings-samples.xml +++ b/sample/src/main/res/values/strings-samples.xml @@ -35,4 +35,6 @@ # \# Images\n\nUsage of different images plugins + # \# Notification\n\nExample usage in notifications and other remote views + \ No newline at end of file From 815f7338921efa2c0775937ec510a60ec8b15cef Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 16 Mar 2020 10:30:44 +0300 Subject: [PATCH 31/33] Sample, anchor links --- .../basicplugins/BasicPluginsActivity.java | 103 +++++++++++++++- .../markwon/sample/html/HtmlActivity.java | 112 +++++++++++++----- .../main/res/layout/activity_text_view.xml | 1 + sample/src/main/res/values/strings.xml | 12 ++ 4 files changed, 197 insertions(+), 31 deletions(-) diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index 632c94ec..ce73a7f8 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -3,8 +3,12 @@ package io.noties.markwon.sample.basicplugins; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; +import android.text.Spannable; +import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; +import android.view.View; +import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.NonNull; @@ -19,6 +23,7 @@ import java.util.Collections; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.BlockHandlerDef; +import io.noties.markwon.LinkResolverDef; import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; @@ -26,6 +31,7 @@ import io.noties.markwon.MarkwonVisitor; import io.noties.markwon.SoftBreakAddsNewLinePlugin; import io.noties.markwon.core.CoreProps; import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.core.spans.HeadingSpan; import io.noties.markwon.core.spans.LastLineSpacingSpan; import io.noties.markwon.image.ImageItem; import io.noties.markwon.image.ImagesPlugin; @@ -39,6 +45,7 @@ import io.noties.markwon.sample.R; public class BasicPluginsActivity extends ActivityWithMenuOptions { private TextView textView; + private ScrollView scrollView; @NonNull @Override @@ -54,7 +61,8 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { .add("additionalSpacing", this::additionalSpacing) .add("headingNoSpace", this::headingNoSpace) .add("headingNoSpaceBlockHandler", this::headingNoSpaceBlockHandler) - .add("allBlocksNoForcedLine", this::allBlocksNoForcedLine); + .add("allBlocksNoForcedLine", this::allBlocksNoForcedLine) + .add("anchor", this::anchor); } @Override @@ -63,6 +71,7 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { setContentView(R.layout.activity_text_view); textView = findViewById(R.id.text_view); + scrollView = findViewById(R.id.scroll_view); paragraphSpan(); // @@ -403,4 +412,96 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { // rendering lifecycle (before/after) // renderProps // process + + private static class AnchorSpan { + final String anchor; + + AnchorSpan(@NonNull String anchor) { + this.anchor = anchor; + } + } + + @NonNull + private String createAnchor(@NonNull CharSequence content) { + return String.valueOf(content) + .replaceAll("[^\\w]", "") + .toLowerCase(); + } + + private static class AnchorLinkResolver extends LinkResolverDef { + + interface ScrollTo { + void scrollTo(@NonNull View view, int top); + } + + private final ScrollTo scrollTo; + + AnchorLinkResolver(@NonNull ScrollTo scrollTo) { + this.scrollTo = scrollTo; + } + + @Override + public void resolve(@NonNull View view, @NonNull String link) { + if (link.startsWith("#")) { + final TextView textView = (TextView) view; + final Spanned spanned = (Spannable) textView.getText(); + final AnchorSpan[] spans = spanned.getSpans(0, spanned.length(), AnchorSpan.class); + if (spans != null) { + final String anchor = link.substring(1); + for (AnchorSpan span: spans) { + if (anchor.equals(span.anchor)) { + final int start = spanned.getSpanStart(span); + final int line = textView.getLayout().getLineForOffset(start); + final int top = textView.getLayout().getLineTop(line); + scrollTo.scrollTo(textView, top); + return; + } + } + } + } + super.resolve(view, link); + } + } + + private void anchor() { + final String lorem = getString(R.string.lorem); + final String md = "" + + "Hello [there](#there)!\n\n\n" + + lorem + "\n\n" + + "# There!\n\n" + + lorem; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.linkResolver(new AnchorLinkResolver((view, top) -> { + scrollView.smoothScrollTo(0, top); + })); + } + + @Override + public void afterSetText(@NonNull TextView textView) { + final Spannable spannable = (Spannable) textView.getText(); + // obtain heading spans + final HeadingSpan[] spans = spannable.getSpans(0, spannable.length(), HeadingSpan.class); + if (spans != null) { + for (HeadingSpan span : spans) { + final int start = spannable.getSpanStart(span); + final int end = spannable.getSpanEnd(span); + final int flags = spannable.getSpanFlags(span); + spannable.setSpan( + new AnchorSpan(createAnchor(spannable.subSequence(start, end))), + start, + end, + flags + ); + } + } + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } } diff --git a/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java b/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java index 21613985..eecd1c3b 100644 --- a/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java @@ -1,6 +1,5 @@ package io.noties.markwon.sample.html; -import android.app.Activity; import android.os.Bundle; import android.text.Layout; import android.text.TextUtils; @@ -27,9 +26,22 @@ import io.noties.markwon.html.HtmlTag; import io.noties.markwon.html.MarkwonHtmlRenderer; import io.noties.markwon.html.TagHandler; import io.noties.markwon.html.tag.SimpleTagHandler; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; -public class HtmlActivity extends Activity { +public class HtmlActivity extends ActivityWithMenuOptions { + + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("align", this::align) + .add("randomCharSize", this::randomCharSize) + .add("enhance", this::enhance); + } + + private TextView textView; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -39,35 +51,9 @@ public class HtmlActivity extends Activity { // let's define some custom tag-handlers - final TextView textView = findViewById(R.id.text_view); + textView = findViewById(R.id.text_view); - final Markwon markwon = Markwon.builder(this) - .usePlugin(HtmlPlugin.create()) - .usePlugin(new AbstractMarkwonPlugin() { - @Override - public void configure(@NonNull Registry registry) { - registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin - .addHandler(new AlignTagHandler()) - .addHandler(new RandomCharSize(new Random(42L), textView.getTextSize())) - .addHandler(new EnhanceTagHandler((int) (textView.getTextSize() * 2 + .05F)))); - } - }) - .build(); - - final String markdown = "# Hello, HTML\n" + - "\n" + - "We are centered\n" + - "\n" + - "We are at the end\n" + - "\n" + - "We should be at the start\n" + - "\n" + - "\n" + - "This message should have a jumpy feeling because of different sizes of characters\n" + - "\n\n" + - "This is text that must be enhanced, at least a part of it"; - - markwon.setMarkdown(textView, markdown); + align(); } // we can use `SimpleTagHandler` for _simple_ cases (when the whole tag content @@ -105,6 +91,31 @@ public class HtmlActivity extends Activity { } } + private void align() { + + final String md = "" + + "We are centered\n" + + "\n" + + "We are at the end\n" + + "\n" + + "We should be at the start\n" + + "\n"; + + + final Markwon markwon = Markwon.builder(this) + .usePlugin(HtmlPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin + .addHandler(new AlignTagHandler())); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } + // each character will have random size private static class RandomCharSize extends TagHandler { @@ -139,6 +150,27 @@ public class HtmlActivity extends Activity { } } + private void randomCharSize() { + + final String md = "" + + "\n" + + "This message should have a jumpy feeling because of different sizes of characters\n" + + "\n\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(HtmlPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin + .addHandler(new RandomCharSize(new Random(42L), textView.getTextSize()))); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } + private static class EnhanceTagHandler extends TagHandler { private final int enhanceTextSize; @@ -187,4 +219,24 @@ public class HtmlActivity extends Activity { return position; } } + + private void enhance() { + + final String md = "" + + "This is text that must be enhanced, at least a part of it"; + + + final Markwon markwon = Markwon.builder(this) + .usePlugin(HtmlPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin + .addHandler(new EnhanceTagHandler((int) (textView.getTextSize() * 2 + .05F)))); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } } diff --git a/sample/src/main/res/layout/activity_text_view.xml b/sample/src/main/res/layout/activity_text_view.xml index 9828f257..c3904e70 100644 --- a/sample/src/main/res/layout/activity_text_view.xml +++ b/sample/src/main/res/layout/activity_text_view.xml @@ -1,5 +1,6 @@ diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 5505eb0c..36d27f2a 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -12,4 +12,16 @@ Sentiment Satisfied 64 red: @ic-sentiment_satisfied-red-64 ]]>
+ + From b5a30a55b344e5fd6c92369b883e5d8b993bfea4 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 18 Mar 2020 13:53:56 +0300 Subject: [PATCH 32/33] Update samples --- build.gradle | 3 +- .../basicplugins/BasicPluginsActivity.java | 4 +-- .../markwon/sample/html/HtmlActivity.java | 31 ++++++++++++++++++- .../markwon/sample/latex/LatexActivity.java | 20 +++++++++++- .../main/res/layout/activity_text_view.xml | 6 ++-- 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 582e26ae..43348c63 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + // on `3.5.3` tests are not run from CLI + classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0' } } diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index ce73a7f8..db509be9 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -475,9 +475,7 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { - builder.linkResolver(new AnchorLinkResolver((view, top) -> { - scrollView.smoothScrollTo(0, top); - })); + builder.linkResolver(new AnchorLinkResolver((view, top) -> scrollView.smoothScrollTo(0, top))); } @Override diff --git a/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java b/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java index eecd1c3b..db5ca541 100644 --- a/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java @@ -11,6 +11,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; +import org.commonmark.node.Paragraph; + import java.util.Collection; import java.util.Collections; import java.util.Random; @@ -26,6 +28,7 @@ import io.noties.markwon.html.HtmlTag; import io.noties.markwon.html.MarkwonHtmlRenderer; import io.noties.markwon.html.TagHandler; import io.noties.markwon.html.tag.SimpleTagHandler; +import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; @@ -38,7 +41,8 @@ public class HtmlActivity extends ActivityWithMenuOptions { return MenuOptions.create() .add("align", this::align) .add("randomCharSize", this::randomCharSize) - .add("enhance", this::enhance); + .add("enhance", this::enhance) + .add("image", this::image); } private TextView textView; @@ -239,4 +243,29 @@ public class HtmlActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + + private void image() { + // treat unclosed/void `img` tag as HTML inline + final String md = "" + + "## Try CommonMark\n" + + "\n" + + "Markwon IMG:\n" + + "\n" + + "![](https://upload.wikimedia.org/wikipedia/it/thumb/c/c5/GTA_2.JPG/220px-GTA_2.JPG)\n" + + "\n" + + "New lines...\n" + + "\n" + + "HTML IMG:\n" + + "\n" + + "\n" + + "\n" + + "New lines\n\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(ImagesPlugin.create()) + .usePlugin(HtmlPlugin.create()) + .build(); + + markwon.setMarkdown(textView, md); + } } 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 7e885a06..62e54c15 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 @@ -5,6 +5,7 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; @@ -51,6 +52,7 @@ public class LatexActivity extends ActivityWithMenuOptions { } private TextView textView; + private View parent; @NonNull @Override @@ -65,7 +67,8 @@ public class LatexActivity extends ActivityWithMenuOptions { .add("legacy", this::legacy) .add("textColor", this::textColor) .add("defaultTextColor", this::defaultTextColor) - .add("inlineAndBlock", this::inlineAndBlock); + .add("inlineAndBlock", this::inlineAndBlock) + .add("dark", this::dark); } @Override @@ -74,6 +77,9 @@ public class LatexActivity extends ActivityWithMenuOptions { // reset text color textView.setTextColor(0xFF000000); + + // reset background + parent.setBackgroundColor(0xFFffffff); } @Override @@ -82,6 +88,7 @@ public class LatexActivity extends ActivityWithMenuOptions { setContentView(R.layout.activity_text_view); textView = findViewById(R.id.text_view); + parent = findViewById(R.id.scroll_view); // array(); longDivision(); @@ -203,6 +210,17 @@ public class LatexActivity extends ActivityWithMenuOptions { renderWithBlocksAndInlines(md); } + private void dark() { + parent.setBackgroundColor(0xFF000000); + textView.setTextColor(0xFFffffff); + + String latex = "W=W_1+W_2=F_1X_1-F_2X_2"; + final String md = "" + + "# LaTeX inside a blockquote\n" + + "> $$" + latex + "$$\n"; + renderWithBlocksAndInlines(md); + } + @NonNull private static String wrapLatexInSampleMarkdown(@NonNull String latex) { return "" + diff --git a/sample/src/main/res/layout/activity_text_view.xml b/sample/src/main/res/layout/activity_text_view.xml index c3904e70..e557a4bc 100644 --- a/sample/src/main/res/layout/activity_text_view.xml +++ b/sample/src/main/res/layout/activity_text_view.xml @@ -2,13 +2,15 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/scroll_view" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" + android:padding="8dip"> Date: Wed, 18 Mar 2020 14:20:58 +0300 Subject: [PATCH 33/33] Prepare 4.3.0 release --- docs/docs/v4/core/visitor.md | 29 +++++++++++ docs/docs/v4/inline-parser/README.md | 10 ++++ gradle.properties | 2 +- .../io/noties/markwon/BlockHandlerDef.java | 2 +- .../io/noties/markwon/LinkResolverDef.java | 4 +- .../io/noties/markwon/MarkwonVisitor.java | 8 +-- .../io/noties/markwon/MarkwonVisitorImpl.java | 4 +- .../markwon/SoftBreakAddsNewLinePlugin.java | 2 +- .../noties/markwon/image/AsyncDrawable.java | 2 +- .../ext/latex/JLatexAsyncDrawableSpan.java | 2 +- .../latex/JLatexInlineAsyncDrawableSpan.java | 2 +- .../ext/latex/JLatexMathBlockParser.java | 2 +- .../latex/JLatexMathBlockParserLegacy.java | 2 +- .../ext/latex/JLatexMathInlineProcessor.java | 2 +- .../markwon/ext/latex/JLatexMathNode.java | 2 +- .../markwon/ext/latex/JLatexMathPlugin.java | 36 ++++++------- .../markwon/ext/latex/JLatexMathTheme.java | 4 +- .../ext/latex/JLatextAsyncDrawable.java | 2 +- .../MarkwonInlineParserPlugin.java | 2 +- .../noties/markwon/linkify/LinkifyPlugin.java | 8 +-- release-management.md | 6 ++- .../basicplugins/BasicPluginsActivity.java | 34 ++++++------- .../inlineparser/InlineParserActivity.java | 51 ++++++++++++++++++- .../markwon/sample/latex/LatexActivity.java | 2 +- 24 files changed, 156 insertions(+), 64 deletions(-) diff --git a/docs/docs/v4/core/visitor.md b/docs/docs/v4/core/visitor.md index d03ff848..6a0591ce 100644 --- a/docs/docs/v4/core/visitor.md +++ b/docs/docs/v4/core/visitor.md @@ -70,4 +70,33 @@ public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { } }); } +``` + +### BlockHandler + +Since there is class to control insertions of new lines after markdown blocks +`BlockHandler` (`MarkwonVisitor.BlockHandler`) and its default implementation `BlockHandlerDef`. For example, +to disable an empty new line after `Heading`: + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.blockHandler(new BlockHandlerDef() { + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (node instanceof Heading) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + // ensure new line but do not force insert one + } + } else { + super.blockEnd(visitor, node); + } + } + }); + } + }) + .build(); ``` \ No newline at end of file diff --git a/docs/docs/v4/inline-parser/README.md b/docs/docs/v4/inline-parser/README.md index e9638832..67fa4de4 100644 --- a/docs/docs/v4/inline-parser/README.md +++ b/docs/docs/v4/inline-parser/README.md @@ -3,6 +3,16 @@ **Experimental** commonmark-java inline parser that allows customizing core features and/or extend with own. +:::tip +Since there is also `MarkwonInlineParserPlugin` which can be used +to allow other plugins to customize inline parser +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + .build(); +``` +::: + Usage of _internal_ classes: ```java import org.commonmark.internal.Bracket; diff --git a/gradle.properties b/gradle.properties index a1dd08b9..f74f7775 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ android.enableJetifier=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=4.3.0-SNAPSHOT +VERSION_NAME=4.3.0 GROUP=io.noties.markwon POM_DESCRIPTION=Markwon markdown for Android diff --git a/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java b/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java index 0c5b3b49..58567f43 100644 --- a/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java +++ b/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; import org.commonmark.node.Node; /** - * @since $nap; + * @since 4.3.0 */ public class BlockHandlerDef implements MarkwonVisitor.BlockHandler { @Override 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 44c5da9c..5882613f 100644 --- a/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java +++ b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java @@ -13,7 +13,7 @@ import androidx.annotation.NonNull; public class LinkResolverDef implements LinkResolver { - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 private static final String DEFAULT_SCHEME = "https"; @Override @@ -30,7 +30,7 @@ public class LinkResolverDef implements LinkResolver { } /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ @NonNull private static Uri parseLink(@NonNull String link) { diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java index f9c4554c..2f69acf6 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java @@ -27,7 +27,7 @@ public interface MarkwonVisitor extends Visitor { * Primary purpose is to control the spacing applied before/after certain blocks, which * visitors are created elsewhere * - * @since $nap; + * @since 4.3.0 */ interface BlockHandler { @@ -50,7 +50,7 @@ public interface MarkwonVisitor extends Visitor { * @param blockHandler to handle block start/end * @see BlockHandler * @see BlockHandlerDef - * @since $nap; + * @since 4.3.0 */ @SuppressWarnings("UnusedReturnValue") @NonNull @@ -158,12 +158,12 @@ public interface MarkwonVisitor extends Visitor { void setSpansForNodeOptional(@NonNull Class node, int start); /** - * @since $nap; + * @since 4.3.0 */ void blockStart(@NonNull Node node); /** - * @since $nap; + * @since 4.3.0 */ void blockEnd(@NonNull Node node); } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java index bd32352e..ce116111 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java @@ -45,7 +45,7 @@ class MarkwonVisitorImpl implements MarkwonVisitor { private final Map, NodeVisitor> nodes; - // @since $nap; + // @since 4.3.0 private final BlockHandler blockHandler; MarkwonVisitorImpl( @@ -316,7 +316,7 @@ class MarkwonVisitorImpl implements MarkwonVisitor { @NonNull @Override public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps) { - // @since $nap; + // @since 4.3.0 BlockHandler blockHandler = this.blockHandler; if (blockHandler == null) { blockHandler = new BlockHandlerDef(); diff --git a/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java index f1aff45d..9c7572fb 100644 --- a/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; import org.commonmark.node.SoftLineBreak; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ public class SoftBreakAddsNewLinePlugin extends AbstractMarkwonPlugin { diff --git a/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java index 935313d5..8447110a 100644 --- a/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java @@ -274,7 +274,7 @@ public class AsyncDrawable extends Drawable { } /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ @NonNull private static Rect noDimensionsBounds(@Nullable Drawable result) { diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java index 799aaf37..fa08ba0c 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexAsyncDrawableSpan.java @@ -15,7 +15,7 @@ import ru.noties.jlatexmath.JLatexMathDrawable; import ru.noties.jlatexmath.awt.Color; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ public class JLatexAsyncDrawableSpan extends AsyncDrawableSpan { 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 index 09fdd553..0edda9d3 100644 --- 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 @@ -12,7 +12,7 @@ import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.image.AsyncDrawable; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ class JLatexInlineAsyncDrawableSpan extends JLatexAsyncDrawableSpan { 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 0d1653c6..cf212e32 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 @@ -12,7 +12,7 @@ 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, + * @since 4.3.0 (although there was a class with the same name, * which is renamed now to {@link JLatexMathBlockParserLegacy}) */ class JLatexMathBlockParser extends AbstractBlockParser { 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 index 2506ca21..9b4565bc 100644 --- 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 @@ -9,7 +9,7 @@ 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) + * @since 4.3.0 (although it is just renamed parser from previous versions) */ class JLatexMathBlockParserLegacy extends AbstractBlockParser { 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 index 364e8d54..d368778c 100644 --- 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 @@ -9,7 +9,7 @@ import java.util.regex.Pattern; import io.noties.markwon.inlineparser.InlineProcessor; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ class JLatexMathInlineProcessor extends InlineProcessor { diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java index db7029a9..46948cc5 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java @@ -3,7 +3,7 @@ package io.noties.markwon.ext.latex; import org.commonmark.node.CustomNode; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ public class JLatexMathNode extends CustomNode { 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 f39b53af..87456358 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 @@ -40,7 +40,7 @@ import ru.noties.jlatexmath.JLatexMathDrawable; public class JLatexMathPlugin extends AbstractMarkwonPlugin { /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ public interface ErrorHandler { @@ -64,7 +64,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ @NonNull public static JLatexMathPlugin create(@Px float inlineTextSize, @Px float blockTextSize) { @@ -84,7 +84,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ @NonNull public static JLatexMathPlugin create( @@ -102,7 +102,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ @NonNull public static JLatexMathPlugin.Builder builder(@Px float inlineTextSize, @Px float blockTextSize) { @@ -112,15 +112,15 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @VisibleForTesting static class Config { - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 final JLatexMathTheme theme; - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 final boolean blocksEnabled; final boolean blocksLegacy; final boolean inlinesEnabled; - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 final ErrorHandler errorHandler; final ExecutorService executorService; @@ -166,7 +166,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void configureParser(@NonNull Parser.Builder builder) { - // @since $nap; + // @since 4.3.0 if (config.blocksEnabled) { if (config.blocksLegacy) { builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory()); @@ -278,15 +278,15 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @SuppressWarnings({"unused", "UnusedReturnValue"}) public static class Builder { - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 private final JLatexMathTheme.Builder theme; - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 private boolean blocksEnabled = true; private boolean blocksLegacy; private boolean inlinesEnabled; - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 private ErrorHandler errorHandler; // @since 4.0.0 @@ -302,7 +302,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } /** - * @since $nap; + * @since 4.3.0 */ @NonNull public Builder blocksEnabled(boolean blocksEnabled) { @@ -312,7 +312,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { /** * @param blocksLegacy indicates if blocks should be handled in legacy mode ({@code pre 4.3.0}) - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ @NonNull public Builder blocksLegacy(boolean blocksLegacy) { @@ -323,7 +323,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { /** * @param inlinesEnabled indicates if inline parsing should be enabled. * NB, this requires `MarkwonInlineParserPlugin` to be used when creating `MarkwonInstance` - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ @NonNull public Builder inlinesEnabled(boolean inlinesEnabled) { @@ -384,7 +384,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { try { execute(); } catch (Throwable t) { - // @since 4.3.0-SNAPSHOT add error handling + // @since 4.3.0 add error handling final ErrorHandler errorHandler = config.errorHandler; if (errorHandler == null) { // as before @@ -444,7 +444,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return null; } - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 @NonNull private JLatexMathDrawable createBlockDrawable(@NonNull JLatextAsyncDrawable drawable) { @@ -476,7 +476,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return builder.build(); } - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 @NonNull private JLatexMathDrawable createInlineDrawable(@NonNull JLatextAsyncDrawable drawable) { @@ -507,7 +507,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return builder.build(); } - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 private void setResult(@NonNull final AsyncDrawable drawable, @NonNull final Drawable result) { // we must post to handler, but also have a way to identify the drawable // for which we are posting (in case of cancellation) 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 e729060b..d882e9c1 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 @@ -10,7 +10,7 @@ import androidx.annotation.Px; import ru.noties.jlatexmath.JLatexMathDrawable; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ public abstract class JLatexMathTheme { @@ -35,7 +35,7 @@ public abstract class JLatexMathTheme { } /** - * Moved from {@link JLatexMathPlugin} in {@code 4.3.0-SNAPSHOT} version + * Moved from {@link JLatexMathPlugin} in {@code 4.3.0} version * * @since 4.0.0 */ 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 index 4376d636..25c67262 100644 --- 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 @@ -9,7 +9,7 @@ import io.noties.markwon.image.ImageSize; import io.noties.markwon.image.ImageSizeResolver; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ class JLatextAsyncDrawable extends AsyncDrawable { 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 index ce80501b..470e2fb8 100644 --- 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 @@ -7,7 +7,7 @@ import org.commonmark.parser.Parser; import io.noties.markwon.AbstractMarkwonPlugin; /** - * @since 4.3.0-SNAPSHOT + * @since 4.3.0 */ public class MarkwonInlineParserPlugin extends AbstractMarkwonPlugin { diff --git a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java index ec087741..ded176f6 100644 --- a/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java +++ b/markwon-linkify/src/main/java/io/noties/markwon/linkify/LinkifyPlugin.java @@ -42,7 +42,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { * @param useCompat If true, use {@link LinkifyCompat} to handle links. * Note that the {@link LinkifyCompat} depends on androidx.core:core, * the dependency must be added on a client side explicitly. - * @since 4.3.0-SNAPSHOT `useCompat` argument + * @since 4.3.0 `useCompat` argument */ @NonNull public static LinkifyPlugin create(boolean useCompat) { @@ -58,7 +58,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { * @param useCompat If true, use {@link LinkifyCompat} to handle links. * Note that the {@link LinkifyCompat} depends on androidx.core:core, * the dependency must be added on a client side explicitly. - * @since 4.3.0-SNAPSHOT `useCompat` argument + * @since 4.3.0 `useCompat` argument */ @NonNull public static LinkifyPlugin create(@LinkifyMask int mask, boolean useCompat) { @@ -80,7 +80,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { @Override public void apply(@NonNull CorePlugin corePlugin) { final LinkifyTextAddedListener listener; - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 if (useCompat) { listener = new LinkifyCompatTextAddedListener(mask); } else { @@ -140,7 +140,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin { } } - // @since 4.3.0-SNAPSHOT + // @since 4.3.0 private static class LinkifyCompatTextAddedListener extends LinkifyTextAddedListener { LinkifyCompatTextAddedListener(int mask) { diff --git a/release-management.md b/release-management.md index 7338d9fc..ce66bb97 100644 --- a/release-management.md +++ b/release-management.md @@ -41,4 +41,8 @@ whenever a new API method/field/functionality-change is introduced (`snc`): @since $nap; ``` -This live template would be possible to use in both inline comment and javadoc comment. \ No newline at end of file +This live template would be possible to use in both inline comment and javadoc comment. + +## documentation + +If there are updates to documentation web site, do not forget to publish it \ No newline at end of file diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index db509be9..e8bb4761 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -323,26 +323,26 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { } private void headingNoSpaceBlockHandler() { - final Markwon markwon = Markwon.builder(this) - .usePlugin(new AbstractMarkwonPlugin() { +final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.blockHandler(new BlockHandlerDef() { @Override - public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { - builder.blockHandler(new BlockHandlerDef() { - @Override - public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { - if (node instanceof Heading) { - if (visitor.hasNext(node)) { - visitor.ensureNewLine(); - // ensure new line but do not force insert one - } - } else { - super.blockEnd(visitor, node); - } + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (node instanceof Heading) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + // ensure new line but do not force insert one } - }); + } else { + super.blockEnd(visitor, node); + } } - }) - .build(); + }); + } + }) + .build(); final String md = "" + "# Title title title title title title title title title title \n\ntext text text text"; diff --git a/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java b/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java index 833a63b1..4e7c87da 100644 --- a/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/inlineparser/InlineParserActivity.java @@ -24,6 +24,7 @@ import io.noties.markwon.Markwon; import io.noties.markwon.inlineparser.BackticksInlineProcessor; import io.noties.markwon.inlineparser.CloseBracketInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParser; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import io.noties.markwon.inlineparser.OpenBracketInlineProcessor; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; @@ -38,7 +39,9 @@ public class InlineParserActivity extends ActivityWithMenuOptions { public MenuOptions menuOptions() { return MenuOptions.create() .add("links_only", this::links_only) - .add("disable_code", this::disable_code); + .add("disable_code", this::disable_code) + .add("pluginWithDefaults", this::pluginWithDefaults) + .add("pluginNoDefaults", this::pluginNoDefaults); } @Override @@ -124,4 +127,50 @@ public class InlineParserActivity extends ActivityWithMenuOptions { "**Good day!**"; markwon.setMarkdown(textView, md); } + + private void pluginWithDefaults() { + // a plugin with defaults registered + + final String md = "no [links](#) for **you** `code`!"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + // the same as: +// .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilder())) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(MarkwonInlineParserPlugin.class, plugin -> { + plugin.factoryBuilder() + .excludeInlineProcessor(OpenBracketInlineProcessor.class); + }); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } + + private void pluginNoDefaults() { + // a plugin with NO defaults registered + + final String md = "no [links](#) for **you** `code`!"; + + final Markwon markwon = Markwon.builder(this) + // pass `MarkwonInlineParser.factoryBuilderNoDefaults()` no disable all + .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults())) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(MarkwonInlineParserPlugin.class, plugin -> { + plugin.factoryBuilder() + .addInlineProcessor(new BackticksInlineProcessor()); + }); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } + } 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 62e54c15..b8a7d9e3 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 @@ -173,7 +173,7 @@ public class LatexActivity extends ActivityWithMenuOptions { } private void defaultTextColor() { - // @since 4.3.0-SNAPSHOT text color is automatically taken from textView + // @since 4.3.0 text color is automatically taken from textView // (if it's not specified explicitly via configuration) textView.setTextColor(0xFFff0000);