diff --git a/markwon-ext-inline-latex/.gitignore b/markwon-ext-inline-latex/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/markwon-ext-inline-latex/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/markwon-ext-inline-latex/build.gradle b/markwon-ext-inline-latex/build.gradle
new file mode 100644
index 00000000..65fe33ca
--- /dev/null
+++ b/markwon-ext-inline-latex/build.gradle
@@ -0,0 +1,50 @@
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ compileSdkVersion 31
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 31
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+
+ api project(':markwon-core')
+ implementation project(':markwon-ext-latex')
+ implementation project(':markwon-span-ext')
+
+ deps.with {
+ // add a compileOnly dependency, so if this artifact is present
+ // we will try to obtain a SpanFactory for a Strikethrough node and use
+ // it to be consistent with markdown (please note that we do not use markwon plugin
+ // for that in case if different implementation is used)
+ compileOnly it['commonmark-strikethrough']
+
+ testImplementation it['ix-java']
+ }
+
+ deps.test.with {
+ testImplementation it['junit']
+ testImplementation it['robolectric']
+ }
+}
\ No newline at end of file
diff --git a/markwon-ext-inline-latex/src/androidTest/java/io/noties/markwon/ext/inlinelatex/ExampleInstrumentedTest.java b/markwon-ext-inline-latex/src/androidTest/java/io/noties/markwon/ext/inlinelatex/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..ea9af7ec
--- /dev/null
+++ b/markwon-ext-inline-latex/src/androidTest/java/io/noties/markwon/ext/inlinelatex/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package io.noties.markwon.ext.inlinelatex;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("io.noties.markwon.ext.inlinelatex.test", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/markwon-ext-inline-latex/src/main/AndroidManifest.xml b/markwon-ext-inline-latex/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..d7bb0e9e
--- /dev/null
+++ b/markwon-ext-inline-latex/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexBlockParser.java b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexBlockParser.java
new file mode 100644
index 00000000..ebe0fd4e
--- /dev/null
+++ b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexBlockParser.java
@@ -0,0 +1,117 @@
+package io.noties.markwon.ext.inlinelatex;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.internal.util.Parsing;
+import org.commonmark.node.Block;
+import org.commonmark.parser.block.AbstractBlockParser;
+import org.commonmark.parser.block.AbstractBlockParserFactory;
+import org.commonmark.parser.block.BlockContinue;
+import org.commonmark.parser.block.BlockStart;
+import org.commonmark.parser.block.MatchedBlockParser;
+import org.commonmark.parser.block.ParserState;
+
+import io.noties.markwon.ext.latex.JLatexMathBlock;
+
+public class InLineLatexBlockParser extends AbstractBlockParser {
+
+ private static final char DOLLAR = '$';
+ private static final char SPACE = ' ';
+
+ private final JLatexMathBlock block = new JLatexMathBlock();
+
+ private final StringBuilder builder = new StringBuilder();
+
+ private final int signs;
+
+ InLineLatexBlockParser(int signs) {
+ this.signs = signs;
+ }
+
+ @Override
+ public Block getBlock() {
+ return block;
+ }
+
+ @Override
+ public BlockContinue tryContinue(ParserState parserState) {
+ final int nextNonSpaceIndex = parserState.getNextNonSpaceIndex();
+ final CharSequence line = parserState.getLine();
+ final int length = line.length();
+
+ // check for closing
+ if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) {
+ if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
+ // okay, we have our number of signs
+ // let's consume spaces until the end
+ if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) {
+ return BlockContinue.finished();
+ }
+ }
+ }
+
+ return BlockContinue.atIndex(parserState.getIndex());
+ }
+
+ @Override
+ public void addLine(CharSequence line) {
+ builder.append(line);
+ builder.append('\n');
+ }
+
+ @Override
+ public void closeBlock() {
+ block.latex(builder.toString());
+ }
+
+ public static class Factory extends AbstractBlockParserFactory {
+
+ @Override
+ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
+
+ // let's define the spec:
+ // * 0-3 spaces before are allowed (Parsing.CODE_BLOCK_INDENT = 4)
+ // * 2+ subsequent `$` signs
+ // * any optional amount of spaces
+ // * new line
+ // * block is closed when the same amount of opening signs is met
+
+ final int indent = state.getIndent();
+
+ // check if it's an indented code block
+ if (indent >= Parsing.CODE_BLOCK_INDENT) {
+ return BlockStart.none();
+ }
+
+ final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
+ final CharSequence line = state.getLine();
+ final int length = line.length();
+
+ final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length);
+
+ // 2 is minimum
+ if (signs < 2) {
+ return BlockStart.none();
+ }
+
+ // consume spaces until the end of the line, if any other content is found -> NONE
+ if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
+ return BlockStart.none();
+ }
+
+ return BlockStart.of(new InLineLatexBlockParser(signs))
+ .atIndex(length + 1);
+ }
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private static int consume(char c, @NonNull CharSequence line, int start, int end) {
+ for (int i = start; i < end; i++) {
+ if (c != line.charAt(i)) {
+ return i - start;
+ }
+ }
+ // all consumed
+ return end - start;
+ }
+}
\ No newline at end of file
diff --git a/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexGroupNode.java b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexGroupNode.java
new file mode 100644
index 00000000..4205a30f
--- /dev/null
+++ b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexGroupNode.java
@@ -0,0 +1,6 @@
+package io.noties.markwon.ext.inlinelatex;
+
+import org.commonmark.node.CustomNode;
+
+public class InLineLatexGroupNode extends CustomNode {
+}
diff --git a/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexNode.java b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexNode.java
new file mode 100644
index 00000000..7ad25e9a
--- /dev/null
+++ b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexNode.java
@@ -0,0 +1,20 @@
+package io.noties.markwon.ext.inlinelatex;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.node.CustomNode;
+import org.commonmark.node.Delimited;
+
+@SuppressWarnings("WeakerAccess")
+public class InLineLatexNode extends CustomNode {
+
+ private String latex;
+
+ public String latex() {
+ return latex;
+ }
+
+ public void latex(String latex) {
+ this.latex = latex;
+ }
+}
diff --git a/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexPlugIn.java b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexPlugIn.java
new file mode 100644
index 00000000..9dd6f47a
--- /dev/null
+++ b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexPlugIn.java
@@ -0,0 +1,126 @@
+package io.noties.markwon.ext.inlinelatex;
+
+import android.graphics.Color;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import androidx.annotation.NonNull;
+import org.commonmark.parser.Parser;
+
+import java.util.ArrayList;
+import java.util.List;
+import io.noties.markwon.AbstractMarkwonPlugin;
+import io.noties.markwon.MarkwonVisitor;
+
+import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
+import io.noties.markwon.span.ext.CenteredImageSpan;
+import ru.noties.jlatexmath.JLatexMathDrawable;
+
+public class InLineLatexPlugIn extends AbstractMarkwonPlugin {
+ private static float mLatextSize;
+ private static int mScreenWidth;
+ @NonNull
+ public static InLineLatexPlugIn create(float latexSize, int screenWidth)
+ {
+ mLatextSize = latexSize;
+ mScreenWidth = screenWidth;
+ return new InLineLatexPlugIn();
+ }
+
+ @Override
+ public void configure(@NonNull Registry registry) {
+ registry.require(MarkwonInlineParserPlugin.class).factoryBuilder().addInlineProcessor(new InLineLatexProcessor());
+ }
+
+ @Override
+ public void configureParser(@NonNull Parser.Builder builder) {
+ builder.customBlockParserFactory(new InLineLatexBlockParser.Factory());
+ }
+
+ @Override
+ public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
+ builder.on(InLineLatexNode.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull InLineLatexNode inLineLinkNode) {
+ final String latex = inLineLinkNode.latex();
+ int latxtColor = Color.BLACK;
+ int backgroundColor = Color.TRANSPARENT;
+ int errorColor = Color.parseColor("#ff3d00");
+ if (!TextUtils.isEmpty(latex)) {
+ if (latex.trim().equalsIgnoreCase("{")) {
+ String errTxt = "LaTeX syntax error";
+ ForegroundColorSpan fgColorSpan = new ForegroundColorSpan(errorColor);
+ visitor.builder().append(errTxt, fgColorSpan);
+ } else {
+ try {
+ int txtLength = latex.length();
+ float textWidth = mScreenWidth;
+ final JLatexMathDrawable latexDrawable = JLatexMathDrawable.builder(replaceLatexTag(latex))
+ .textSize(mLatextSize)
+ .color(latxtColor)
+ .background(backgroundColor)
+ .fitCanvas(false) // It will fix the truncated issue of inline latex
+ .build();
+ float latexWidth = latexDrawable.getIntrinsicWidth();
+ // Inline latex wrap
+ if (latexWidth > textWidth) {
+ float oneLetterWidth = latexWidth / txtLength;
+ if (oneLetterWidth < 22) {
+ oneLetterWidth = 22;
+ }
+ int allowLen = Math.round(textWidth / oneLetterWidth) - 2;
+ List spiltedText = splitStringByLen(latex, allowLen);
+ for (int txtIndex = 0; txtIndex < spiltedText.size(); txtIndex ++) {
+ String subText = spiltedText.get(txtIndex);
+ int subTextLen = subText.length();
+ final JLatexMathDrawable subLatexDrawable = JLatexMathDrawable.builder(replaceLatexTag(subText))
+ .textSize(mLatextSize)
+ .color(latxtColor)
+ .background(backgroundColor)
+ .fitCanvas(true) // It will fix the truncated issue of inline latex
+ .build();
+ visitor.builder().append(subText, new CenteredImageSpan(subLatexDrawable));
+ }
+ } else {
+ visitor.builder().append(latex, new CenteredImageSpan(latexDrawable));
+ }
+ visitor.builder().append(' ');
+ } catch (Exception e) {
+ String errTxt = "LaTeX syntax error";
+ ForegroundColorSpan fgColorSpan = new ForegroundColorSpan(errorColor);
+ visitor.builder().append(errTxt, fgColorSpan);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @NonNull
+ @Override
+ public String processMarkdown(@NonNull String markdown) {
+ return InLineLatexProcessor.prepare(markdown);
+ }
+
+ public String replaceLatexTag(String latex) {
+ String latexText = latex.replaceAll("\\\\exist ", "\\\\exists ");
+ return latexText;
+ }
+
+ public List splitStringByLen(String text, int length) {
+ List strings = new ArrayList();
+ int index = 0;
+ while (index < text.length()) {
+ String splitText = text.substring(index, Math.min(index + length,text.length()));
+ int nWhistSpace = splitText.lastIndexOf(" ");
+ if (nWhistSpace > 0 && splitText.length() >= length) {
+ String splitWord = splitText.substring(0, nWhistSpace);
+ strings.add(splitWord);
+ index += nWhistSpace;
+ } else {
+ strings.add(splitText);
+ index += length;
+ }
+ }
+ return strings;
+ }
+}
diff --git a/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexProcessor.java b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexProcessor.java
new file mode 100644
index 00000000..48301929
--- /dev/null
+++ b/markwon-ext-inline-latex/src/main/java/io/noties/markwon/ext/inlinelatex/InLineLatexProcessor.java
@@ -0,0 +1,50 @@
+package io.noties.markwon.ext.inlinelatex;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+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.Pattern;
+
+import io.noties.markwon.inlineparser.InlineProcessor;
+
+public class InLineLatexProcessor extends InlineProcessor {
+
+ @NonNull
+ public static InLineLatexProcessor create() {
+ return new InLineLatexProcessor();
+ }
+
+
+ @NonNull
+ public static String prepare(@NonNull String input) {
+ final StringBuilder builder = new StringBuilder(input);
+ return builder.toString();
+ }
+
+ private static final Pattern RE = Pattern.compile("(\\${2})([\\s\\S]+?)\\1");
+
+ @Override
+ public char specialCharacter() {
+ return '$';
+ }
+
+ @Nullable
+ @Override
+ protected Node parse() {
+
+ final String latex = match(RE);
+ if (latex == null) {
+ return null;
+ }
+
+ final InLineLatexNode node = new InLineLatexNode();
+ node.latex(latex.substring(2, latex.length() - 2));
+ return node;
+ }
+}
diff --git a/markwon-ext-inline-latex/src/test/java/io/noties/markwon/ext/inlinelatex/ExampleUnitTest.java b/markwon-ext-inline-latex/src/test/java/io/noties/markwon/ext/inlinelatex/ExampleUnitTest.java
new file mode 100644
index 00000000..7b74376d
--- /dev/null
+++ b/markwon-ext-inline-latex/src/test/java/io/noties/markwon/ext/inlinelatex/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package io.noties.markwon.ext.inlinelatex;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/markwon-ext-inline/.gitignore b/markwon-ext-inline/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/markwon-ext-inline/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/markwon-ext-inline/build.gradle b/markwon-ext-inline/build.gradle
new file mode 100644
index 00000000..c24bf0a7
--- /dev/null
+++ b/markwon-ext-inline/build.gradle
@@ -0,0 +1,48 @@
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ compileSdkVersion 31
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 31
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+
+ api project(':markwon-core')
+
+ deps.with {
+ // add a compileOnly dependency, so if this artifact is present
+ // we will try to obtain a SpanFactory for a Strikethrough node and use
+ // it to be consistent with markdown (please note that we do not use markwon plugin
+ // for that in case if different implementation is used)
+ compileOnly it['commonmark-strikethrough']
+
+ testImplementation it['ix-java']
+ }
+
+ deps.test.with {
+ testImplementation it['junit']
+ testImplementation it['robolectric']
+ }
+}
\ No newline at end of file
diff --git a/markwon-ext-inline/src/androidTest/java/io/noties/markwon/ext/inline/ExampleInstrumentedTest.java b/markwon-ext-inline/src/androidTest/java/io/noties/markwon/ext/inline/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..13cfbe87
--- /dev/null
+++ b/markwon-ext-inline/src/androidTest/java/io/noties/markwon/ext/inline/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package io.noties.markwon.ext.inline;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("io.noties.markwon.ext.inline.test", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/markwon-ext-inline/src/main/AndroidManifest.xml b/markwon-ext-inline/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..60d809d4
--- /dev/null
+++ b/markwon-ext-inline/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkGroupNode.java b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkGroupNode.java
new file mode 100644
index 00000000..59c46124
--- /dev/null
+++ b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkGroupNode.java
@@ -0,0 +1,6 @@
+package io.noties.markwon.ext.inline;
+
+import org.commonmark.node.CustomNode;
+
+public class InLineLinkGroupNode extends CustomNode {
+}
diff --git a/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkNode.java b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkNode.java
new file mode 100644
index 00000000..3191dfcb
--- /dev/null
+++ b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkNode.java
@@ -0,0 +1,41 @@
+package io.noties.markwon.ext.inline;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.node.CustomNode;
+import org.commonmark.node.Delimited;
+
+@SuppressWarnings("WeakerAccess")
+public class InLineLinkNode extends CustomNode implements Delimited {
+
+ public static final String DELIMITER_STRING = "#";
+
+
+ private final String link;
+
+ public InLineLinkNode(@NonNull String link) {
+ this.link = link;
+ }
+
+ @NonNull
+ public String link() {
+ return link;
+ }
+
+
+ @Override
+ public String getOpeningDelimiter() {
+ return DELIMITER_STRING;
+ }
+
+ @Override
+ public String getClosingDelimiter() {
+ return DELIMITER_STRING;
+ }
+
+ @Override
+ public String toString() {
+ return "InLineLinkNode{" +
+ "link='" + link +'\'' + '\"' + '}';
+ }
+}
diff --git a/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkPlugIn.java b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkPlugIn.java
new file mode 100644
index 00000000..1133126d
--- /dev/null
+++ b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkPlugIn.java
@@ -0,0 +1,51 @@
+package io.noties.markwon.ext.inline;
+
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import org.commonmark.parser.Parser;
+import io.noties.markwon.AbstractMarkwonPlugin;
+import io.noties.markwon.LinkResolver;
+import io.noties.markwon.MarkwonVisitor;
+import io.noties.markwon.core.spans.LinkSpan;
+
+public class InLineLinkPlugIn extends AbstractMarkwonPlugin {
+
+ @NonNull
+ public static InLineLinkPlugIn create() {
+ return new InLineLinkPlugIn();
+ }
+
+ @Override
+ public void configureParser(@NonNull Parser.Builder builder) {
+ builder.customDelimiterProcessor(InLineLinkProcessor.create());
+ }
+
+ @Override
+ public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
+ builder.on(InLineLinkNode.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull InLineLinkNode inLineLinkNode) {
+
+ final String link = inLineLinkNode.link();
+ if (!TextUtils.isEmpty(link)) {
+ final int length = visitor.length();
+ visitor.builder().append(link);
+ visitor.setSpans(length, new LinkSpan(visitor.configuration().theme(), link, new LinkResolver() {
+ @Override
+ public void resolve(@NonNull View view, @NonNull String link) {
+ }
+ }));
+ visitor.builder().append(' ');
+ }
+ }
+ });
+ }
+
+ @NonNull
+ @Override
+ public String processMarkdown(@NonNull String markdown) {
+ return InLineLinkProcessor.prepare(markdown);
+ }
+}
diff --git a/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkProcessor.java b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkProcessor.java
new file mode 100644
index 00000000..890e8c72
--- /dev/null
+++ b/markwon-ext-inline/src/main/java/io/noties/markwon/ext/inline/InLineLinkProcessor.java
@@ -0,0 +1,139 @@
+package io.noties.markwon.ext.inline;
+
+import android.text.TextUtils;
+import android.util.Patterns;
+
+import androidx.annotation.NonNull;
+
+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 InLineLinkProcessor implements DelimiterProcessor {
+
+ private static final String TO_FIND = InLineLinkNode.DELIMITER_STRING;
+ private static final Pattern PATTERN = Pattern.compile("#[0-9]{1,10}");
+
+ @NonNull
+ public static InLineLinkProcessor create() {
+ return new InLineLinkProcessor();
+ }
+
+
+ @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 = inLineDefinitionEnd(start + TO_FIND.length(), builder);
+ if (iconDefinitionValid(builder.subSequence(start, end))) {
+ builder.insert(end, '#');
+ }
+ // move to next
+ start = builder.indexOf(TO_FIND, end);
+ }
+ }
+
+ private static int inLineDefinitionEnd(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.isDigit(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();
+ }
+
+ @Override
+ public char getOpeningCharacter() {
+ return '#';
+ }
+
+ @Override
+ public char getClosingCharacter() {
+ return '#';
+ }
+
+ @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 InLineLinkGroupNode inLineLinkGroupNode = new InLineLinkGroupNode();
+
+ final Node next = opener.getNext();
+
+ boolean handled = false;
+
+ // process only if we have exactly one Text node
+
+ if (next instanceof Text) {
+ final String text = ((Text) next).getLiteral();
+
+ if (!TextUtils.isEmpty(text)) {
+ // attempt to match
+ InLineLinkNode iconNode = new InLineLinkNode(InLineLinkNode.DELIMITER_STRING + text);
+ inLineLinkGroupNode.appendChild(iconNode);
+ next.unlink();
+ handled = true;
+ }
+ }
+
+
+ if (!handled) {
+
+ // restore delimiters if we didn't match
+
+ inLineLinkGroupNode.appendChild(new Text(InLineLinkNode.DELIMITER_STRING));
+
+ Node node;
+ for (Node tmp = opener.getNext(); tmp != null && tmp != closer; tmp = node) {
+ node = tmp.getNext();
+ // append a child anyway
+ inLineLinkGroupNode.appendChild(tmp);
+ }
+
+ inLineLinkGroupNode.appendChild(new Text(InLineLinkNode.DELIMITER_STRING));
+ }
+ opener.insertBefore(inLineLinkGroupNode);
+
+ }
+}
diff --git a/markwon-ext-inline/src/test/java/io/noties/markwon/ext/inline/ExampleUnitTest.java b/markwon-ext-inline/src/test/java/io/noties/markwon/ext/inline/ExampleUnitTest.java
new file mode 100644
index 00000000..1bd5676e
--- /dev/null
+++ b/markwon-ext-inline/src/test/java/io/noties/markwon/ext/inline/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package io.noties.markwon.ext.inline;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file