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