Latex inline parsing WIP
This commit is contained in:
		
							parent
							
								
									a298016ac2
								
							
						
					
					
						commit
						d78b278b86
					
				| @ -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 | ||||
|                         } | ||||
| 
 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
| 
 | ||||
| @ -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, | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov