diff --git a/app/build.gradle b/app/build.gradle
index bc2bca96..286e6bb9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,13 +27,13 @@ android {
 
 dependencies {
 
-    compile project(':library')
-    compile project(':library-image-loader')
+    implementation project(':library')
+    implementation project(':library-image-loader')
 
-    compile 'ru.noties:debug:3.0.0@jar'
+    implementation 'ru.noties:debug:3.0.0@jar'
 
-    compile OK_HTTP
+    implementation OK_HTTP
 
-    compile 'com.google.dagger:dagger:2.10'
+    implementation 'com.google.dagger:dagger:2.10'
     annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
 }
diff --git a/build.gradle b/build.gradle
index 036e147b..ef441eaf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.3'
+        classpath 'com.android.tools.build:gradle:3.0.1'
     }
 }
 
@@ -22,7 +22,7 @@ task clean(type: Delete) {
 }
 
 task wrapper(type: Wrapper) {
-    gradleVersion '4.3'
+    gradleVersion '4.5'
     distributionType 'all'
 }
 
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7a3265ee..27768f1b 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 590f0e81..610ad4c5 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip
diff --git a/library-image-loader/build.gradle b/library-image-loader/build.gradle
index 3a909101..5c8f993b 100644
--- a/library-image-loader/build.gradle
+++ b/library-image-loader/build.gradle
@@ -19,10 +19,10 @@ android {
 }
 
 dependencies {
-    compile project(':library')
-    compile ANDROID_SVG
-    compile ANDROID_GIF
-    compile OK_HTTP
+    api project(':library')
+    api ANDROID_SVG
+    api ANDROID_GIF
+    api OK_HTTP
 }
 
 if (project.hasProperty('release')) {
diff --git a/library-view/build.gradle b/library-view/build.gradle
index c0675588..bb6073b7 100644
--- a/library-view/build.gradle
+++ b/library-view/build.gradle
@@ -14,8 +14,8 @@ android {
 }
 
 dependencies {
-    compile project(':library')
-    provided SUPPORT_APP_COMPAT
+    api project(':library')
+    compileOnly SUPPORT_APP_COMPAT
 }
 
 if (project.hasProperty('release')) {
diff --git a/library/build.gradle b/library/build.gradle
index c384c1b6..702b609b 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -14,10 +14,10 @@ android {
 }
 
 dependencies {
-    compile SUPPORT_ANNOTATIONS
-    compile COMMON_MARK
-    compile COMMON_MARK_STRIKETHROUGHT
-    compile COMMON_MARK_TABLE
+    api SUPPORT_ANNOTATIONS
+    api COMMON_MARK
+    api COMMON_MARK_STRIKETHROUGHT
+    api COMMON_MARK_TABLE
 }
 
 if (project.hasProperty('release')) {
diff --git a/sample-custom-extension/README.md b/sample-custom-extension/README.md
new file mode 100644
index 00000000..194557ab
--- /dev/null
+++ b/sample-custom-extension/README.md
@@ -0,0 +1,26 @@
+# Custom extension
+
+This module provides a simple implementation for icons that are bundled in your application resources using custom `DelimiterProcessor`. It can be used as a reference when dealing with new functionality based on _delimiters_. 
+
+```markdown
+# Hello @ic-android-black-24
+
+**Please** click @ic-home-green-24 (home icon) if you want to go home.
+```
+
+Here we will substitute elements starting with `@ic-` for icons that we have in our resources:
+
+* `@ic-android-black-24` -> `R.drawable.ic_android_black_24dp`
+* `@ic-home-green-24` -> `R.drawable.ic_home_green_24dp`
+
+In order to provide reliable parsing we need to have delimiters _around_ desired content. So, `@ic-home-green-24` would become `@ic-home-green-24@`. This is current limitation of [commonmark-java](https://github.com/atlassian/commonmark-java) library that Markwon uses underneath. There is an ongoing [issue](https://github.com/atlassian/commonmark-java/issues/113) that might change this in future thought.
+
+But as we known the pattern beforehand it's pretty easy to pre-process raw markdown and make it the way we want it. Please refer to `IconProcessor#process` method for the reference.
+
+So, the our steps would be:
+
+* prepare raw markdown (wrap icons with `@` if it's not already)
+* construct a Parser with our registered delimiter processor
+* parse markdown and obtain a `Node`
+* create a node visitor that will additionally visit custom node (`IconNode`)
+* use markdown
diff --git a/sample-custom-extension/build.gradle b/sample-custom-extension/build.gradle
new file mode 100644
index 00000000..baa37484
--- /dev/null
+++ b/sample-custom-extension/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'com.android.application'
+
+android {
+
+    compileSdkVersion TARGET_SDK
+    buildToolsVersion BUILD_TOOLS
+
+    defaultConfig {
+
+        applicationId "ru.noties.markwon.sample.extension"
+
+        // using 21 as minimum only to be able to vector assets
+        minSdkVersion 21
+        targetSdkVersion TARGET_SDK
+        versionCode 1
+        versionName version
+    }
+}
+
+dependencies {
+    implementation 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..1553a0a6
--- /dev/null
+++ b/sample-custom-extension/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="ru.noties.markwon.sample.extension">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconGroupNode.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconGroupNode.java
new file mode 100644
index 00000000..193b33b8
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconGroupNode.java
@@ -0,0 +1,8 @@
+package ru.noties.markwon.sample.extension;
+
+import org.commonmark.node.CustomNode;
+
+@SuppressWarnings("WeakerAccess")
+public class IconGroupNode extends CustomNode {
+
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconNode.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconNode.java
new file mode 100644
index 00000000..3d40ec2f
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconNode.java
@@ -0,0 +1,61 @@
+package ru.noties.markwon.sample.extension;
+
+import android.support.annotation.NonNull;
+
+import org.commonmark.node.CustomNode;
+import org.commonmark.node.Delimited;
+
+@SuppressWarnings("WeakerAccess")
+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/ru/noties/markwon/sample/extension/IconProcessor.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconProcessor.java
new file mode 100644
index 00000000..500726e0
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconProcessor.java
@@ -0,0 +1,160 @@
+package ru.noties.markwon.sample.extension;
+
+import android.support.annotation.NonNull;
+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;
+
+@SuppressWarnings("WeakerAccess")
+public class IconProcessor implements DelimiterProcessor {
+
+    @NonNull
+    public static IconProcessor create() {
+        return new IconProcessor();
+    }
+
+    // ic-home-black-24
+    private static final Pattern PATTERN = Pattern.compile("ic-(\\w+)-(\\w+)-(\\d+)");
+
+    private static final String TO_FIND = IconNode.DELIMITER_STRING + "ic-";
+
+    /**
+     * Should be used when input string does not wrap icon definition with `@` from both ends.
+     * So, `@ic-home-white-24` would become `@ic-home-white-24@`. This way parsing is easier
+     * and more predictable (cannot specify multiple ending delimiters, as we would require them:
+     * space, newline, end of a document, and a lot of more)
+     *
+     * @param input to process
+     * @return processed string
+     * @see #prepare(StringBuilder)
+     */
+    @NonNull
+    public static String prepare(@NonNull String input) {
+        final StringBuilder builder = new StringBuilder(input);
+        prepare(builder);
+        return builder.toString();
+    }
+
+    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);
+
+            // if we match our pattern, append `@` else ignore
+            if (iconDefinitionValid(builder.subSequence(start + 1, end))) {
+                builder.insert(end, '@');
+            }
+
+            // move to next
+            start = builder.indexOf(TO_FIND, end);
+        }
+    }
+
+    @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);
+    }
+
+    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 static boolean iconDefinitionValid(@NonNull CharSequence cs) {
+        final Matcher matcher = PATTERN.matcher(cs);
+        return matcher.matches();
+    }
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpan.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpan.java
new file mode 100644
index 00000000..690ba5b2
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpan.java
@@ -0,0 +1,77 @@
+package ru.noties.markwon.sample.extension;
+
+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;
+
+@SuppressWarnings("WeakerAccess")
+public class IconSpan extends ReplacementSpan {
+
+    @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
+    @Retention(RetentionPolicy.CLASS)
+    @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/ru/noties/markwon/sample/extension/IconSpanProvider.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpanProvider.java
new file mode 100644
index 00000000..cd6e3bf3
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpanProvider.java
@@ -0,0 +1,67 @@
+package ru.noties.markwon.sample.extension;
+
+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;
+
+@SuppressWarnings("WeakerAccess")
+public abstract class IconSpanProvider {
+
+    @SuppressWarnings("SameParameterValue")
+    @NonNull
+    public static IconSpanProvider create(@NonNull Context context, @DrawableRes int fallBack) {
+        return new Impl(context, fallBack);
+    }
+
+
+    @NonNull
+    public abstract IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size);
+
+
+    private static class Impl extends 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;
+
+        Impl(@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 = iconName(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 iconName(@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/ru/noties/markwon/sample/extension/IconVisitor.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java
new file mode 100644
index 00000000..d373ff75
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java
@@ -0,0 +1,63 @@
+package ru.noties.markwon.sample.extension;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import org.commonmark.node.CustomNode;
+
+import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.SpannableConfiguration;
+import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
+
+@SuppressWarnings("WeakerAccess")
+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 color = node.color();
+            final String size = node.size();
+
+            if (!TextUtils.isEmpty(name)
+                    && !TextUtils.isEmpty(color)
+                    && !TextUtils.isEmpty(size)) {
+
+                final int length = builder.length();
+
+                builder.append(name);
+                builder.setSpan(iconSpanProvider.provide(name, color, size), length);
+                builder.append(' ');
+                
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java
new file mode 100644
index 00000000..a81f8eb0
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java
@@ -0,0 +1,66 @@
+package ru.noties.markwon.sample.extension;
+
+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(R.layout.activity_main);
+
+        final TextView textView = findViewById(R.id.text_view);
+
+        // obtain an instance of parser
+        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(IconProcessor.create())
+                .build();
+
+        // we process input to wrap icon definitions with `@` on both ends
+        // if your input already does it, there is not need for `IconProcessor.prepare()` call.
+        final String markdown = IconProcessor.prepare(getString(R.string.input));
+
+        final Node node = parser.parse(markdown);
+
+        final SpannableBuilder builder = new SpannableBuilder();
+
+        // please note that here I am passing `0` as fallback it means that if markdown references
+        // unknown icon, it will try to load fallback one and will fail with ResourceNotFound. It's
+        // better to provide a valid fallback option
+        final IconSpanProvider spanProvider = IconSpanProvider.create(this, 0);
+
+        // create an instance of visitor to process parsed markdown
+        final IconVisitor visitor = new IconVisitor(
+                SpannableConfiguration.create(this),
+                builder,
+                spanProvider
+        );
+
+        // trigger visit
+        node.accept(visitor);
+
+        // apply
+        textView.setText(builder.text());
+    }
+}
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 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1" />
+</vector>
diff --git a/sample-custom-extension/src/main/res/drawable/ic_android_black_24dp.xml b/sample-custom-extension/src/main/res/drawable/ic_android_black_24dp.xml
new file mode 100644
index 00000000..401cbf63
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_android_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L11,19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L16,19h1c0.55,0 1,-0.45 1,-1L18,8L6,8v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zM20.5,8c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zM15.53,2.16l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1.23 12.95,1 12,1c-0.96,0 -1.86,0.23 -2.66,0.63L7.85,0.15c-0.2,-0.2 -0.51,-0.2 -0.71,0 -0.2,0.2 -0.2,0.51 0,0.71l1.31,1.31C6.97,3.26 6,5.01 6,7h12c0,-1.99 -0.97,-3.75 -2.47,-4.84zM10,5L9,5L9,4h1v1zM15,5h-1L14,4h1v1z"/>
+</vector>
diff --git a/sample-custom-extension/src/main/res/drawable/ic_home_black_36dp.xml b/sample-custom-extension/src/main/res/drawable/ic_home_black_36dp.xml
new file mode 100644
index 00000000..c3b7e150
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_home_black_36dp.xml
@@ -0,0 +1,4 @@
+<vector android:height="36dp" android:viewportHeight="24.0"
+    android:viewportWidth="24.0" android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
+</vector>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillColor="#26A69A"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+</vector>
diff --git a/sample-custom-extension/src/main/res/drawable/ic_memory_black_48dp.xml b/sample-custom-extension/src/main/res/drawable/ic_memory_black_48dp.xml
new file mode 100644
index 00000000..88ac2954
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_memory_black_48dp.xml
@@ -0,0 +1,4 @@
+<vector android:height="48dp" android:viewportHeight="24.0"
+    android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M15,9L9,9v6h6L15,9zM13,13h-2v-2h2v2zM21,11L21,9h-2L19,7c0,-1.1 -0.9,-2 -2,-2h-2L15,3h-2v2h-2L11,3L9,3v2L7,5c-1.1,0 -2,0.9 -2,2v2L3,9v2h2v2L3,13v2h2v2c0,1.1 0.9,2 2,2h2v2h2v-2h2v2h2v-2h2c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2zM17,17L7,17L7,7h10v10z"/>
+</vector>
diff --git a/sample-custom-extension/src/main/res/drawable/ic_sentiment_satisfied_red_64dp.xml b/sample-custom-extension/src/main/res/drawable/ic_sentiment_satisfied_red_64dp.xml
new file mode 100644
index 00000000..f9c60705
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_sentiment_satisfied_red_64dp.xml
@@ -0,0 +1,6 @@
+<vector android:height="64dp" android:viewportHeight="24.0"
+    android:viewportWidth="24.0" android:width="64dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FFFF0000" android:pathData="M15.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+    <path android:fillColor="#FFFF0000" android:pathData="M8.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+    <path android:fillColor="#FFFF0000" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12,16c-1.48,0 -2.75,-0.81 -3.45,-2L6.88,14c0.8,2.05 2.79,3.5 5.12,3.5s4.32,-1.45 5.12,-3.5h-1.67c-0.7,1.19 -1.97,2 -3.45,2z"/>
+</vector>
diff --git a/sample-custom-extension/src/main/res/layout/activity_main.xml b/sample-custom-extension/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..439dac71
--- /dev/null
+++ b/sample-custom-extension/src/main/res/layout/activity_main.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dip"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        tools:text="@string/input"/>
+
+</ScrollView>
\ No newline at end of file
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ 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/strings.xml b/sample-custom-extension/src/main/res/values/strings.xml
new file mode 100644
index 00000000..7dc177e7
--- /dev/null
+++ b/sample-custom-extension/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+<resources>
+
+    <string name="app_name">Markwon-SampleCustomExtension</string>
+
+    <string name="input"><![CDATA[
+        # Hello! @ic-android-black-24\n\n
+        Home 36 black: @ic-home-black-36\n\n
+        Memory 48 black: @ic-memory-black-48\n\n
+        Sentiment Satisfied 64 red: @ic-sentiment_satisfied-red-64
+    ]]>
+    </string>
+
+</resources>
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..49c8cb25
--- /dev/null
+++ b/sample-custom-extension/src/main/res/values/styles.xml
@@ -0,0 +1,5 @@
+<resources>
+
+    <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar"/>
+
+</resources>
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'