diff --git a/sample-custom-extension/build.gradle b/sample-custom-extension/build.gradle
new file mode 100644
index 00000000..91749f0a
--- /dev/null
+++ b/sample-custom-extension/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+
+ compileSdkVersion TARGET_SDK
+ buildToolsVersion BUILD_TOOLS
+
+ defaultConfig {
+ applicationId "noties.ru.markwon_samplecustomextension"
+ minSdkVersion MIN_SDK
+ targetSdkVersion TARGET_SDK
+ versionCode 1
+ versionName version
+ }
+}
+
+dependencies {
+ compile project(':library')
+}
diff --git a/sample-custom-extension/src/main/AndroidManifest.xml b/sample-custom-extension/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..dadcdf93
--- /dev/null
+++ b/sample-custom-extension/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconGroupNode.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconGroupNode.java
new file mode 100644
index 00000000..9438569c
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconGroupNode.java
@@ -0,0 +1,7 @@
+package noties.ru.markwon_samplecustomextension;
+
+import org.commonmark.node.CustomNode;
+
+public class IconGroupNode extends CustomNode {
+
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconNode.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconNode.java
new file mode 100644
index 00000000..f7190883
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconNode.java
@@ -0,0 +1,61 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.support.annotation.NonNull;
+
+import org.commonmark.node.CustomNode;
+import org.commonmark.node.Delimited;
+
+public class IconNode extends CustomNode implements Delimited {
+
+
+ public static final char DELIMITER = '@';
+
+ public static final String DELIMITER_STRING = "" + DELIMITER;
+
+
+ private final String name;
+
+ private final String color;
+
+ private final String size;
+
+ public IconNode(@NonNull String name, @NonNull String color, @NonNull String size) {
+ this.name = name;
+ this.color = color;
+ this.size = size;
+ }
+
+ @NonNull
+ public String name() {
+ return name;
+ }
+
+ @NonNull
+ public String color() {
+ return color;
+ }
+
+ @NonNull
+ public String size() {
+ return size;
+ }
+
+ @Override
+ public String getOpeningDelimiter() {
+ return DELIMITER_STRING;
+ }
+
+ @Override
+ public String getClosingDelimiter() {
+ return DELIMITER_STRING;
+ }
+
+ @Override
+ public String toString() {
+ return "IconNode{" +
+ "name='" + name + '\'' +
+ ", color='" + color + '\'' +
+ ", size='" + size + '\'' +
+ '}';
+ }
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconProcessor.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconProcessor.java
new file mode 100644
index 00000000..7ad68527
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconProcessor.java
@@ -0,0 +1,86 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.text.TextUtils;
+
+import org.commonmark.node.Node;
+import org.commonmark.node.Text;
+import org.commonmark.parser.delimiter.DelimiterProcessor;
+import org.commonmark.parser.delimiter.DelimiterRun;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class IconProcessor implements DelimiterProcessor {
+
+ private static final Pattern PATTERN = Pattern.compile("material-icon-(\\w+)-(\\w+)-(\\w+)");
+
+ @Override
+ public char getOpeningCharacter() {
+ return IconNode.DELIMITER;
+ }
+
+ @Override
+ public char getClosingCharacter() {
+ return IconNode.DELIMITER;
+ }
+
+ @Override
+ public int getMinLength() {
+ return 1;
+ }
+
+ @Override
+ public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
+ return opener.length() >= 1 && closer.length() >= 1 ? 1 : 0;
+ }
+
+ @Override
+ public void process(Text opener, Text closer, int delimiterUse) {
+
+ final IconGroupNode iconGroupNode = new IconGroupNode();
+
+ final Node next = opener.getNext();
+
+ boolean handled = false;
+
+ // process only if we have exactly one Text node
+ if (next instanceof Text && next.getNext() == closer) {
+
+ final String text = ((Text) next).getLiteral();
+
+ if (!TextUtils.isEmpty(text)) {
+
+ // attempt to match
+ final Matcher matcher = PATTERN.matcher(text);
+ if (matcher.matches()) {
+ final IconNode iconNode = new IconNode(
+ matcher.group(1),
+ matcher.group(2),
+ matcher.group(3)
+ );
+ iconGroupNode.appendChild(iconNode);
+ next.unlink();
+ handled = true;
+ }
+ }
+ }
+
+ if (!handled) {
+
+ // restore delimiters if we didn't match
+
+ iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
+
+ Node node;
+ for (Node tmp = opener.getNext(); tmp != null && tmp != closer; tmp = node) {
+ node = tmp.getNext();
+ // append a child anyway
+ iconGroupNode.appendChild(tmp);
+ }
+
+ iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
+ }
+
+ opener.insertBefore(iconGroupNode);
+ }
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpan.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpan.java
new file mode 100644
index 00000000..bc197492
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpan.java
@@ -0,0 +1,76 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.style.ReplacementSpan;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class IconSpan extends ReplacementSpan {
+
+ @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Alignment {
+ }
+
+ public static final int ALIGN_BOTTOM = 0;
+ public static final int ALIGN_BASELINE = 1;
+ public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height
+
+
+ private final Drawable drawable;
+
+ private final int alignment;
+
+ public IconSpan(@NonNull Drawable drawable, @Alignment int alignment) {
+ this.drawable = drawable;
+ this.alignment = alignment;
+ if (drawable.getBounds().isEmpty()) {
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ }
+ }
+
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
+
+ final Rect rect = drawable.getBounds();
+
+ if (fm != null) {
+ fm.ascent = -rect.bottom;
+ fm.descent = 0;
+
+ fm.top = fm.ascent;
+ fm.bottom = 0;
+ }
+
+ return rect.right;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
+
+ final int b = bottom - drawable.getBounds().bottom;
+
+ final int save = canvas.save();
+ try {
+ final int translationY;
+ if (ALIGN_CENTER == alignment) {
+ translationY = b - ((bottom - top - drawable.getBounds().height()) / 2);
+ } else if (ALIGN_BASELINE == alignment) {
+ translationY = b - paint.getFontMetricsInt().descent;
+ } else {
+ translationY = b;
+ }
+ canvas.translate(x, translationY);
+ drawable.draw(canvas);
+ } finally {
+ canvas.restoreToCount(save);
+ }
+ }
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpanProvider.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpanProvider.java
new file mode 100644
index 00000000..9f8d517e
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpanProvider.java
@@ -0,0 +1,9 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.support.annotation.NonNull;
+
+public interface IconSpanProvider {
+
+ @NonNull
+ IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size);
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpanProviderImpl.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpanProviderImpl.java
new file mode 100644
index 00000000..8f674a78
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconSpanProviderImpl.java
@@ -0,0 +1,51 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+
+public class IconSpanProviderImpl implements IconSpanProvider {
+
+ private static final boolean IS_L = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+
+ private final Context context;
+ private final Resources resources;
+ private final int fallBack;
+
+ public IconSpanProviderImpl(@NonNull Context context, @DrawableRes int fallBack) {
+ this.context = context;
+ this.resources = context.getResources();
+ this.fallBack = fallBack;
+ }
+
+ @NonNull
+ @Override
+ public IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size) {
+ final String resName = materialIconName(name, color, size);
+ int resId = resources.getIdentifier(resName, "drawable", context.getPackageName());
+ if (resId == 0) {
+ resId = fallBack;
+ }
+ return new IconSpan(getDrawable(resId), IconSpan.ALIGN_CENTER);
+ }
+
+ @NonNull
+ private static String materialIconName(@NonNull String name, @NonNull String color, @NonNull String size) {
+ return "ic_" + name + "_" + color + "_" + size + "dp";
+ }
+
+ @NonNull
+ private Drawable getDrawable(int resId) {
+ final Drawable drawable;
+ if (IS_L) {
+ drawable = context.getDrawable(resId);
+ } else {
+ drawable = resources.getDrawable(resId);
+ }
+ //noinspection ConstantConditions
+ return drawable;
+ }
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconUtils.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconUtils.java
new file mode 100644
index 00000000..03cef1c1
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconUtils.java
@@ -0,0 +1,46 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.support.annotation.NonNull;
+
+public abstract class IconUtils {
+
+ private static final String TO_FIND = "@material-icon-";
+
+ public static void prepare(@NonNull StringBuilder builder) {
+
+ int start = builder.indexOf(TO_FIND);
+ int end;
+
+ while (start > -1) {
+ end = iconDefinitionEnd(start + TO_FIND.length(), builder);
+ builder.insert(end, '@');
+ start = builder.indexOf(TO_FIND, end);
+ }
+ }
+
+ private static int iconDefinitionEnd(int index, @NonNull StringBuilder builder) {
+
+ // all spaces, new lines, non-words or digits,
+
+ char c;
+
+ int end = -1;
+ for (int i = index; i < builder.length(); i++) {
+ c = builder.charAt(i);
+ if (Character.isWhitespace(c)
+ || !(Character.isLetterOrDigit(c) || c == '-' || c == '_')) {
+ end = i;
+ break;
+ }
+ }
+
+ if (end == -1) {
+ end = builder.length();
+ }
+
+ return end;
+ }
+
+ private IconUtils() {
+ }
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconVisitor.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconVisitor.java
new file mode 100644
index 00000000..ca047451
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/IconVisitor.java
@@ -0,0 +1,56 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+import org.commonmark.node.CustomNode;
+
+import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.SpannableConfiguration;
+import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
+
+public class IconVisitor extends SpannableMarkdownVisitor {
+
+ private final SpannableBuilder builder;
+
+ private final IconSpanProvider iconSpanProvider;
+
+ public IconVisitor(
+ @NonNull SpannableConfiguration configuration,
+ @NonNull SpannableBuilder builder,
+ @NonNull IconSpanProvider iconSpanProvider
+ ) {
+ super(configuration, builder);
+ this.builder = builder;
+ this.iconSpanProvider = iconSpanProvider;
+ }
+
+ @Override
+ public void visit(CustomNode customNode) {
+ if (!visitIconNode(customNode)) {
+ super.visit(customNode);
+ }
+ }
+
+ private boolean visitIconNode(@NonNull CustomNode customNode) {
+
+ if (customNode instanceof IconNode) {
+
+ final IconNode node = (IconNode) customNode;
+
+ final String name = node.name();
+ final String size = node.size();
+
+ if (!TextUtils.isEmpty(name)
+ && !TextUtils.isEmpty(size)) {
+ final int length = builder.length();
+ builder.append(name);
+ builder.setSpan(iconSpanProvider.provide(node.name(), node.color(), node.size()), length);
+ builder.append(' ');
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/MainActivity.java b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/MainActivity.java
new file mode 100644
index 00000000..07bca60e
--- /dev/null
+++ b/sample-custom-extension/src/main/java/noties/ru/markwon_samplecustomextension/MainActivity.java
@@ -0,0 +1,44 @@
+package noties.ru.markwon_samplecustomextension;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
+import org.commonmark.ext.gfm.tables.TablesExtension;
+import org.commonmark.node.Node;
+import org.commonmark.parser.Parser;
+
+import java.util.Arrays;
+
+import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.SpannableConfiguration;
+import ru.noties.markwon.tasklist.TaskListExtension;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(0);
+
+ final TextView textView = findViewById(0);
+
+ final Parser parser = new Parser.Builder()
+ // we will register all known to Markwon extensions
+ .extensions(Arrays.asList(
+ StrikethroughExtension.create(),
+ TablesExtension.create(),
+ TaskListExtension.create()
+ ))
+ // this is the handler for custom icons
+ .customDelimiterProcessor(new IconProcessor())
+ .build();
+
+ final Node node = parser.parse("# Hello icons! @material-icon-home-black-24@\n\n Your account @material-icon-account_balance-white-26@ is 0.00003");
+ final SpannableBuilder builder = new SpannableBuilder();
+ final IconVisitor visitor = new IconVisitor(SpannableConfiguration.create(this), builder, new IconSpanProviderImpl(this, R.drawable.ic_home_black_24dp));
+ node.accept(visitor);
+ textView.setText(builder.text());
+ }
+}
diff --git a/sample-custom-extension/src/main/res/drawable-hdpi/ic_3d_rotation_white_24dp.png b/sample-custom-extension/src/main/res/drawable-hdpi/ic_3d_rotation_white_24dp.png
new file mode 100644
index 00000000..a243e01c
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-hdpi/ic_3d_rotation_white_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-hdpi/ic_account_balance_white_26dp.png b/sample-custom-extension/src/main/res/drawable-hdpi/ic_account_balance_white_26dp.png
new file mode 100644
index 00000000..dc5dfb78
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-hdpi/ic_account_balance_white_26dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-hdpi/ic_home_black_24dp.png b/sample-custom-extension/src/main/res/drawable-hdpi/ic_home_black_24dp.png
new file mode 100644
index 00000000..e5a7bfc6
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-hdpi/ic_home_black_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-mdpi/ic_3d_rotation_white_24dp.png b/sample-custom-extension/src/main/res/drawable-mdpi/ic_3d_rotation_white_24dp.png
new file mode 100644
index 00000000..9ebba4c0
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-mdpi/ic_3d_rotation_white_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-mdpi/ic_account_balance_white_26dp.png b/sample-custom-extension/src/main/res/drawable-mdpi/ic_account_balance_white_26dp.png
new file mode 100644
index 00000000..e685049a
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-mdpi/ic_account_balance_white_26dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-mdpi/ic_home_black_24dp.png b/sample-custom-extension/src/main/res/drawable-mdpi/ic_home_black_24dp.png
new file mode 100644
index 00000000..b77359f4
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-mdpi/ic_home_black_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample-custom-extension/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..c7bd21db
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-custom-extension/src/main/res/drawable-xhdpi/ic_3d_rotation_white_24dp.png b/sample-custom-extension/src/main/res/drawable-xhdpi/ic_3d_rotation_white_24dp.png
new file mode 100644
index 00000000..aa366a14
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xhdpi/ic_3d_rotation_white_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xhdpi/ic_account_balance_white_26dp.png b/sample-custom-extension/src/main/res/drawable-xhdpi/ic_account_balance_white_26dp.png
new file mode 100644
index 00000000..eb565ec0
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xhdpi/ic_account_balance_white_26dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xhdpi/ic_home_black_24dp.png b/sample-custom-extension/src/main/res/drawable-xhdpi/ic_home_black_24dp.png
new file mode 100644
index 00000000..587efd5a
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xhdpi/ic_home_black_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_3d_rotation_white_24dp.png b/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_3d_rotation_white_24dp.png
new file mode 100644
index 00000000..07cf86a4
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_3d_rotation_white_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_account_balance_white_26dp.png b/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_account_balance_white_26dp.png
new file mode 100644
index 00000000..a3931f6d
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_account_balance_white_26dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_home_black_24dp.png b/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_home_black_24dp.png
new file mode 100644
index 00000000..b38dc4f3
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xxhdpi/ic_home_black_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_3d_rotation_white_24dp.png b/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_3d_rotation_white_24dp.png
new file mode 100644
index 00000000..aea49c26
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_3d_rotation_white_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_account_balance_white_26dp.png b/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_account_balance_white_26dp.png
new file mode 100644
index 00000000..31a06f71
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_account_balance_white_26dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_home_black_24dp.png b/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_home_black_24dp.png
new file mode 100644
index 00000000..2f5dc1ce
Binary files /dev/null and b/sample-custom-extension/src/main/res/drawable-xxxhdpi/ic_home_black_24dp.png differ
diff --git a/sample-custom-extension/src/main/res/drawable/ic_launcher_background.xml b/sample-custom-extension/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..d5fccc53
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..a2f59082
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..1b523998
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..ff10afd6
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..115a4c76
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..dcd3cd80
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..459ca609
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..8ca12fe0
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..8e19b410
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b824ebdd
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..4c19a13c
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/values/colors.xml b/sample-custom-extension/src/main/res/values/colors.xml
new file mode 100644
index 00000000..3ab3e9cb
--- /dev/null
+++ b/sample-custom-extension/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/sample-custom-extension/src/main/res/values/strings.xml b/sample-custom-extension/src/main/res/values/strings.xml
new file mode 100644
index 00000000..229056d3
--- /dev/null
+++ b/sample-custom-extension/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Markwon-SampleCustomExtension
+
diff --git a/sample-custom-extension/src/main/res/values/styles.xml b/sample-custom-extension/src/main/res/values/styles.xml
new file mode 100644
index 00000000..9785e0c9
--- /dev/null
+++ b/sample-custom-extension/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index 59f2e834..fbd8c3c4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app', ':library', ':library-image-loader', ':library-view'
+include ':app', ':library', ':library-image-loader', ':library-view', ':sample-custom-extension'