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; package io.noties.markwon.ext.latex;
import org.commonmark.internal.util.Parsing;
import org.commonmark.node.Block; import org.commonmark.node.Block;
import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.AbstractBlockParser;
import org.commonmark.parser.block.AbstractBlockParserFactory; import org.commonmark.parser.block.AbstractBlockParserFactory;
@ -60,18 +61,30 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
@Override @Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
// ^\s{0,3}\$\$\s*$ as a regex to star the block 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 CharSequence line = state.getLine();
final int length = line != null final int length = line.length();
? line.length() // we are looking for 2 `$$` subsequent signs
: 0; // 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 (diff > 0) {
if ('$' == line.charAt(0) if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) {
&& '$' == line.charAt(1)) { return BlockStart.none();
return BlockStart.of(new JLatexMathBlockParser()) }
.atIndex(state.getIndex() + 2); // 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; 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; private String latex;

View File

@ -14,7 +14,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.Px; import androidx.annotation.Px;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.commonmark.node.Node;
import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.InlineParserFactory;
import org.commonmark.parser.Parser; 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.AsyncDrawableSpan;
import io.noties.markwon.image.ImageSizeResolver; import io.noties.markwon.image.ImageSizeResolver;
import io.noties.markwon.image.ImageSizeResolverDef; import io.noties.markwon.image.ImageSizeResolverDef;
import io.noties.markwon.inlineparser.InlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParser; import io.noties.markwon.inlineparser.MarkwonInlineParser;
import ru.noties.jlatexmath.JLatexMathDrawable; import ru.noties.jlatexmath.JLatexMathDrawable;
@ -132,8 +130,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
// if it's $$\\dhdsfjh$$ -> inline // if it's $$\\dhdsfjh$$ -> inline
// builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); // builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
final InlineParserFactory factory = MarkwonInlineParser.factoryBuilderNoDefaults() final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
.addInlineProcessor(new LatexInlineProcessor()) .addInlineProcessor(new JLatexMathInlineProcessor())
.build(); .build();
builder.inlineParserFactory(factory); builder.inlineParserFactory(factory);
} }
@ -155,6 +153,33 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
final MarkwonConfiguration configuration = visitor.configuration(); 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( final AsyncDrawableSpan span = new AsyncDrawableSpan(
configuration.theme(), configuration.theme(),
new AsyncDrawable( new AsyncDrawable(
@ -170,76 +195,76 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}); });
} }
private static class LatexInlineProcessor extends InlineProcessor { // private static class LatexInlineProcessor extends InlineProcessor {
//
@Override // @Override
public char specialCharacter() { // public char specialCharacter() {
return '$'; // return '$';
} // }
//
@Nullable // @Nullable
@Override // @Override
protected Node parse() { // protected Node parse() {
//
final int start = index; // final int start = index;
//
index += 1; // index += 1;
if (peek() != '$') { // if (peek() != '$') {
index = start; // index = start;
return null; // return null;
} // }
//
// must be not $ // // must be not $
index += 1; // index += 1;
if (peek() == '$') { // if (peek() == '$') {
return text("$"); // return text("$");
} // }
//
// find next '$$', but not broken with 2(or more) new lines // // find next '$$', but not broken with 2(or more) new lines
//
boolean dollar = false; // boolean dollar = false;
boolean newLine = false; // boolean newLine = false;
boolean found = false; // boolean found = false;
//
index += 1; // index += 1;
final int length = input.length(); // final int length = input.length();
//
while (index < length) { // while (index < length) {
final char c = peek(); // final char c = peek();
if (c == '\n') { // if (c == '\n') {
if (newLine) { // if (newLine) {
// second new line // // second new line
break; // break;
} // }
newLine = true; // newLine = true;
dollar = false; // cannot be on another line // dollar = false; // cannot be on another line
} else { // } else {
newLine = false; // newLine = false;
if (c == '$') { // if (c == '$') {
if (dollar) { // if (dollar) {
found = true; // found = true;
// advance // // advance
index += 1; // index += 1;
break; // break;
} // }
dollar = true; // dollar = true;
} else { // } else {
dollar = false; // dollar = false;
} // }
} // }
index += 1; // index += 1;
} // }
//
if (found) { // if (found) {
final JLatexMathBlock block = new JLatexMathBlock(); // final JLatexMathBlock block = new JLatexMathBlock();
block.latex(input.substring(start + 2, index - 2)); // block.latex(input.substring(start + 2, index - 2));
index += 1; // index += 1;
return block; // return block;
} // }
//
return null; // return null;
} // }
} // }
@Override @Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
@ -383,7 +408,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
.textSize(config.textSize) .textSize(config.textSize)
.background(backgroundProvider != null ? backgroundProvider.provide() : null) .background(backgroundProvider != null ? backgroundProvider.provide() : null)
.align(config.align) .align(config.align)
.fitCanvas(config.fitCanvas) .fitCanvas(false /*config.fitCanvas*/)
.padding( .padding(
config.paddingHorizontal, config.paddingHorizontal,
config.paddingVertical, config.paddingVertical,

View File

@ -24,18 +24,18 @@ public class LatexActivity extends Activity {
final TextView textView = findViewById(R.id.text_view); final TextView textView = findViewById(R.id.text_view);
String latex = "\\begin{array}{l}"; // 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 += "\\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 += "\\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 += "\\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_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_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 += "\\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 += "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 += "\\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 += "\\end{array}";
// String latex = "\\text{A long division \\longdiv{12345}{13}"; 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 = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}";
// String latex = "\\begin{array}{cc}"; // String latex = "\\begin{array}{cc}";
@ -43,7 +43,7 @@ public class LatexActivity extends Activity {
// latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; // latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr";
// latex += "\\end{array}"; // 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**"; + latex + "$$\n\n something like **this**";
final Markwon markwon = Markwon.builder(this) final Markwon markwon = Markwon.builder(this)