Latex inline parsing WIP
This commit is contained in:
		
							parent
							
								
									a298016ac2
								
							
						
					
					
						commit
						d78b278b86
					
				| @ -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(); | ||||||
| 
 | 
 | ||||||
|             final CharSequence line = state.getLine(); |             // check if it's an indented code block | ||||||
|             final int length = line != null |             if (indent < Parsing.CODE_BLOCK_INDENT) { | ||||||
|                     ? line.length() |                 final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); | ||||||
|                     : 0; |                 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 (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 | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | 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; | ||||||
| 
 | 
 | ||||||
| @ -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, | ||||||
|  | |||||||
| @ -24,26 +24,26 @@ 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}"; | ||||||
| //        latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; | //        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 += "\\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) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov