diff --git a/README.md b/README.md index 594d9781..350479bf 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![Build](https://github.com/noties/Markwon/workflows/Build/badge.svg)](https://github.com/noties/Markwon/actions) +![hey](http://img.xiaoyv.top/bbs/201603246-20aa1b8ad8bf27df3c906473619c2d84.jpg) + **Markwon** is a markdown library for Android. It parses markdown following [commonmark-spec] with the help of amazing [commonmark-java] library and renders result as _Android-native_ Spannables. **No HTML** diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java index 575692bb..cb159aef 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathBlockParser.java @@ -1,5 +1,9 @@ package io.noties.markwon.ext.latex; +import android.util.Log; + +import androidx.annotation.NonNull; + import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.parser.block.AbstractBlockParser; @@ -11,11 +15,21 @@ import org.commonmark.parser.block.ParserState; public class JLatexMathBlockParser 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 boolean isClosed; +// private boolean isClosed; + + private final int signs; + + @SuppressWarnings("WeakerAccess") + JLatexMathBlockParser(int signs) { + this.signs = signs; + } @Override public Block getBlock() { @@ -24,9 +38,22 @@ public class JLatexMathBlockParser extends AbstractBlockParser { @Override public BlockContinue tryContinue(ParserState parserState) { + final int nextNonSpaceIndex = parserState.getNextNonSpaceIndex(); + final CharSequence line = parserState.getLine(); + final int length = line.length(); - if (isClosed) { - return BlockContinue.finished(); + // check for closing + if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) { + Log.e("LTX", String.format("signs: %d, skip dollar: %s", signs, Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length))); +// if (Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) == signs) { + if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) { + // okay, we have our number of signs + // let's consume spaces until the end + Log.e("LTX", String.format("length; %d, skip spaces: %s", length, Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length))); + if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) { + return BlockContinue.finished(); + } + } } return BlockContinue.atIndex(parserState.getIndex()); @@ -34,21 +61,24 @@ public class JLatexMathBlockParser extends AbstractBlockParser { @Override public void addLine(CharSequence line) { - - if (builder.length() > 0) { - builder.append('\n'); - } - +// +// if (builder.length() > 0) { +// builder.append('\n'); +// } +// +// builder.append(line); +// +// final int length = builder.length(); +// if (length > 1) { +// isClosed = '$' == builder.charAt(length - 1) +// && '$' == builder.charAt(length - 2); +// if (isClosed) { +// builder.replace(length - 2, length, ""); +// } +// } + Log.e("LTX", "addLine: " + line); builder.append(line); - - final int length = builder.length(); - if (length > 1) { - isClosed = '$' == builder.charAt(length - 1) - && '$' == builder.charAt(length - 2); - if (isClosed) { - builder.replace(length - 2, length, ""); - } - } + builder.append('\n'); } @Override @@ -58,37 +88,111 @@ public class JLatexMathBlockParser extends AbstractBlockParser { public static class Factory extends AbstractBlockParserFactory { +// private static final Pattern RE = Pattern.compile("(\\${2,}) *$"); + @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) { - final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); - final CharSequence line = state.getLine(); - final int length = line.length(); - // we are looking for 2 `$$` subsequent signs - // and immediate new-line or arbitrary number of white spaces (we check for the first one) - // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s - final int diff = length - (nextNonSpaceIndex + 2); - if (diff >= 0) { - // check for both `$` - if (line.charAt(nextNonSpaceIndex) == '$' - && line.charAt(nextNonSpaceIndex + 1) == '$') { - - if (diff > 0) { - if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) { - return BlockStart.none(); - } - // consume all until new-line or first not-white-space char - } - - } - } + if (indent >= Parsing.CODE_BLOCK_INDENT) { + return BlockStart.none(); } - return BlockStart.none(); + final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); + final CharSequence line = state.getLine(); + final int length = line.length(); + +// final int signs = Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) - 1; + 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(); + } + + Log.e("LTX", String.format("signs: %s, next: %d, length: %d, line: '%s'", signs, nextNonSpaceIndex, length, line)); + + return BlockStart.of(new JLatexMathBlockParser(signs)) + .atIndex(length + 1); + + +// // check if it's an indented code block +// if (indent < Parsing.CODE_BLOCK_INDENT) { +// +// final int nextNonSpaceIndex = state.getNextNonSpaceIndex(); +// final CharSequence line = state.getLine(); +// final int length = line.length(); +// +// final int signs = Parsing.skip('$', 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(' ', line, nextNonSpaceIndex + signs, length) != length) { +// return BlockStart.none(); +// } +// +//// // consume spaces until the end of the line, if any other content is found -> NONE +//// if ((nextNonSpaceIndex + signs) < length) { +//// // check if more content is available +//// if (Parsing.skip(' ', line,nextNonSpaceIndex + signs, length) != length) { +//// return BlockStart.none(); +//// } +//// } +// +//// final Matcher matcher = RE.matcher(line); +//// matcher.region(nextNonSpaceIndex, length); +// +//// Log.e("LATEX", String.format("nonSpace: %d, length: %s, line: '%s'", nextNonSpaceIndex, length, line)); +// +// // we are looking for 2 `$$` subsequent signs +// // and immediate new-line or arbitrary number of white spaces (we check for the first one) +// // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s +// final int diff = length - (nextNonSpaceIndex + 2); +// if (diff >= 0) { +// // check for both `$` +// if (line.charAt(nextNonSpaceIndex) == '$' +// && line.charAt(nextNonSpaceIndex + 1) == '$') { +// +// if (diff > 0) { +// if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) { +// return BlockStart.none(); +// } +// return BlockStart.of(new JLatexMathBlockParser()).atIndex(nextNonSpaceIndex + 3); +// } +// +// } +// } +// } +// +// return BlockStart.none(); } } + + 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; + } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 57485623..68308f6c 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -1,5 +1,6 @@ package io.noties.markwon.ext.latex; +import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -9,6 +10,7 @@ import android.text.Spanned; import android.util.Log; import android.widget.TextView; +import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; @@ -26,10 +28,12 @@ import java.util.concurrent.Future; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.image.AsyncDrawable; import io.noties.markwon.image.AsyncDrawableLoader; import io.noties.markwon.image.AsyncDrawableScheduler; import io.noties.markwon.image.AsyncDrawableSpan; +import io.noties.markwon.image.ImageSize; import io.noties.markwon.image.ImageSizeResolver; import io.noties.markwon.image.ImageSizeResolverDef; import io.noties.markwon.inlineparser.MarkwonInlineParser; @@ -129,7 +133,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // if it's $$\n -> block // if it's $$\\dhdsfjh$$ -> inline -// builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); + final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder() .addInlineProcessor(new JLatexMathInlineProcessor()) .build(); @@ -142,6 +147,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) { + visitor.ensureNewLine(); + final String latex = jLatexMathBlock.latex(); final int length = visitor.length(); @@ -155,15 +162,21 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final AsyncDrawableSpan span = new AsyncDrawableSpan( configuration.theme(), - new AsyncDrawable( + new JLatextAsyncDrawable( latex, jLatextAsyncDrawableLoader, jLatexImageSizeResolver, - null), + null, + true), AsyncDrawableSpan.ALIGN_CENTER, false); visitor.setSpans(length, span); + + if (visitor.hasNext(jLatexMathBlock)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } } }); builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor() { @@ -180,13 +193,14 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final MarkwonConfiguration configuration = visitor.configuration(); - final AsyncDrawableSpan span = new AsyncDrawableSpan( + final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan( configuration.theme(), - new AsyncDrawable( + new JLatextAsyncDrawable( latex, jLatextAsyncDrawableLoader, new ImageSizeResolverDef(), - null), + null, + false), AsyncDrawableSpan.ALIGN_CENTER, false); @@ -195,77 +209,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { }); } -// private static class LatexInlineProcessor extends InlineProcessor { -// -// @Override -// public char specialCharacter() { -// return '$'; -// } -// -// @Nullable -// @Override -// protected Node parse() { -// -// final int start = index; -// -// index += 1; -// if (peek() != '$') { -// index = start; -// return null; -// } -// -// // must be not $ -// index += 1; -// if (peek() == '$') { -// return text("$"); -// } -// -// // find next '$$', but not broken with 2(or more) new lines -// -// boolean dollar = false; -// boolean newLine = false; -// boolean found = false; -// -// index += 1; -// final int length = input.length(); -// -// while (index < length) { -// final char c = peek(); -// if (c == '\n') { -// if (newLine) { -// // second new line -// break; -// } -// newLine = true; -// dollar = false; // cannot be on another line -// } else { -// newLine = false; -// if (c == '$') { -// if (dollar) { -// found = true; -// // advance -// index += 1; -// break; -// } -// dollar = true; -// } else { -// dollar = false; -// } -// } -// index += 1; -// } -// -// if (found) { -// final JLatexMathBlock block = new JLatexMathBlock(); -// block.latex(input.substring(start + 2, index - 2)); -// index += 1; -// return block; -// } -// -// return null; -// } -// } - @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { AsyncDrawableScheduler.unschedule(textView); @@ -401,20 +344,53 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // @since 4.0.1 (background provider can be null) final BackgroundProvider backgroundProvider = config.backgroundProvider; + final JLatexMathDrawable jLatexMathDrawable; + + final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable; + if (jLatextAsyncDrawable.isBlock) { + // create JLatexMathDrawable + //noinspection ConstantConditions + jLatexMathDrawable = + JLatexMathDrawable.builder(drawable.getDestination()) + .textSize(config.textSize) + .background(backgroundProvider != null ? backgroundProvider.provide() : null) + .align(config.align) + .fitCanvas(config.fitCanvas) + .padding( + config.paddingHorizontal, + config.paddingVertical, + config.paddingHorizontal, + config.paddingVertical) + .build(); + } else { + jLatexMathDrawable = + JLatexMathDrawable.builder(drawable.getDestination()) + .textSize(config.textSize) +// .background(backgroundProvider != null ? backgroundProvider.provide() : null) +// .align(config.align) +// .fitCanvas(config.fitCanvas) +// .padding( +// config.paddingHorizontal, +// config.paddingVertical, +// config.paddingHorizontal, +// config.paddingVertical) + .build(); + } + // create JLatexMathDrawable - //noinspection ConstantConditions - final JLatexMathDrawable jLatexMathDrawable = - JLatexMathDrawable.builder(drawable.getDestination()) - .textSize(config.textSize) - .background(backgroundProvider != null ? backgroundProvider.provide() : null) - .align(config.align) - .fitCanvas(false /*config.fitCanvas*/) - .padding( - config.paddingHorizontal, - config.paddingVertical, - config.paddingHorizontal, - config.paddingVertical) - .build(); +// //noinspection ConstantConditions +// final JLatexMathDrawable jLatexMathDrawable = +// JLatexMathDrawable.builder(drawable.getDestination()) +// .textSize(config.textSize) +// .background(backgroundProvider != null ? backgroundProvider.provide() : null) +// .align(config.align) +// .fitCanvas(config.fitCanvas) +// .padding( +// config.paddingHorizontal, +// config.paddingVertical, +// config.paddingHorizontal, +// config.paddingVertical) +// .build(); // we must post to handler, but also have a way to identify the drawable // for which we are posting (in case of cancellation) @@ -496,4 +472,66 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { return imageBounds; } } + + private static class JLatextAsyncDrawable extends AsyncDrawable { + + private final boolean isBlock; + + public JLatextAsyncDrawable( + @NonNull String destination, + @NonNull AsyncDrawableLoader loader, + @NonNull ImageSizeResolver imageSizeResolver, + @Nullable ImageSize imageSize, + boolean isBlock + ) { + super(destination, loader, imageSizeResolver, imageSize); + this.isBlock = isBlock; + } + } + + private static class JLatexAsyncDrawableSpan extends AsyncDrawableSpan { + + private final AsyncDrawable drawable; + + public JLatexAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) { + super(theme, drawable, alignment, replacementTextIsLink); + this.drawable = drawable; + } + + @Override + public int getSize( + @NonNull Paint paint, + CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @Nullable Paint.FontMetricsInt fm) { + + // if we have no async drawable result - we will just render text + + final int size; + + if (drawable.hasResult()) { + + final Rect rect = drawable.getBounds(); + + if (fm != null) { + final int half = rect.bottom / 2; + fm.ascent = -half; + fm.descent = half; + + fm.top = fm.ascent; + fm.bottom = 0; + } + + size = rect.right; + + } else { + + // NB, no specific text handling (no new lines, etc) + size = (int) (paint.measureText(text, start, end) + .5F); + } + + return size; + } + } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java new file mode 100644 index 00000000..956eb20f --- /dev/null +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java @@ -0,0 +1,28 @@ +package io.noties.markwon.ext.latex; + +import android.graphics.Rect; + +/** + * @since 4.3.0-SNAPSHOT + */ +public class JLatexMathTheme { + + private float textSize; + private float inlineTextSize; + private float blockTextSize; + + // TODO: move to a class + private JLatexMathPlugin.BackgroundProvider backgroundProvider; + private JLatexMathPlugin.BackgroundProvider inlineBackgroundProvider; + private JLatexMathPlugin.BackgroundProvider blockBackgroundProvider; + + private boolean blockFitCanvas; + // horizontal alignment (when there is additional horizontal space) + private int blockAlign; + + private Rect padding; + private Rect inlinePadding; + private Rect blockPadding; + + +} diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index 8b81d119..f7c382b3 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -4,14 +4,19 @@ import android.app.Activity; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.util.Log; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.commonmark.node.Node; + +import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.ext.latex.JLatexMathPlugin; import io.noties.markwon.sample.R; +import io.noties.markwon.utils.DumpNodes; import ru.noties.jlatexmath.JLatexMathDrawable; public class LatexActivity extends Activity { @@ -44,27 +49,33 @@ public class LatexActivity extends Activity { // latex += "\\end{array}"; final String markdown = "# Example of LaTeX\n\nhello there: $$" - + latex + "$$\n\n something like **this**"; + + latex + "$$ so nice, really?\n\n $$ \n" + latex + "\n$$\n\n $$ \n" + latex + "\n$$"; final Markwon markwon = Markwon.builder(this) -// .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { -// @Override -// public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { -// builder -// .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() { -// @NonNull -// @Override -// public Drawable provide() { -// return new ColorDrawable(0x40ff0000); -// } -// }) -// .fitCanvas(true) -// .align(JLatexMathDrawable.ALIGN_LEFT) -// .padding(48) -// ; -// } -// })) - .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + builder + .backgroundProvider(new JLatexMathPlugin.BackgroundProvider() { + @NonNull + @Override + public Drawable provide() { + return new ColorDrawable(0x40ff0000); + } + }) + .fitCanvas(true) + .align(JLatexMathDrawable.ALIGN_CENTER) + .padding(48) + ; + } + })) +// .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void beforeRender(@NonNull Node node) { + Log.e("LTX", DumpNodes.dump(node)); + } + }) .build(); // // if (true) {