Add markwon-ext-latex module extension

This commit is contained in:
Dimitry Ivanov 2018-12-02 16:13:08 +03:00
parent 6a82b75aba
commit 577c3fc782
13 changed files with 198 additions and 210 deletions

View File

@ -61,6 +61,7 @@ ext {
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion", 'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",
'android-svg' : 'com.caverock:androidsvg:1.2.1', 'android-svg' : 'com.caverock:androidsvg:1.2.1',
'android-gif' : 'pl.droidsonroids.gif:android-gif-drawable:1.2.14', 'android-gif' : 'pl.droidsonroids.gif:android-gif-drawable:1.2.14',
'jlatexmath-android' : 'ru.noties:jlatexmath-android:0.1.0',
'okhttp' : 'com.squareup.okhttp3:okhttp:3.9.0', 'okhttp' : 'com.squareup.okhttp3:okhttp:3.9.0',
'prism4j' : 'ru.noties:prism4j:1.1.0', 'prism4j' : 'ru.noties:prism4j:1.1.0',
'debug' : 'ru.noties:debug:3.0.0@jar', 'debug' : 'ru.noties:debug:3.0.0@jar',

View File

@ -0,0 +1,22 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion config['compile-sdk']
buildToolsVersion config['build-tools']
defaultConfig {
minSdkVersion config['min-sdk']
targetSdkVersion config['target-sdk']
versionCode 1
versionName version
}
}
dependencies {
api project(':markwon')
api deps['jlatexmath-android']
}
registerArtifact(this)

View File

@ -0,0 +1 @@
<manifest package="ru.noties.markwon.ext.latex" />

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.sample.jlatexmath; package ru.noties.markwon.ext.latex;
import org.commonmark.node.CustomBlock; import org.commonmark.node.CustomBlock;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.sample.jlatexmath; package ru.noties.markwon.ext.latex;
import org.commonmark.node.Block; import org.commonmark.node.Block;
import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.AbstractBlockParser;

View File

@ -0,0 +1,143 @@
package ru.noties.markwon.ext.latex;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.commonmark.parser.Parser;
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.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.MarkwonVisitor;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.image.ImageItem;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.MediaDecoder;
import ru.noties.markwon.image.SchemeHandler;
public class JLatexMathPlugin extends AbstractMarkwonPlugin {
@NonNull
public static JLatexMathPlugin create(@NonNull Config config) {
return new JLatexMathPlugin(config);
}
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;
}
}
@NonNull
public static String makeDestination(@NonNull String latex) {
return SCHEME + "://" + latex;
}
private static final String SCHEME = "jlatexmath";
private static final String CONTENT_TYPE = "text/jlatexmath";
private final Config config;
JLatexMathPlugin(@NonNull Config config) {
this.config = config;
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(JLatexMathBlock.class, new MarkwonVisitor.NodeVisitor<JLatexMathBlock>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) {
final String latex = jLatexMathBlock.latex();
final int length = visitor.length();
visitor.builder().append(latex);
final MarkwonConfiguration configuration = visitor.configuration();
visitor.setSpans(
length,
configuration.factory().image(
visitor.theme(),
makeDestination(latex),
configuration.asyncDrawableLoader(),
configuration.imageSizeResolver(),
new ImageSize(new ImageSize.Dimension(100, "%"), null),
false
)
);
}
});
}
@Override
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
builder
.addSchemeHandler(SCHEME, new SchemeHandler() {
@Nullable
@Override
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
ImageItem item = null;
try {
final byte[] bytes = raw.substring(SCHEME.length()).getBytes("UTF-8");
item = new ImageItem(
CONTENT_TYPE,
new ByteArrayInputStream(bytes));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return item;
}
})
.addMediaDecoder(CONTENT_TYPE, new MediaDecoder() {
@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;
if (latex == null) {
return null;
}
return JLatexMathDrawable.builder(latex)
.textSize(config.textSize)
.background(config.background)
.align(config.align)
.fitCanvas(config.fitCanvas)
.padding(config.padding)
.build();
}
});
}
}

View File

@ -79,12 +79,6 @@ public class CorePlugin extends AbstractMarkwonPlugin {
@Override @Override
public void beforeSetText(@NonNull TextView textView, @NonNull CharSequence markdown) { public void beforeSetText(@NonNull TextView textView, @NonNull CharSequence markdown) {
OrderedListItemSpan.measure(textView, markdown); OrderedListItemSpan.measure(textView, markdown);
AsyncDrawableScheduler.unschedule(textView);
}
@Override
public void afterSetText(@NonNull TextView textView) {
AsyncDrawableScheduler.schedule(textView);
} }
protected void text(@NonNull MarkwonVisitor.Builder builder) { protected void text(@NonNull MarkwonVisitor.Builder builder) {

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.core; package ru.noties.markwon.image;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@ -15,12 +15,11 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import ru.noties.markwon.renderer.R; import ru.noties.markwon.renderer.R;
import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.spans.AsyncDrawableSpan; import ru.noties.markwon.spans.AsyncDrawableSpan;
abstract class AsyncDrawableScheduler { public abstract class AsyncDrawableScheduler {
static void schedule(@NonNull final TextView textView) { public static void schedule(@NonNull final TextView textView) {
final List<AsyncDrawable> list = extract(textView); final List<AsyncDrawable> list = extract(textView);
if (list.size() > 0) { if (list.size() > 0) {
@ -50,7 +49,7 @@ abstract class AsyncDrawableScheduler {
} }
// must be called when text manually changed in TextView // must be called when text manually changed in TextView
static void unschedule(@NonNull TextView view) { public static void unschedule(@NonNull TextView view) {
for (AsyncDrawable drawable : extract(view)) { for (AsyncDrawable drawable : extract(view)) {
drawable.setCallback2(null); drawable.setCallback2(null);
} }

View File

@ -2,6 +2,7 @@ package ru.noties.markwon.image;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.widget.TextView;
import org.commonmark.node.Image; import org.commonmark.node.Image;
import org.commonmark.node.Link; import org.commonmark.node.Link;
@ -89,4 +90,14 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
} }
}); });
} }
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull CharSequence markdown) {
AsyncDrawableScheduler.unschedule(textView);
}
@Override
public void afterSetText(@NonNull TextView textView) {
AsyncDrawableScheduler.schedule(textView);
}
} }

View File

@ -18,6 +18,7 @@ android {
dependencies { dependencies {
implementation project(':markwon') implementation project(':markwon')
implementation project(':markwon-ext-latex')
// implementation project(':markwon-image-loader') // implementation project(':markwon-image-loader')
implementation 'ru.noties:jlatexmath-android:0.1.0' // implementation 'ru.noties:jlatexmath-android:0.1.0'
} }

View File

@ -1,138 +0,0 @@
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 java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Collections;
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;
}
}
@NonNull
public static String makeDestination(@NonNull String latex) {
return SCHEME + "://" + latex;
}
private static final String SCHEME = "jlatexmath";
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) {
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
}
@NonNull
@Override
public Collection<String> schemes() {
return Collections.singleton(SCHEME);
}
}
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;
if (latex == null) {
return null;
}
return JLatexMathDrawable.builder(latex)
.textSize(config.textSize)
.background(config.background)
.align(config.align)
.fitCanvas(config.fitCanvas)
.padding(config.padding)
.build();
}
}
}

View File

@ -4,17 +4,10 @@ import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.widget.TextView; 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.markwon.Markwon; import ru.noties.markwon.Markwon;
import ru.noties.markwon.MarkwonConfiguration; import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.ext.latex.JLatexMathPlugin;
import ru.noties.markwon.il.AsyncDrawableLoader; import ru.noties.markwon.image.ImagesPlugin;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
public class MainActivity extends Activity { public class MainActivity extends Activity {
@ -45,61 +38,21 @@ public class MainActivity extends Activity {
// latex += "\\end{array}"; // latex += "\\end{array}";
final JLatexMathMedia.Config config = new JLatexMathMedia.Config(textView.getTextSize()) {{ final JLatexMathPlugin.Config config = new JLatexMathPlugin.Config(textView.getTextSize()) {{
// align = JLatexMathDrawable.ALIGN_RIGHT; // align = JLatexMathDrawable.ALIGN_RIGHT;
}}; }};
final JLatexMathMedia jLatexMathMedia = new JLatexMathMedia(config);
final AsyncDrawableLoader asyncDrawableLoader = AsyncDrawableLoader.builder()
.addSchemeHandler(jLatexMathMedia.schemeHandler())
.mediaDecoders(jLatexMathMedia.mediaDecoder())
.build();
final MarkwonConfiguration configuration = MarkwonConfiguration.builder(this)
.asyncDrawableLoader(asyncDrawableLoader)
.build();
final String markdown = "# Example of LaTeX\n\n$$" final String markdown = "# Example of LaTeX\n\n$$"
+ latex + "$$\n\n something like **this**"; + latex + "$$\n\n something like **this**";
final Parser parser = new Parser.Builder() final Markwon markwon = Markwon.builder(this)
.customBlockParserFactory(new JLatexMathBlockParser.Factory()) .use(CorePlugin.create())
// strictly speaking this one is not required as long as JLatexMathPlugin schedules
// drawables on it's own
.use(ImagesPlugin.create(this))
.use(JLatexMathPlugin.create(config))
.build(); .build();
final Node node = parser.parse(markdown); markwon.setMarkdown(textView, markdown);
final SpannableBuilder builder = new SpannableBuilder();
final SpannableMarkdownVisitor visitor = new SpannableMarkdownVisitor(MarkwonConfiguration.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

@ -1,6 +1,7 @@
rootProject.name = 'MarkwonProject' rootProject.name = 'MarkwonProject'
include ':app', include ':app',
':markwon', ':markwon',
':markwon-ext-latex',
':markwon-ext-strikethrough', ':markwon-ext-strikethrough',
':markwon-ext-tables', ':markwon-ext-tables',
':markwon-ext-tasklist', ':markwon-ext-tasklist',