Latex inline parsing WIP

This commit is contained in:
Dimitry Ivanov 2020-02-10 22:25:20 +03:00
parent a298016ac2
commit d78b278b86
5 changed files with 174 additions and 100 deletions

View File

@ -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
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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)