diff --git a/app/build.gradle b/app/build.gradle
index 0869e5e5..9512e8d2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -29,7 +29,8 @@ android {
dependencies {
implementation project(':markwon')
- implementation project(':markwon-image-loader')
+ implementation project(':markwon-image-gif')
+ implementation project(':markwon-image-svg')
implementation project(':markwon-syntax-highlight')
deps.with {
diff --git a/app/src/main/java/ru/noties/markwon/AppModule.java b/app/src/main/java/ru/noties/markwon/AppModule.java
index 3e2f3967..32d3e931 100644
--- a/app/src/main/java/ru/noties/markwon/AppModule.java
+++ b/app/src/main/java/ru/noties/markwon/AppModule.java
@@ -14,11 +14,6 @@ import dagger.Module;
import dagger.Provides;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
-import ru.noties.markwon.il.AsyncDrawableLoader;
-import ru.noties.markwon.il.GifMediaDecoder;
-import ru.noties.markwon.il.ImageMediaDecoder;
-import ru.noties.markwon.il.SvgMediaDecoder;
-import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
import ru.noties.markwon.syntax.Prism4jThemeDefault;
import ru.noties.prism4j.Prism4j;
@@ -72,23 +67,6 @@ class AppModule {
return new UriProcessorImpl();
}
- @Provides
- AsyncDrawable.Loader asyncDrawableLoader(
- OkHttpClient client,
- ExecutorService executorService,
- Resources resources) {
- return AsyncDrawableLoader.builder()
- .client(client)
- .executorService(executorService)
- .resources(resources)
- .mediaDecoders(
- SvgMediaDecoder.create(resources),
- GifMediaDecoder.create(false),
- ImageMediaDecoder.create(resources)
- )
- .build();
- }
-
@Provides
@Singleton
Prism4j prism4j() {
@@ -104,12 +82,12 @@ class AppModule {
@Singleton
@Provides
Prism4jThemeDarkula prism4jThemeDarkula() {
- return Prism4jThemeDarkula.create();
- }
-
- @Singleton
- @Provides
- GifProcessor gifProcessor() {
- return GifProcessor.create();
+ return Prism4jThemeDarkula.create(0x0Fffffff);
}
+//
+// @Singleton
+// @Provides
+// GifProcessor gifProcessor() {
+// return GifProcessor.create();
+// }
}
diff --git a/app/src/main/java/ru/noties/markwon/GifAwareAsyncDrawable.java b/app/src/main/java/ru/noties/markwon/GifAwareAsyncDrawable.java
index 78d286af..ed16554f 100644
--- a/app/src/main/java/ru/noties/markwon/GifAwareAsyncDrawable.java
+++ b/app/src/main/java/ru/noties/markwon/GifAwareAsyncDrawable.java
@@ -6,9 +6,10 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import pl.droidsonroids.gif.GifDrawable;
+import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
-import ru.noties.markwon.spans.AsyncDrawable;
+import ru.noties.markwon.image.AsyncDrawable;
public class GifAwareAsyncDrawable extends AsyncDrawable {
@@ -23,7 +24,7 @@ public class GifAwareAsyncDrawable extends AsyncDrawable {
public GifAwareAsyncDrawable(
@NonNull Drawable gifPlaceholder,
@NonNull String destination,
- @NonNull Loader loader,
+ @NonNull AsyncDrawableLoader loader,
@Nullable ImageSizeResolver imageSizeResolver,
@Nullable ImageSize imageSize) {
super(destination, loader, imageSizeResolver, imageSize);
diff --git a/app/src/main/java/ru/noties/markwon/GifAwarePlugin.java b/app/src/main/java/ru/noties/markwon/GifAwarePlugin.java
new file mode 100644
index 00000000..bb2fafd4
--- /dev/null
+++ b/app/src/main/java/ru/noties/markwon/GifAwarePlugin.java
@@ -0,0 +1,35 @@
+package ru.noties.markwon;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.widget.TextView;
+
+public class GifAwarePlugin extends AbstractMarkwonPlugin {
+
+ @NonNull
+ public static GifAwarePlugin create(@NonNull Context context) {
+ return new GifAwarePlugin(context);
+ }
+
+ private final Context context;
+ private final GifProcessor processor;
+
+ public GifAwarePlugin(@NonNull Context context) {
+ this.context = context;
+ this.processor = GifProcessor.create();
+ }
+
+ @Override
+ public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
+ final GifPlaceholder gifPlaceholder = new GifPlaceholder(
+ context.getResources().getDrawable(R.drawable.ic_play_circle_filled_18dp_white),
+ 0x20000000
+ );
+ builder.factory(new GifAwareSpannableFactory(gifPlaceholder));
+ }
+
+ @Override
+ public void afterSetText(@NonNull TextView textView) {
+ processor.process(textView);
+ }
+}
diff --git a/app/src/main/java/ru/noties/markwon/GifAwareSpannableFactory.java b/app/src/main/java/ru/noties/markwon/GifAwareSpannableFactory.java
index 7c387a1c..432a379c 100644
--- a/app/src/main/java/ru/noties/markwon/GifAwareSpannableFactory.java
+++ b/app/src/main/java/ru/noties/markwon/GifAwareSpannableFactory.java
@@ -3,9 +3,10 @@ package ru.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
-import ru.noties.markwon.spans.AsyncDrawable;
+import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.spans.AsyncDrawableSpan;
import ru.noties.markwon.spans.MarkwonTheme;
@@ -19,7 +20,7 @@ public class GifAwareSpannableFactory extends SpannableFactoryDef {
@Nullable
@Override
- public Object image(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
+ public Object image(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull AsyncDrawableLoader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
return new AsyncDrawableSpan(
theme,
new GifAwareAsyncDrawable(
diff --git a/app/src/main/java/ru/noties/markwon/MainActivity.java b/app/src/main/java/ru/noties/markwon/MainActivity.java
index bd477bcd..84904934 100644
--- a/app/src/main/java/ru/noties/markwon/MainActivity.java
+++ b/app/src/main/java/ru/noties/markwon/MainActivity.java
@@ -29,9 +29,9 @@ public class MainActivity extends Activity {
@Inject
UriProcessor uriProcessor;
-
- @Inject
- GifProcessor gifProcessor;
+//
+// @Inject
+// GifProcessor gifProcessor;
@Override
protected void onCreate(final Bundle savedInstanceState) {
@@ -66,26 +66,14 @@ public class MainActivity extends Activity {
appBarRenderer.render(appBarState());
- if (true) {
- final Markwon2 markwon2 = Markwon2.builder(this)
- .use(new CorePlugin())
- .use(TaskListPlugin.create(new TaskListDrawable(0xffff0000, 0xffff0000, -1)))
- .build();
- final CharSequence markdown = markwon2.toMarkdown("**hello _dear_** `code`\n\n- [ ] first\n- [x] second");
- textView.setText(markdown);
- return;
- }
-
markdownLoader.load(uri(), new MarkdownLoader.OnMarkdownTextLoaded() {
@Override
public void apply(final String text) {
markdownRenderer.render(MainActivity.this, themes.isLight(), uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
@Override
- public void onMarkdownReady(CharSequence markdown) {
+ public void onMarkdownReady(@NonNull Markwon2 markwon2, CharSequence markdown) {
- Markwon.setText(textView, markdown);
-
- gifProcessor.process(textView);
+ markwon2.setParsedMarkdown(textView, markdown);
Views.setVisible(progress, false);
}
diff --git a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java
index f0eb38e5..053fde93 100644
--- a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java
+++ b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java
@@ -13,24 +13,23 @@ import java.util.concurrent.Future;
import javax.inject.Inject;
import ru.noties.debug.Debug;
-import ru.noties.markwon.spans.AsyncDrawable;
-import ru.noties.markwon.spans.MarkwonTheme;
-import ru.noties.markwon.syntax.Prism4jSyntaxHighlight;
+import ru.noties.markwon.core.CorePlugin;
+import ru.noties.markwon.image.ImagesPlugin;
+import ru.noties.markwon.image.gif.GifPlugin;
+import ru.noties.markwon.image.svg.SvgPlugin;
import ru.noties.markwon.syntax.Prism4jTheme;
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
import ru.noties.markwon.syntax.Prism4jThemeDefault;
+import ru.noties.markwon.syntax.SyntaxHighlightPlugin;
import ru.noties.prism4j.Prism4j;
@ActivityScope
public class MarkdownRenderer {
interface MarkdownReadyListener {
- void onMarkdownReady(CharSequence markdown);
+ void onMarkdownReady(@NonNull Markwon2 markwon2, CharSequence markdown);
}
- @Inject
- AsyncDrawable.Loader loader;
-
@Inject
ExecutorService service;
@@ -78,40 +77,39 @@ public class MarkdownRenderer {
? prism4jThemeDefault
: prism4JThemeDarkula;
- final int background = isLightTheme
- ? prism4jTheme.background()
- : 0x0Fffffff;
+// final int background = isLightTheme
+// ? prism4jTheme.background()
+// : 0x0Fffffff;
- final GifPlaceholder gifPlaceholder = new GifPlaceholder(
- context.getResources().getDrawable(R.drawable.ic_play_circle_filled_18dp_white),
- 0x20000000
- );
-
- final MarkwonConfiguration configuration = MarkwonConfiguration.builder(context)
- .asyncDrawableLoader(loader)
- .urlProcessor(urlProcessor)
- .syntaxHighlight(Prism4jSyntaxHighlight.create(prism4j, prism4jTheme))
- .theme(MarkwonTheme.builderWithDefaults(context)
- .codeBackgroundColor(background)
- .codeTextColor(prism4jTheme.textColor())
- .build())
- .factory(new GifAwareSpannableFactory(gifPlaceholder))
+ final Markwon2 markwon2 = Markwon2.builder(context)
+ .use(CorePlugin.create())
+ .use(ImagesPlugin.createWithAssets(context))
+ .use(SvgPlugin.create(context.getResources()))
+ .use(GifPlugin.create(false))
+ .use(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
+ .use(GifAwarePlugin.create(context))
+ .use(new AbstractMarkwonPlugin() {
+ @Override
+ public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
+ builder.urlProcessor(urlProcessor);
+ }
+ })
.build();
final long start = SystemClock.uptimeMillis();
- final CharSequence text = Markwon.markdown(configuration, markdown);
+ final CharSequence text = markwon2.toMarkdown(markdown);
final long end = SystemClock.uptimeMillis();
- Debug.i("toMarkdown rendered: %d ms", end - start);
+ Debug.i("markdown rendered: %d ms", end - start);
if (!isCancelled()) {
handler.post(new Runnable() {
@Override
public void run() {
if (!isCancelled()) {
- listener.onMarkdownReady(text);
+ listener.onMarkdownReady(markwon2, text);
task = null;
}
}
diff --git a/markwon-image-gif/build.gradle b/markwon-image-gif/build.gradle
new file mode 100644
index 00000000..2eeb680e
--- /dev/null
+++ b/markwon-image-gif/build.gradle
@@ -0,0 +1,25 @@
+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')
+
+ deps.with {
+ api it['android-gif']
+ }
+}
+
+registerArtifact(this)
\ No newline at end of file
diff --git a/markwon-image-gif/src/main/AndroidManifest.xml b/markwon-image-gif/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..649a9a70
--- /dev/null
+++ b/markwon-image-gif/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java
new file mode 100644
index 00000000..7a6600b8
--- /dev/null
+++ b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java
@@ -0,0 +1,82 @@
+package ru.noties.markwon.image.gif;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import pl.droidsonroids.gif.GifDrawable;
+import ru.noties.markwon.image.MediaDecoder;
+import ru.noties.markwon.utils.DrawableUtils;
+
+/**
+ * @since 1.1.0
+ */
+@SuppressWarnings("WeakerAccess")
+public class GifMediaDecoder extends MediaDecoder {
+
+ public static final String CONTENT_TYPE = "image/gif";
+
+ @NonNull
+ public static GifMediaDecoder create(boolean autoPlayGif) {
+ return new GifMediaDecoder(autoPlayGif);
+ }
+
+ private final boolean autoPlayGif;
+
+ protected GifMediaDecoder(boolean autoPlayGif) {
+ this.autoPlayGif = autoPlayGif;
+ }
+
+ @Nullable
+ @Override
+ public Drawable decode(@NonNull InputStream inputStream) {
+
+ Drawable out = null;
+
+ final byte[] bytes = readBytes(inputStream);
+ if (bytes != null) {
+ try {
+ out = newGifDrawable(bytes);
+ DrawableUtils.intrinsicBounds(out);
+
+ if (!autoPlayGif) {
+ ((GifDrawable) out).pause();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return out;
+ }
+
+ @NonNull
+ protected Drawable newGifDrawable(@NonNull byte[] bytes) throws IOException {
+ return new GifDrawable(bytes);
+ }
+
+ @Nullable
+ protected static byte[] readBytes(@NonNull InputStream stream) {
+
+ byte[] out = null;
+
+ try {
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final int length = 1024 * 8;
+ final byte[] buffer = new byte[length];
+ int read;
+ while ((read = stream.read(buffer, 0, length)) != -1) {
+ outputStream.write(buffer, 0, read);
+ }
+ out = outputStream.toByteArray();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return out;
+ }
+}
diff --git a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java
new file mode 100644
index 00000000..3ee0c7aa
--- /dev/null
+++ b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java
@@ -0,0 +1,30 @@
+package ru.noties.markwon.image.gif;
+
+import android.support.annotation.NonNull;
+
+import ru.noties.markwon.AbstractMarkwonPlugin;
+import ru.noties.markwon.image.AsyncDrawableLoader;
+
+public class GifPlugin extends AbstractMarkwonPlugin {
+
+ @NonNull
+ public static GifPlugin create() {
+ return create(true);
+ }
+
+ @NonNull
+ public static GifPlugin create(boolean autoPlay) {
+ return new GifPlugin(autoPlay);
+ }
+
+ private final boolean autoPlay;
+
+ public GifPlugin(boolean autoPlay) {
+ this.autoPlay = autoPlay;
+ }
+
+ @Override
+ public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
+ builder.addMediaDecoder(GifMediaDecoder.CONTENT_TYPE, GifMediaDecoder.create(autoPlay));
+ }
+}
diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java
index 432e8797..5f7a5f01 100644
--- a/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java
+++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java
@@ -20,7 +20,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import okhttp3.OkHttpClient;
-import ru.noties.markwon.spans.AsyncDrawable;
+import ru.noties.markwon.image.AsyncDrawable;
public class AsyncDrawableLoader implements AsyncDrawable.Loader {
diff --git a/markwon-image-svg/build.gradle b/markwon-image-svg/build.gradle
new file mode 100644
index 00000000..06243a28
--- /dev/null
+++ b/markwon-image-svg/build.gradle
@@ -0,0 +1,25 @@
+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')
+
+ deps.with {
+ api it['android-svg']
+ }
+}
+
+registerArtifact(this)
\ No newline at end of file
diff --git a/markwon-image-svg/src/main/AndroidManifest.xml b/markwon-image-svg/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..10432a1d
--- /dev/null
+++ b/markwon-image-svg/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java
new file mode 100644
index 00000000..5c8661d8
--- /dev/null
+++ b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java
@@ -0,0 +1,73 @@
+package ru.noties.markwon.image.svg;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.caverock.androidsvg.SVG;
+import com.caverock.androidsvg.SVGParseException;
+
+import java.io.InputStream;
+
+import ru.noties.markwon.image.MediaDecoder;
+import ru.noties.markwon.utils.DrawableUtils;
+
+/**
+ * @since 1.1.0
+ */
+public class SvgMediaDecoder extends MediaDecoder {
+
+ public static final String CONTENT_TYPE = "image/svg+xml";
+
+ @NonNull
+ public static SvgMediaDecoder create(@NonNull Resources resources) {
+ return new SvgMediaDecoder(resources);
+ }
+
+ private final Resources resources;
+
+ @SuppressWarnings("WeakerAccess")
+ SvgMediaDecoder(Resources resources) {
+ this.resources = resources;
+ }
+
+ @Nullable
+ @Override
+ public Drawable decode(@NonNull InputStream inputStream) {
+
+ final Drawable out;
+
+ SVG svg = null;
+ try {
+ svg = SVG.getFromInputStream(inputStream);
+ } catch (SVGParseException e) {
+ e.printStackTrace();
+ }
+
+ if (svg == null) {
+ out = null;
+ } else {
+
+ final float w = svg.getDocumentWidth();
+ final float h = svg.getDocumentHeight();
+ final float density = resources.getDisplayMetrics().density;
+
+ final int width = (int) (w * density + .5F);
+ final int height = (int) (h * density + .5F);
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
+ final Canvas canvas = new Canvas(bitmap);
+ canvas.scale(density, density);
+ svg.renderToCanvas(canvas);
+
+ out = new BitmapDrawable(resources, bitmap);
+ DrawableUtils.intrinsicBounds(out);
+ }
+
+ return out;
+ }
+}
diff --git a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java
new file mode 100644
index 00000000..d2396741
--- /dev/null
+++ b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java
@@ -0,0 +1,26 @@
+package ru.noties.markwon.image.svg;
+
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
+
+import ru.noties.markwon.AbstractMarkwonPlugin;
+import ru.noties.markwon.image.AsyncDrawableLoader;
+
+public class SvgPlugin extends AbstractMarkwonPlugin {
+
+ @NonNull
+ public static SvgPlugin create(@NonNull Resources resources) {
+ return new SvgPlugin(resources);
+ }
+
+ private final Resources resources;
+
+ public SvgPlugin(@NonNull Resources resources) {
+ this.resources = resources;
+ }
+
+ @Override
+ public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
+ builder.addMediaDecoder(SvgMediaDecoder.CONTENT_TYPE, SvgMediaDecoder.create(resources));
+ }
+}
diff --git a/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java
index 7d5c90b4..9a941951 100644
--- a/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java
+++ b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java
@@ -1,5 +1,6 @@
package ru.noties.markwon.syntax;
+import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
@@ -12,12 +13,23 @@ public class Prism4jThemeDarkula extends Prism4jThemeBase {
@NonNull
public static Prism4jThemeDarkula create() {
- return new Prism4jThemeDarkula();
+ return new Prism4jThemeDarkula(0xFF2d2d2d);
+ }
+
+ @NonNull
+ public static Prism4jThemeDarkula create(@ColorInt int background) {
+ return new Prism4jThemeDarkula(background);
+ }
+
+ private final int background;
+
+ public Prism4jThemeDarkula(@ColorInt int background) {
+ this.background = background;
}
@Override
public int background() {
- return 0xFF2d2d2d;
+ return background;
}
@Override
diff --git a/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/SyntaxHighlightPlugin.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/SyntaxHighlightPlugin.java
new file mode 100644
index 00000000..08bf2a73
--- /dev/null
+++ b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/SyntaxHighlightPlugin.java
@@ -0,0 +1,52 @@
+package ru.noties.markwon.syntax;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import ru.noties.markwon.AbstractMarkwonPlugin;
+import ru.noties.markwon.MarkwonConfiguration;
+import ru.noties.markwon.spans.MarkwonTheme;
+import ru.noties.prism4j.Prism4j;
+
+public class SyntaxHighlightPlugin extends AbstractMarkwonPlugin {
+
+ @NonNull
+ public static SyntaxHighlightPlugin create(
+ @NonNull Prism4j prism4j,
+ @NonNull Prism4jTheme theme) {
+ return create(prism4j, theme, null);
+ }
+
+ @NonNull
+ public static SyntaxHighlightPlugin create(
+ @NonNull Prism4j prism4j,
+ @NonNull Prism4jTheme theme,
+ @Nullable String fallbackLanguage) {
+ return new SyntaxHighlightPlugin(prism4j, theme, fallbackLanguage);
+ }
+
+ private final Prism4j prism4j;
+ private final Prism4jTheme theme;
+ private final String fallbackLanguage;
+
+ public SyntaxHighlightPlugin(
+ @NonNull Prism4j prism4j,
+ @NonNull Prism4jTheme theme,
+ @Nullable String fallbackLanguage) {
+ this.prism4j = prism4j;
+ this.theme = theme;
+ this.fallbackLanguage = fallbackLanguage;
+ }
+
+ @Override
+ public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
+ builder
+ .codeTextColor(theme.textColor())
+ .codeBackgroundColor(theme.background());
+ }
+
+ @Override
+ public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
+ builder.syntaxHighlight(Prism4jSyntaxHighlight.create(prism4j, theme, fallbackLanguage));
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java b/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
index d0187fac..b1671899 100644
--- a/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
+++ b/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
@@ -5,6 +5,7 @@ import android.widget.TextView;
import org.commonmark.parser.Parser;
+import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.spans.MarkwonTheme;
public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
@@ -18,6 +19,11 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
}
+ @Override
+ public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
+
+ }
+
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
@@ -40,7 +46,7 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
}
@Override
- public void afterSetText(@NonNull TextView textView, @NonNull CharSequence markdown) {
+ public void afterSetText(@NonNull TextView textView) {
}
}
diff --git a/markwon/src/main/java/ru/noties/markwon/Markwon.java b/markwon/src/main/java/ru/noties/markwon/Markwon.java
index 5728f21a..022c3ca8 100644
--- a/markwon/src/main/java/ru/noties/markwon/Markwon.java
+++ b/markwon/src/main/java/ru/noties/markwon/Markwon.java
@@ -14,8 +14,11 @@ import org.commonmark.parser.Parser;
import java.util.Arrays;
+import ru.noties.markwon.image.AsyncDrawable;
+//import ru.noties.markwon.image.DrawablesScheduler;
import ru.noties.markwon.renderer.SpannableRenderer;
import ru.noties.markwon.spans.OrderedListItemSpan;
+import ru.noties.markwon.table.TableRowSpan;
import ru.noties.markwon.tasklist.TaskListExtension;
@SuppressWarnings({"WeakerAccess", "unused"})
@@ -148,7 +151,7 @@ public abstract class Markwon {
}
/**
- * This method adds support for {@link ru.noties.markwon.spans.AsyncDrawable} to be used. As
+ * This method adds support for {@link AsyncDrawable} to be used. As
* textView seems not to support drawables that change bounds (and gives no means
* to update the layout), we create own {@link android.graphics.drawable.Drawable.Callback}
* and apply it. So, textView can display drawables, that are: async (loading from disk, network);
@@ -157,14 +160,14 @@ public abstract class Markwon {
* in order to avoid keeping drawables in memory after they have been removed from layout
*
* @param view a {@link TextView}
- * @see ru.noties.markwon.spans.AsyncDrawable
+ * @see AsyncDrawable
* @see ru.noties.markwon.spans.AsyncDrawableSpan
* @see DrawablesScheduler#schedule(TextView)
* @see DrawablesScheduler#unschedule(TextView)
* @since 1.0.0
*/
public static void scheduleDrawables(@NonNull TextView view) {
- DrawablesScheduler.schedule(view);
+// DrawablesScheduler.schedule(view);
}
/**
@@ -175,7 +178,7 @@ public abstract class Markwon {
* @since 1.0.0
*/
public static void unscheduleDrawables(@NonNull TextView view) {
- DrawablesScheduler.unschedule(view);
+// DrawablesScheduler.unschedule(view);
}
/**
@@ -185,28 +188,28 @@ public abstract class Markwon {
* to return `size` (width) of our replacement, but we are not provided
* with the total one (canvas width). In order to correctly calculate height of our
* table cell text, we must have available width first. This method gives
- * ability for {@link ru.noties.markwon.spans.TableRowSpan} to invalidate
+ * ability for {@link TableRowSpan} to invalidate
* `view` when it encounters such a situation (when available width is not known or have changed).
* Precede this call with {@link #unscheduleTableRows(TextView)} in order to
- * de-reference previously scheduled {@link ru.noties.markwon.spans.TableRowSpan}'s
+ * de-reference previously scheduled {@link TableRowSpan}'s
*
* @param view a {@link TextView}
* @see #unscheduleTableRows(TextView)
* @since 1.0.0
*/
public static void scheduleTableRows(@NonNull TextView view) {
- TableRowsScheduler.schedule(view);
+// TableRowsScheduler.schedule(view);
}
/**
- * De-references previously scheduled {@link ru.noties.markwon.spans.TableRowSpan}'s
+ * De-references previously scheduled {@link TableRowSpan}'s
*
* @param view a {@link TextView}
* @see #scheduleTableRows(TextView)
* @since 1.0.0
*/
public static void unscheduleTableRows(@NonNull TextView view) {
- TableRowsScheduler.unschedule(view);
+// TableRowsScheduler.unschedule(view);
}
private Markwon() {
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java
index 841c70d4..5bbe9fa5 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java
@@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.spans.MarkwonTheme;
class MarkwonBuilderImpl implements Markwon2.Builder {
@@ -34,19 +35,25 @@ class MarkwonBuilderImpl implements Markwon2.Builder {
final Parser.Builder parserBuilder = new Parser.Builder();
final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context);
+ final AsyncDrawableLoader.Builder asyncDrawableLoaderBuilder = new AsyncDrawableLoader.Builder();
final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder(context);
final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl();
for (MarkwonPlugin plugin : plugins) {
plugin.configureParser(parserBuilder);
plugin.configureTheme(themeBuilder);
+ plugin.configureImages(asyncDrawableLoaderBuilder);
plugin.configureConfiguration(configurationBuilder);
plugin.configureVisitor(visitorBuilder);
}
+ final MarkwonConfiguration configuration = configurationBuilder.build(
+ themeBuilder.build(),
+ asyncDrawableLoaderBuilder.build());
+
return new MarkwonImpl(
parserBuilder.build(),
- visitorBuilder.build(configurationBuilder.build(themeBuilder.build())),
+ visitorBuilder.build(configuration),
Collections.unmodifiableList(plugins)
);
}
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java
index 82d48f4f..1f6e2216 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java
@@ -4,10 +4,11 @@ import android.content.Context;
import android.support.annotation.NonNull;
import ru.noties.markwon.html.api.MarkwonHtmlParser;
+import ru.noties.markwon.image.AsyncDrawableLoader;
+import ru.noties.markwon.image.AsyncDrawableLoaderNoOp;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.renderer.ImageSizeResolverDef;
import ru.noties.markwon.renderer.html2.MarkwonHtmlRenderer;
-import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;
@@ -20,7 +21,7 @@ public class MarkwonConfiguration {
// creates default configuration
@NonNull
public static MarkwonConfiguration create(@NonNull Context context) {
- return new Builder(context).build(MarkwonTheme.create(context));
+ return new Builder(context).build(MarkwonTheme.create(context), new AsyncDrawableLoaderNoOp());
}
@NonNull
@@ -30,7 +31,7 @@ public class MarkwonConfiguration {
private final MarkwonTheme theme;
- private final AsyncDrawable.Loader asyncDrawableLoader;
+ private final AsyncDrawableLoader asyncDrawableLoader;
private final SyntaxHighlight syntaxHighlight;
private final LinkSpan.Resolver linkResolver;
private final UrlProcessor urlProcessor;
@@ -69,7 +70,7 @@ public class MarkwonConfiguration {
}
@NonNull
- public AsyncDrawable.Loader asyncDrawableLoader() {
+ public AsyncDrawableLoader asyncDrawableLoader() {
return asyncDrawableLoader;
}
@@ -136,7 +137,7 @@ public class MarkwonConfiguration {
private final Context context;
private MarkwonTheme theme;
- private AsyncDrawable.Loader asyncDrawableLoader;
+ private AsyncDrawableLoader asyncDrawableLoader;
private SyntaxHighlight syntaxHighlight;
private LinkSpan.Resolver linkResolver;
private UrlProcessor urlProcessor;
@@ -166,19 +167,6 @@ public class MarkwonConfiguration {
this.htmlAllowNonClosedTags = configuration.htmlAllowNonClosedTags;
}
-// @NonNull
-// @Deprecated
-// public Builder theme(@NonNull MarkwonTheme theme) {
-// this.theme = theme;
-// return this;
-// }
-
- @NonNull
- public Builder asyncDrawableLoader(@NonNull AsyncDrawable.Loader asyncDrawableLoader) {
- this.asyncDrawableLoader = asyncDrawableLoader;
- return this;
- }
-
@NonNull
public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
this.syntaxHighlight = syntaxHighlight;
@@ -260,13 +248,10 @@ public class MarkwonConfiguration {
}
@NonNull
- public MarkwonConfiguration build(@NonNull MarkwonTheme theme) {
+ public MarkwonConfiguration build(@NonNull MarkwonTheme theme, @NonNull AsyncDrawableLoader asyncDrawableLoader) {
this.theme = theme;
-
- if (asyncDrawableLoader == null) {
- asyncDrawableLoader = new AsyncDrawableLoaderNoOp();
- }
+ this.asyncDrawableLoader = asyncDrawableLoader;
if (syntaxHighlight == null) {
syntaxHighlight = new SyntaxHighlightNoOp();
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonImpl.java
index db38e183..f92881e2 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonImpl.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonImpl.java
@@ -62,7 +62,7 @@ class MarkwonImpl extends Markwon2 {
textView.setText(markdown);
for (MarkwonPlugin plugin : plugins) {
- plugin.afterSetText(textView, markdown);
+ plugin.afterSetText(textView);
}
}
}
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java b/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java
index bd54d1d0..690c8f97 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java
@@ -5,6 +5,7 @@ import android.widget.TextView;
import org.commonmark.parser.Parser;
+import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.spans.MarkwonTheme;
public interface MarkwonPlugin {
@@ -13,11 +14,12 @@ public interface MarkwonPlugin {
void configureTheme(@NonNull MarkwonTheme.Builder builder);
+ void configureImages(@NonNull AsyncDrawableLoader.Builder builder);
+
void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder);
void configureVisitor(@NonNull MarkwonVisitor.Builder builder);
- // images
// html
@NonNull
@@ -25,5 +27,8 @@ public interface MarkwonPlugin {
void beforeSetText(@NonNull TextView textView, @NonNull CharSequence markdown);
- void afterSetText(@NonNull TextView textView, @NonNull CharSequence markdown);
+ // this method do not receive markdown like `beforeSetText` does because at this
+ // point TextView already has markdown set and to manipulate spans one must
+ // request them from TextView (getText())
+ void afterSetText(@NonNull TextView textView);
}
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java
index 12a912fc..5bb22998 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java
@@ -17,7 +17,7 @@ public interface MarkwonVisitor extends Visitor {
interface Builder {
@NonNull
- Builder on(@NonNull Class node, @NonNull NodeVisitor nodeVisitor);
+ Builder on(@NonNull Class node, @Nullable NodeVisitor nodeVisitor);
@NonNull
MarkwonVisitor build(@NonNull MarkwonConfiguration configuration);
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java
index c4f98d04..07684737 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java
@@ -281,8 +281,14 @@ class MarkwonVisitorImpl implements MarkwonVisitor {
@NonNull
@Override
- public Builder on(@NonNull Class node, @NonNull NodeVisitor nodeVisitor) {
- nodes.put(node, nodeVisitor);
+ public Builder on(@NonNull Class node, @Nullable NodeVisitor nodeVisitor) {
+ // we should allow `null` to exclude node from being visited (for example to disable
+ // some functionality)
+ if (nodeVisitor == null) {
+ nodes.remove(node);
+ } else {
+ nodes.put(node, nodeVisitor);
+ }
return this;
}
diff --git a/markwon/src/main/java/ru/noties/markwon/SpannableFactory.java b/markwon/src/main/java/ru/noties/markwon/SpannableFactory.java
index 6297b777..13a64cf4 100644
--- a/markwon/src/main/java/ru/noties/markwon/SpannableFactory.java
+++ b/markwon/src/main/java/ru/noties/markwon/SpannableFactory.java
@@ -3,14 +3,11 @@ package ru.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import java.util.List;
-
+import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
-import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;
-import ru.noties.markwon.spans.TableRowSpan;
/**
* Each method can return null or a Span object or an array of spans
@@ -46,16 +43,6 @@ public interface SpannableFactory {
@Nullable
Object strikethrough();
- @Nullable
- Object taskListItem(@NonNull MarkwonTheme theme, int blockIndent, boolean isDone);
-
- @Nullable
- Object tableRow(
- @NonNull MarkwonTheme theme,
- @NonNull List cells,
- boolean isHeader,
- boolean isOdd);
-
/**
* @since 1.1.1
*/
@@ -66,7 +53,7 @@ public interface SpannableFactory {
Object image(
@NonNull MarkwonTheme theme,
@NonNull String destination,
- @NonNull AsyncDrawable.Loader loader,
+ @NonNull AsyncDrawableLoader loader,
@NonNull ImageSizeResolver imageSizeResolver,
@Nullable ImageSize imageSize,
boolean replacementTextIsLink);
diff --git a/markwon/src/main/java/ru/noties/markwon/SpannableFactoryDef.java b/markwon/src/main/java/ru/noties/markwon/SpannableFactoryDef.java
index 6fc8d48f..21074983 100644
--- a/markwon/src/main/java/ru/noties/markwon/SpannableFactoryDef.java
+++ b/markwon/src/main/java/ru/noties/markwon/SpannableFactoryDef.java
@@ -5,11 +5,10 @@ import android.support.annotation.Nullable;
import android.text.style.StrikethroughSpan;
import android.text.style.UnderlineSpan;
-import java.util.List;
-
+import ru.noties.markwon.image.AsyncDrawable;
+import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
-import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.AsyncDrawableSpan;
import ru.noties.markwon.spans.BlockQuoteSpan;
import ru.noties.markwon.spans.BulletListItemSpan;
@@ -17,13 +16,11 @@ import ru.noties.markwon.spans.CodeSpan;
import ru.noties.markwon.spans.EmphasisSpan;
import ru.noties.markwon.spans.HeadingSpan;
import ru.noties.markwon.spans.LinkSpan;
-import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.spans.MarkwonTheme;
+import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.spans.StrongEmphasisSpan;
import ru.noties.markwon.spans.SubScriptSpan;
import ru.noties.markwon.spans.SuperScriptSpan;
-import ru.noties.markwon.spans.TableRowSpan;
-import ru.noties.markwon.tasklist.TaskListSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
/**
@@ -91,18 +88,6 @@ public class SpannableFactoryDef implements SpannableFactory {
return new StrikethroughSpan();
}
- @Nullable
- @Override
- public Object taskListItem(@NonNull MarkwonTheme theme, int blockIndent, boolean isDone) {
- return new TaskListSpan(theme, blockIndent, isDone);
- }
-
- @Nullable
- @Override
- public Object tableRow(@NonNull MarkwonTheme theme, @NonNull List cells, boolean isHeader, boolean isOdd) {
- return new TableRowSpan(theme, cells, isHeader, isOdd);
- }
-
/**
* @since 1.1.1
*/
@@ -114,7 +99,7 @@ public class SpannableFactoryDef implements SpannableFactory {
@Nullable
@Override
- public Object image(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
+ public Object image(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull AsyncDrawableLoader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
return new AsyncDrawableSpan(
theme,
new AsyncDrawable(
diff --git a/markwon/src/main/java/ru/noties/markwon/DrawablesScheduler.java b/markwon/src/main/java/ru/noties/markwon/core/AsyncDrawableScheduler.java
similarity index 97%
rename from markwon/src/main/java/ru/noties/markwon/DrawablesScheduler.java
rename to markwon/src/main/java/ru/noties/markwon/core/AsyncDrawableScheduler.java
index fd64d9d5..3d7fae23 100644
--- a/markwon/src/main/java/ru/noties/markwon/DrawablesScheduler.java
+++ b/markwon/src/main/java/ru/noties/markwon/core/AsyncDrawableScheduler.java
@@ -1,4 +1,4 @@
-package ru.noties.markwon;
+package ru.noties.markwon.core;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -15,10 +15,10 @@ import java.util.Collections;
import java.util.List;
import ru.noties.markwon.renderer.R;
-import ru.noties.markwon.spans.AsyncDrawable;
+import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.spans.AsyncDrawableSpan;
-abstract class DrawablesScheduler {
+abstract class AsyncDrawableScheduler {
static void schedule(@NonNull final TextView textView) {
@@ -104,7 +104,7 @@ abstract class DrawablesScheduler {
return list;
}
- private DrawablesScheduler() {
+ private AsyncDrawableScheduler() {
}
private static class DrawableCallbackImpl implements Drawable.Callback {
diff --git a/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java b/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java
index 3d4ef0c4..e2893c53 100644
--- a/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java
+++ b/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java
@@ -11,7 +11,6 @@ import org.commonmark.node.Emphasis;
import org.commonmark.node.FencedCodeBlock;
import org.commonmark.node.HardLineBreak;
import org.commonmark.node.Heading;
-import org.commonmark.node.Image;
import org.commonmark.node.IndentedCodeBlock;
import org.commonmark.node.Link;
import org.commonmark.node.ListBlock;
@@ -73,13 +72,19 @@ public class CorePlugin extends AbstractMarkwonPlugin {
softLineBreak(builder);
hardLineBreak(builder);
paragraph(builder);
- image(builder);
+// image(builder);
link(builder);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull CharSequence 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) {
@@ -366,41 +371,6 @@ public class CorePlugin extends AbstractMarkwonPlugin {
});
}
- protected void image(@NonNull MarkwonVisitor.Builder builder) {
- builder.on(Image.class, new MarkwonVisitor.NodeVisitor() {
- @Override
- public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) {
-
- final int length = visitor.length();
-
- visitor.visitChildren(image);
-
- // we must check if anything _was_ added, as we need at least one char to render
- if (length == visitor.length()) {
- visitor.builder().append('\uFFFC');
- }
-
- final MarkwonConfiguration configuration = visitor.configuration();
-
- final Node parent = image.getParent();
- final boolean link = parent instanceof Link;
- final String destination = configuration
- .urlProcessor()
- .process(image.getDestination());
-
- final Object spans = visitor.factory().image(
- visitor.theme(),
- destination,
- configuration.asyncDrawableLoader(),
- configuration.imageSizeResolver(),
- null,
- link);
-
- visitor.setSpans(length, spans);
- }
- });
- }
-
protected void link(@NonNull MarkwonVisitor.Builder builder) {
builder.on(Link.class, new MarkwonVisitor.NodeVisitor() {
@Override
diff --git a/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawable.java
similarity index 93%
rename from markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java
rename to markwon/src/main/java/ru/noties/markwon/image/AsyncDrawable.java
index 588b8a04..3184f599 100644
--- a/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java
+++ b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawable.java
@@ -1,4 +1,4 @@
-package ru.noties.markwon.spans;
+package ru.noties.markwon.image;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
@@ -15,15 +15,8 @@ import ru.noties.markwon.renderer.ImageSizeResolver;
public class AsyncDrawable extends Drawable {
- public interface Loader {
-
- void load(@NonNull String destination, @NonNull AsyncDrawable drawable);
-
- void cancel(@NonNull String destination);
- }
-
private final String destination;
- private final Loader loader;
+ private final AsyncDrawableLoader loader;
private final ImageSize imageSize;
private final ImageSizeResolver imageSizeResolver;
@@ -38,7 +31,7 @@ public class AsyncDrawable extends Drawable {
*/
public AsyncDrawable(
@NonNull String destination,
- @NonNull Loader loader,
+ @NonNull AsyncDrawableLoader loader,
@Nullable ImageSizeResolver imageSizeResolver,
@Nullable ImageSize imageSize
) {
diff --git a/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java
new file mode 100644
index 00000000..1e841f49
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java
@@ -0,0 +1,104 @@
+package ru.noties.markwon.image;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public abstract class AsyncDrawableLoader {
+
+
+ public abstract void load(@NonNull String destination, @NonNull AsyncDrawable drawable);
+
+ public abstract void cancel(@NonNull String destination);
+
+
+ public static class Builder {
+
+ ExecutorService executorService;
+ final Map schemeHandlers = new HashMap<>(3);
+ final Map mediaDecoders = new HashMap<>(3);
+ MediaDecoder defaultMediaDecoder;
+ Drawable errorDrawable;
+
+ @NonNull
+ public Builder executorService(@NonNull ExecutorService executorService) {
+ this.executorService = executorService;
+ return this;
+ }
+
+ @NonNull
+ public Builder addSchemeHandler(@NonNull String scheme, @NonNull SchemeHandler schemeHandler) {
+ schemeHandlers.put(scheme, schemeHandler);
+ return this;
+ }
+
+ @NonNull
+ public Builder addSchemeHandler(@NonNull Collection schemes, @NonNull SchemeHandler schemeHandler) {
+ for (String scheme : schemes) {
+ schemeHandlers.put(scheme, schemeHandler);
+ }
+ return this;
+ }
+
+ @NonNull
+ public Builder addMediaDecoder(@NonNull String contentType, @NonNull MediaDecoder mediaDecoder) {
+ mediaDecoders.put(contentType, mediaDecoder);
+ return this;
+ }
+
+ @NonNull
+ public Builder addMediaDecoder(@NonNull Collection contentTypes, @NonNull MediaDecoder mediaDecoder) {
+ for (String contentType : contentTypes) {
+ mediaDecoders.put(contentType, mediaDecoder);
+ }
+ return this;
+ }
+
+ @NonNull
+ public Builder removeSchemeHandler(@NonNull String scheme) {
+ schemeHandlers.remove(scheme);
+ return this;
+ }
+
+ @NonNull
+ public Builder removeMediaDecoder(@NonNull String contentType) {
+ mediaDecoders.remove(contentType);
+ return this;
+ }
+
+ @NonNull
+ public Builder defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) {
+ this.defaultMediaDecoder = mediaDecoder;
+ return this;
+ }
+
+ @NonNull
+ public Builder errorDrawable(Drawable errorDrawable) {
+ this.errorDrawable = errorDrawable;
+ return this;
+ }
+
+ @NonNull
+ public AsyncDrawableLoader build() {
+
+ // if we have no schemeHandlers -> we cannot show anything
+ // OR if we have no media decoders
+ if (schemeHandlers.size() == 0
+ || (mediaDecoders.size() == 0 && defaultMediaDecoder == null)) {
+ return new AsyncDrawableLoaderNoOp();
+ }
+
+ if (executorService == null) {
+ executorService = Executors.newCachedThreadPool();
+ }
+
+ return new AsyncDrawableLoaderImpl(this);
+ }
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java
new file mode 100644
index 00000000..281d61fc
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java
@@ -0,0 +1,135 @@
+package ru.noties.markwon.image;
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
+
+ private final ExecutorService executorService;
+ private final Map schemeHandlers;
+ private final Map mediaDecoders;
+ private final MediaDecoder defaultMediaDecoder;
+ private final Drawable errorDrawable;
+
+ private final Handler mainThread;
+
+ private final Map> requests = new HashMap<>(2);
+
+ AsyncDrawableLoaderImpl(@NonNull Builder builder) {
+ this.executorService = builder.executorService;
+ this.schemeHandlers = builder.schemeHandlers;
+ this.mediaDecoders = builder.mediaDecoders;
+ this.defaultMediaDecoder = builder.defaultMediaDecoder;
+ this.errorDrawable = builder.errorDrawable;
+ this.mainThread = new Handler(Looper.getMainLooper());
+ }
+
+ @Override
+ public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
+ // if drawable is not a link -> show loading placeholder...
+ requests.put(destination, execute(destination, drawable));
+ }
+
+ @Override
+ public void cancel(@NonNull String destination) {
+ final Future> request = requests.remove(destination);
+ if (request != null) {
+ request.cancel(true);
+ }
+ }
+
+ private Future> execute(@NonNull final String destination, @NonNull AsyncDrawable drawable) {
+
+ final WeakReference reference = new WeakReference(drawable);
+
+ // todo: should we cancel pending request for the same destination?
+ // we _could_ but there is possibility that one resource is request in multiple places
+
+ // todo: error handing (simply applying errorDrawable is not a good solution
+ // as reason for an error is unclear (no scheme handler, no input data, error decoding, etc)
+
+ // todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal
+ // for big images for sure. We _could_ introduce internal Drawable that will check for
+ // image bounds (but we will need to cache inputStream in order to inspect and optimize
+ // input image...)
+
+ return executorService.submit(new Runnable() {
+ @Override
+ public void run() {
+
+ final ImageItem item;
+
+ final Uri uri = Uri.parse(destination);
+
+ final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme());
+
+ if (schemeHandler != null) {
+ item = schemeHandler.handle(destination, uri);
+ } else {
+ item = null;
+ }
+
+ final InputStream inputStream = item != null
+ ? item.inputStream()
+ : null;
+
+ Drawable result = null;
+
+ if (inputStream != null) {
+ try {
+
+ MediaDecoder mediaDecoder = mediaDecoders.get(item.contentType());
+ if (mediaDecoder == null) {
+ mediaDecoder = defaultMediaDecoder;
+ }
+
+ if (mediaDecoder != null) {
+ result = mediaDecoder.decode(inputStream);
+ }
+
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // ignored
+ }
+ }
+ }
+
+ // if result is null, we assume it's an error
+ if (result == null) {
+ result = errorDrawable;
+ }
+
+ if (result != null) {
+ final Drawable out = result;
+ mainThread.post(new Runnable() {
+ @Override
+ public void run() {
+ final boolean canDeliver = requests.remove(destination) != null;
+ if (canDeliver) {
+ final AsyncDrawable asyncDrawable = reference.get();
+ if (asyncDrawable != null && asyncDrawable.isAttached()) {
+ asyncDrawable.setResult(out);
+ }
+ }
+ }
+ });
+ } else {
+ requests.remove(destination);
+ }
+ }
+ });
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java
similarity index 62%
rename from markwon/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java
rename to markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java
index 6a568e3a..08e1283f 100644
--- a/markwon/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java
+++ b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java
@@ -1,10 +1,8 @@
-package ru.noties.markwon;
+package ru.noties.markwon.image;
import android.support.annotation.NonNull;
-import ru.noties.markwon.spans.AsyncDrawable;
-
-class AsyncDrawableLoaderNoOp implements AsyncDrawable.Loader {
+public class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader {
@Override
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
diff --git a/markwon/src/main/java/ru/noties/markwon/image/ImageItem.java b/markwon/src/main/java/ru/noties/markwon/image/ImageItem.java
new file mode 100644
index 00000000..2dc4b729
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/ImageItem.java
@@ -0,0 +1,31 @@
+package ru.noties.markwon.image;
+
+import android.support.annotation.Nullable;
+
+import java.io.InputStream;
+
+/**
+ * @since 2.0.0
+ */
+public class ImageItem {
+
+ private final String contentType;
+ private final InputStream inputStream;
+
+ public ImageItem(
+ @Nullable String contentType,
+ @Nullable InputStream inputStream) {
+ this.contentType = contentType;
+ this.inputStream = inputStream;
+ }
+
+ @Nullable
+ public String contentType() {
+ return contentType;
+ }
+
+ @Nullable
+ public InputStream inputStream() {
+ return inputStream;
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java b/markwon/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java
new file mode 100644
index 00000000..796d016e
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java
@@ -0,0 +1,52 @@
+package ru.noties.markwon.image;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.InputStream;
+
+import ru.noties.markwon.utils.DrawableUtils;
+
+/**
+ * This class can be used as the last {@link MediaDecoder} to _try_ to handle all rest cases.
+ * Here we just assume that supplied InputStream is of image type and try to decode it.
+ *
+ * @since 1.1.0
+ */
+public class ImageMediaDecoder extends MediaDecoder {
+
+ @NonNull
+ public static ImageMediaDecoder create(@NonNull Resources resources) {
+ return new ImageMediaDecoder(resources);
+ }
+
+ private final Resources resources;
+
+ @SuppressWarnings("WeakerAccess")
+ ImageMediaDecoder(Resources resources) {
+ this.resources = resources;
+ }
+
+ @Nullable
+ @Override
+ public Drawable decode(@NonNull InputStream inputStream) {
+
+ final Drawable out;
+
+ // absolutely not optimal... thing
+ final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+ if (bitmap != null) {
+ out = new BitmapDrawable(resources, bitmap);
+ DrawableUtils.intrinsicBounds(out);
+ } else {
+ out = null;
+ }
+
+ return out;
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java b/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java
new file mode 100644
index 00000000..0154eb8f
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java
@@ -0,0 +1,92 @@
+package ru.noties.markwon.image;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.commonmark.node.Image;
+import org.commonmark.node.Link;
+import org.commonmark.node.Node;
+
+import java.util.Arrays;
+
+import ru.noties.markwon.AbstractMarkwonPlugin;
+import ru.noties.markwon.MarkwonConfiguration;
+import ru.noties.markwon.MarkwonVisitor;
+import ru.noties.markwon.image.data.DataUriSchemeHandler;
+import ru.noties.markwon.image.file.FileSchemeHandler;
+import ru.noties.markwon.image.network.NetworkSchemeHandler;
+
+public class ImagesPlugin extends AbstractMarkwonPlugin {
+
+ @NonNull
+ public static ImagesPlugin create(@NonNull Context context) {
+ return new ImagesPlugin(context, false);
+ }
+
+ @NonNull
+ public static ImagesPlugin createWithAssets(@NonNull Context context) {
+ return new ImagesPlugin(context, true);
+ }
+
+ private final Context context;
+ private final boolean useAssets;
+
+ private ImagesPlugin(Context context, boolean useAssets) {
+ this.context = context;
+ this.useAssets = useAssets;
+ }
+
+ @Override
+ public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
+
+ final FileSchemeHandler fileSchemeHandler = useAssets
+ ? FileSchemeHandler.createWithAssets(context.getAssets())
+ : FileSchemeHandler.create();
+
+ builder
+ .addSchemeHandler(DataUriSchemeHandler.SCHEME, DataUriSchemeHandler.create())
+ .addSchemeHandler(FileSchemeHandler.SCHEME, fileSchemeHandler)
+ .addSchemeHandler(
+ Arrays.asList(
+ NetworkSchemeHandler.SCHEME_HTTP,
+ NetworkSchemeHandler.SCHEME_HTTPS),
+ NetworkSchemeHandler.create())
+ .defaultMediaDecoder(ImageMediaDecoder.create(context.getResources()));
+ }
+
+ @Override
+ public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
+ builder.on(Image.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) {
+
+ final int length = visitor.length();
+
+ visitor.visitChildren(image);
+
+ // we must check if anything _was_ added, as we need at least one char to render
+ if (length == visitor.length()) {
+ visitor.builder().append('\uFFFC');
+ }
+
+ final MarkwonConfiguration configuration = visitor.configuration();
+
+ final Node parent = image.getParent();
+ final boolean link = parent instanceof Link;
+ final String destination = configuration
+ .urlProcessor()
+ .process(image.getDestination());
+
+ final Object spans = visitor.factory().image(
+ visitor.theme(),
+ destination,
+ configuration.asyncDrawableLoader(),
+ configuration.imageSizeResolver(),
+ null,
+ link);
+
+ visitor.setSpans(length, spans);
+ }
+ });
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/MediaDecoder.java b/markwon/src/main/java/ru/noties/markwon/image/MediaDecoder.java
new file mode 100644
index 00000000..68d0ff33
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/MediaDecoder.java
@@ -0,0 +1,16 @@
+package ru.noties.markwon.image;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.InputStream;
+
+/**
+ * @since 3.0.0
+ */
+public abstract class MediaDecoder {
+
+ @Nullable
+ public abstract Drawable decode(@NonNull InputStream inputStream);
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/SchemeHandler.java b/markwon/src/main/java/ru/noties/markwon/image/SchemeHandler.java
new file mode 100644
index 00000000..cac1c801
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/SchemeHandler.java
@@ -0,0 +1,14 @@
+package ru.noties.markwon.image;
+
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * @since 3.0.0
+ */
+public abstract class SchemeHandler {
+
+ @Nullable
+ public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/data/DataUri.java b/markwon/src/main/java/ru/noties/markwon/image/data/DataUri.java
new file mode 100644
index 00000000..6e812c92
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/data/DataUri.java
@@ -0,0 +1,60 @@
+package ru.noties.markwon.image.data;
+
+import android.support.annotation.Nullable;
+
+public class DataUri {
+
+ private final String contentType;
+ private final boolean base64;
+ private final String data;
+
+ public DataUri(@Nullable String contentType, boolean base64, @Nullable String data) {
+ this.contentType = contentType;
+ this.base64 = base64;
+ this.data = data;
+ }
+
+ @Nullable
+ public String contentType() {
+ return contentType;
+ }
+
+ public boolean base64() {
+ return base64;
+ }
+
+ @Nullable
+ public String data() {
+ return data;
+ }
+
+ @Override
+ public String toString() {
+ return "DataUri{" +
+ "contentType='" + contentType + '\'' +
+ ", base64=" + base64 +
+ ", data='" + data + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DataUri dataUri = (DataUri) o;
+
+ if (base64 != dataUri.base64) return false;
+ if (contentType != null ? !contentType.equals(dataUri.contentType) : dataUri.contentType != null)
+ return false;
+ return data != null ? data.equals(dataUri.data) : dataUri.data == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = contentType != null ? contentType.hashCode() : 0;
+ result = 31 * result + (base64 ? 1 : 0);
+ result = 31 * result + (data != null ? data.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java
new file mode 100644
index 00000000..7e3d4f73
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java
@@ -0,0 +1,41 @@
+package ru.noties.markwon.image.data;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Base64;
+
+public abstract class DataUriDecoder {
+
+ @Nullable
+ public abstract byte[] decode(@NonNull DataUri dataUri);
+
+ @NonNull
+ public static DataUriDecoder create() {
+ return new Impl();
+ }
+
+ static class Impl extends DataUriDecoder {
+
+ @Nullable
+ @Override
+ public byte[] decode(@NonNull DataUri dataUri) {
+
+ final String data = dataUri.data();
+
+ if (!TextUtils.isEmpty(data)) {
+ try {
+ if (dataUri.base64()) {
+ return Base64.decode(data.getBytes("UTF-8"), Base64.DEFAULT);
+ } else {
+ return data.getBytes("UTF-8");
+ }
+ } catch (Throwable t) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/data/DataUriParser.java b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriParser.java
new file mode 100644
index 00000000..0768ee4a
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriParser.java
@@ -0,0 +1,79 @@
+package ru.noties.markwon.image.data;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public abstract class DataUriParser {
+
+ @Nullable
+ public abstract DataUri parse(@NonNull String input);
+
+
+ @NonNull
+ public static DataUriParser create() {
+ return new Impl();
+ }
+
+ static class Impl extends DataUriParser {
+
+ @Nullable
+ @Override
+ public DataUri parse(@NonNull String input) {
+
+ final int index = input.indexOf(',');
+ // we expect exactly one comma
+ if (index < 0) {
+ return null;
+ }
+
+ final String contentType;
+ final boolean base64;
+
+ if (index > 0) {
+ final String part = input.substring(0, index);
+ final String[] parts = part.split(";");
+ final int length = parts.length;
+ if (length > 0) {
+ // if one: either content-type or base64
+ if (length == 1) {
+ final String value = parts[0];
+ if ("base64".equals(value)) {
+ contentType = null;
+ base64 = true;
+ } else {
+ contentType = value.indexOf('/') > -1
+ ? value
+ : null;
+ base64 = false;
+ }
+ } else {
+ contentType = parts[0].indexOf('/') > -1
+ ? parts[0]
+ : null;
+ base64 = "base64".equals(parts[length - 1]);
+ }
+ } else {
+ contentType = null;
+ base64 = false;
+ }
+ } else {
+ contentType = null;
+ base64 = false;
+ }
+
+ final String data;
+ if (index < input.length()) {
+ final String value = input.substring(index + 1, input.length()).replaceAll("\n", "");
+ if (value.length() == 0) {
+ data = null;
+ } else {
+ data = value;
+ }
+ } else {
+ data = null;
+ }
+
+ return new DataUri(contentType, base64, data);
+ }
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java
new file mode 100644
index 00000000..8f44bb02
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java
@@ -0,0 +1,65 @@
+package ru.noties.markwon.image.data;
+
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.ByteArrayInputStream;
+
+import ru.noties.markwon.image.ImageItem;
+import ru.noties.markwon.image.SchemeHandler;
+
+/**
+ * @since 3.0.0
+ */
+public class DataUriSchemeHandler extends SchemeHandler {
+
+ public static final String SCHEME = "data";
+
+ @NonNull
+ public static DataUriSchemeHandler create() {
+ return new DataUriSchemeHandler(DataUriParser.create(), DataUriDecoder.create());
+ }
+
+ private static final String START = "data:";
+
+ private final DataUriParser uriParser;
+ private final DataUriDecoder uriDecoder;
+
+ @SuppressWarnings("WeakerAccess")
+ DataUriSchemeHandler(@NonNull DataUriParser uriParser, @NonNull DataUriDecoder uriDecoder) {
+ this.uriParser = uriParser;
+ this.uriDecoder = uriDecoder;
+ }
+
+ @Nullable
+ @Override
+ public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
+
+ if (!raw.startsWith(START)) {
+ return null;
+ }
+
+ String part = raw.substring(START.length());
+
+ // this part is added to support `data://` with which this functionality was released
+ if (part.startsWith("//")) {
+ part = part.substring(2);
+ }
+
+ final DataUri dataUri = uriParser.parse(part);
+ if (dataUri == null) {
+ return null;
+ }
+
+ final byte[] bytes = uriDecoder.decode(dataUri);
+ if (bytes == null) {
+ return null;
+ }
+
+ return new ImageItem(
+ dataUri.contentType(),
+ new ByteArrayInputStream(bytes)
+ );
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java b/markwon/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java
new file mode 100644
index 00000000..712899aa
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java
@@ -0,0 +1,105 @@
+package ru.noties.markwon.image.file;
+
+import android.content.res.AssetManager;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.webkit.MimeTypeMap;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import ru.noties.markwon.image.ImageItem;
+import ru.noties.markwon.image.SchemeHandler;
+
+/**
+ * @since 3.0.0
+ */
+public class FileSchemeHandler extends SchemeHandler {
+
+ public static final String SCHEME = "file";
+
+ @NonNull
+ public static FileSchemeHandler createWithAssets(@NonNull AssetManager assetManager) {
+ return new FileSchemeHandler(assetManager);
+ }
+
+ @NonNull
+ public static FileSchemeHandler create() {
+ return new FileSchemeHandler(null);
+ }
+
+ private static final String FILE_ANDROID_ASSETS = "android_asset";
+
+ @Nullable
+ private final AssetManager assetManager;
+
+ @SuppressWarnings("WeakerAccess")
+ FileSchemeHandler(@Nullable AssetManager assetManager) {
+ this.assetManager = assetManager;
+ }
+
+ @Nullable
+ @Override
+ public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
+
+ final List segments = uri.getPathSegments();
+ if (segments == null
+ || segments.size() == 0) {
+ // pointing to file & having no path segments is no use
+ return null;
+ }
+
+ final ImageItem out;
+
+ InputStream inputStream = null;
+
+ final boolean assets = FILE_ANDROID_ASSETS.equals(segments.get(0));
+ final String fileName = uri.getLastPathSegment();
+
+ if (assets) {
+
+ // no handling of assets here if we have no assetsManager
+ if (assetManager != null) {
+
+ final StringBuilder path = new StringBuilder();
+ for (int i = 1, size = segments.size(); i < size; i++) {
+ if (i != 1) {
+ path.append('/');
+ }
+ path.append(segments.get(i));
+ }
+ // load assets
+
+ try {
+ inputStream = assetManager.open(path.toString());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ } else {
+ try {
+ inputStream = new BufferedInputStream(new FileInputStream(new File(uri.getPath())));
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (inputStream != null) {
+ final String contentType = MimeTypeMap
+ .getSingleton()
+ .getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(fileName));
+ out = new ImageItem(contentType, inputStream);
+ } else {
+ out = null;
+ }
+
+ return out;
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java b/markwon/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java
new file mode 100644
index 00000000..c5352d4b
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java
@@ -0,0 +1,64 @@
+package ru.noties.markwon.image.network;
+
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import ru.noties.markwon.image.ImageItem;
+import ru.noties.markwon.image.SchemeHandler;
+
+public class NetworkSchemeHandler extends SchemeHandler {
+
+ public static final String SCHEME_HTTP = "http";
+ public static final String SCHEME_HTTPS = "https";
+
+ @NonNull
+ public static NetworkSchemeHandler create() {
+ return new NetworkSchemeHandler();
+ }
+
+ @Nullable
+ @Override
+ public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
+
+ try {
+
+ final URL url = new URL(raw);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.connect();
+
+ final int responseCode = connection.getResponseCode();
+ if (responseCode >= 200 && responseCode < 300) {
+ final String contentType = contentType(connection.getHeaderField("Content-Type"));
+ final InputStream inputStream = new BufferedInputStream(connection.getInputStream());
+ return new ImageItem(contentType, inputStream);
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ @Nullable
+ static String contentType(@Nullable String contentType) {
+
+ if (contentType == null) {
+ return null;
+ }
+
+ final int index = contentType.indexOf(';');
+ if (index > -1) {
+ return contentType.substring(0, index);
+ }
+
+ return contentType;
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java b/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
index 3ea3cc83..6a3a94e3 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java
@@ -42,7 +42,7 @@ import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.SpannableFactory;
import ru.noties.markwon.html.api.MarkwonHtmlParser;
import ru.noties.markwon.spans.MarkwonTheme;
-import ru.noties.markwon.spans.TableRowSpan;
+import ru.noties.markwon.table.TableRowSpan;
import ru.noties.markwon.tasklist.TaskListBlock;
import ru.noties.markwon.tasklist.TaskListItem;
@@ -321,112 +321,92 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(customNode);
setSpan(length, factory.strikethrough());
- } else if (customNode instanceof TaskListItem) {
-
- // new in 1.0.1
-
- final TaskListItem listItem = (TaskListItem) customNode;
-
- final int length = builder.length();
-
- blockQuoteIndent += listItem.indent();
-
- visitChildren(customNode);
-
- setSpan(length, factory.taskListItem(theme, blockQuoteIndent, listItem.done()));
-
- if (hasNext(customNode)) {
- newLine();
- }
-
- blockQuoteIndent -= listItem.indent();
-
- } else if (!handleTableNodes(customNode)) {
+ } else {
super.visit(customNode);
}
}
- private boolean handleTableNodes(CustomNode node) {
-
- final boolean handled;
-
- if (node instanceof TableBody) {
-
- visitChildren(node);
- tableRows = 0;
- handled = true;
-
- if (hasNext(node)) {
- newLine();
- builder.append('\n');
- }
-
- } else if (node instanceof TableRow || node instanceof TableHead) {
-
- final int length = builder.length();
-
- visitChildren(node);
-
- if (pendingTableRow != null) {
-
- // @since 2.0.0
- // we cannot rely on hasNext(TableHead) as it's not reliable
- // we must apply new line manually and then exclude it from tableRow span
- final boolean addNewLine;
- {
- final int builderLength = builder.length();
- addNewLine = builderLength > 0
- && '\n' != builder.charAt(builderLength - 1);
- }
- if (addNewLine) {
- builder.append('\n');
- }
-
- // @since 1.0.4 Replace table char with non-breakable space
- // we need this because if table is at the end of the text, then it will be
- // trimmed from the final result
- builder.append('\u00a0');
-
- final Object span = factory.tableRow(
- theme,
- pendingTableRow,
- tableRowIsHeader,
- tableRows % 2 == 1);
-
- tableRows = tableRowIsHeader
- ? 0
- : tableRows + 1;
-
- setSpan(addNewLine ? length + 1 : length, span);
-
- pendingTableRow = null;
- }
-
- handled = true;
-
- } else if (node instanceof TableCell) {
-
- final TableCell cell = (TableCell) node;
- final int length = builder.length();
- visitChildren(cell);
- if (pendingTableRow == null) {
- pendingTableRow = new ArrayList<>(2);
- }
-
- pendingTableRow.add(new TableRowSpan.Cell(
- tableCellAlignment(cell.getAlignment()),
- builder.removeFromEnd(length)
- ));
-
- tableRowIsHeader = cell.isHeader();
-
- handled = true;
- } else {
- handled = false;
- }
-
- return handled;
- }
+// private boolean handleTableNodes(CustomNode node) {
+//
+// final boolean handled;
+//
+// if (node instanceof TableBody) {
+//
+// visitChildren(node);
+// tableRows = 0;
+// handled = true;
+//
+// if (hasNext(node)) {
+// newLine();
+// builder.append('\n');
+// }
+//
+// } else if (node instanceof TableRow || node instanceof TableHead) {
+//
+// final int length = builder.length();
+//
+// visitChildren(node);
+//
+// if (pendingTableRow != null) {
+//
+// // @since 2.0.0
+// // we cannot rely on hasNext(TableHead) as it's not reliable
+// // we must apply new line manually and then exclude it from tableRow span
+// final boolean addNewLine;
+// {
+// final int builderLength = builder.length();
+// addNewLine = builderLength > 0
+// && '\n' != builder.charAt(builderLength - 1);
+// }
+// if (addNewLine) {
+// builder.append('\n');
+// }
+//
+// // @since 1.0.4 Replace table char with non-breakable space
+// // we need this because if table is at the end of the text, then it will be
+// // trimmed from the final result
+// builder.append('\u00a0');
+//
+// final Object span = factory.tableRow(
+// theme,
+// pendingTableRow,
+// tableRowIsHeader,
+// tableRows % 2 == 1);
+//
+// tableRows = tableRowIsHeader
+// ? 0
+// : tableRows + 1;
+//
+// setSpan(addNewLine ? length + 1 : length, span);
+//
+// pendingTableRow = null;
+// }
+//
+// handled = true;
+//
+// } else if (node instanceof TableCell) {
+//
+// final TableCell cell = (TableCell) node;
+// final int length = builder.length();
+// visitChildren(cell);
+// if (pendingTableRow == null) {
+// pendingTableRow = new ArrayList<>(2);
+// }
+//
+// pendingTableRow.add(new TableRowSpan.Cell(
+// tableCellAlignment(cell.getAlignment()),
+// builder.removeFromEnd(length)
+// ));
+//
+// tableRowIsHeader = cell.isHeader();
+//
+// handled = true;
+// } else {
+// handled = false;
+// }
+//
+// return handled;
+// }
@Override
public void visit(Paragraph paragraph) {
@@ -530,26 +510,26 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
return false;
}
- @TableRowSpan.Alignment
- private static int tableCellAlignment(TableCell.Alignment alignment) {
- final int out;
- if (alignment != null) {
- switch (alignment) {
- case CENTER:
- out = TableRowSpan.ALIGN_CENTER;
- break;
- case RIGHT:
- out = TableRowSpan.ALIGN_RIGHT;
- break;
- default:
- out = TableRowSpan.ALIGN_LEFT;
- break;
- }
- } else {
- out = TableRowSpan.ALIGN_LEFT;
- }
- return out;
- }
+// @TableRowSpan.Alignment
+// private static int tableCellAlignment(TableCell.Alignment alignment) {
+// final int out;
+// if (alignment != null) {
+// switch (alignment) {
+// case CENTER:
+// out = TableRowSpan.ALIGN_CENTER;
+// break;
+// case RIGHT:
+// out = TableRowSpan.ALIGN_RIGHT;
+// break;
+// default:
+// out = TableRowSpan.ALIGN_LEFT;
+// break;
+// }
+// } else {
+// out = TableRowSpan.ALIGN_LEFT;
+// }
+// return out;
+// }
/**
* @since 2.0.0
diff --git a/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java
index b1ac85d4..20b5fe9d 100644
--- a/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java
+++ b/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java
@@ -12,6 +12,8 @@ import android.text.style.ReplacementSpan;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import ru.noties.markwon.image.AsyncDrawable;
+
@SuppressWarnings("WeakerAccess")
public class AsyncDrawableSpan extends ReplacementSpan {
@@ -29,16 +31,16 @@ public class AsyncDrawableSpan extends ReplacementSpan {
private final int alignment;
private final boolean replacementTextIsLink;
- public AsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable) {
- this(theme, drawable, ALIGN_BOTTOM);
- }
+// public AsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable) {
+// this(theme, drawable, ALIGN_BOTTOM);
+// }
- public AsyncDrawableSpan(
- @NonNull MarkwonTheme theme,
- @NonNull AsyncDrawable drawable,
- @Alignment int alignment) {
- this(theme, drawable, alignment, false);
- }
+// public AsyncDrawableSpan(
+// @NonNull MarkwonTheme theme,
+// @NonNull AsyncDrawable drawable,
+// @Alignment int alignment) {
+// this(theme, drawable, alignment, false);
+// }
public AsyncDrawableSpan(
@NonNull MarkwonTheme theme,
@@ -137,7 +139,7 @@ public class AsyncDrawableSpan extends ReplacementSpan {
// will it make sense to have additional background/borders for an image replacement?
// let's focus on main functionality and then think of it
- final float textY = CanvasUtils.textCenterY(top, bottom, paint);
+ final float textY = textCenterY(top, bottom, paint);
if (replacementTextIsLink) {
theme.applyLinkStyle(paint);
}
@@ -150,4 +152,9 @@ public class AsyncDrawableSpan extends ReplacementSpan {
public AsyncDrawable getDrawable() {
return drawable;
}
+
+ private static float textCenterY(int top, int bottom, @NonNull Paint paint) {
+ // @since 1.1.1 it's `top +` and not `bottom -`
+ return (int) (top + ((bottom - top) / 2) - ((paint.descent() + paint.ascent()) / 2.F + .5F));
+ }
}
diff --git a/markwon/src/main/java/ru/noties/markwon/spans/CanvasUtils.java b/markwon/src/main/java/ru/noties/markwon/spans/CanvasUtils.java
deleted file mode 100644
index 0851e855..00000000
--- a/markwon/src/main/java/ru/noties/markwon/spans/CanvasUtils.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package ru.noties.markwon.spans;
-
-import android.graphics.Paint;
-import android.support.annotation.NonNull;
-
-abstract class CanvasUtils {
-
- static float textCenterY(int top, int bottom, @NonNull Paint paint) {
- // @since 1.1.1 it's `top +` and not `bottom -`
- return (int) (top + ((bottom - top) / 2) - ((paint.descent() + paint.ascent()) / 2.F + .5F));
- }
-
- private CanvasUtils() {
- }
-}
diff --git a/markwon/src/main/java/ru/noties/markwon/spans/ColorUtils.java b/markwon/src/main/java/ru/noties/markwon/spans/ColorUtils.java
deleted file mode 100644
index 4739d531..00000000
--- a/markwon/src/main/java/ru/noties/markwon/spans/ColorUtils.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package ru.noties.markwon.spans;
-
-abstract class ColorUtils {
-
- static int applyAlpha(int color, int alpha) {
- return (color & 0x00FFFFFF) | (alpha << 24);
- }
-
- private ColorUtils() {
- }
-}
diff --git a/markwon/src/main/java/ru/noties/markwon/spans/MarkwonTheme.java b/markwon/src/main/java/ru/noties/markwon/spans/MarkwonTheme.java
index b5c9f34a..8f803841 100644
--- a/markwon/src/main/java/ru/noties/markwon/spans/MarkwonTheme.java
+++ b/markwon/src/main/java/ru/noties/markwon/spans/MarkwonTheme.java
@@ -1,25 +1,22 @@
package ru.noties.markwon.spans;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
-import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.annotation.Px;
import android.support.annotation.Size;
import android.text.TextPaint;
-import android.util.TypedValue;
import java.util.Arrays;
import java.util.Locale;
-import ru.noties.markwon.tasklist.TaskListDrawable;
+import ru.noties.markwon.utils.ColorUtils;
+import ru.noties.markwon.utils.Dip;
@SuppressWarnings("WeakerAccess")
public class MarkwonTheme {
@@ -77,36 +74,14 @@ public class MarkwonTheme {
@NonNull
public static Builder builderWithDefaults(@NonNull Context context) {
- // by default we will be using link color for the checkbox color
- // & window background as a checkMark color
- final int linkColor = resolve(context, android.R.attr.textColorLink);
- final int backgroundColor = resolve(context, android.R.attr.colorBackground);
-
- // before 1.0.5 build had `linkColor` set, but in order for spans to use default link color
- // set directly in widget (or any caller), we should not pass it here
-
- final Dip dip = new Dip(context);
+ final Dip dip = Dip.create(context);
return new Builder()
.codeMultilineMargin(dip.toPx(8))
.blockMargin(dip.toPx(24))
.blockQuoteWidth(dip.toPx(4))
.bulletListItemStrokeWidth(dip.toPx(1))
.headingBreakHeight(dip.toPx(1))
- .thematicBreakHeight(dip.toPx(4))
- .tableCellPadding(dip.toPx(4))
- .tableBorderWidth(dip.toPx(1))
- .taskListDrawable(new TaskListDrawable(linkColor, linkColor, backgroundColor));
- }
-
- private static int resolve(Context context, @AttrRes int attr) {
- final TypedValue typedValue = new TypedValue();
- final int attrs[] = new int[]{attr};
- final TypedArray typedArray = context.obtainStyledAttributes(typedValue.data, attrs);
- try {
- return typedArray.getColor(0, 0);
- } finally {
- typedArray.recycle();
- }
+ .thematicBreakHeight(dip.toPx(4));
}
protected static final int BLOCK_QUOTE_DEF_COLOR_ALPHA = 25;
@@ -126,10 +101,6 @@ public class MarkwonTheme {
protected static final int THEMATIC_BREAK_DEF_ALPHA = 25;
- protected static final int TABLE_BORDER_DEF_ALPHA = 75;
-
- protected static final int TABLE_ODD_ROW_DEF_ALPHA = 22;
-
protected final int linkColor;
// used in quote, lists
@@ -197,30 +168,6 @@ public class MarkwonTheme {
// by default paint.strokeWidth
protected final int thematicBreakHeight;
- // by default 0
- protected final int tableCellPadding;
-
- // by default paint.color * TABLE_BORDER_DEF_ALPHA
- protected final int tableBorderColor;
-
- protected final int tableBorderWidth;
-
- // by default paint.color * TABLE_ODD_ROW_DEF_ALPHA
- protected final int tableOddRowBackgroundColor;
-
- // @since 1.1.1
- // by default no background
- protected final int tableEventRowBackgroundColor;
-
- // @since 1.1.1
- // by default no background
- protected final int tableHeaderRowBackgroundColor;
-
- // drawable that will be used to render checkbox (should be stateful)
- // TaskListDrawable can be used
- @Deprecated
- protected final Drawable taskListDrawable;
-
protected MarkwonTheme(@NonNull Builder builder) {
this.linkColor = builder.linkColor;
this.blockMargin = builder.blockMargin;
@@ -243,13 +190,6 @@ public class MarkwonTheme {
this.scriptTextSizeRatio = builder.scriptTextSizeRatio;
this.thematicBreakColor = builder.thematicBreakColor;
this.thematicBreakHeight = builder.thematicBreakHeight;
- this.tableCellPadding = builder.tableCellPadding;
- this.tableBorderColor = builder.tableBorderColor;
- this.tableBorderWidth = builder.tableBorderWidth;
- this.tableOddRowBackgroundColor = builder.tableOddRowBackgroundColor;
- this.tableEventRowBackgroundColor = builder.tableEvenRowBackgroundColor;
- this.tableHeaderRowBackgroundColor = builder.tableHeaderRowBackgroundColor;
- this.taskListDrawable = builder.taskListDrawable;
}
/**
@@ -468,71 +408,6 @@ public class MarkwonTheme {
}
}
- public int tableCellPadding() {
- return tableCellPadding;
- }
-
- public int tableBorderWidth(@NonNull Paint paint) {
- final int out;
- if (tableBorderWidth == -1) {
- out = (int) (paint.getStrokeWidth() + .5F);
- } else {
- out = tableBorderWidth;
- }
- return out;
- }
-
- public void applyTableBorderStyle(@NonNull Paint paint) {
-
- final int color;
- if (tableBorderColor == 0) {
- color = ColorUtils.applyAlpha(paint.getColor(), TABLE_BORDER_DEF_ALPHA);
- } else {
- color = tableBorderColor;
- }
-
- paint.setColor(color);
- paint.setStyle(Paint.Style.STROKE);
- }
-
- public void applyTableOddRowStyle(@NonNull Paint paint) {
- final int color;
- if (tableOddRowBackgroundColor == 0) {
- color = ColorUtils.applyAlpha(paint.getColor(), TABLE_ODD_ROW_DEF_ALPHA);
- } else {
- color = tableOddRowBackgroundColor;
- }
- paint.setColor(color);
- paint.setStyle(Paint.Style.FILL);
- }
-
- /**
- * @since 1.1.1
- */
- public void applyTableEvenRowStyle(@NonNull Paint paint) {
- // by default to background to even row
- paint.setColor(tableEventRowBackgroundColor);
- paint.setStyle(Paint.Style.FILL);
- }
-
- /**
- * @since 1.1.1
- */
- public void applyTableHeaderRowStyle(@NonNull Paint paint) {
- paint.setColor(tableHeaderRowBackgroundColor);
- paint.setStyle(Paint.Style.FILL);
- }
-
- /**
- * @return a Drawable to be used as a checkbox indication in task lists
- * @since 1.0.1
- */
- @Nullable
- @Deprecated
- public Drawable getTaskListDrawable() {
- return taskListDrawable;
- }
-
@SuppressWarnings("unused")
public static class Builder {
@@ -557,13 +432,6 @@ public class MarkwonTheme {
private float scriptTextSizeRatio;
private int thematicBreakColor;
private int thematicBreakHeight = -1;
- private int tableCellPadding;
- private int tableBorderColor;
- private int tableBorderWidth = -1;
- private int tableOddRowBackgroundColor;
- private int tableEvenRowBackgroundColor; // @since 1.1.1
- private int tableHeaderRowBackgroundColor; // @since 1.1.1
- private Drawable taskListDrawable;
Builder() {
}
@@ -590,11 +458,6 @@ public class MarkwonTheme {
this.scriptTextSizeRatio = theme.scriptTextSizeRatio;
this.thematicBreakColor = theme.thematicBreakColor;
this.thematicBreakHeight = theme.thematicBreakHeight;
- this.tableCellPadding = theme.tableCellPadding;
- this.tableBorderColor = theme.tableBorderColor;
- this.tableBorderWidth = theme.tableBorderWidth;
- this.tableOddRowBackgroundColor = theme.tableOddRowBackgroundColor;
- this.taskListDrawable = theme.taskListDrawable;
}
@NonNull
@@ -742,81 +605,10 @@ public class MarkwonTheme {
return this;
}
- @NonNull
- public Builder tableCellPadding(@Px int tableCellPadding) {
- this.tableCellPadding = tableCellPadding;
- return this;
- }
-
- @NonNull
- public Builder tableBorderColor(@ColorInt int tableBorderColor) {
- this.tableBorderColor = tableBorderColor;
- return this;
- }
-
- @NonNull
- public Builder tableBorderWidth(@Px int tableBorderWidth) {
- this.tableBorderWidth = tableBorderWidth;
- return this;
- }
-
- @NonNull
- public Builder tableOddRowBackgroundColor(@ColorInt int tableOddRowBackgroundColor) {
- this.tableOddRowBackgroundColor = tableOddRowBackgroundColor;
- return this;
- }
-
- /**
- * @since 1.1.1
- */
- @NonNull
- public Builder tableEvenRowBackgroundColor(@ColorInt int tableEvenRowBackgroundColor) {
- this.tableEvenRowBackgroundColor = tableEvenRowBackgroundColor;
- return this;
- }
-
- /**
- * @since 1.1.1
- */
- @NonNull
- public Builder tableHeaderRowBackgroundColor(@ColorInt int tableHeaderRowBackgroundColor) {
- this.tableHeaderRowBackgroundColor = tableHeaderRowBackgroundColor;
- return this;
- }
-
- /**
- * Supplied Drawable must be stateful ({@link Drawable#isStateful()} returns true). If a task
- * is marked as done, then this drawable will be updated with an {@code int[] { android.R.attr.state_checked }}
- * as the state, otherwise an empty array will be used. This library provides a ready to be
- * used Drawable: {@link TaskListDrawable}
- *
- * @param taskListDrawable Drawable to be used as the task list indication (checkbox)
- * @see TaskListDrawable
- * @since 1.0.1
- */
- @NonNull
- @Deprecated
- public Builder taskListDrawable(@NonNull Drawable taskListDrawable) {
- this.taskListDrawable = taskListDrawable;
- return this;
- }
-
@NonNull
public MarkwonTheme build() {
return new MarkwonTheme(this);
}
}
- private static class Dip {
-
- private final float density;
-
- Dip(@NonNull Context context) {
- this.density = context.getResources().getDisplayMetrics().density;
- }
-
- int toPx(int dp) {
- return (int) (dp * density + .5F);
- }
- }
}
diff --git a/markwon/src/main/java/ru/noties/markwon/table/TablePlugin.java b/markwon/src/main/java/ru/noties/markwon/table/TablePlugin.java
new file mode 100644
index 00000000..506b8ff3
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/table/TablePlugin.java
@@ -0,0 +1,189 @@
+package ru.noties.markwon.table;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.widget.TextView;
+
+import org.commonmark.ext.gfm.tables.TableBody;
+import org.commonmark.ext.gfm.tables.TableCell;
+import org.commonmark.ext.gfm.tables.TableHead;
+import org.commonmark.ext.gfm.tables.TableRow;
+import org.commonmark.ext.gfm.tables.TablesExtension;
+import org.commonmark.node.Node;
+import org.commonmark.parser.Parser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import ru.noties.markwon.AbstractMarkwonPlugin;
+import ru.noties.markwon.MarkwonVisitor;
+import ru.noties.markwon.SpannableBuilder;
+
+public class TablePlugin extends AbstractMarkwonPlugin {
+
+ @NonNull
+ public static TablePlugin create(@NonNull Context context) {
+ return new TablePlugin(TableTheme.create(context));
+ }
+
+ @NonNull
+ public static TablePlugin create(@NonNull TableTheme tableTheme) {
+ return new TablePlugin(tableTheme);
+ }
+
+ private final TableTheme tableTheme;
+
+ TablePlugin(@NonNull TableTheme tableTheme) {
+ this.tableTheme = tableTheme;
+ }
+
+ @Override
+ public void configureParser(@NonNull Parser.Builder builder) {
+ builder.extensions(Collections.singleton(TablesExtension.create()));
+ }
+
+ @Override
+ public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
+ TableVisitor.configure(tableTheme, builder);
+ }
+
+ @Override
+ public void beforeSetText(@NonNull TextView textView, @NonNull CharSequence markdown) {
+ TableRowsScheduler.unschedule(textView);
+ }
+
+ @Override
+ public void afterSetText(@NonNull TextView textView) {
+ TableRowsScheduler.schedule(textView);
+ }
+
+ private static class TableVisitor {
+
+ static void configure(@NonNull TableTheme tableTheme, @NonNull MarkwonVisitor.Builder builder) {
+ new TableVisitor(tableTheme, builder);
+ }
+
+ private final TableTheme tableTheme;
+
+ private List pendingTableRow;
+ private boolean tableRowIsHeader;
+ private int tableRows;
+
+ private TableVisitor(@NonNull TableTheme tableTheme, @NonNull MarkwonVisitor.Builder builder) {
+ this.tableTheme = tableTheme;
+ builder
+ .on(TableBody.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableBody tableBody) {
+
+ visitor.visitChildren(tableBody);
+ tableRows = 0;
+
+ if (visitor.hasNext(tableBody)) {
+ visitor.ensureNewLine();
+ visitor.forceNewLine();
+ }
+ }
+ })
+ .on(TableRow.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableRow tableRow) {
+ visitRow(visitor, tableRow);
+ }
+ })
+ .on(TableHead.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableHead tableHead) {
+ visitRow(visitor, tableHead);
+ }
+ })
+ .on(TableCell.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableCell tableCell) {
+
+ final int length = visitor.length();
+
+ visitor.visitChildren(tableCell);
+
+ if (pendingTableRow == null) {
+ pendingTableRow = new ArrayList<>(2);
+ }
+
+ pendingTableRow.add(new TableRowSpan.Cell(
+ tableCellAlignment(tableCell.getAlignment()),
+ visitor.builder().removeFromEnd(length)
+ ));
+
+ tableRowIsHeader = tableCell.isHeader();
+ }
+ });
+ }
+
+ private void visitRow(@NonNull MarkwonVisitor visitor, @NonNull Node node) {
+
+ final int length = visitor.length();
+
+ visitor.visitChildren(node);
+
+ if (pendingTableRow != null) {
+
+ final SpannableBuilder builder = visitor.builder();
+
+ // @since 2.0.0
+ // we cannot rely on hasNext(TableHead) as it's not reliable
+ // we must apply new line manually and then exclude it from tableRow span
+ final boolean addNewLine;
+ {
+ final int builderLength = builder.length();
+ addNewLine = builderLength > 0
+ && '\n' != builder.charAt(builderLength - 1);
+ }
+
+ if (addNewLine) {
+ visitor.forceNewLine();
+ }
+
+ // @since 1.0.4 Replace table char with non-breakable space
+ // we need this because if table is at the end of the text, then it will be
+ // trimmed from the final result
+ builder.append('\u00a0');
+
+ final Object span = new TableRowSpan(
+ tableTheme,
+ pendingTableRow,
+ tableRowIsHeader,
+ tableRows % 2 == 1);
+
+ tableRows = tableRowIsHeader
+ ? 0
+ : tableRows + 1;
+
+ visitor.setSpans(addNewLine ? length + 1 : length, span);
+
+ pendingTableRow = null;
+ }
+ }
+
+ @TableRowSpan.Alignment
+ private static int tableCellAlignment(TableCell.Alignment alignment) {
+ final int out;
+ if (alignment != null) {
+ switch (alignment) {
+ case CENTER:
+ out = TableRowSpan.ALIGN_CENTER;
+ break;
+ case RIGHT:
+ out = TableRowSpan.ALIGN_RIGHT;
+ break;
+ default:
+ out = TableRowSpan.ALIGN_LEFT;
+ break;
+ }
+ } else {
+ out = TableRowSpan.ALIGN_LEFT;
+ }
+ return out;
+ }
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/spans/TableRowSpan.java b/markwon/src/main/java/ru/noties/markwon/table/TableRowSpan.java
similarity index 96%
rename from markwon/src/main/java/ru/noties/markwon/spans/TableRowSpan.java
rename to markwon/src/main/java/ru/noties/markwon/table/TableRowSpan.java
index a54b3206..903cf5f3 100644
--- a/markwon/src/main/java/ru/noties/markwon/spans/TableRowSpan.java
+++ b/markwon/src/main/java/ru/noties/markwon/table/TableRowSpan.java
@@ -1,4 +1,4 @@
-package ru.noties.markwon.spans;
+package ru.noties.markwon.table;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
@@ -61,22 +61,22 @@ public class TableRowSpan extends ReplacementSpan {
}
}
- private final MarkwonTheme theme;
+ private final TableTheme theme;
private final List cells;
private final List layouts;
private final TextPaint textPaint;
private final boolean header;
private final boolean odd;
- private final Rect rect = ObjectsPool.rect();
- private final Paint paint = ObjectsPool.paint();
+ private final Rect rect = new Rect();
+ private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int width;
private int height;
private Invalidator invalidator;
public TableRowSpan(
- @NonNull MarkwonTheme theme,
+ @NonNull TableTheme theme,
@NonNull List cells,
boolean header,
boolean odd) {
@@ -272,8 +272,7 @@ public class TableRowSpan extends ReplacementSpan {
return out;
}
- public TableRowSpan invalidator(Invalidator invalidator) {
+ public void invalidator(@Nullable Invalidator invalidator) {
this.invalidator = invalidator;
- return this;
}
}
diff --git a/markwon/src/main/java/ru/noties/markwon/TableRowsScheduler.java b/markwon/src/main/java/ru/noties/markwon/table/TableRowsScheduler.java
similarity index 96%
rename from markwon/src/main/java/ru/noties/markwon/TableRowsScheduler.java
rename to markwon/src/main/java/ru/noties/markwon/table/TableRowsScheduler.java
index 6fc9a584..1d1246b0 100644
--- a/markwon/src/main/java/ru/noties/markwon/TableRowsScheduler.java
+++ b/markwon/src/main/java/ru/noties/markwon/table/TableRowsScheduler.java
@@ -1,13 +1,13 @@
-package ru.noties.markwon;
+package ru.noties.markwon.table;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import ru.noties.markwon.renderer.R;
-import ru.noties.markwon.spans.TableRowSpan;
abstract class TableRowsScheduler {
@@ -57,6 +57,7 @@ abstract class TableRowsScheduler {
}
}
+ @Nullable
private static Object[] extract(@NonNull TextView view) {
final Object[] out;
final CharSequence text = view.getText();
diff --git a/markwon/src/main/java/ru/noties/markwon/table/TableTheme.java b/markwon/src/main/java/ru/noties/markwon/table/TableTheme.java
new file mode 100644
index 00000000..56a72321
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/table/TableTheme.java
@@ -0,0 +1,164 @@
+package ru.noties.markwon.table;
+
+import android.content.Context;
+import android.graphics.Paint;
+import android.support.annotation.NonNull;
+
+import ru.noties.markwon.utils.ColorUtils;
+import ru.noties.markwon.utils.Dip;
+
+public class TableTheme {
+
+ @NonNull
+ public static TableTheme create(@NonNull Context context) {
+ final Dip dip = Dip.create(context);
+ return builder()
+ .tableCellPadding(dip.toPx(4))
+ .tableBorderWidth(dip.toPx(1))
+ .build();
+ }
+
+ @NonNull
+ public static Builder builder() {
+ return new Builder();
+ }
+
+
+ protected static final int TABLE_BORDER_DEF_ALPHA = 75;
+
+ protected static final int TABLE_ODD_ROW_DEF_ALPHA = 22;
+
+ // by default 0
+ protected final int tableCellPadding;
+
+ // by default paint.color * TABLE_BORDER_DEF_ALPHA
+ protected final int tableBorderColor;
+
+ protected final int tableBorderWidth;
+
+ // by default paint.color * TABLE_ODD_ROW_DEF_ALPHA
+ protected final int tableOddRowBackgroundColor;
+
+ // @since 1.1.1
+ // by default no background
+ protected final int tableEvenRowBackgroundColor;
+
+ // @since 1.1.1
+ // by default no background
+ protected final int tableHeaderRowBackgroundColor;
+
+ protected TableTheme(@NonNull Builder builder) {
+ this.tableCellPadding = builder.tableCellPadding;
+ this.tableBorderColor = builder.tableBorderColor;
+ this.tableBorderWidth = builder.tableBorderWidth;
+ this.tableOddRowBackgroundColor = builder.tableOddRowBackgroundColor;
+ this.tableEvenRowBackgroundColor = builder.tableEvenRowBackgroundColor;
+ this.tableHeaderRowBackgroundColor = builder.tableHeaderRowBackgroundColor;
+ }
+
+ public int tableCellPadding() {
+ return tableCellPadding;
+ }
+
+ public int tableBorderWidth(@NonNull Paint paint) {
+ final int out;
+ if (tableBorderWidth == -1) {
+ out = (int) (paint.getStrokeWidth() + .5F);
+ } else {
+ out = tableBorderWidth;
+ }
+ return out;
+ }
+
+ public void applyTableBorderStyle(@NonNull Paint paint) {
+
+ final int color;
+ if (tableBorderColor == 0) {
+ color = ColorUtils.applyAlpha(paint.getColor(), TABLE_BORDER_DEF_ALPHA);
+ } else {
+ color = tableBorderColor;
+ }
+
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.STROKE);
+ }
+
+ public void applyTableOddRowStyle(@NonNull Paint paint) {
+ final int color;
+ if (tableOddRowBackgroundColor == 0) {
+ color = ColorUtils.applyAlpha(paint.getColor(), TABLE_ODD_ROW_DEF_ALPHA);
+ } else {
+ color = tableOddRowBackgroundColor;
+ }
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.FILL);
+ }
+
+ /**
+ * @since 1.1.1
+ */
+ public void applyTableEvenRowStyle(@NonNull Paint paint) {
+ // by default to background to even row
+ paint.setColor(tableEvenRowBackgroundColor);
+ paint.setStyle(Paint.Style.FILL);
+ }
+
+ /**
+ * @since 1.1.1
+ */
+ public void applyTableHeaderRowStyle(@NonNull Paint paint) {
+ paint.setColor(tableHeaderRowBackgroundColor);
+ paint.setStyle(Paint.Style.FILL);
+ }
+
+ public static class Builder {
+
+ private int tableCellPadding;
+ private int tableBorderColor;
+ private int tableBorderWidth = -1;
+ private int tableOddRowBackgroundColor;
+ private int tableEvenRowBackgroundColor; // @since 1.1.1
+ private int tableHeaderRowBackgroundColor; // @since 1.1.1
+
+ @NonNull
+ public Builder tableCellPadding(int tableCellPadding) {
+ this.tableCellPadding = tableCellPadding;
+ return this;
+ }
+
+ @NonNull
+ public Builder tableBorderColor(int tableBorderColor) {
+ this.tableBorderColor = tableBorderColor;
+ return this;
+ }
+
+ @NonNull
+ public Builder tableBorderWidth(int tableBorderWidth) {
+ this.tableBorderWidth = tableBorderWidth;
+ return this;
+ }
+
+ @NonNull
+ public Builder tableOddRowBackgroundColor(int tableOddRowBackgroundColor) {
+ this.tableOddRowBackgroundColor = tableOddRowBackgroundColor;
+ return this;
+ }
+
+ @NonNull
+ public Builder tableEvenRowBackgroundColor(int tableEvenRowBackgroundColor) {
+ this.tableEvenRowBackgroundColor = tableEvenRowBackgroundColor;
+ return this;
+ }
+
+ @NonNull
+ public Builder tableHeaderRowBackgroundColor(int tableHeaderRowBackgroundColor) {
+ this.tableHeaderRowBackgroundColor = tableHeaderRowBackgroundColor;
+ return this;
+ }
+
+ @NonNull
+ public TableTheme build() {
+ return new TableTheme(this);
+ }
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListPlugin.java b/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListPlugin.java
index bf75d5c7..77c865c4 100644
--- a/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListPlugin.java
+++ b/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListPlugin.java
@@ -1,9 +1,12 @@
package ru.noties.markwon.tasklist;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
+import android.util.TypedValue;
import org.commonmark.parser.Parser;
@@ -13,6 +16,11 @@ import ru.noties.markwon.MarkwonVisitor;
public class TaskListPlugin extends AbstractMarkwonPlugin {
/**
+ * Supplied Drawable must be stateful ({@link Drawable#isStateful()} returns true). If a task
+ * is marked as done, then this drawable will be updated with an {@code int[] { android.R.attr.state_checked }}
+ * as the state, otherwise an empty array will be used. This library provides a ready to be
+ * used Drawable: {@link TaskListDrawable}
+ *
* @see TaskListDrawable
*/
@NonNull
@@ -22,8 +30,13 @@ public class TaskListPlugin extends AbstractMarkwonPlugin {
@NonNull
public static TaskListPlugin create(@NonNull Context context) {
- // resolve link color and background color
- return null;
+
+ // by default we will be using link color for the checkbox color
+ // & window background as a checkMark color
+ final int linkColor = resolve(context, android.R.attr.textColorLink);
+ final int backgroundColor = resolve(context, android.R.attr.colorBackground);
+
+ return new TaskListPlugin(new TaskListDrawable(linkColor, linkColor, backgroundColor));
}
@NonNull
@@ -90,4 +103,15 @@ public class TaskListPlugin extends AbstractMarkwonPlugin {
}
});
}
+
+ private static int resolve(Context context, @AttrRes int attr) {
+ final TypedValue typedValue = new TypedValue();
+ final int attrs[] = new int[]{attr};
+ final TypedArray typedArray = context.obtainStyledAttributes(typedValue.data, attrs);
+ try {
+ return typedArray.getColor(0, 0);
+ } finally {
+ typedArray.recycle();
+ }
+ }
}
diff --git a/markwon/src/main/java/ru/noties/markwon/utils/ColorUtils.java b/markwon/src/main/java/ru/noties/markwon/utils/ColorUtils.java
new file mode 100644
index 00000000..d6305132
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/utils/ColorUtils.java
@@ -0,0 +1,11 @@
+package ru.noties.markwon.utils;
+
+public abstract class ColorUtils {
+
+ public static int applyAlpha(int color, int alpha) {
+ return (color & 0x00FFFFFF) | (alpha << 24);
+ }
+
+ private ColorUtils() {
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/utils/Dip.java b/markwon/src/main/java/ru/noties/markwon/utils/Dip.java
new file mode 100644
index 00000000..6899df8f
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/utils/Dip.java
@@ -0,0 +1,29 @@
+package ru.noties.markwon.utils;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import ru.noties.markwon.spans.MarkwonTheme;
+
+public class Dip {
+
+ @NonNull
+ public static Dip create(@NonNull Context context) {
+ return new Dip(context.getResources().getDisplayMetrics().density);
+ }
+
+ @NonNull
+ public static Dip create(float density) {
+ return new Dip(density);
+ }
+
+ private final float density;
+
+ public Dip(float density) {
+ this.density = density;
+ }
+
+ public int toPx(int dp) {
+ return (int) (dp * density + .5F);
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/utils/DrawableUtils.java b/markwon/src/main/java/ru/noties/markwon/utils/DrawableUtils.java
new file mode 100644
index 00000000..34342093
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/utils/DrawableUtils.java
@@ -0,0 +1,13 @@
+package ru.noties.markwon.utils;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+
+public abstract class DrawableUtils {
+
+ public static void intrinsicBounds(@NonNull Drawable drawable) {
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ }
+
+ private DrawableUtils() {}
+}
diff --git a/markwon/src/test/java/ru/noties/markwon/image/data/DataUriParserTest.java b/markwon/src/test/java/ru/noties/markwon/image/data/DataUriParserTest.java
new file mode 100644
index 00000000..b361a0d3
--- /dev/null
+++ b/markwon/src/test/java/ru/noties/markwon/image/data/DataUriParserTest.java
@@ -0,0 +1,119 @@
+package ru.noties.markwon.image.data;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class DataUriParserTest {
+
+ private DataUriParser.Impl impl;
+
+ @Before
+ public void before() {
+ impl = new DataUriParser.Impl();
+ }
+
+ @Test
+ public void test() {
+
+ final Map data = new LinkedHashMap() {{
+ put(",", new DataUri(null, false, null));
+ put("image/svg+xml;base64,!@#$%^&*(", new DataUri("image/svg+xml", true, "!@#$%^&*("));
+ put("text/vnd-example+xyz;foo=bar;base64,R0lGODdh", new DataUri("text/vnd-example+xyz", true, "R0lGODdh"));
+ put("text/plain;charset=UTF-8;page=21,the%20data:1234,5678", new DataUri("text/plain", false, "the%20data:1234,5678"));
+ }};
+
+ for (Map.Entry entry : data.entrySet()) {
+ assertEquals(entry.getKey(), entry.getValue(), impl.parse(entry.getKey()));
+ }
+ }
+
+ @Test
+ public void data_new_lines_are_ignored() {
+
+ final String input = "image/png;base64,iVBORw0KGgoAAA\n" +
+ "ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4\n" +
+ "//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU\n" +
+ "5ErkJggg==";
+
+ assertEquals(
+ new DataUri("image/png", true, "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="),
+ impl.parse(input)
+ );
+ }
+
+ @Test
+ public void no_comma_returns_null() {
+
+ final String[] inputs = {
+ "",
+ "what-ever",
+ ";;;;;;;",
+ "some crazy data"
+ };
+
+ for (String input : inputs) {
+ assertNull(input, impl.parse(input));
+ }
+ }
+
+ @Test
+ public void two_commas() {
+ final String input = ",,"; // <- second one would be considered data...
+ assertEquals(
+ input,
+ new DataUri(null, false, ","),
+ impl.parse(input)
+ );
+ }
+
+ @Test
+ public void more_commas() {
+ final String input = "first,second,third"; // <- first is just a value (will be ignored)
+ assertEquals(
+ input,
+ new DataUri(null, false, "second,third"),
+ impl.parse(input)
+ );
+ }
+
+ @Test
+ public void base64_no_content_type() {
+ final String input = ";base64,12345";
+ assertEquals(
+ input,
+ new DataUri(null, true, "12345"),
+ impl.parse(input)
+ );
+ }
+
+ @Test
+ public void not_base64_no_content_type() {
+ final String input = ",qweRTY";
+ assertEquals(
+ input,
+ new DataUri(null, false, "qweRTY"),
+ impl.parse(input)
+ );
+ }
+
+ @Test
+ public void content_type_data_no_base64() {
+ final String input = "image/png,aSdFg";
+ assertEquals(
+ input,
+ new DataUri("image/png", false, "aSdFg"),
+ impl.parse(input)
+ );
+ }
+}
\ No newline at end of file
diff --git a/markwon/src/test/java/ru/noties/markwon/image/data/DataUriSchemeHandlerTest.java b/markwon/src/test/java/ru/noties/markwon/image/data/DataUriSchemeHandlerTest.java
new file mode 100644
index 00000000..16dc73b5
--- /dev/null
+++ b/markwon/src/test/java/ru/noties/markwon/image/data/DataUriSchemeHandlerTest.java
@@ -0,0 +1,114 @@
+package ru.noties.markwon.image.data;
+
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+import ru.noties.markwon.image.ImageItem;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class DataUriSchemeHandlerTest {
+
+ private DataUriSchemeHandler handler;
+
+ @Before
+ public void before() {
+ handler = DataUriSchemeHandler.create();
+ }
+
+ @Test
+ public void scheme_specific_part_is_empty() {
+ assertNull(handler.handle("data:", Uri.parse("data:")));
+ }
+
+ @Test
+ public void data_uri_is_empty() {
+ assertNull(handler.handle("data://whatever", Uri.parse("data://whatever")));
+ }
+
+ @Test
+ public void no_data() {
+ assertNull(handler.handle("data://,", Uri.parse("data://,")));
+ }
+
+ @Test
+ public void correct() {
+
+ final class Item {
+
+ final String contentType;
+ final String data;
+
+ Item(String contentType, String data) {
+ this.contentType = contentType;
+ this.data = data;
+ }
+ }
+
+ final Map expected = new HashMap() {{
+ put("data://text/plain;,123", new Item("text/plain", "123"));
+ put("data://image/svg+xml;base64,MTIz", new Item("image/svg+xml", "123"));
+ }};
+
+ for (Map.Entry entry : expected.entrySet()) {
+ final ImageItem item = handler.handle(entry.getKey(), Uri.parse(entry.getKey()));
+ assertNotNull(entry.getKey(), item);
+ assertEquals(entry.getKey(), entry.getValue().contentType, item.contentType());
+ assertEquals(entry.getKey(), entry.getValue().data, readStream(item.inputStream()));
+ }
+ }
+
+ @Test
+ public void correct_real() {
+
+ final class Item {
+
+ final String contentType;
+ final String data;
+
+ Item(String contentType, String data) {
+ this.contentType = contentType;
+ this.data = data;
+ }
+ }
+
+ final Map expected = new HashMap() {{
+ put("data:text/plain;,123", new Item("text/plain", "123"));
+ put("data:image/svg+xml;base64,MTIz", new Item("image/svg+xml", "123"));
+ }};
+
+ for (Map.Entry entry : expected.entrySet()) {
+ final ImageItem item = handler.handle(entry.getKey(), Uri.parse(entry.getKey()));
+ assertNotNull(entry.getKey(), item);
+ assertEquals(entry.getKey(), entry.getValue().contentType, item.contentType());
+ assertEquals(entry.getKey(), entry.getValue().data, readStream(item.inputStream()));
+ }
+ }
+
+ @NonNull
+ private static String readStream(@NonNull InputStream stream) {
+ try {
+ final Scanner scanner = new Scanner(stream, "UTF-8").useDelimiter("\\A");
+ return scanner.hasNext()
+ ? scanner.next()
+ : "";
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+}
\ No newline at end of file
diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/MarkwonConfigurationTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/MarkwonConfigurationTest.java
index e094164b..108df62d 100644
--- a/markwon/src/test/java/ru/noties/markwon/renderer/MarkwonConfigurationTest.java
+++ b/markwon/src/test/java/ru/noties/markwon/renderer/MarkwonConfigurationTest.java
@@ -8,7 +8,7 @@ import ru.noties.markwon.SyntaxHighlight;
import ru.noties.markwon.UrlProcessor;
import ru.noties.markwon.html.api.MarkwonHtmlParser;
import ru.noties.markwon.renderer.html2.MarkwonHtmlRenderer;
-import ru.noties.markwon.spans.AsyncDrawable;
+import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;
diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java
index bfba93b1..7f04566b 100644
--- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java
+++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java
@@ -30,7 +30,7 @@ import java.util.Set;
import ix.Ix;
import ix.IxFunction;
import ix.IxPredicate;
-import ru.noties.markwon.spans.TableRowSpan;
+import ru.noties.markwon.table.TableRowSpan;
import static ru.noties.markwon.renderer.visitor.TestSpan.BLOCK_QUOTE;
import static ru.noties.markwon.renderer.visitor.TestSpan.BULLET_LIST;
diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java
index 42d46f99..6e6feb30 100644
--- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java
+++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java
@@ -11,10 +11,10 @@ import java.util.Map;
import ru.noties.markwon.SpannableFactory;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
-import ru.noties.markwon.spans.AsyncDrawable;
+import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;
-import ru.noties.markwon.spans.TableRowSpan;
+import ru.noties.markwon.table.TableRowSpan;
import static ru.noties.markwon.renderer.visitor.TestSpan.BLOCK_QUOTE;
import static ru.noties.markwon.renderer.visitor.TestSpan.BULLET_LIST;
diff --git a/sample-latex-math/build.gradle b/sample-latex-math/build.gradle
index 81732d95..f9f7c1eb 100644
--- a/sample-latex-math/build.gradle
+++ b/sample-latex-math/build.gradle
@@ -18,6 +18,6 @@ android {
dependencies {
implementation project(':markwon')
- implementation project(':markwon-image-loader')
+// implementation project(':markwon-image-loader')
implementation 'ru.noties:jlatexmath-android:0.1.0'
}
diff --git a/settings.gradle b/settings.gradle
index 11192dbe..3a1678e4 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', ':sample-latex-math',
+include ':app', ':markwon', ':markwon-view', ':sample-custom-extension', ':sample-latex-math', ':markwon-image-svg', ':markwon-image-gif',
':markwon-syntax-highlight', ':markwon-html-parser-api', ':markwon-html-parser-impl'
| |