From a298016ac2c795e3b6b9ba61285aa1a89106b13b Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Sun, 2 Feb 2020 17:46:18 +0300 Subject: [PATCH 1/8] Working with inline latex parsing --- markwon-ext-latex/build.gradle | 2 + .../ext/latex/JLatexMathBlockParser.java | 2 + .../markwon/ext/latex/JLatexMathInline.java | 19 ++++ .../markwon/ext/latex/JLatexMathPlugin.java | 94 ++++++++++++++++++- sample/build.gradle | 1 + 5 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java diff --git a/markwon-ext-latex/build.gradle b/markwon-ext-latex/build.gradle index 6e684440..9d5f50b0 100644 --- a/markwon-ext-latex/build.gradle +++ b/markwon-ext-latex/build.gradle @@ -19,6 +19,8 @@ dependencies { api deps['jlatexmath-android'] + debugImplementation project(':markwon-inline-parser') + deps['test'].with { testImplementation it['junit'] testImplementation it['robolectric'] diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java index 8f54f245..ba941c35 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java @@ -60,6 +60,8 @@ public class JLatexMathBlockParser extends AbstractBlockParser { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + // ^\s{0,3}\$\$\s*$ as a regex to star the block + final CharSequence line = state.getLine(); final int length = line != null ? line.length() diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java new file mode 100644 index 00000000..b5d6810d --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java @@ -0,0 +1,19 @@ +package io.noties.markwon.ext.latex; + +import org.commonmark.node.CustomNode; + +/** + * @since 4.2.1-SNAPSHOT + */ +public class JLatexMathInline extends CustomNode { + + private String latex; + + public String latex() { + return latex; + } + + public void latex(String latex) { + this.latex = latex; + } +} diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 5d136ece..10c4865b 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -14,6 +14,8 @@ import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.VisibleForTesting; +import org.commonmark.node.Node; +import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.Parser; import java.util.HashMap; @@ -30,6 +32,9 @@ import io.noties.markwon.image.AsyncDrawableLoader; import io.noties.markwon.image.AsyncDrawableScheduler; import io.noties.markwon.image.AsyncDrawableSpan; import io.noties.markwon.image.ImageSizeResolver; +import io.noties.markwon.image.ImageSizeResolverDef; +import io.noties.markwon.inlineparser.InlineProcessor; +import io.noties.markwon.inlineparser.MarkwonInlineParser; import ru.noties.jlatexmath.JLatexMathDrawable; /** @@ -120,7 +125,17 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void configureParser(@NonNull Parser.Builder builder) { - builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + + // what we can do: + // [0-3] spaces before block start/end + // if it's $$\n -> block + // if it's $$\\dhdsfjh$$ -> inline + +// builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + final InlineParserFactory factory = MarkwonInlineParser.factoryBuilderNoDefaults() + .addInlineProcessor(new LatexInlineProcessor()) + .build(); + builder.inlineParserFactory(factory); } @Override @@ -145,9 +160,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { new AsyncDrawable( latex, jLatextAsyncDrawableLoader, - jLatexImageSizeResolver, + new ImageSizeResolverDef(), null), - AsyncDrawableSpan.ALIGN_BOTTOM, + AsyncDrawableSpan.ALIGN_CENTER, false); visitor.setSpans(length, span); @@ -155,6 +170,77 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { }); } + private static class LatexInlineProcessor extends InlineProcessor { + + @Override + public char specialCharacter() { + return '$'; + } + + @Nullable + @Override + protected Node parse() { + + final int start = index; + + index += 1; + if (peek() != '$') { + index = start; + return null; + } + + // must be not $ + index += 1; + if (peek() == '$') { + return text("$"); + } + + // find next '$$', but not broken with 2(or more) new lines + + boolean dollar = false; + boolean newLine = false; + boolean found = false; + + index += 1; + final int length = input.length(); + + while (index < length) { + final char c = peek(); + if (c == '\n') { + if (newLine) { + // second new line + break; + } + newLine = true; + dollar = false; // cannot be on another line + } else { + newLine = false; + if (c == '$') { + if (dollar) { + found = true; + // advance + index += 1; + break; + } + dollar = true; + } else { + dollar = false; + } + } + index += 1; + } + + if (found) { + final JLatexMathBlock block = new JLatexMathBlock(); + block.latex(input.substring(start + 2, index - 2)); + index += 1; + return block; + } + + return null; + } + } + @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { AsyncDrawableScheduler.unschedule(textView); @@ -182,7 +268,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @JLatexMathDrawable.Align private int align = JLatexMathDrawable.ALIGN_CENTER; - private boolean fitCanvas = true; + private boolean fitCanvas = false; // @since 4.0.0 private int paddingHorizontal; diff --git a/sample/build.gradle b/sample/build.gradle index d2a9e27f..595fd54b 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -49,6 +49,7 @@ dependencies { implementation project(':markwon-syntax-highlight') implementation project(':markwon-image-picasso') + implementation project(':markwon-image-glide') deps.with { implementation it['x-recycler-view'] From d78b278b866e1fbc17207e3fca5306fddaa2cced Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Mon, 10 Feb 2020 22:25:20 +0300 Subject: [PATCH 2/8] Latex inline parsing WIP --- .../ext/latex/JLatexMathBlockParser.java | 33 +++- .../ext/latex/JLatexMathInlineProcessor.java | 36 ++++ ...texMathInline.java => JLatexMathNode.java} | 4 +- .../markwon/ext/latex/JLatexMathPlugin.java | 175 ++++++++++-------- .../markwon/sample/latex/LatexActivity.java | 26 +-- 5 files changed, 174 insertions(+), 100 deletions(-) create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java rename markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/{JLatexMathInline.java => JLatexMathNode.java} (76%) diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java index ba941c35..575692bb 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java @@ -1,5 +1,6 @@ package io.noties.markwon.ext.latex; +import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.AbstractBlockParserFactory; @@ -60,18 +61,30 @@ public class JLatexMathBlockParser extends AbstractBlockParser { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - // ^\s{0,3}\$\$\s*$ as a regex to star the block + final int indent = state.getIndent(); - final CharSequence line = state.getLine(); - final int length = line != null - ? line.length() - : 0; + // check if it's an indented code block + if (indent < Parsing.CODE_BLOCK_INDENT) { + final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); + final CharSequence line = state.getLine(); + final int length = line.length(); + // we are looking for 2 `$$` subsequent signs + // and immediate new-line or arbitrary number of white spaces (we check for the first one) + // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s + final int diff = length - (nextNonSpaceIndex + 2); + if (diff >= 0) { + // check for both `$` + if (line.charAt(nextNonSpaceIndex) == '$' + && line.charAt(nextNonSpaceIndex + 1) == '$') { - if (length > 1) { - if ('$' == line.charAt(0) - && '$' == line.charAt(1)) { - return BlockStart.of(new JLatexMathBlockParser()) - .atIndex(state.getIndex() + 2); + if (diff > 0) { + if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) { + return BlockStart.none(); + } + // consume all until new-line or first not-white-space char + } + + } } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java new file mode 100644 index 00000000..84c26b4f --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java @@ -0,0 +1,36 @@ +package io.noties.markwon.ext.latex; + +import androidx.annotation.Nullable; + +import org.commonmark.node.Node; + +import java.util.regex.Pattern; + +import io.noties.markwon.inlineparser.InlineProcessor; + +/** + * @since 4.3.0-SNAPSHOT + */ +public class JLatexMathInlineProcessor extends InlineProcessor { + + private static final Pattern RE = Pattern.compile("(\\${2})([\\s\\S]+?)\\1"); + + @Override + public char specialCharacter() { + return '$'; + } + + @Nullable + @Override + protected Node parse() { + + final String latex = match(RE); + if (latex == null) { + return null; + } + + final JLatexMathNode node = new JLatexMathNode(); + node.latex(latex.substring(2, latex.length() - 2)); + return node; + } +} diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java similarity index 76% rename from markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java rename to markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java index b5d6810d..db7029a9 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInline.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathNode.java @@ -3,9 +3,9 @@ package io.noties.markwon.ext.latex; import org.commonmark.node.CustomNode; /** - * @since 4.2.1-SNAPSHOT + * @since 4.3.0-SNAPSHOT */ -public class JLatexMathInline extends CustomNode { +public class JLatexMathNode extends CustomNode { private String latex; diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 10c4865b..57485623 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -14,7 +14,6 @@ import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.VisibleForTesting; -import org.commonmark.node.Node; import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.Parser; @@ -33,7 +32,6 @@ import io.noties.markwon.image.AsyncDrawableScheduler; import io.noties.markwon.image.AsyncDrawableSpan; import io.noties.markwon.image.ImageSizeResolver; import io.noties.markwon.image.ImageSizeResolverDef; -import io.noties.markwon.inlineparser.InlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParser; import ru.noties.jlatexmath.JLatexMathDrawable; @@ -132,8 +130,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // if it's $$\\dhdsfjh$$ -> inline // builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); - final InlineParserFactory factory = MarkwonInlineParser.factoryBuilderNoDefaults() - .addInlineProcessor(new LatexInlineProcessor()) + final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder() + .addInlineProcessor(new JLatexMathInlineProcessor()) .build(); builder.inlineParserFactory(factory); } @@ -155,6 +153,33 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final MarkwonConfiguration configuration = visitor.configuration(); + final AsyncDrawableSpan span = new AsyncDrawableSpan( + configuration.theme(), + new AsyncDrawable( + latex, + jLatextAsyncDrawableLoader, + jLatexImageSizeResolver, + null), + AsyncDrawableSpan.ALIGN_CENTER, + false); + + visitor.setSpans(length, span); + } + }); + builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) { + final String latex = jLatexMathNode.latex(); + + final int length = visitor.length(); + + // @since 4.0.2 we cannot append _raw_ latex as a placeholder-text, + // because Android will draw formula for each line of text, thus + // leading to formula duplicated (drawn on each line of text) + visitor.builder().append(prepareLatexTextPlaceholder(latex)); + + final MarkwonConfiguration configuration = visitor.configuration(); + final AsyncDrawableSpan span = new AsyncDrawableSpan( configuration.theme(), new AsyncDrawable( @@ -170,76 +195,76 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { }); } - private static class LatexInlineProcessor extends InlineProcessor { - - @Override - public char specialCharacter() { - return '$'; - } - - @Nullable - @Override - protected Node parse() { - - final int start = index; - - index += 1; - if (peek() != '$') { - index = start; - return null; - } - - // must be not $ - index += 1; - if (peek() == '$') { - return text("$"); - } - - // find next '$$', but not broken with 2(or more) new lines - - boolean dollar = false; - boolean newLine = false; - boolean found = false; - - index += 1; - final int length = input.length(); - - while (index < length) { - final char c = peek(); - if (c == '\n') { - if (newLine) { - // second new line - break; - } - newLine = true; - dollar = false; // cannot be on another line - } else { - newLine = false; - if (c == '$') { - if (dollar) { - found = true; - // advance - index += 1; - break; - } - dollar = true; - } else { - dollar = false; - } - } - index += 1; - } - - if (found) { - final JLatexMathBlock block = new JLatexMathBlock(); - block.latex(input.substring(start + 2, index - 2)); - index += 1; - return block; - } - - return null; - } - } +// private static class LatexInlineProcessor extends InlineProcessor { +// +// @Override +// public char specialCharacter() { +// return '$'; +// } +// +// @Nullable +// @Override +// protected Node parse() { +// +// final int start = index; +// +// index += 1; +// if (peek() != '$') { +// index = start; +// return null; +// } +// +// // must be not $ +// index += 1; +// if (peek() == '$') { +// return text("$"); +// } +// +// // find next '$$', but not broken with 2(or more) new lines +// +// boolean dollar = false; +// boolean newLine = false; +// boolean found = false; +// +// index += 1; +// final int length = input.length(); +// +// while (index < length) { +// final char c = peek(); +// if (c == '\n') { +// if (newLine) { +// // second new line +// break; +// } +// newLine = true; +// dollar = false; // cannot be on another line +// } else { +// newLine = false; +// if (c == '$') { +// if (dollar) { +// found = true; +// // advance +// index += 1; +// break; +// } +// dollar = true; +// } else { +// dollar = false; +// } +// } +// index += 1; +// } +// +// if (found) { +// final JLatexMathBlock block = new JLatexMathBlock(); +// block.latex(input.substring(start + 2, index - 2)); +// index += 1; +// return block; +// } +// +// return null; +// } +// } @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { @@ -383,7 +408,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { .textSize(config.textSize) .background(backgroundProvider != null ? backgroundProvider.provide() : null) .align(config.align) - .fitCanvas(config.fitCanvas) + .fitCanvas(false /*config.fitCanvas*/) .padding( config.paddingHorizontal, config.paddingVertical, diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index 44d9aac3..8b81d119 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -24,26 +24,26 @@ public class LatexActivity extends Activity { final TextView textView = findViewById(R.id.text_view); - String latex = "\\begin{array}{l}"; - latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; - latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; - latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; - latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; - latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; - latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; - latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; - latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; - latex += "\\end{array}"; +// String latex = "\\begin{array}{l}"; +// latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; +// latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; +// latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; +// latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; +// latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; +// latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; +// latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; +// latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; +// latex += "\\end{array}"; -// String latex = "\\text{A long division \\longdiv{12345}{13}"; -// String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; + String latex = "\\text{A long division \\longdiv{12345}{13}"; +// String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; // String latex = "\\begin{array}{cc}"; // latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; // latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; // latex += "\\end{array}"; - final String markdown = "# Example of LaTeX\n\n$$" + final String markdown = "# Example of LaTeX\n\nhello there: $$" + latex + "$$\n\n something like **this**"; final Markwon markwon = Markwon.builder(this) From 7af0ead3a31a5805f2eb7e0cdc708a9a673f9048 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Fri, 14 Feb 2020 18:35:44 +0300 Subject: [PATCH 3/8] Working with latex plugin --- README.md | 2 + .../ext/latex/JLatexMathBlockParser.java | 184 +++++++++++---- .../markwon/ext/latex/JLatexMathPlugin.java | 218 ++++++++++-------- .../markwon/ext/latex/JLatexMathTheme.java | 28 +++ .../markwon/sample/latex/LatexActivity.java | 49 ++-- 5 files changed, 332 insertions(+), 149 deletions(-) create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java diff --git a/README.md b/README.md index 594d9781..350479bf 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [](https://github.com/noties/Markwon/actions) + + **Markwon** is a markdown library for Android. It parses markdown following [commonmark-spec] with the help of amazing [commonmark-java] library and renders result as _Android-native_ Spannables. **No HTML** diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java index 575692bb..cb159aef 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java @@ -1,5 +1,9 @@ package io.noties.markwon.ext.latex; +import android.util.Log; + +import androidx.annotation.NonNull; + import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.parser.block.AbstractBlockParser; @@ -11,11 +15,21 @@ import org.commonmark.parser.block.ParserState; public class JLatexMathBlockParser extends AbstractBlockParser { + private static final char DOLLAR = '$'; + private static final char SPACE = ' '; + private final JLatexMathBlock block = new JLatexMathBlock(); private final StringBuilder builder = new StringBuilder(); - private boolean isClosed; +// private boolean isClosed; + + private final int signs; + + @SuppressWarnings("WeakerAccess") + JLatexMathBlockParser(int signs) { + this.signs = signs; + } @Override public Block getBlock() { @@ -24,9 +38,22 @@ public class JLatexMathBlockParser extends AbstractBlockParser { @Override public BlockContinue tryContinue(ParserState parserState) { + final int nextNonSpaceIndex = parserState.getNextNonSpaceIndex(); + final CharSequence line = parserState.getLine(); + final int length = line.length(); - if (isClosed) { - return BlockContinue.finished(); + // check for closing + if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) { + Log.e("LTX", String.format("signs: %d, skip dollar: %s", signs, Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length))); +// if (Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) == signs) { + if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) { + // okay, we have our number of signs + // let's consume spaces until the end + Log.e("LTX", String.format("length; %d, skip spaces: %s", length, Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length))); + if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) { + return BlockContinue.finished(); + } + } } return BlockContinue.atIndex(parserState.getIndex()); @@ -34,21 +61,24 @@ public class JLatexMathBlockParser extends AbstractBlockParser { @Override public void addLine(CharSequence line) { - - if (builder.length() > 0) { - builder.append('\n'); - } - +// +// if (builder.length() > 0) { +// builder.append('\n'); +// } +// +// builder.append(line); +// +// final int length = builder.length(); +// if (length > 1) { +// isClosed = '$' == builder.charAt(length - 1) +// && '$' == builder.charAt(length - 2); +// if (isClosed) { +// builder.replace(length - 2, length, ""); +// } +// } + Log.e("LTX", "addLine: " + line); builder.append(line); - - final int length = builder.length(); - if (length > 1) { - isClosed = '$' == builder.charAt(length - 1) - && '$' == builder.charAt(length - 2); - if (isClosed) { - builder.replace(length - 2, length, ""); - } - } + builder.append('\n'); } @Override @@ -58,37 +88,111 @@ public class JLatexMathBlockParser extends AbstractBlockParser { public static class Factory extends AbstractBlockParserFactory { +// private static final Pattern RE = Pattern.compile("(\\${2,}) *$"); + @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + // let's define the spec: + // * 0-3 spaces before are allowed (Parsing.CODE_BLOCK_INDENT = 4) + // * 2+ subsequent `$` signs + // * any optional amount of spaces + // * new line + // * block is closed when the same amount of opening signs is met + final int indent = state.getIndent(); // check if it's an indented code block - if (indent < Parsing.CODE_BLOCK_INDENT) { - final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); - final CharSequence line = state.getLine(); - final int length = line.length(); - // we are looking for 2 `$$` subsequent signs - // and immediate new-line or arbitrary number of white spaces (we check for the first one) - // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s - final int diff = length - (nextNonSpaceIndex + 2); - if (diff >= 0) { - // check for both `$` - if (line.charAt(nextNonSpaceIndex) == '$' - && line.charAt(nextNonSpaceIndex + 1) == '$') { - - if (diff > 0) { - if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) { - return BlockStart.none(); - } - // consume all until new-line or first not-white-space char - } - - } - } + if (indent >= Parsing.CODE_BLOCK_INDENT) { + return BlockStart.none(); } - return BlockStart.none(); + final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); + final CharSequence line = state.getLine(); + final int length = line.length(); + +// final int signs = Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) - 1; + final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length); + + // 2 is minimum + if (signs < 2) { + return BlockStart.none(); + } + + // consume spaces until the end of the line, if any other content is found -> NONE + if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) { + return BlockStart.none(); + } + + Log.e("LTX", String.format("signs: %s, next: %d, length: %d, line: '%s'", signs, nextNonSpaceIndex, length, line)); + + return BlockStart.of(new JLatexMathBlockParser(signs)) + .atIndex(length + 1); + + +// // check if it's an indented code block +// if (indent < Parsing.CODE_BLOCK_INDENT) { +// +// final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); +// final CharSequence line = state.getLine(); +// final int length = line.length(); +// +// final int signs = Parsing.skip('$', line, nextNonSpaceIndex, length); +// +// // 2 is minimum +// if (signs < 2) { +// return BlockStart.none(); +// } +// +// // consume spaces until the end of the line, if any other content is found -> NONE +// if (Parsing.skip(' ', line, nextNonSpaceIndex + signs, length) != length) { +// return BlockStart.none(); +// } +// +//// // consume spaces until the end of the line, if any other content is found -> NONE +//// if ((nextNonSpaceIndex + signs) < length) { +//// // check if more content is available +//// if (Parsing.skip(' ', line,nextNonSpaceIndex + signs, length) != length) { +//// return BlockStart.none(); +//// } +//// } +// +//// final Matcher matcher = RE.matcher(line); +//// matcher.region(nextNonSpaceIndex, length); +// +//// Log.e("LATEX", String.format("nonSpace: %d, length: %s, line: '%s'", nextNonSpaceIndex, length, line)); +// +// // we are looking for 2 `$$` subsequent signs +// // and immediate new-line or arbitrary number of white spaces (we check for the first one) +// // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s +// final int diff = length - (nextNonSpaceIndex + 2); +// if (diff >= 0) { +// // check for both `$` +// if (line.charAt(nextNonSpaceIndex) == '$' +// && line.charAt(nextNonSpaceIndex + 1) == '$') { +// +// if (diff > 0) { +// if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) { +// return BlockStart.none(); +// } +// return BlockStart.of(new JLatexMathBlockParser()).atIndex(nextNonSpaceIndex + 3); +// } +// +// } +// } +// } +// +// return BlockStart.none(); } } + + private static int consume(char c, @NonNull CharSequence line, int start, int end) { + for (int i = start; i < end; i++) { + if (c != line.charAt(i)) { + return i - start; + } + } + // all consumed + return end - start; + } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 57485623..68308f6c 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -1,5 +1,6 @@ package io.noties.markwon.ext.latex; +import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -9,6 +10,7 @@ import android.text.Spanned; import android.util.Log; import android.widget.TextView; +import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; @@ -26,10 +28,12 @@ import java.util.concurrent.Future; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.image.AsyncDrawable; import io.noties.markwon.image.AsyncDrawableLoader; import io.noties.markwon.image.AsyncDrawableScheduler; import io.noties.markwon.image.AsyncDrawableSpan; +import io.noties.markwon.image.ImageSize; import io.noties.markwon.image.ImageSizeResolver; import io.noties.markwon.image.ImageSizeResolverDef; import io.noties.markwon.inlineparser.MarkwonInlineParser; @@ -129,7 +133,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // if it's $$\n -> block // if it's $$\\dhdsfjh$$ -> inline -// builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder() .addInlineProcessor(new JLatexMathInlineProcessor()) .build(); @@ -142,6 +147,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) { + visitor.ensureNewLine(); + final String latex = jLatexMathBlock.latex(); final int length = visitor.length(); @@ -155,15 +162,21 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final AsyncDrawableSpan span = new AsyncDrawableSpan( configuration.theme(), - new AsyncDrawable( + new JLatextAsyncDrawable( latex, jLatextAsyncDrawableLoader, jLatexImageSizeResolver, - null), + null, + true), AsyncDrawableSpan.ALIGN_CENTER, false); visitor.setSpans(length, span); + + if (visitor.hasNext(jLatexMathBlock)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } } }); builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() { @@ -180,13 +193,14 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final MarkwonConfiguration configuration = visitor.configuration(); - final AsyncDrawableSpan span = new AsyncDrawableSpan( + final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan( configuration.theme(), - new AsyncDrawable( + new JLatextAsyncDrawable( latex, jLatextAsyncDrawableLoader, new ImageSizeResolverDef(), - null), + null, + false), AsyncDrawableSpan.ALIGN_CENTER, false); @@ -195,77 +209,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { }); } -// private static class LatexInlineProcessor extends InlineProcessor { -// -// @Override -// public char specialCharacter() { -// return '$'; -// } -// -// @Nullable -// @Override -// protected Node parse() { -// -// final int start = index; -// -// index += 1; -// if (peek() != '$') { -// index = start; -// return null; -// } -// -// // must be not $ -// index += 1; -// if (peek() == '$') { -// return text("$"); -// } -// -// // find next '$$', but not broken with 2(or more) new lines -// -// boolean dollar = false; -// boolean newLine = false; -// boolean found = false; -// -// index += 1; -// final int length = input.length(); -// -// while (index < length) { -// final char c = peek(); -// if (c == '\n') { -// if (newLine) { -// // second new line -// break; -// } -// newLine = true; -// dollar = false; // cannot be on another line -// } else { -// newLine = false; -// if (c == '$') { -// if (dollar) { -// found = true; -// // advance -// index += 1; -// break; -// } -// dollar = true; -// } else { -// dollar = false; -// } -// } -// index += 1; -// } -// -// if (found) { -// final JLatexMathBlock block = new JLatexMathBlock(); -// block.latex(input.substring(start + 2, index - 2)); -// index += 1; -// return block; -// } -// -// return null; -// } -// } - @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { AsyncDrawableScheduler.unschedule(textView); @@ -401,20 +344,53 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // @since 4.0.1 (background provider can be null) final BackgroundProvider backgroundProvider = config.backgroundProvider; + final JLatexMathDrawable jLatexMathDrawable; + + final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable; + if (jLatextAsyncDrawable.isBlock) { + // create JLatexMathDrawable + //noinspection ConstantConditions + jLatexMathDrawable = + JLatexMathDrawable.builder(drawable.getDestination()) + .textSize(config.textSize) + .background(backgroundProvider != null ? backgroundProvider.provide() : null) + .align(config.align) + .fitCanvas(config.fitCanvas) + .padding( + config.paddingHorizontal, + config.paddingVertical, + config.paddingHorizontal, + config.paddingVertical) + .build(); + } else { + jLatexMathDrawable = + JLatexMathDrawable.builder(drawable.getDestination()) + .textSize(config.textSize) +// .background(backgroundProvider != null ? backgroundProvider.provide() : null) +// .align(config.align) +// .fitCanvas(config.fitCanvas) +// .padding( +// config.paddingHorizontal, +// config.paddingVertical, +// config.paddingHorizontal, +// config.paddingVertical) + .build(); + } + // create JLatexMathDrawable - //noinspection ConstantConditions - final JLatexMathDrawable jLatexMathDrawable = - JLatexMathDrawable.builder(drawable.getDestination()) - .textSize(config.textSize) - .background(backgroundProvider != null ? backgroundProvider.provide() : null) - .align(config.align) - .fitCanvas(false /*config.fitCanvas*/) - .padding( - config.paddingHorizontal, - config.paddingVertical, - config.paddingHorizontal, - config.paddingVertical) - .build(); +// //noinspection ConstantConditions +// final JLatexMathDrawable jLatexMathDrawable = +// JLatexMathDrawable.builder(drawable.getDestination()) +// .textSize(config.textSize) +// .background(backgroundProvider != null ? backgroundProvider.provide() : null) +// .align(config.align) +// .fitCanvas(config.fitCanvas) +// .padding( +// config.paddingHorizontal, +// config.paddingVertical, +// config.paddingHorizontal, +// config.paddingVertical) +// .build(); // we must post to handler, but also have a way to identify the drawable // for which we are posting (in case of cancellation) @@ -496,4 +472,66 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return imageBounds; } } + + private static class JLatextAsyncDrawable extends AsyncDrawable { + + private final boolean isBlock; + + public JLatextAsyncDrawable( + @NonNull String destination, + @NonNull AsyncDrawableLoader loader, + @NonNull ImageSizeResolver imageSizeResolver, + @Nullable ImageSize imageSize, + boolean isBlock + ) { + super(destination, loader, imageSizeResolver, imageSize); + this.isBlock = isBlock; + } + } + + private static class JLatexAsyncDrawableSpan extends AsyncDrawableSpan { + + private final AsyncDrawable drawable; + + public JLatexAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) { + super(theme, drawable, alignment, replacementTextIsLink); + this.drawable = drawable; + } + + @Override + public int getSize( + @NonNull Paint paint, + CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @Nullable Paint.FontMetricsInt fm) { + + // if we have no async drawable result - we will just render text + + final int size; + + if (drawable.hasResult()) { + + final Rect rect = drawable.getBounds(); + + if (fm != null) { + final int half = rect.bottom / 2; + fm.ascent = -half; + fm.descent = half; + + fm.top = fm.ascent; + fm.bottom = 0; + } + + size = rect.right; + + } else { + + // NB, no specific text handling (no new lines, etc) + size = (int) (paint.measureText(text, start, end) + .5F); + } + + return size; + } + } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java new file mode 100644 index 00000000..956eb20f --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java @@ -0,0 +1,28 @@ +package io.noties.markwon.ext.latex; + +import android.graphics.Rect; + +/** + * @since 4.3.0-SNAPSHOT + */ +public class JLatexMathTheme { + + private float textSize; + private float inlineTextSize; + private float blockTextSize; + + // TODO: move to a class + private JLatexMathPlugin.BackgroundProvider backgroundProvider; + private JLatexMathPlugin.BackgroundProvider inlineBackgroundProvider; + private JLatexMathPlugin.BackgroundProvider blockBackgroundProvider; + + private boolean blockFitCanvas; + // horizontal alignment (when there is additional horizontal space) + private int blockAlign; + + private Rect padding; + private Rect inlinePadding; + private Rect blockPadding; + + +} diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index 8b81d119..f7c382b3 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -4,14 +4,19 @@ import android.app.Activity; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.util.Log; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.commonmark.node.Node; + +import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.ext.latex.JLatexMathPlugin; import io.noties.markwon.sample.R; +import io.noties.markwon.utils.DumpNodes; import ru.noties.jlatexmath.JLatexMathDrawable; public class LatexActivity extends Activity { @@ -44,27 +49,33 @@ public class LatexActivity extends Activity { // latex += "\\end{array}"; final String markdown = "# Example of LaTeX\n\nhello there: $$" - + latex + "$$\n\n something like **this**"; + + latex + "$$ so nice, really?\n\n $$ \n" + latex + "\n$$\n\n $$ \n" + latex + "\n$$"; final Markwon markwon = Markwon.builder(this) -// .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { -// @Override -// public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { -// builder -// .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() { -// @NonNull -// @Override -// public Drawable provide() { -// return new ColorDrawable(0x40ff0000); -// } -// }) -// .fitCanvas(true) -// .align(JLatexMathDrawable.ALIGN_LEFT) -// .padding(48) -// ; -// } -// })) - .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + builder + .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() { + @NonNull + @Override + public Drawable provide() { + return new ColorDrawable(0x40ff0000); + } + }) + .fitCanvas(true) + .align(JLatexMathDrawable.ALIGN_CENTER) + .padding(48) + ; + } + })) +// .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void beforeRender(@NonNull Node node) { + Log.e("LTX", DumpNodes.dump(node)); + } + }) .build(); // // if (true) { From 8d483fe49dc2211dfcb4e4c27860ce7c2714cdd6 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Wed, 26 Feb 2020 09:51:33 +0300 Subject: [PATCH 4/8] Sample, add task list toggle --- .../markwon/ext/latex/JLatexMathPlugin.java | 18 +-- .../markwon/ext/latex/JLatexMathTheme.java | 130 ++++++++++++++++-- sample/src/main/AndroidManifest.xml | 1 + .../noties/markwon/sample/MainActivity.java | 5 + .../java/io/noties/markwon/sample/Sample.java | 4 +- .../markwon/sample/latex/LatexActivity.java | 3 +- .../sample/tasklist/TaskListActivity.java | 111 +++++++++++++++ .../src/main/res/values/strings-samples.xml | 2 + 8 files changed, 245 insertions(+), 29 deletions(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 68308f6c..3f85b9f7 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -42,15 +42,7 @@ import ru.noties.jlatexmath.JLatexMathDrawable; /** * @since 3.0.0 */ -public class JLatexMathPlugin extends AbstractMarkwonPlugin { - - /** - * @since 4.0.0 - */ - public interface BackgroundProvider { - @NonNull - Drawable provide(); - } +public class JLatexMathPlugin extends AbstractMarkwonPlugin { public interface BuilderConfigure { void configureBuilder(@NonNull Builder builder); @@ -83,7 +75,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { private final float textSize; // @since 4.0.0 - private final BackgroundProvider backgroundProvider; + private final JLatexMathTheme.BackgroundProvider backgroundProvider; @JLatexMathDrawable.Align private final int align; @@ -231,7 +223,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { private final float textSize; // @since 4.0.0 - private BackgroundProvider backgroundProvider; + private JLatexMathTheme.BackgroundProvider backgroundProvider; @JLatexMathDrawable.Align private int align = JLatexMathDrawable.ALIGN_CENTER; @@ -252,7 +244,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } @NonNull - public Builder backgroundProvider(@NonNull BackgroundProvider backgroundProvider) { + public Builder backgroundProvider(@NonNull JLatexMathTheme.BackgroundProvider backgroundProvider) { this.backgroundProvider = backgroundProvider; return this; } @@ -342,7 +334,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { private void execute() { // @since 4.0.1 (background provider can be null) - final BackgroundProvider backgroundProvider = config.backgroundProvider; + final JLatexMathTheme.BackgroundProvider backgroundProvider = config.backgroundProvider; final JLatexMathDrawable jLatexMathDrawable; diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java index 956eb20f..04ea346c 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java @@ -1,28 +1,130 @@ package io.noties.markwon.ext.latex; import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; + +import ru.noties.jlatexmath.JLatexMathDrawable; /** * @since 4.3.0-SNAPSHOT */ -public class JLatexMathTheme { +public abstract class JLatexMathTheme { - private float textSize; - private float inlineTextSize; - private float blockTextSize; + @NonNull + public static JLatexMathTheme create(@Px float textSize) { + return null; + } - // TODO: move to a class - private JLatexMathPlugin.BackgroundProvider backgroundProvider; - private JLatexMathPlugin.BackgroundProvider inlineBackgroundProvider; - private JLatexMathPlugin.BackgroundProvider blockBackgroundProvider; + @NonNull + public static JLatexMathTheme builer() { + return null; + } - private boolean blockFitCanvas; - // horizontal alignment (when there is additional horizontal space) - private int blockAlign; + /** + * Moved from {@link JLatexMathPlugin} in {@code 4.3.0-SNAPSHOT} version + * + * @since 4.0.0 + */ + public interface BackgroundProvider { + @NonNull + Drawable provide(); + } - private Rect padding; - private Rect inlinePadding; - private Rect blockPadding; + /** + * Special immutable class to hold padding information + */ + public static class Padding { + public final int left; + public final int top; + public final int right; + public final int bottom; + + public Padding(int left, int top, int right, int bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + @Override + public String toString() { + return "Padding{" + + "left=" + left + + ", top=" + top + + ", right=" + right + + ", bottom=" + bottom + + '}'; + } + + @NonNull + public static Padding all(int value) { + return new Padding(value, value, value, value); + } + + @NonNull + public static Padding symmetric(int vertical, int horizontal) { + return new Padding(horizontal, vertical, horizontal, vertical); + } + } + + /** + * @return text size in pixels for <strong>inline LaTeX</strong> + * @see #blockTexxtSize() + */ + @Px + public abstract float inlineTextSize(); + + /** + * @return text size in pixels for <strong>block LaTeX</strong> + * @see #inlineTextSize() + */ + @Px + public abstract float blockTexxtSize(); + + @Nullable + public abstract BackgroundProvider inlineBackgroundProvider(); + + @Nullable + public abstract BackgroundProvider blockBackgroundProvider(); + + /** + * @return boolean if <strong>block LaTeX</strong> must fit the width of canvas + */ + public abstract boolean blockFitCanvas(); + + /** + * @return horizontal alignment of <strong>block LaTeX</strong> if {@link #blockFitCanvas()} + * is enabled (thus space for alignment is available) + */ + @JLatexMathDrawable.Align + public abstract int blockHorizontalAlignment(); + + @Nullable + public abstract Padding inlinePadding(); + + @Nullable + public abstract Padding blockPadding(); + public static class Builder { + private float textSize; + private float inlineTextSize; + private float blockTextSize; + + private BackgroundProvider backgroundProvider; + private BackgroundProvider inlineBackgroundProvider; + private BackgroundProvider blockBackgroundProvider; + + private boolean blockFitCanvas; + // horizontal alignment (when there is additional horizontal space) + private int blockAlign; + + private Padding padding; + private Padding inlinePadding; + private Padding blockPadding; + } } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 5e0ae714..0c02f47f 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ <activity android:name=".inlineparser.InlineParserActivity" /> <activity android:name=".htmldetails.HtmlDetailsActivity" /> + <activity android:name=".tasklist.TaskListActivity" /> </application> diff --git a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java index 4cdd6d73..a14a8183 100644 --- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java @@ -30,6 +30,7 @@ import io.noties.markwon.sample.latex.LatexActivity; import io.noties.markwon.sample.precomputed.PrecomputedActivity; import io.noties.markwon.sample.recycler.RecyclerActivity; import io.noties.markwon.sample.simpleext.SimpleExtActivity; +import io.noties.markwon.sample.tasklist.TaskListActivity; public class MainActivity extends Activity { @@ -132,6 +133,10 @@ public class MainActivity extends Activity { activity = HtmlDetailsActivity.class; break; + case TASK_LIST: + activity = TaskListActivity.class; + break; + default: throw new IllegalStateException("No Activity is associated with sample-item: " + item); } diff --git a/sample/src/main/java/io/noties/markwon/sample/Sample.java b/sample/src/main/java/io/noties/markwon/sample/Sample.java index 36b13cd2..f243c0ec 100644 --- a/sample/src/main/java/io/noties/markwon/sample/Sample.java +++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java @@ -27,7 +27,9 @@ public enum Sample { INLINE_PARSER(R.string.sample_inline_parser), - HTML_DETAILS(R.string.sample_html_details); + HTML_DETAILS(R.string.sample_html_details), + + TASK_LIST(R.string.sample_task_list); private final int textResId; diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index f7c382b3..3186c1cc 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -15,6 +15,7 @@ import org.commonmark.node.Node; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.ext.latex.JLatexMathTheme; import io.noties.markwon.sample.R; import io.noties.markwon.utils.DumpNodes; import ru.noties.jlatexmath.JLatexMathDrawable; @@ -56,7 +57,7 @@ public class LatexActivity extends Activity { @Override public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { builder - .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() { + .backgroundProvider(new JLatexMathTheme.BackgroundProvider() { @NonNull @Override public Drawable provide() { diff --git a/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java new file mode 100644 index 00000000..dfbf59af --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java @@ -0,0 +1,111 @@ +package io.noties.markwon.sample.tasklist; + +import android.app.Activity; +import android.os.Bundle; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.debug.Debug; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.ext.tasklist.TaskListItem; +import io.noties.markwon.ext.tasklist.TaskListPlugin; +import io.noties.markwon.ext.tasklist.TaskListSpan; +import io.noties.markwon.sample.R; + +public class TaskListActivity extends Activity { + + private TextView textView; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_text_view); + + textView = findViewById(R.id.text_view); + + mutate(); + } + + private void mutate() { + + final Markwon markwon = Markwon.builder(this) + .usePlugin(TaskListPlugin.create(this)) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // obtain origin task-list-factory + final SpanFactory origin = builder.getFactory(TaskListItem.class); + if (origin == null) { + return; + } + + builder.setFactory(TaskListItem.class, (configuration, props) -> { + // maybe it's better to validate the actual type here also + // and not force cast to task-list-span + final TaskListSpan span = (TaskListSpan) origin.getSpans(configuration, props); + if (span == null) { + return null; + } + + return new Object[]{ + span, + new TaskListToggleSpan(span) + }; + }); + } + }) + .build(); + + final String md = "" + + "- [ ] Not done here!\n" + + "- [x] and done\n" + + "- [X] and again!\n" + + "* [ ] **and** syntax _included_ `code`"; + + markwon.setMarkdown(textView, md); + } + + private static class TaskListToggleSpan extends ClickableSpan { + + private final TaskListSpan span; + + TaskListToggleSpan(@NonNull TaskListSpan span) { + this.span = span; + } + + @Override + public void onClick(@NonNull View widget) { + // toggle span (this is a mere visual change) + span.setDone(!span.isDone()); + // request visual update + widget.invalidate(); + + // it must be a TextView + final TextView textView = (TextView) widget; + // it must be spanned + final Spanned spanned = (Spanned) textView.getText(); + + // actual text of the span (this can be used along with the `span`) + final CharSequence task = spanned.subSequence( + spanned.getSpanStart(this), + spanned.getSpanEnd(this) + ); + + Debug.i("task done: %s, '%s'", span.isDone(), task); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + // no op, so text is not rendered as a link + } + } +} diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml index d87585fd..f5a97644 100644 --- a/sample/src/main/res/values/strings-samples.xml +++ b/sample/src/main/res/values/strings-samples.xml @@ -31,4 +31,6 @@ <string name="sample_html_details"># \# HTML <details> tag\n\n<details> tag parsed and rendered</string> + <string name="sample_task_list"># \# TaskList\n\nUsage of TaskListPlugin</string> + </resources> \ No newline at end of file From c7494a922504acb922aba705a09cb073a6974078 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Wed, 26 Feb 2020 13:39:37 +0300 Subject: [PATCH 5/8] Latex, introduce theme and render-mode --- .../io/noties/markwon/LinkResolverDef.java | 2 +- .../ext/latex/JLatexMathBlockParser.java | 1 + .../markwon/ext/latex/JLatexMathPlugin.java | 28 ++- .../markwon/ext/latex/JLatexMathTheme.java | 187 +++++++++++++++++- .../sample/ActivityWithMenuOptions.java | 34 ++++ .../io/noties/markwon/sample/MenuOptions.java | 46 +++++ .../markwon/sample/editor/EditorActivity.java | 27 +-- .../sample/editor/LinkEditHandler.java | 34 ++-- .../markwon/sample/latex/LatexActivity.java | 125 ++++++------ .../sample/tasklist/TaskListActivity.java | 78 +++++++- .../main/res/drawable/custom_task_list.xml | 5 + 11 files changed, 462 insertions(+), 105 deletions(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java create mode 100644 sample/src/main/java/io/noties/markwon/sample/MenuOptions.java create mode 100644 sample/src/main/res/drawable/custom_task_list.xml diff --git a/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java index 999dcee5..f61ad21f 100644 --- a/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java +++ b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java @@ -20,7 +20,7 @@ public class LinkResolverDef implements LinkResolver { try { context.startActivity(intent); } catch (ActivityNotFoundException e) { - Log.w("LinkResolverDef", "Actvity was not found for intent, " + intent.toString()); + Log.w("LinkResolverDef", "Actvity was not found for the link: '" + link + "'"); } } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java index cb159aef..3ef61a24 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java @@ -120,6 +120,7 @@ public class JLatexMathBlockParser extends AbstractBlockParser { } // consume spaces until the end of the line, if any other content is found -> NONE + // TODO: here we can check mode in which we operate (legacy or not) if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) { return BlockStart.none(); } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 3f85b9f7..d758be38 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -42,8 +42,33 @@ import ru.noties.jlatexmath.JLatexMathDrawable; /** * @since 3.0.0 */ -public class JLatexMathPlugin extends AbstractMarkwonPlugin { +public class JLatexMathPlugin extends AbstractMarkwonPlugin { + /** + * @since 4.3.0-SNAPSHOT + */ + public enum RenderMode { + /** + * <em>LEGACY</em> mode mimics pre {@code 4.3.0-SNAPSHOT} behavior by rendering LaTeX blocks only. + * In this mode LaTeX is started by `$$` (that must be exactly at the start of a line) and + * ended at whatever line that is ended with `$$` characters exactly. + */ + LEGACY, + + /** + * Starting with {@code 4.3.0-SNAPSHOT} it is possible to have LaTeX inlines (which flows inside + * a text paragraph without breaking it). Inline LaTeX starts and ends with `$$` symbols. For example: + * {@code + * **bold $$\\begin{array}\\end{array}$$ bold-end**, and whatever more + * } + * LaTeX block starts on a new line started by 0-3 spaces and 2 (or more) {@code $} signs + * followed by a new-line (with any amount of space characters in-between). And ends on a new-line + * starting with 0-3 spaces followed by number of {@code $} signs that was used to <em>start the block</em>. + */ + BLOCKS_AND_INLINES + } + + // TODO: inlines are not moved to a new line when exceed available width.. (api 23, emulator) public interface BuilderConfigure { void configureBuilder(@NonNull Builder builder); } @@ -338,6 +363,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final JLatexMathDrawable jLatexMathDrawable; + // TODO: obtain real values from theme (for blocks and inlines) final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable; if (jLatextAsyncDrawable.isBlock) { // create JLatexMathDrawable diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java index 04ea346c..30b9e04a 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java @@ -1,6 +1,5 @@ package io.noties.markwon.ext.latex; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; @@ -16,12 +15,22 @@ public abstract class JLatexMathTheme { @NonNull public static JLatexMathTheme create(@Px float textSize) { - return null; + return builder(textSize).build(); } @NonNull - public static JLatexMathTheme builer() { - return null; + public static JLatexMathTheme create(@Px float inlineTextSize, @Px float blockTextSize) { + return builder(inlineTextSize, blockTextSize).build(); + } + + @NonNull + public static JLatexMathTheme.Builder builder(@Px float textSize) { + return new JLatexMathTheme.Builder(textSize, 0F, 0F); + } + + @NonNull + public static JLatexMathTheme.Builder builder(@Px float inlineTextSize, @Px float blockTextSize) { + return new Builder(0F, inlineTextSize, blockTextSize); } /** @@ -73,7 +82,7 @@ public abstract class JLatexMathTheme { /** * @return text size in pixels for <strong>inline LaTeX</strong> - * @see #blockTexxtSize() + * @see #blockTextSize() */ @Px public abstract float inlineTextSize(); @@ -83,7 +92,7 @@ public abstract class JLatexMathTheme { * @see #inlineTextSize() */ @Px - public abstract float blockTexxtSize(); + public abstract float blockTextSize(); @Nullable public abstract BackgroundProvider inlineBackgroundProvider(); @@ -111,9 +120,9 @@ public abstract class JLatexMathTheme { public static class Builder { - private float textSize; - private float inlineTextSize; - private float blockTextSize; + private final float textSize; + private final float inlineTextSize; + private final float blockTextSize; private BackgroundProvider backgroundProvider; private BackgroundProvider inlineBackgroundProvider; @@ -121,10 +130,168 @@ public abstract class JLatexMathTheme { private boolean blockFitCanvas; // horizontal alignment (when there is additional horizontal space) - private int blockAlign; + private int blockHorizontalAlignment; private Padding padding; private Padding inlinePadding; private Padding blockPadding; + + Builder(float textSize, float inlineTextSize, float blockTextSize) { + this.textSize = textSize; + this.inlineTextSize = inlineTextSize; + this.blockTextSize = blockTextSize; + } + + @NonNull + public Builder backgroundProvider(@Nullable BackgroundProvider backgroundProvider) { + this.backgroundProvider = backgroundProvider; + this.inlineBackgroundProvider = backgroundProvider; + this.blockBackgroundProvider = backgroundProvider; + return this; + } + + @NonNull + public Builder inlineBackgroundProvider(@Nullable BackgroundProvider inlineBackgroundProvider) { + this.inlineBackgroundProvider = inlineBackgroundProvider; + return this; + } + + @NonNull + public Builder blockBackgroundProvider(@Nullable BackgroundProvider blockBackgroundProvider) { + this.blockBackgroundProvider = blockBackgroundProvider; + return this; + } + + @NonNull + public Builder blockFitCanvas(boolean blockFitCanvas) { + this.blockFitCanvas = blockFitCanvas; + return this; + } + + @NonNull + public Builder blockHorizontalAlignment(@JLatexMathDrawable.Align int blockHorizontalAlignment) { + this.blockHorizontalAlignment = blockHorizontalAlignment; + return this; + } + + @NonNull + public Builder padding(@Nullable Padding padding) { + this.padding = padding; + this.inlinePadding = padding; + this.blockPadding = padding; + return this; + } + + @NonNull + public Builder inlinePadding(@Nullable Padding inlinePadding) { + this.inlinePadding = inlinePadding; + return this; + } + + @NonNull + public Builder blockPadding(@Nullable Padding blockPadding) { + this.blockPadding = blockPadding; + return this; + } + + @NonNull + public JLatexMathTheme build() { + return null; + } + } + + static class Impl extends JLatexMathTheme { + + private final float textSize; + private final float inlineTextSize; + private final float blockTextSize; + + private final BackgroundProvider backgroundProvider; + private final BackgroundProvider inlineBackgroundProvider; + private final BackgroundProvider blockBackgroundProvider; + + private final boolean blockFitCanvas; + // horizontal alignment (when there is additional horizontal space) + private int blockHorizontalAlignment; + + private final Padding padding; + private final Padding inlinePadding; + private final Padding blockPadding; + + Impl(@NonNull Builder builder) { + this.textSize = builder.textSize; + this.inlineTextSize = builder.inlineTextSize; + this.blockTextSize = builder.blockTextSize; + this.backgroundProvider = builder.backgroundProvider; + this.inlineBackgroundProvider = builder.inlineBackgroundProvider; + this.blockBackgroundProvider = builder.blockBackgroundProvider; + this.blockFitCanvas = builder.blockFitCanvas; + this.blockHorizontalAlignment = builder.blockHorizontalAlignment; + this.padding = builder.padding; + this.inlinePadding = builder.inlinePadding; + this.blockPadding = builder.blockPadding; + } + + @Override + public float inlineTextSize() { + if (inlineTextSize > 0F) { + return inlineTextSize; + } + return textSize; + } + + @Override + public float blockTextSize() { + if (blockTextSize > 0F) { + return blockTextSize; + } + return textSize; + } + + @Nullable + @Override + public BackgroundProvider inlineBackgroundProvider() { + if (inlineBackgroundProvider != null) { + return inlineBackgroundProvider; + } + return backgroundProvider; + } + + @Nullable + @Override + public BackgroundProvider blockBackgroundProvider() { + if (blockBackgroundProvider != null) { + return blockBackgroundProvider; + } + return backgroundProvider; + } + + @Override + public boolean blockFitCanvas() { + return blockFitCanvas; + } + + @Override + public int blockHorizontalAlignment() { + return blockHorizontalAlignment; + } + + @Nullable + @Override + public Padding inlinePadding() { + if (inlinePadding != null) { + return inlinePadding; + } + return padding; + } + + @Nullable + @Override + public Padding blockPadding() { + if (blockPadding != null) { + return blockPadding; + } + return padding; + } } } diff --git a/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java new file mode 100644 index 00000000..66690f7b --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java @@ -0,0 +1,34 @@ +package io.noties.markwon.sample; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public abstract class ActivityWithMenuOptions extends Activity { + + @NonNull + public abstract MenuOptions menuOptions(); + + private MenuOptions menuOptions; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + menuOptions = menuOptions(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + return menuOptions.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return menuOptions.onOptionsItemSelected(item); + } +} diff --git a/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java new file mode 100644 index 00000000..1c349bd8 --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java @@ -0,0 +1,46 @@ +package io.noties.markwon.sample; + +import android.view.Menu; +import android.view.MenuItem; + +import androidx.annotation.NonNull; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class MenuOptions { + + @NonNull + public static MenuOptions create() { + return new MenuOptions(); + } + + // to preserve order use LinkedHashMap + private final Map<String, Runnable> actions = new LinkedHashMap<>(); + + @NonNull + public MenuOptions add(@NonNull String title, @NonNull Runnable action) { + actions.put(title, action); + return this; + } + + boolean onCreateOptionsMenu(Menu menu) { + if (!actions.isEmpty()) { + for (String key : actions.keySet()) { + menu.add(key); + } + return true; + } + return false; + } + + boolean onOptionsItemSelected(MenuItem item) { + final String title = String.valueOf(item.getTitle()); + final Runnable action = actions.get(title); + if (action != null) { + action.run(); + return true; + } + return false; + } +} diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java index 5553c9f8..0dc05ef9 100644 --- a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java @@ -1,6 +1,5 @@ package io.noties.markwon.sample.editor; -import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.text.SpannableStringBuilder; @@ -42,12 +41,26 @@ import io.noties.markwon.inlineparser.EntityInlineProcessor; import io.noties.markwon.inlineparser.HtmlInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParser; import io.noties.markwon.linkify.LinkifyPlugin; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; -public class EditorActivity extends Activity { +public class EditorActivity extends ActivityWithMenuOptions { private EditText editText; + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("simpleProcess", this::simple_process) + .add("simplePreRender", this::simple_pre_render) + .add("customPunctuationSpan", this::custom_punctuation_span) + .add("additionalEditSpan", this::additional_edit_span) + .add("additionalPlugins", this::additional_plugins) + .add("multipleEditSpans", this::multiple_edit_spans); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,16 +69,6 @@ public class EditorActivity extends Activity { this.editText = findViewById(R.id.edit_text); initBottomBar(); -// simple_process(); - -// simple_pre_render(); - -// custom_punctuation_span(); - -// additional_edit_span(); - -// additional_plugins(); - multiple_edit_spans(); } diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java b/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java index 743428d0..3a6d60fd 100644 --- a/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java +++ b/sample/src/main/java/io/noties/markwon/sample/editor/LinkEditHandler.java @@ -40,24 +40,28 @@ class LinkEditHandler extends AbstractEditHandler<LinkSpan> { final EditLinkSpan editLinkSpan = persistedSpans.get(EditLinkSpan.class); editLinkSpan.link = span.getLink(); - final int s; - final int e; + // First first __letter__ to find link content (scheme start in URL, receiver in email address) + // NB! do not use phone number auto-link (via LinkifyPlugin) as we cannot guarantee proper link + // display. For example, we _could_ also look for a digit, but: + // * if phone number start with special symbol, we won't have it (`+`, `(`) + // * it might interfere with an ordered-list + int start = -1; - // markdown link vs. autolink - if ('[' == input.charAt(spanStart)) { - s = spanStart + 1; - e = spanStart + 1 + spanTextLength; - } else { - s = spanStart; - e = spanStart + spanTextLength; + for (int i = spanStart, length = input.length(); i < length; i++) { + if (Character.isLetter(input.charAt(i))) { + start = i; + break; + } } - editable.setSpan( - editLinkSpan, - s, - e, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ); + if (start > -1) { + editable.setSpan( + editLinkSpan, + start, + start + spanTextLength, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ); + } } @NonNull diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index 3186c1cc..c669cde0 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -1,57 +1,100 @@ package io.noties.markwon.sample.latex; -import android.app.Activity; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.util.Log; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.commonmark.node.Node; - -import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.ext.latex.JLatexMathPlugin; import io.noties.markwon.ext.latex.JLatexMathTheme; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; -import io.noties.markwon.utils.DumpNodes; import ru.noties.jlatexmath.JLatexMathDrawable; -public class LatexActivity extends Activity { +public class LatexActivity extends ActivityWithMenuOptions { + + private TextView textView; + + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("array", this::array) + .add("longDivision", this::longDivision) + .add("bangle", this::bangle) + .add("boxes", this::boxes) + .add("insideBlockQuote", this::insideBlockQuote); + } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_text_view); - final TextView textView = findViewById(R.id.text_view); + textView = findViewById(R.id.text_view); -// String latex = "\\begin{array}{l}"; -// latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; -// latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; -// latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; -// latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; -// latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; -// latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; -// latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; -// latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; -// latex += "\\end{array}"; +// array(); + longDivision(); + } + private void array() { + String latex = "\\begin{array}{l}"; + latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; + latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; + latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; + latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; + latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; + latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; + latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; + latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; + latex += "\\end{array}"; + + render(wrapLatexInSampleMarkdown(latex)); + } + + private void longDivision() { String latex = "\\text{A long division \\longdiv{12345}{13}"; -// String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; + render(wrapLatexInSampleMarkdown(latex)); + } -// String latex = "\\begin{array}{cc}"; -// latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; -// latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; -// latex += "\\end{array}"; + private void bangle() { + String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; + render(wrapLatexInSampleMarkdown(latex)); + } - final String markdown = "# Example of LaTeX\n\nhello there: $$" - + latex + "$$ so nice, really?\n\n $$ \n" + latex + "\n$$\n\n $$ \n" + latex + "\n$$"; + private void boxes() { + String latex = "\\begin{array}{cc}"; + latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; + latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; + latex += "\\end{array}"; + render(wrapLatexInSampleMarkdown(latex)); + } + private void insideBlockQuote() { + String latex = "W=W_1+W_2=F_1X_1-F_2X_2"; + final String md = "" + + "# LaTeX inside a blockquote\n" + + "> $$" + latex + "$$\n"; + render(md); + } + + @NonNull + private static String wrapLatexInSampleMarkdown(@NonNull String latex) { + return "" + + "# Example of LaTeX\n\n" + + "(inline): $$" + latex + "$$ so nice, really? Now, (block):\n\n" + + "$$\n" + + "" + latex + "\n" + + "$$\n\n" + + "the end"; + } + + private void render(@NonNull String markdown) { final Markwon markwon = Markwon.builder(this) .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { @Override @@ -70,37 +113,7 @@ public class LatexActivity extends Activity { ; } })) -// .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) - .usePlugin(new AbstractMarkwonPlugin() { - @Override - public void beforeRender(@NonNull Node node) { - Log.e("LTX", DumpNodes.dump(node)); - } - }) .build(); -// -// if (true) { -//// final String l = "$$\n" + -//// " P(X=r)=\\frac{\\lambda^r e^{-\\lambda}}{r!}\n" + -//// "$$\n" + -//// "\n" + -//// "$$\n" + -//// " P(X<r)=P(X<r-1)\n" + -//// "$$\n" + -//// "\n" + -//// "$$\n" + -//// " P(X>r)=1-P(X<r=1)\n" + -//// "$$\n" + -//// "\n" + -//// "$$\n" + -//// " \\text{Variance} = \\lambda\n" + -//// "$$"; -// final String l = "$$ \n" + -// " \\sigma_T^2 = \\frac{1-p}{p^2}\n" + -// "$$"; -// markwon.setMarkdown(textView, l); -// return; -// } markwon.setMarkdown(textView, markdown); } diff --git a/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java index dfbf59af..b5c421d6 100644 --- a/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/tasklist/TaskListActivity.java @@ -1,6 +1,7 @@ package io.noties.markwon.sample.tasklist; -import android.app.Activity; +import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.Spanned; import android.text.TextPaint; @@ -10,6 +11,9 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import java.util.Objects; import io.noties.debug.Debug; import io.noties.markwon.AbstractMarkwonPlugin; @@ -19,12 +23,34 @@ import io.noties.markwon.SpanFactory; import io.noties.markwon.ext.tasklist.TaskListItem; import io.noties.markwon.ext.tasklist.TaskListPlugin; import io.noties.markwon.ext.tasklist.TaskListSpan; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; -public class TaskListActivity extends Activity { +public class TaskListActivity extends ActivityWithMenuOptions { + + private static final String MD = "" + + "- [ ] Not done here!\n" + + "- [x] and done\n" + + "- [X] and again!\n" + + "* [ ] **and** syntax _included_ `code`\n" + + "- [ ] [link](#)\n" + + "- [ ] [a check box](https://goog.le)\n" + + "- [x] [test]()\n" + + "- [List](https://goog.le) 3"; private TextView textView; + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("regular", this::regular) + .add("customColors", this::customColors) + .add("customDrawableResources", this::customDrawableResources) + .add("mutate", this::mutate); + } + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -32,7 +58,44 @@ public class TaskListActivity extends Activity { textView = findViewById(R.id.text_view); - mutate(); +// mutate(); + regular(); + } + + private void regular() { + // default theme + + final Markwon markwon = Markwon.builder(this) + .usePlugin(TaskListPlugin.create(this)) + .build(); + + markwon.setMarkdown(textView, MD); + } + + private void customColors() { + + final int checkedFillColor = Color.RED; + final int normalOutlineColor = Color.GREEN; + final int checkMarkColor = Color.BLUE; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(TaskListPlugin.create(checkedFillColor, normalOutlineColor, checkMarkColor)) + .build(); + + markwon.setMarkdown(textView, MD); + } + + private void customDrawableResources() { + // drawable **must** be stateful + + final Drawable drawable = Objects.requireNonNull( + ContextCompat.getDrawable(this, R.drawable.custom_task_list)); + + final Markwon markwon = Markwon.builder(this) + .usePlugin(TaskListPlugin.create(drawable)) + .build(); + + markwon.setMarkdown(textView, MD); } private void mutate() { @@ -56,6 +119,7 @@ public class TaskListActivity extends Activity { return null; } + // NB, toggle click will intercept possible links inside task-list-item return new Object[]{ span, new TaskListToggleSpan(span) @@ -65,13 +129,7 @@ public class TaskListActivity extends Activity { }) .build(); - final String md = "" + - "- [ ] Not done here!\n" + - "- [x] and done\n" + - "- [X] and again!\n" + - "* [ ] **and** syntax _included_ `code`"; - - markwon.setMarkdown(textView, md); + markwon.setMarkdown(textView, MD); } private static class TaskListToggleSpan extends ClickableSpan { diff --git a/sample/src/main/res/drawable/custom_task_list.xml b/sample/src/main/res/drawable/custom_task_list.xml new file mode 100644 index 00000000..43c2e2a8 --- /dev/null +++ b/sample/src/main/res/drawable/custom_task_list.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" android:drawable="@drawable/ic_android_black_24dp" /> + <item android:drawable="@drawable/ic_home_black_36dp" /> +</selector> \ No newline at end of file From a80ff09e15b55a78e49dcf1af610e2a3716f5cb6 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Wed, 26 Feb 2020 15:08:00 +0300 Subject: [PATCH 6/8] Update sample configuration for latex block_and_inline renderMode --- .../latex/JLatexBlockImageSizeResolver.java | 50 +++ .../latex/JLatexInlineAsyncDrawableSpan.java | 61 +++ .../ext/latex/JLatexMathBlockParser.java | 89 +--- .../latex/JLatexMathBlockParserLegacy.java | 82 ++++ .../markwon/ext/latex/JLatexMathPlugin.java | 418 +++++++----------- .../markwon/ext/latex/JLatexMathTheme.java | 6 +- .../ext/latex/JLatextAsyncDrawable.java | 32 ++ .../markwon/sample/latex/LatexActivity.java | 36 +- sample/src/main/res/values/dimens.xml | 5 + 9 files changed, 419 insertions(+), 360 deletions(-) create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java create mode 100644 markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java create mode 100644 sample/src/main/res/values/dimens.xml diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java new file mode 100644 index 00000000..7e9ac95c --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexBlockImageSizeResolver.java @@ -0,0 +1,50 @@ +package io.noties.markwon.ext.latex; + +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.ImageSizeResolver; + +// we must make drawable fit canvas (if specified), but do not keep the ratio whilst scaling up +// @since 4.0.0 +class JLatexBlockImageSizeResolver extends ImageSizeResolver { + + private final boolean fitCanvas; + + JLatexBlockImageSizeResolver(boolean fitCanvas) { + this.fitCanvas = fitCanvas; + } + + @NonNull + @Override + public Rect resolveImageSize(@NonNull AsyncDrawable drawable) { + + final Rect imageBounds = drawable.getResult().getBounds(); + final int canvasWidth = drawable.getLastKnownCanvasWidth(); + + if (fitCanvas) { + + // we modify bounds only if `fitCanvas` is true + final int w = imageBounds.width(); + + if (w < canvasWidth) { + // increase width and center formula (keep height as-is) + return new Rect(0, 0, canvasWidth, imageBounds.height()); + } + + // @since 4.0.2 we additionally scale down the resulting formula (keeping the ratio) + // the thing is - JLatexMathDrawable will do it anyway, but it will modify its own + // bounds (which AsyncDrawable won't catch), thus leading to an empty space after the formula + if (w > canvasWidth) { + // here we must scale it down (keeping the ratio) + final float ratio = (float) w / imageBounds.height(); + final int h = (int) (canvasWidth / ratio + .5F); + return new Rect(0, 0, canvasWidth, h); + } + } + + return imageBounds; + } +} diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java new file mode 100644 index 00000000..10b59837 --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexInlineAsyncDrawableSpan.java @@ -0,0 +1,61 @@ +package io.noties.markwon.ext.latex; + +import android.graphics.Paint; +import android.graphics.Rect; + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.AsyncDrawableSpan; + +/** + * @since 4.3.0-SNAPSHOT + */ +class JLatexInlineAsyncDrawableSpan extends AsyncDrawableSpan { + + private final AsyncDrawable drawable; + + JLatexInlineAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) { + super(theme, drawable, alignment, replacementTextIsLink); + this.drawable = drawable; + } + + @Override + public int getSize( + @NonNull Paint paint, + CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @Nullable Paint.FontMetricsInt fm) { + + // if we have no async drawable result - we will just render text + + final int size; + + if (drawable.hasResult()) { + + final Rect rect = drawable.getBounds(); + + if (fm != null) { + final int half = rect.bottom / 2; + fm.ascent = -half; + fm.descent = half; + + fm.top = fm.ascent; + fm.bottom = 0; + } + + size = rect.right; + + } else { + + // NB, no specific text handling (no new lines, etc) + size = (int) (paint.measureText(text, start, end) + .5F); + } + + return size; + } +} diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java index 3ef61a24..ef65bb15 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java @@ -1,7 +1,5 @@ package io.noties.markwon.ext.latex; -import android.util.Log; - import androidx.annotation.NonNull; import org.commonmark.internal.util.Parsing; @@ -13,6 +11,10 @@ import org.commonmark.parser.block.BlockStart; import org.commonmark.parser.block.MatchedBlockParser; import org.commonmark.parser.block.ParserState; +/** + * @since 4.3.0-SNAPSHOT (although there was a class with the same name, + * which is renamed now to {@link JLatexMathBlockParserLegacy}) + */ public class JLatexMathBlockParser extends AbstractBlockParser { private static final char DOLLAR = '$'; @@ -22,8 +24,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser { private final StringBuilder builder = new StringBuilder(); -// private boolean isClosed; - private final int signs; @SuppressWarnings("WeakerAccess") @@ -44,12 +44,9 @@ public class JLatexMathBlockParser extends AbstractBlockParser { // check for closing if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) { - Log.e("LTX", String.format("signs: %d, skip dollar: %s", signs, Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length))); -// if (Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) == signs) { if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) { // okay, we have our number of signs // let's consume spaces until the end - Log.e("LTX", String.format("length; %d, skip spaces: %s", length, Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length))); if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) { return BlockContinue.finished(); } @@ -61,22 +58,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser { @Override public void addLine(CharSequence line) { -// -// if (builder.length() > 0) { -// builder.append('\n'); -// } -// -// builder.append(line); -// -// final int length = builder.length(); -// if (length > 1) { -// isClosed = '$' == builder.charAt(length - 1) -// && '$' == builder.charAt(length - 2); -// if (isClosed) { -// builder.replace(length - 2, length, ""); -// } -// } - Log.e("LTX", "addLine: " + line); builder.append(line); builder.append('\n'); } @@ -88,8 +69,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser { public static class Factory extends AbstractBlockParserFactory { -// private static final Pattern RE = Pattern.compile("(\\${2,}) *$"); - @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { @@ -111,7 +90,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser { final CharSequence line = state.getLine(); final int length = line.length(); -// final int signs = Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) - 1; final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length); // 2 is minimum @@ -120,73 +98,16 @@ public class JLatexMathBlockParser extends AbstractBlockParser { } // consume spaces until the end of the line, if any other content is found -> NONE - // TODO: here we can check mode in which we operate (legacy or not) if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) { return BlockStart.none(); } - Log.e("LTX", String.format("signs: %s, next: %d, length: %d, line: '%s'", signs, nextNonSpaceIndex, length, line)); - return BlockStart.of(new JLatexMathBlockParser(signs)) .atIndex(length + 1); - - -// // check if it's an indented code block -// if (indent < Parsing.CODE_BLOCK_INDENT) { -// -// final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); -// final CharSequence line = state.getLine(); -// final int length = line.length(); -// -// final int signs = Parsing.skip('$', line, nextNonSpaceIndex, length); -// -// // 2 is minimum -// if (signs < 2) { -// return BlockStart.none(); -// } -// -// // consume spaces until the end of the line, if any other content is found -> NONE -// if (Parsing.skip(' ', line, nextNonSpaceIndex + signs, length) != length) { -// return BlockStart.none(); -// } -// -//// // consume spaces until the end of the line, if any other content is found -> NONE -//// if ((nextNonSpaceIndex + signs) < length) { -//// // check if more content is available -//// if (Parsing.skip(' ', line,nextNonSpaceIndex + signs, length) != length) { -//// return BlockStart.none(); -//// } -//// } -// -//// final Matcher matcher = RE.matcher(line); -//// matcher.region(nextNonSpaceIndex, length); -// -//// Log.e("LATEX", String.format("nonSpace: %d, length: %s, line: '%s'", nextNonSpaceIndex, length, line)); -// -// // we are looking for 2 `$$` subsequent signs -// // and immediate new-line or arbitrary number of white spaces (we check for the first one) -// // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s -// final int diff = length - (nextNonSpaceIndex + 2); -// if (diff >= 0) { -// // check for both `$` -// if (line.charAt(nextNonSpaceIndex) == '$' -// && line.charAt(nextNonSpaceIndex + 1) == '$') { -// -// if (diff > 0) { -// if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) { -// return BlockStart.none(); -// } -// return BlockStart.of(new JLatexMathBlockParser()).atIndex(nextNonSpaceIndex + 3); -// } -// -// } -// } -// } -// -// return BlockStart.none(); } } + @SuppressWarnings("SameParameterValue") private static int consume(char c, @NonNull CharSequence line, int start, int end) { for (int i = start; i < end; i++) { if (c != line.charAt(i)) { diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java new file mode 100644 index 00000000..1a5ce282 --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParserLegacy.java @@ -0,0 +1,82 @@ +package io.noties.markwon.ext.latex; + +import org.commonmark.node.Block; +import org.commonmark.parser.block.AbstractBlockParser; +import org.commonmark.parser.block.AbstractBlockParserFactory; +import org.commonmark.parser.block.BlockContinue; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.MatchedBlockParser; +import org.commonmark.parser.block.ParserState; + +/** + * @since 4.3.0-SNAPSHOT (although it is just renamed parser from previous versions) + */ +public class JLatexMathBlockParserLegacy extends AbstractBlockParser { + + private final JLatexMathBlock block = new JLatexMathBlock(); + + private final StringBuilder builder = new StringBuilder(); + + private boolean isClosed; + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + + if (isClosed) { + return BlockContinue.finished(); + } + + return BlockContinue.atIndex(parserState.getIndex()); + } + + @Override + public void addLine(CharSequence line) { + + if (builder.length() > 0) { + builder.append('\n'); + } + + builder.append(line); + + final int length = builder.length(); + if (length > 1) { + isClosed = '$' == builder.charAt(length - 1) + && '$' == builder.charAt(length - 2); + if (isClosed) { + builder.replace(length - 2, length, ""); + } + } + } + + @Override + public void closeBlock() { + block.latex(builder.toString()); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + + final CharSequence line = state.getLine(); + final int length = line != null + ? line.length() + : 0; + + if (length > 1) { + if ('$' == line.charAt(0) + && '$' == line.charAt(1)) { + return BlockStart.of(new JLatexMathBlockParserLegacy()) + .atIndex(state.getIndex() + 2); + } + } + + return BlockStart.none(); + } + } +} diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index d758be38..5ecc0723 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -1,6 +1,5 @@ package io.noties.markwon.ext.latex; -import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -10,7 +9,6 @@ import android.text.Spanned; import android.util.Log; import android.widget.TextView; -import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; @@ -28,14 +26,11 @@ import java.util.concurrent.Future; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonVisitor; -import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.image.AsyncDrawable; import io.noties.markwon.image.AsyncDrawableLoader; import io.noties.markwon.image.AsyncDrawableScheduler; import io.noties.markwon.image.AsyncDrawableSpan; -import io.noties.markwon.image.ImageSize; import io.noties.markwon.image.ImageSizeResolver; -import io.noties.markwon.image.ImageSizeResolverDef; import io.noties.markwon.inlineparser.MarkwonInlineParser; import ru.noties.jlatexmath.JLatexMathDrawable; @@ -68,7 +63,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { BLOCKS_AND_INLINES } - // TODO: inlines are not moved to a new line when exceed available width.. (api 23, emulator) public interface BuilderConfigure { void configureBuilder(@NonNull Builder builder); } @@ -78,52 +72,65 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return new JLatexMathPlugin(builder(textSize).build()); } + /** + * @since 4.3.0-SNAPSHOT + */ + @NonNull + public static JLatexMathPlugin create(@Px float inlineTextSize, @Px float blockTextSize) { + return new JLatexMathPlugin(builder(inlineTextSize, blockTextSize).build()); + } + @NonNull public static JLatexMathPlugin create(@NonNull Config config) { return new JLatexMathPlugin(config); } @NonNull - public static JLatexMathPlugin create(float textSize, @NonNull BuilderConfigure builderConfigure) { - final Builder builder = new Builder(textSize); + public static JLatexMathPlugin create(@Px float textSize, @NonNull BuilderConfigure builderConfigure) { + final Builder builder = builder(textSize); + builderConfigure.configureBuilder(builder); + return new JLatexMathPlugin(builder.build()); + } + + /** + * @since 4.3.0-SNAPSHOT + */ + @NonNull + public static JLatexMathPlugin create( + @Px float inlineTextSize, + @Px float blockTextSize, + @NonNull BuilderConfigure builderConfigure) { + final Builder builder = builder(inlineTextSize, blockTextSize); builderConfigure.configureBuilder(builder); return new JLatexMathPlugin(builder.build()); } @NonNull - public static JLatexMathPlugin.Builder builder(float textSize) { - return new Builder(textSize); + public static JLatexMathPlugin.Builder builder(@Px float textSize) { + return new Builder(JLatexMathTheme.builder(textSize)); + } + + /** + * @since 4.3.0-SNAPSHOT + */ + @NonNull + public static JLatexMathPlugin.Builder builder(@Px float inlineTextSize, @Px float blockTextSize) { + return new Builder(JLatexMathTheme.builder(inlineTextSize, blockTextSize)); } public static class Config { - private final float textSize; + // @since 4.3.0-SNAPSHOT + private final JLatexMathTheme theme; - // @since 4.0.0 - private final JLatexMathTheme.BackgroundProvider backgroundProvider; + // @since 4.3.0-SNAPSHOT + private final RenderMode renderMode; - @JLatexMathDrawable.Align - private final int align; - - private final boolean fitCanvas; - - // @since 4.0.0 - private final int paddingHorizontal; - - // @since 4.0.0 - private final int paddingVertical; - - // @since 4.0.0 private final ExecutorService executorService; Config(@NonNull Builder builder) { - this.textSize = builder.textSize; - this.backgroundProvider = builder.backgroundProvider; - this.align = builder.align; - this.fitCanvas = builder.fitCanvas; - this.paddingHorizontal = builder.paddingHorizontal; - this.paddingVertical = builder.paddingVertical; - + this.theme = builder.theme.build(); + this.renderMode = builder.renderMode; // @since 4.0.0 ExecutorService executorService = builder.executorService; if (executorService == null) { @@ -133,29 +140,46 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } } + private final Config config; private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader; - private final JLatexImageSizeResolver jLatexImageSizeResolver; + private final JLatexBlockImageSizeResolver jLatexBlockImageSizeResolver; + private final ImageSizeResolver inlineImageSizeResolver; @SuppressWarnings("WeakerAccess") JLatexMathPlugin(@NonNull Config config) { + this.config = config; this.jLatextAsyncDrawableLoader = new JLatextAsyncDrawableLoader(config); - this.jLatexImageSizeResolver = new JLatexImageSizeResolver(config.fitCanvas); + this.jLatexBlockImageSizeResolver = new JLatexBlockImageSizeResolver(config.theme.blockFitCanvas()); + this.inlineImageSizeResolver = new InlineImageSizeResolver(); } @Override public void configureParser(@NonNull Parser.Builder builder) { - // what we can do: - // [0-3] spaces before block start/end - // if it's $$\n -> block - // if it's $$\\dhdsfjh$$ -> inline + // TODO: depending on renderMode we should register our parsing here + // * for LEGACY -> just add custom block parser + // * for INLINE.. -> require InlinePlugin, add inline processor + add block parser - builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + switch (config.renderMode) { - final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder() - .addInlineProcessor(new JLatexMathInlineProcessor()) - .build(); - builder.inlineParserFactory(factory); + case LEGACY: { + builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory()); + } + break; + + case BLOCKS_AND_INLINES: { + builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + + final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder() + .addInlineProcessor(new JLatexMathInlineProcessor()) + .build(); + builder.inlineParserFactory(factory); + } + break; + + default: + throw new RuntimeException("Unexpected `renderMode`: " + config.renderMode); + } } @Override @@ -182,7 +206,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { new JLatextAsyncDrawable( latex, jLatextAsyncDrawableLoader, - jLatexImageSizeResolver, + jLatexBlockImageSizeResolver, null, true), AsyncDrawableSpan.ALIGN_CENTER, @@ -196,34 +220,39 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } } }); - builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) { - final String latex = jLatexMathNode.latex(); - final int length = visitor.length(); - // @since 4.0.2 we cannot append _raw_ latex as a placeholder-text, - // because Android will draw formula for each line of text, thus - // leading to formula duplicated (drawn on each line of text) - visitor.builder().append(prepareLatexTextPlaceholder(latex)); + if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) { - final MarkwonConfiguration configuration = visitor.configuration(); + builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) { + final String latex = jLatexMathNode.latex(); - final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan( - configuration.theme(), - new JLatextAsyncDrawable( - latex, - jLatextAsyncDrawableLoader, - new ImageSizeResolverDef(), - null, - false), - AsyncDrawableSpan.ALIGN_CENTER, - false); + final int length = visitor.length(); - visitor.setSpans(length, span); - } - }); + // @since 4.0.2 we cannot append _raw_ latex as a placeholder-text, + // because Android will draw formula for each line of text, thus + // leading to formula duplicated (drawn on each line of text) + visitor.builder().append(prepareLatexTextPlaceholder(latex)); + + final MarkwonConfiguration configuration = visitor.configuration(); + + final AsyncDrawableSpan span = new JLatexInlineAsyncDrawableSpan( + configuration.theme(), + new JLatextAsyncDrawable( + latex, + jLatextAsyncDrawableLoader, + inlineImageSizeResolver, + null, + false), + AsyncDrawableSpan.ALIGN_CENTER, + false); + + visitor.setSpans(length, span); + } + }); + } } @Override @@ -245,61 +274,30 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { public static class Builder { - private final float textSize; + // @since 4.3.0-SNAPSHOT + private final JLatexMathTheme.Builder theme; - // @since 4.0.0 - private JLatexMathTheme.BackgroundProvider backgroundProvider; - - @JLatexMathDrawable.Align - private int align = JLatexMathDrawable.ALIGN_CENTER; - - private boolean fitCanvas = false; - - // @since 4.0.0 - private int paddingHorizontal; - - // @since 4.0.0 - private int paddingVertical; + // @since 4.3.0-SNAPSHOT + private RenderMode renderMode = RenderMode.BLOCKS_AND_INLINES; // @since 4.0.0 private ExecutorService executorService; - Builder(float textSize) { - this.textSize = textSize; + Builder(@NonNull JLatexMathTheme.Builder builder) { + this.theme = builder; } @NonNull - public Builder backgroundProvider(@NonNull JLatexMathTheme.BackgroundProvider backgroundProvider) { - this.backgroundProvider = backgroundProvider; - return this; - } - - @NonNull - public Builder align(@JLatexMathDrawable.Align int align) { - this.align = align; - return this; - } - - @NonNull - public Builder fitCanvas(boolean fitCanvas) { - this.fitCanvas = fitCanvas; - return this; - } - - @NonNull - public Builder padding(@Px int padding) { - this.paddingHorizontal = padding; - this.paddingVertical = padding; - return this; + public JLatexMathTheme.Builder theme() { + return theme; } /** - * @since 4.0.0 + * @since 4.3.0-SNAPSHOT */ @NonNull - public Builder builder(@Px int paddingHorizontal, @Px int paddingVertical) { - this.paddingHorizontal = paddingHorizontal; - this.paddingVertical = paddingVertical; + public Builder renderMode(@NonNull RenderMode renderMode) { + this.renderMode = renderMode; return this; } @@ -319,7 +317,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { } // @since 4.0.0 - private static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader { + static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader { private final Config config; private final Handler handler = new Handler(Looper.getMainLooper()); @@ -358,57 +356,15 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { private void execute() { - // @since 4.0.1 (background provider can be null) - final JLatexMathTheme.BackgroundProvider backgroundProvider = config.backgroundProvider; - final JLatexMathDrawable jLatexMathDrawable; - // TODO: obtain real values from theme (for blocks and inlines) final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable; - if (jLatextAsyncDrawable.isBlock) { - // create JLatexMathDrawable - //noinspection ConstantConditions - jLatexMathDrawable = - JLatexMathDrawable.builder(drawable.getDestination()) - .textSize(config.textSize) - .background(backgroundProvider != null ? backgroundProvider.provide() : null) - .align(config.align) - .fitCanvas(config.fitCanvas) - .padding( - config.paddingHorizontal, - config.paddingVertical, - config.paddingHorizontal, - config.paddingVertical) - .build(); - } else { - jLatexMathDrawable = - JLatexMathDrawable.builder(drawable.getDestination()) - .textSize(config.textSize) -// .background(backgroundProvider != null ? backgroundProvider.provide() : null) -// .align(config.align) -// .fitCanvas(config.fitCanvas) -// .padding( -// config.paddingHorizontal, -// config.paddingVertical, -// config.paddingHorizontal, -// config.paddingVertical) - .build(); - } - // create JLatexMathDrawable -// //noinspection ConstantConditions -// final JLatexMathDrawable jLatexMathDrawable = -// JLatexMathDrawable.builder(drawable.getDestination()) -// .textSize(config.textSize) -// .background(backgroundProvider != null ? backgroundProvider.provide() : null) -// .align(config.align) -// .fitCanvas(config.fitCanvas) -// .padding( -// config.paddingHorizontal, -// config.paddingVertical, -// config.paddingHorizontal, -// config.paddingVertical) -// .build(); + if (jLatextAsyncDrawable.isBlock()) { + jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable.getDestination()); + } else { + jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable.getDestination()); + } // we must post to handler, but also have a way to identify the drawable // for which we are posting (in case of cancellation) @@ -447,109 +403,63 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { public Drawable placeholder(@NonNull AsyncDrawable drawable) { return null; } + + // @since 4.3.0-SNAPSHOT + @NonNull + private JLatexMathDrawable createBlockDrawable(@NonNull String latex) { + + final JLatexMathTheme theme = config.theme; + + final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.blockBackgroundProvider(); + final JLatexMathTheme.Padding padding = theme.blockPadding(); + + final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex) + .textSize(theme.blockTextSize()) + .align(theme.blockHorizontalAlignment()) + .fitCanvas(theme.blockFitCanvas()); + + if (backgroundProvider != null) { + builder.background(backgroundProvider.provide()); + } + + if (padding != null) { + builder.padding(padding.left, padding.top, padding.right, padding.bottom); + } + + return builder.build(); + } + + // @since 4.3.0-SNAPSHOT + @NonNull + private JLatexMathDrawable createInlineDrawable(@NonNull String latex) { + + final JLatexMathTheme theme = config.theme; + + final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.inlineBackgroundProvider(); + final JLatexMathTheme.Padding padding = theme.inlinePadding(); + + final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex) + .textSize(theme.inlineTextSize()) + .fitCanvas(false); + + if (backgroundProvider != null) { + builder.background(backgroundProvider.provide()); + } + + if (padding != null) { + builder.padding(padding.left, padding.top, padding.right, padding.bottom); + } + + return builder.build(); + } } - // we must make drawable fit canvas (if specified), but do not keep the ratio whilst scaling up - // @since 4.0.0 - private static class JLatexImageSizeResolver extends ImageSizeResolver { - - private final boolean fitCanvas; - - JLatexImageSizeResolver(boolean fitCanvas) { - this.fitCanvas = fitCanvas; - } + private static class InlineImageSizeResolver extends ImageSizeResolver { @NonNull @Override public Rect resolveImageSize(@NonNull AsyncDrawable drawable) { - - final Rect imageBounds = drawable.getResult().getBounds(); - final int canvasWidth = drawable.getLastKnownCanvasWidth(); - - if (fitCanvas) { - - // we modify bounds only if `fitCanvas` is true - final int w = imageBounds.width(); - - if (w < canvasWidth) { - // increase width and center formula (keep height as-is) - return new Rect(0, 0, canvasWidth, imageBounds.height()); - } - - // @since 4.0.2 we additionally scale down the resulting formula (keeping the ratio) - // the thing is - JLatexMathDrawable will do it anyway, but it will modify its own - // bounds (which AsyncDrawable won't catch), thus leading to an empty space after the formula - if (w > canvasWidth) { - // here we must scale it down (keeping the ratio) - final float ratio = (float) w / imageBounds.height(); - final int h = (int) (canvasWidth / ratio + .5F); - return new Rect(0, 0, canvasWidth, h); - } - } - - return imageBounds; - } - } - - private static class JLatextAsyncDrawable extends AsyncDrawable { - - private final boolean isBlock; - - public JLatextAsyncDrawable( - @NonNull String destination, - @NonNull AsyncDrawableLoader loader, - @NonNull ImageSizeResolver imageSizeResolver, - @Nullable ImageSize imageSize, - boolean isBlock - ) { - super(destination, loader, imageSizeResolver, imageSize); - this.isBlock = isBlock; - } - } - - private static class JLatexAsyncDrawableSpan extends AsyncDrawableSpan { - - private final AsyncDrawable drawable; - - public JLatexAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) { - super(theme, drawable, alignment, replacementTextIsLink); - this.drawable = drawable; - } - - @Override - public int getSize( - @NonNull Paint paint, - CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @Nullable Paint.FontMetricsInt fm) { - - // if we have no async drawable result - we will just render text - - final int size; - - if (drawable.hasResult()) { - - final Rect rect = drawable.getBounds(); - - if (fm != null) { - final int half = rect.bottom / 2; - fm.ascent = -half; - fm.descent = half; - - fm.top = fm.ascent; - fm.bottom = 0; - } - - size = rect.right; - - } else { - - // NB, no specific text handling (no new lines, etc) - size = (int) (paint.measureText(text, start, end) + .5F); - } - - return size; + return drawable.getResult().getBounds(); } } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java index 30b9e04a..8a1d8801 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java @@ -128,9 +128,9 @@ public abstract class JLatexMathTheme { private BackgroundProvider inlineBackgroundProvider; private BackgroundProvider blockBackgroundProvider; - private boolean blockFitCanvas; + private boolean blockFitCanvas = true; // horizontal alignment (when there is additional horizontal space) - private int blockHorizontalAlignment; + private int blockHorizontalAlignment = JLatexMathDrawable.ALIGN_CENTER; private Padding padding; private Padding inlinePadding; @@ -196,7 +196,7 @@ public abstract class JLatexMathTheme { @NonNull public JLatexMathTheme build() { - return null; + return new Impl(this); } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java new file mode 100644 index 00000000..4376d636 --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatextAsyncDrawable.java @@ -0,0 +1,32 @@ +package io.noties.markwon.ext.latex; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.AsyncDrawableLoader; +import io.noties.markwon.image.ImageSize; +import io.noties.markwon.image.ImageSizeResolver; + +/** + * @since 4.3.0-SNAPSHOT + */ +class JLatextAsyncDrawable extends AsyncDrawable { + + private final boolean isBlock; + + JLatextAsyncDrawable( + @NonNull String destination, + @NonNull AsyncDrawableLoader loader, + @NonNull ImageSizeResolver imageSizeResolver, + @Nullable ImageSize imageSize, + boolean isBlock + ) { + super(destination, loader, imageSizeResolver, imageSize); + this.isBlock = isBlock; + } + + public boolean isBlock() { + return isBlock; + } +} diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index c669cde0..075c2ca4 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -1,7 +1,7 @@ package io.noties.markwon.sample.latex; +import android.content.res.Resources; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.TextView; @@ -14,7 +14,6 @@ import io.noties.markwon.ext.latex.JLatexMathTheme; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; -import ru.noties.jlatexmath.JLatexMathDrawable; public class LatexActivity extends ActivityWithMenuOptions { @@ -87,7 +86,7 @@ public class LatexActivity extends ActivityWithMenuOptions { private static String wrapLatexInSampleMarkdown(@NonNull String latex) { return "" + "# Example of LaTeX\n\n" + - "(inline): $$" + latex + "$$ so nice, really? Now, (block):\n\n" + + "(inline): $$" + latex + "$$ so nice, really-really really-really really-really? Now, (block):\n\n" + "$$\n" + "" + latex + "\n" + "$$\n\n" + @@ -95,23 +94,22 @@ public class LatexActivity extends ActivityWithMenuOptions { } private void render(@NonNull String markdown) { + + final float textSize = textView.getTextSize(); + final Resources r = getResources(); + final Markwon markwon = Markwon.builder(this) - .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { - @Override - public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { - builder - .backgroundProvider(new JLatexMathTheme.BackgroundProvider() { - @NonNull - @Override - public Drawable provide() { - return new ColorDrawable(0x40ff0000); - } - }) - .fitCanvas(true) - .align(JLatexMathDrawable.ALIGN_CENTER) - .padding(48) - ; - } + .usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> { + builder.theme() + .inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00)) + .blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000)) + .blockPadding(JLatexMathTheme.Padding.symmetric( + r.getDimensionPixelSize(R.dimen.latex_block_padding_vertical), + r.getDimensionPixelSize(R.dimen.latex_block_padding_horizontal) + )); + + // explicitly request LEGACY rendering mode +// builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY); })) .build(); diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml new file mode 100644 index 00000000..b88d4ed5 --- /dev/null +++ b/sample/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="latex_block_padding_vertical">8dip</dimen> + <dimen name="latex_block_padding_horizontal">16dip</dimen> +</resources> \ No newline at end of file From 74682ae605e5fbf470fad74206e8b18a2acbe3bf Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Wed, 26 Feb 2020 15:54:08 +0300 Subject: [PATCH 7/8] MarkwonInlineParserPlugin --- markwon-inline-parser/build.gradle | 1 + .../MarkwonInlineParserPlugin.java | 59 ++++++++++ .../sample/ActivityWithMenuOptions.java | 17 ++- .../io/noties/markwon/sample/MenuOptions.java | 19 ++- .../markwon/sample/editor/EditorActivity.java | 108 +++++++++++++++++- .../src/main/res/values/strings-samples.xml | 2 +- 6 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java diff --git a/markwon-inline-parser/build.gradle b/markwon-inline-parser/build.gradle index 703a18ff..32a45d7c 100644 --- a/markwon-inline-parser/build.gradle +++ b/markwon-inline-parser/build.gradle @@ -14,6 +14,7 @@ android { } dependencies { + api project(':markwon-core') api deps['x-annotations'] api deps['commonmark'] diff --git a/markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java b/markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java new file mode 100644 index 00000000..ce80501b --- /dev/null +++ b/markwon-inline-parser/src/main/java/io/noties/markwon/inlineparser/MarkwonInlineParserPlugin.java @@ -0,0 +1,59 @@ +package io.noties.markwon.inlineparser; + +import androidx.annotation.NonNull; + +import org.commonmark.parser.Parser; + +import io.noties.markwon.AbstractMarkwonPlugin; + +/** + * @since 4.3.0-SNAPSHOT + */ +public class MarkwonInlineParserPlugin extends AbstractMarkwonPlugin { + + public interface BuilderConfigure<B extends MarkwonInlineParser.FactoryBuilder> { + void configureBuilder(@NonNull B factoryBuilder); + } + + @NonNull + public static MarkwonInlineParserPlugin create() { + return create(MarkwonInlineParser.factoryBuilder()); + } + + @NonNull + public static MarkwonInlineParserPlugin create(@NonNull BuilderConfigure<MarkwonInlineParser.FactoryBuilder> configure) { + final MarkwonInlineParser.FactoryBuilder factoryBuilder = MarkwonInlineParser.factoryBuilder(); + configure.configureBuilder(factoryBuilder); + return new MarkwonInlineParserPlugin(factoryBuilder); + } + + @NonNull + public static MarkwonInlineParserPlugin create(@NonNull MarkwonInlineParser.FactoryBuilder factoryBuilder) { + return new MarkwonInlineParserPlugin(factoryBuilder); + } + + @NonNull + public static <B extends MarkwonInlineParser.FactoryBuilder> MarkwonInlineParserPlugin create( + @NonNull B factoryBuilder, + @NonNull BuilderConfigure<B> configure) { + configure.configureBuilder(factoryBuilder); + return new MarkwonInlineParserPlugin(factoryBuilder); + } + + private final MarkwonInlineParser.FactoryBuilder factoryBuilder; + + @SuppressWarnings("WeakerAccess") + MarkwonInlineParserPlugin(@NonNull MarkwonInlineParser.FactoryBuilder factoryBuilder) { + this.factoryBuilder = factoryBuilder; + } + + @Override + public void configureParser(@NonNull Parser.Builder builder) { + builder.inlineParserFactory(factoryBuilder.build()); + } + + @NonNull + public MarkwonInlineParser.FactoryBuilder factoryBuilder() { + return factoryBuilder; + } +} diff --git a/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java index 66690f7b..54f5342f 100644 --- a/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java +++ b/sample/src/main/java/io/noties/markwon/sample/ActivityWithMenuOptions.java @@ -13,6 +13,14 @@ public abstract class ActivityWithMenuOptions extends Activity { @NonNull public abstract MenuOptions menuOptions(); + protected void beforeOptionSelected(@NonNull String option) { + // no op, override to customize + } + + protected void afterOptionSelected(@NonNull String option) { + // no op, override to customize + } + private MenuOptions menuOptions; @Override @@ -29,6 +37,13 @@ public abstract class ActivityWithMenuOptions extends Activity { @Override public boolean onOptionsItemSelected(MenuItem item) { - return menuOptions.onOptionsItemSelected(item); + final MenuOptions.Option option = menuOptions.onOptionsItemSelected(item); + if (option != null) { + beforeOptionSelected(option.title); + option.action.run(); + afterOptionSelected(option.title); + return true; + } + return false; } } diff --git a/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java index 1c349bd8..6fb5b310 100644 --- a/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java +++ b/sample/src/main/java/io/noties/markwon/sample/MenuOptions.java @@ -4,6 +4,7 @@ import android.view.Menu; import android.view.MenuItem; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.LinkedHashMap; import java.util.Map; @@ -15,6 +16,16 @@ public class MenuOptions { return new MenuOptions(); } + static class Option { + final String title; + final Runnable action; + + Option(@NonNull String title, @NonNull Runnable action) { + this.title = title; + this.action = action; + } + } + // to preserve order use LinkedHashMap private final Map<String, Runnable> actions = new LinkedHashMap<>(); @@ -34,13 +45,13 @@ public class MenuOptions { return false; } - boolean onOptionsItemSelected(MenuItem item) { + @Nullable + Option onOptionsItemSelected(MenuItem item) { final String title = String.valueOf(item.getTitle()); final Runnable action = actions.get(title); if (action != null) { - action.run(); - return true; + return new Option(title, action); } - return false; + return null; } } diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java index 0dc05ef9..e1181a7f 100644 --- a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java @@ -5,6 +5,7 @@ import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; +import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.ForegroundColorSpan; import android.text.style.MetricAffectingSpan; @@ -40,6 +41,7 @@ import io.noties.markwon.inlineparser.BangInlineProcessor; import io.noties.markwon.inlineparser.EntityInlineProcessor; import io.noties.markwon.inlineparser.HtmlInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParser; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import io.noties.markwon.linkify.LinkifyPlugin; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; @@ -48,6 +50,7 @@ import io.noties.markwon.sample.R; public class EditorActivity extends ActivityWithMenuOptions { private EditText editText; + private String pendingInput; @NonNull @Override @@ -58,16 +61,41 @@ public class EditorActivity extends ActivityWithMenuOptions { .add("customPunctuationSpan", this::custom_punctuation_span) .add("additionalEditSpan", this::additional_edit_span) .add("additionalPlugins", this::additional_plugins) - .add("multipleEditSpans", this::multiple_edit_spans); + .add("multipleEditSpans", this::multiple_edit_spans) + .add("multipleEditSpansPlugin", this::multiple_edit_spans_plugin) + .add("pluginRequire", this::plugin_require) + .add("pluginNoDefaults", this::plugin_no_defaults); + } + + @Override + protected void beforeOptionSelected(@NonNull String option) { + // we cannot _clear_ editText of text-watchers without keeping a reference to them... + pendingInput = editText != null + ? editText.getText().toString() + : null; + + createView(); + } + + @Override + protected void afterOptionSelected(@NonNull String option) { + if (!TextUtils.isEmpty(pendingInput)) { + editText.setText(pendingInput); + } + } + + private void createView() { + setContentView(R.layout.activity_editor); + + this.editText = findViewById(R.id.edit_text); + + initBottomBar(); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_editor); - - this.editText = findViewById(R.id.edit_text); - initBottomBar(); + createView(); multiple_edit_spans(); } @@ -219,6 +247,76 @@ public class EditorActivity extends ActivityWithMenuOptions { editor, Executors.newSingleThreadExecutor(), editText)); } + private void multiple_edit_spans_plugin() { + // inline parsing is configured via MarkwonInlineParserPlugin + + // for links to be clickable + editText.setMovementMethod(LinkMovementMethod.getInstance()); + + final Markwon markwon = Markwon.builder(this) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(LinkifyPlugin.create()) + .usePlugin(MarkwonInlineParserPlugin.create(builder -> { + builder + .excludeInlineProcessor(BangInlineProcessor.class) + .excludeInlineProcessor(HtmlInlineProcessor.class) + .excludeInlineProcessor(EntityInlineProcessor.class); + })) + .build(); + + final LinkEditHandler.OnClick onClick = (widget, link) -> markwon.configuration().linkResolver().resolve(widget, link); + + final MarkwonEditor editor = MarkwonEditor.builder(markwon) + .useEditHandler(new EmphasisEditHandler()) + .useEditHandler(new StrongEmphasisEditHandler()) + .useEditHandler(new StrikethroughEditHandler()) + .useEditHandler(new CodeEditHandler()) + .useEditHandler(new BlockQuoteEditHandler()) + .useEditHandler(new LinkEditHandler(onClick)) + .build(); + + editText.addTextChangedListener(MarkwonEditorTextWatcher.withPreRender( + editor, Executors.newSingleThreadExecutor(), editText)); + } + + private void plugin_require() { + // usage of plugin from other plugins + + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(MarkwonInlineParserPlugin.class) + .factoryBuilder() + .excludeInlineProcessor(HtmlInlineProcessor.class); + } + }) + .build(); + + final MarkwonEditor editor = MarkwonEditor.create(markwon); + + editText.addTextChangedListener(MarkwonEditorTextWatcher.withPreRender( + editor, Executors.newSingleThreadExecutor(), editText)); + } + + private void plugin_no_defaults() { + // a plugin with no defaults registered + + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults())) +// .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults(), factoryBuilder -> { +// // if anything, they can be included here +//// factoryBuilder.includeDefaults() +// })) + .build(); + + final MarkwonEditor editor = MarkwonEditor.create(markwon); + + editText.addTextChangedListener(MarkwonEditorTextWatcher.withPreRender( + editor, Executors.newSingleThreadExecutor(), editText)); + } + private void initBottomBar() { // all except block-quote wraps if have selection, or inserts at current cursor position diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml index f5a97644..d7f11e1a 100644 --- a/sample/src/main/res/values/strings-samples.xml +++ b/sample/src/main/res/values/strings-samples.xml @@ -29,7 +29,7 @@ <string name="sample_inline_parser"># \# Inline Parser\n\nUsage of custom inline parser</string> - <string name="sample_html_details"># \# HTML <details> tag\n\n<details> tag parsed and rendered</string> + <string name="sample_html_details"># \# HTML\n\n`details` tag parsed and rendered</string> <string name="sample_task_list"># \# TaskList\n\nUsage of TaskListPlugin</string> From 1c08e3f240ccc5fa341935483782e9e28d17294c Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Wed, 26 Feb 2020 16:10:20 +0300 Subject: [PATCH 8/8] LatexPlugin now depens on inline-parser plugin --- markwon-ext-latex/build.gradle | 3 +- .../markwon/ext/latex/JLatexMathPlugin.java | 20 +++-- .../markwon/sample/latex/LatexActivity.java | 75 +++++++++++++------ 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/markwon-ext-latex/build.gradle b/markwon-ext-latex/build.gradle index 9d5f50b0..b0d3fc92 100644 --- a/markwon-ext-latex/build.gradle +++ b/markwon-ext-latex/build.gradle @@ -16,11 +16,10 @@ android { dependencies { api project(':markwon-core') + api project(':markwon-inline-parser') api deps['jlatexmath-android'] - debugImplementation project(':markwon-inline-parser') - deps['test'].with { testImplementation it['junit'] testImplementation it['robolectric'] diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 5ecc0723..5a3d70b9 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -14,7 +14,6 @@ import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.VisibleForTesting; -import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.Parser; import java.util.HashMap; @@ -31,7 +30,7 @@ import io.noties.markwon.image.AsyncDrawableLoader; import io.noties.markwon.image.AsyncDrawableScheduler; import io.noties.markwon.image.AsyncDrawableSpan; import io.noties.markwon.image.ImageSizeResolver; -import io.noties.markwon.inlineparser.MarkwonInlineParser; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import ru.noties.jlatexmath.JLatexMathDrawable; /** @@ -153,10 +152,19 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { this.inlineImageSizeResolver = new InlineImageSizeResolver(); } + @Override + public void configure(@NonNull Registry registry) { + if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) { + registry.require(MarkwonInlineParserPlugin.class) + .factoryBuilder() + .addInlineProcessor(new JLatexMathInlineProcessor()); + } + } + @Override public void configureParser(@NonNull Parser.Builder builder) { - // TODO: depending on renderMode we should register our parsing here + // depending on renderMode we should register our parsing here // * for LEGACY -> just add custom block parser // * for INLINE.. -> require InlinePlugin, add inline processor + add block parser @@ -169,11 +177,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { case BLOCKS_AND_INLINES: { builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); - - final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder() - .addInlineProcessor(new JLatexMathInlineProcessor()) - .build(); - builder.inlineParserFactory(factory); + // inline processor is added through `registry` } break; diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index 075c2ca4..8dcdcd11 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -11,12 +11,41 @@ import androidx.annotation.Nullable; import io.noties.markwon.Markwon; import io.noties.markwon.ext.latex.JLatexMathPlugin; import io.noties.markwon.ext.latex.JLatexMathTheme; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; public class LatexActivity extends ActivityWithMenuOptions { + private static final String LATEX_ARRAY; + + static { + String latex = "\\begin{array}{l}"; + latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; + latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; + latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; + latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; + latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; + latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; + latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; + latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; + latex += "\\end{array}"; + LATEX_ARRAY = latex; + } + + private static final String LATEX_LONG_DIVISION = "\\text{A long division \\longdiv{12345}{13}"; + private static final String LATEX_BANGLE = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; + private static final String LATEX_BOXES; + + static { + String latex = "\\begin{array}{cc}"; + latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; + latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; + latex += "\\end{array}"; + LATEX_BOXES = latex; + } + private TextView textView; @NonNull @@ -27,7 +56,8 @@ public class LatexActivity extends ActivityWithMenuOptions { .add("longDivision", this::longDivision) .add("bangle", this::bangle) .add("boxes", this::boxes) - .add("insideBlockQuote", this::insideBlockQuote); + .add("insideBlockQuote", this::insideBlockQuote) + .add("legacy", this::legacy); } @Override @@ -42,36 +72,19 @@ public class LatexActivity extends ActivityWithMenuOptions { } private void array() { - String latex = "\\begin{array}{l}"; - latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; - latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; - latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; - latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; - latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; - latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; - latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; - latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; - latex += "\\end{array}"; - - render(wrapLatexInSampleMarkdown(latex)); + render(wrapLatexInSampleMarkdown(LATEX_ARRAY)); } private void longDivision() { - String latex = "\\text{A long division \\longdiv{12345}{13}"; - render(wrapLatexInSampleMarkdown(latex)); + render(wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION)); } private void bangle() { - String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; - render(wrapLatexInSampleMarkdown(latex)); + render(wrapLatexInSampleMarkdown(LATEX_BANGLE)); } private void boxes() { - String latex = "\\begin{array}{cc}"; - latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; - latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; - latex += "\\end{array}"; - render(wrapLatexInSampleMarkdown(latex)); + render(wrapLatexInSampleMarkdown(LATEX_BOXES)); } private void insideBlockQuote() { @@ -82,6 +95,22 @@ public class LatexActivity extends ActivityWithMenuOptions { render(md); } + private void legacy() { + final String md = wrapLatexInSampleMarkdown(LATEX_BANGLE); + + final Markwon markwon = Markwon.builder(this) + // LEGACY does not require inline parser + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY); + builder.theme() + .backgroundProvider(() -> new ColorDrawable(0x100000ff)) + .padding(JLatexMathTheme.Padding.all(48)); + })) + .build(); + + markwon.setMarkdown(textView, md); + } + @NonNull private static String wrapLatexInSampleMarkdown(@NonNull String latex) { return "" + @@ -99,6 +128,8 @@ public class LatexActivity extends ActivityWithMenuOptions { final Resources r = getResources(); final Markwon markwon = Markwon.builder(this) + // NB! `MarkwonInlineParserPlugin` is required in order to parse inlines + .usePlugin(MarkwonInlineParserPlugin.create()) .usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> { builder.theme() .inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00))