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'