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