From 073f41b5f6775c165bb6ebe33b217f02215677d5 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sat, 8 Sep 2018 15:46:41 +0300 Subject: [PATCH] Add sample-latex-math module --- sample-latex-math/build.gradle | 23 +++ .../src/main/AndroidManifest.xml | 18 +++ .../sample/jlatexmath/JLatexMathBlock.java | 16 ++ .../jlatexmath/JLatexMathBlockParser.java | 79 ++++++++++ .../sample/jlatexmath/JLatexMathMedia.java | 137 ++++++++++++++++++ .../sample/jlatexmath/MainActivity.java | 113 +++++++++++++++ .../src/main/res/layout/activity_main.xml | 14 ++ .../src/main/res/values/strings.xml | 3 + .../src/main/res/values/styles.xml | 6 + settings.gradle | 2 +- 10 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 sample-latex-math/build.gradle create mode 100644 sample-latex-math/src/main/AndroidManifest.xml create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java create mode 100644 sample-latex-math/src/main/res/layout/activity_main.xml create mode 100644 sample-latex-math/src/main/res/values/strings.xml create mode 100644 sample-latex-math/src/main/res/values/styles.xml diff --git a/sample-latex-math/build.gradle b/sample-latex-math/build.gradle new file mode 100644 index 00000000..81732d95 --- /dev/null +++ b/sample-latex-math/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.application' + +android { + + compileSdkVersion config['compile-sdk'] + buildToolsVersion config['build-tools'] + + defaultConfig { + + applicationId "ru.noties.markwon.sample.jlatexmath" + + minSdkVersion config['min-sdk'] + targetSdkVersion config['target-sdk'] + versionCode 1 + versionName version + } +} + +dependencies { + implementation project(':markwon') + implementation project(':markwon-image-loader') + implementation 'ru.noties:jlatexmath-android:0.1.0' +} diff --git a/sample-latex-math/src/main/AndroidManifest.xml b/sample-latex-math/src/main/AndroidManifest.xml new file mode 100644 index 00000000..327ca324 --- /dev/null +++ b/sample-latex-math/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java new file mode 100644 index 00000000..3e3e4479 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java @@ -0,0 +1,16 @@ +package ru.noties.markwon.sample.jlatexmath; + +import org.commonmark.node.CustomBlock; + +public class JLatexMathBlock extends CustomBlock { + + private String latex; + + public String latex() { + return latex; + } + + public void latex(String latex) { + this.latex = latex; + } +} diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java new file mode 100644 index 00000000..542b3869 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java @@ -0,0 +1,79 @@ +package ru.noties.markwon.sample.jlatexmath; + +import org.commonmark.node.Block; +import org.commonmark.parser.block.AbstractBlockParser; +import org.commonmark.parser.block.AbstractBlockParserFactory; +import org.commonmark.parser.block.BlockContinue; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.MatchedBlockParser; +import org.commonmark.parser.block.ParserState; + +public class JLatexMathBlockParser extends AbstractBlockParser { + + private final JLatexMathBlock block = new JLatexMathBlock(); + + private final StringBuilder builder = new StringBuilder(); + + private boolean isClosed; + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + + if (isClosed) { + return BlockContinue.finished(); + } + + return BlockContinue.atIndex(parserState.getIndex()); + } + + @Override + public void addLine(CharSequence line) { + + if (builder.length() > 0) { + builder.append('\n'); + } + + builder.append(line); + + final int length = builder.length(); + if (length > 1) { + isClosed = '$' == builder.charAt(length - 1) + && '$' == builder.charAt(length - 2); + if (isClosed) { + builder.replace(length - 2, length, ""); + } + } + } + + @Override + public void closeBlock() { + block.latex(builder.toString()); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + + final CharSequence line = state.getLine(); + final int length = line != null + ? line.length() + : 0; + + if (length > 1) { + if ('$' == line.charAt(0) + && '$' == line.charAt(1)) { + return BlockStart.of(new JLatexMathBlockParser()) + .atIndex(state.getIndex() + 2); + } + } + + return BlockStart.none(); + } + } +} diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java new file mode 100644 index 00000000..60fe52b9 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java @@ -0,0 +1,137 @@ +package ru.noties.markwon.sample.jlatexmath; + +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Scanner; + +import ru.noties.jlatexmath.JLatexMathDrawable; +import ru.noties.markwon.il.ImageItem; +import ru.noties.markwon.il.MediaDecoder; +import ru.noties.markwon.il.SchemeHandler; + +public class JLatexMathMedia { + + public static class Config { + + protected final float textSize; + + protected Drawable background; + + @JLatexMathDrawable.Align + protected int align = JLatexMathDrawable.ALIGN_CENTER; + + protected boolean fitCanvas = true; + + protected int padding; + + public Config(float textSize) { + this.textSize = textSize; + } + } + + public static final String SCHEME = "jlatexmath"; + + @NonNull + public static String makeDestination(@NonNull String latex) { + return SCHEME + "://" + latex; + } + + private static final String CONTENT_TYPE = "text/jlatexmath"; + + private final Config config; + + public JLatexMathMedia(@NonNull Config config) { + this.config = config; + } + + @NonNull + public SchemeHandler schemeHandler() { + return new SchemeHandlerImpl(); + } + + @NonNull + public MediaDecoder mediaDecoder() { + return new MediaDecoderImpl(config); + } + + static class SchemeHandlerImpl extends SchemeHandler { + + @Nullable + @Override + public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { + + Log.e("handle", raw); + + ImageItem item = null; + + try { + final byte[] bytes = raw.substring(SCHEME.length()).getBytes("UTF-8"); + item = new ImageItem( + CONTENT_TYPE, + new ByteArrayInputStream(bytes), + null + ); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return item; + } + + @Override + public void cancel(@NonNull String raw) { + // no op + } + } + + static class MediaDecoderImpl extends MediaDecoder { + + private final Config config; + + MediaDecoderImpl(@NonNull Config config) { + this.config = config; + } + + @Override + public boolean canDecodeByContentType(@Nullable String contentType) { + return CONTENT_TYPE.equals(contentType); + } + + @Override + public boolean canDecodeByFileName(@NonNull String fileName) { + return false; + } + + @Nullable + @Override + public Drawable decode(@NonNull InputStream inputStream) { + + final Scanner scanner = new Scanner(inputStream, "UTF-8").useDelimiter("\\A"); + final String latex = scanner.hasNext() + ? scanner.next() + : null; + + Log.e("decode", latex); + + if (latex == null) { + return null; + } + + // todo: change to float + return JLatexMathDrawable.builder(latex) + .textSize((int) config.textSize) + .background(config.background) + .align(config.align) + .fitCanvas(config.fitCanvas) + .padding(config.padding) + .build(); + } + } +} diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java new file mode 100644 index 00000000..dcc1c8d5 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java @@ -0,0 +1,113 @@ +package ru.noties.markwon.sample.jlatexmath; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.widget.TextView; + +import org.commonmark.node.CustomBlock; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; + +import ru.noties.jlatexmath.JLatexMathAndroid; +import ru.noties.jlatexmath.JLatexMathDrawable; +import ru.noties.markwon.Markwon; +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.il.AsyncDrawableLoader; +import ru.noties.markwon.renderer.ImageSize; +import ru.noties.markwon.renderer.SpannableMarkdownVisitor; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // todo: later version has automatic initialization + JLatexMathAndroid.init(this); + + final TextView textView = findViewById(R.id.text_view); + + // this one fails due input sanitizing on commonmark-java side + 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 = "\\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}"; + + // mention another way of doing this: through async drawable to process formulas in background + + + final JLatexMathMedia.Config config = new JLatexMathMedia.Config(textView.getTextSize()) {{ +// align = JLatexMathDrawable.ALIGN_RIGHT; + }}; + final JLatexMathMedia jLatexMathMedia = new JLatexMathMedia(config); + + final AsyncDrawableLoader asyncDrawableLoader = AsyncDrawableLoader.builder() + .schemeHandler(JLatexMathMedia.SCHEME, jLatexMathMedia.schemeHandler()) + .mediaDecoders(jLatexMathMedia.mediaDecoder()) + .build(); + + final SpannableConfiguration configuration = SpannableConfiguration.builder(this) + .asyncDrawableLoader(asyncDrawableLoader) + .build(); + + final String markdown = "# Example of LaTeX\n\n$$" + + latex + "$$\n\n something like **this**"; + + final Parser parser = new Parser.Builder() + .customBlockParserFactory(new JLatexMathBlockParser.Factory()) + .build(); + + final Node node = parser.parse(markdown); + final SpannableBuilder builder = new SpannableBuilder(); + final SpannableMarkdownVisitor visitor = new SpannableMarkdownVisitor(SpannableConfiguration.create(this), builder) { + + @Override + public void visit(CustomBlock customBlock) { + + if (!(customBlock instanceof JLatexMathBlock)) { + super.visit(customBlock); + return; + } + + final String latex = ((JLatexMathBlock) customBlock).latex(); + + final int length = builder.length(); + builder.append(latex); + + SpannableBuilder.setSpans( + builder, + configuration.factory().image( + configuration.theme(), + JLatexMathMedia.makeDestination(latex), + configuration.asyncDrawableLoader(), + configuration.imageSizeResolver(), + new ImageSize(new ImageSize.Dimension(100, "%"), null), + false + ), + length, + builder.length() + ); + } + }; + node.accept(visitor); + + Markwon.setText(textView, builder.text()); + } +} diff --git a/sample-latex-math/src/main/res/layout/activity_main.xml b/sample-latex-math/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..23e9e463 --- /dev/null +++ b/sample-latex-math/src/main/res/layout/activity_main.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/sample-latex-math/src/main/res/values/strings.xml b/sample-latex-math/src/main/res/values/strings.xml new file mode 100644 index 00000000..58b56912 --- /dev/null +++ b/sample-latex-math/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Markwon-JLatexMath + diff --git a/sample-latex-math/src/main/res/values/styles.xml b/sample-latex-math/src/main/res/values/styles.xml new file mode 100644 index 00000000..b0e65b65 --- /dev/null +++ b/sample-latex-math/src/main/res/values/styles.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/settings.gradle b/settings.gradle index 13f0d802..11192dbe 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'MarkwonProject' -include ':app', ':markwon', ':markwon-image-loader', ':markwon-view', ':sample-custom-extension', +include ':app', ':markwon', ':markwon-image-loader', ':markwon-view', ':sample-custom-extension', ':sample-latex-math', ':markwon-syntax-highlight', ':markwon-html-parser-api', ':markwon-html-parser-impl'