Add sample-latex-math module

This commit is contained in:
Dimitry Ivanov 2018-09-08 15:46:41 +03:00
parent fde9712454
commit 073f41b5f6
10 changed files with 410 additions and 1 deletions

View File

@ -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'
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.noties.markwon.sample.jlatexmath">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dip" />
</ScrollView>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Markwon-JLatexMath</string>
</resources>

View File

@ -0,0 +1,6 @@
<resources>
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
</style>
</resources>

View File

@ -1,3 +1,3 @@
rootProject.name = 'MarkwonProject' 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' ':markwon-syntax-highlight', ':markwon-html-parser-api', ':markwon-html-parser-impl'