Add extension modules

This commit is contained in:
Dimitry Ivanov 2018-11-26 16:46:55 +03:00
parent 6eb8e64d75
commit d48b33e9a5
54 changed files with 231 additions and 969 deletions

View File

@ -29,6 +29,9 @@ android {
dependencies {
implementation project(':markwon')
implementation project(':markwon-ext-strikethrough')
implementation project(':markwon-ext-tables')
implementation project(':markwon-ext-tasklist')
implementation project(':markwon-html')
implementation project(':markwon-image-gif')
implementation project(':markwon-image-svg')

View File

@ -9,7 +9,7 @@ import android.util.AttributeSet;
import android.view.View;
import ru.noties.markwon.R;
import ru.noties.markwon.tasklist.TaskListDrawable;
import ru.noties.markwon.ext.tasklist.TaskListDrawable;
public class DebugCheckboxDrawableView extends View {

View File

@ -13,9 +13,6 @@ import android.widget.TextView;
import javax.inject.Inject;
import ru.noties.debug.Debug;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.tasklist.TaskListDrawable;
import ru.noties.markwon.tasklist.TaskListPlugin;
public class MainActivity extends Activity {
@ -74,9 +71,9 @@ public class MainActivity extends Activity {
public void apply(final String text) {
markdownRenderer.render(MainActivity.this, themes.isLight(), uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
@Override
public void onMarkdownReady(@NonNull Markwon2 markwon2, CharSequence markdown) {
public void onMarkdownReady(@NonNull Markwon markwon, CharSequence markdown) {
markwon2.setParsedMarkdown(textView, markdown);
markwon.setParsedMarkdown(textView, markdown);
Views.setVisible(progress, false);
}

View File

@ -14,6 +14,10 @@ import javax.inject.Inject;
import ru.noties.debug.Debug;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import ru.noties.markwon.ext.tables.TablePlugin;
import ru.noties.markwon.ext.tasklist.TaskListPlugin;
import ru.noties.markwon.gif.GifAwarePlugin;
import ru.noties.markwon.html.impl.HtmlPlugin;
import ru.noties.markwon.image.ImagesPlugin;
import ru.noties.markwon.image.gif.GifPlugin;
@ -22,15 +26,13 @@ import ru.noties.markwon.syntax.Prism4jTheme;
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
import ru.noties.markwon.syntax.Prism4jThemeDefault;
import ru.noties.markwon.syntax.SyntaxHighlightPlugin;
import ru.noties.markwon.table.TablePlugin;
import ru.noties.markwon.tasklist.TaskListPlugin;
import ru.noties.prism4j.Prism4j;
@ActivityScope
public class MarkdownRenderer {
interface MarkdownReadyListener {
void onMarkdownReady(@NonNull Markwon2 markwon2, CharSequence markdown);
void onMarkdownReady(@NonNull Markwon markwon, CharSequence markdown);
}
@Inject
@ -80,7 +82,7 @@ public class MarkdownRenderer {
? prism4jThemeDefault
: prism4JThemeDarkula;
final Markwon2 markwon2 = Markwon2.builder(context)
final Markwon markwon = Markwon.builder(context)
.use(CorePlugin.create())
.use(ImagesPlugin.createWithAssets(context))
.use(SvgPlugin.create(context.getResources()))
@ -89,6 +91,7 @@ public class MarkdownRenderer {
.use(GifAwarePlugin.create(context))
.use(TablePlugin.create(context))
.use(TaskListPlugin.create(context))
.use(StrikethroughPlugin.create())
.use(HtmlPlugin.create())
.use(new AbstractMarkwonPlugin() {
@Override
@ -100,7 +103,7 @@ public class MarkdownRenderer {
final long start = SystemClock.uptimeMillis();
final CharSequence text = markwon2.toMarkdown(markdown);
final CharSequence text = markwon.toMarkdown(markdown);
final long end = SystemClock.uptimeMillis();
@ -111,7 +114,7 @@ public class MarkdownRenderer {
@Override
public void run() {
if (!isCancelled()) {
listener.onMarkdownReady(markwon2, text);
listener.onMarkdownReady(markwon, text);
task = null;
}
}

View File

@ -1,4 +1,4 @@
package ru.noties.markwon;
package ru.noties.markwon.gif;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
@ -7,8 +7,8 @@ import android.support.annotation.Nullable;
import pl.droidsonroids.gif.GifDrawable;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.image.AsyncDrawable;
public class GifAwareAsyncDrawable extends AsyncDrawable {

View File

@ -1,9 +1,13 @@
package ru.noties.markwon;
package ru.noties.markwon.gif;
import android.content.Context;
import android.support.annotation.NonNull;
import android.widget.TextView;
import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.R;
public class GifAwarePlugin extends AbstractMarkwonPlugin {
@NonNull

View File

@ -1,12 +1,12 @@
package ru.noties.markwon;
package ru.noties.markwon.gif;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import ru.noties.markwon.SpannableFactoryDef;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.spans.AsyncDrawableSpan;
import ru.noties.markwon.spans.MarkwonTheme;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon;
package ru.noties.markwon.gif;
import android.graphics.Canvas;
import android.graphics.ColorFilter;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon;
package ru.noties.markwon.gif;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

View File

@ -0,0 +1,25 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion config['compile-sdk']
buildToolsVersion config['build-tools']
defaultConfig {
minSdkVersion config['min-sdk']
targetSdkVersion config['target-sdk']
versionCode 1
versionName version
}
}
dependencies {
api project(':markwon')
deps.with {
api it['commonmark-strikethrough']
}
}
registerArtifact(this)

View File

@ -0,0 +1 @@
<manifest package="ru.noties.markwon.ext.strikethrough" />

View File

@ -0,0 +1,38 @@
package ru.noties.markwon.ext.strikethrough;
import android.support.annotation.NonNull;
import android.text.style.StrikethroughSpan;
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
import org.commonmark.parser.Parser;
import java.util.Collections;
import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonVisitor;
public class StrikethroughPlugin extends AbstractMarkwonPlugin {
@NonNull
public static StrikethroughPlugin create() {
return new StrikethroughPlugin();
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.extensions(Collections.singleton(StrikethroughExtension.create()));
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(Strikethrough.class, new MarkwonVisitor.NodeVisitor<Strikethrough>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Strikethrough strikethrough) {
final int length = visitor.length();
visitor.visitChildren(strikethrough);
visitor.setSpans(length, new StrikethroughSpan());
}
});
}
}

View File

@ -0,0 +1,25 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion config['compile-sdk']
buildToolsVersion config['build-tools']
defaultConfig {
minSdkVersion config['min-sdk']
targetSdkVersion config['target-sdk']
versionCode 1
versionName version
}
}
dependencies {
api project(':markwon')
deps.with {
api it['commonmark-table']
}
}
registerArtifact(this)

View File

@ -0,0 +1 @@
<manifest package="ru.noties.markwon.ext.tables" />

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.table;
package ru.noties.markwon.ext.tables;
import android.content.Context;
import android.support.annotation.NonNull;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.table;
package ru.noties.markwon.ext.tables;
import android.annotation.SuppressLint;
import android.graphics.Canvas;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.table;
package ru.noties.markwon.ext.tables;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.table;
package ru.noties.markwon.ext.tables;
import android.content.Context;
import android.graphics.Paint;

View File

@ -0,0 +1,20 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion config['compile-sdk']
buildToolsVersion config['build-tools']
defaultConfig {
minSdkVersion config['min-sdk']
targetSdkVersion config['target-sdk']
versionCode 1
versionName version
}
}
dependencies {
api project(':markwon')
}
registerArtifact(this)

View File

@ -0,0 +1 @@
<manifest package="ru.noties.markwon.ext.tasklist" />

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.tasklist;
package ru.noties.markwon.ext.tasklist;
import org.commonmark.node.CustomBlock;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.tasklist;
package ru.noties.markwon.ext.tasklist;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.tasklist;
package ru.noties.markwon.ext.tasklist;
import android.graphics.Canvas;
import android.graphics.ColorFilter;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.tasklist;
package ru.noties.markwon.ext.tasklist;
import org.commonmark.node.CustomNode;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.tasklist;
package ru.noties.markwon.ext.tasklist;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.tasklist;
package ru.noties.markwon.ext.tasklist;
import android.graphics.Canvas;
import android.graphics.Paint;

View File

@ -9,7 +9,7 @@ import java.util.Map;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.html.HtmlTag;
import ru.noties.markwon.html.impl.CssInlineStyleParser;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.image.ImageSize;
public class ImageHandler extends SimpleTagHandler {

View File

@ -9,7 +9,7 @@ import java.util.Map;
import ru.noties.markwon.html.impl.CssInlineStyleParser;
import ru.noties.markwon.html.impl.CssProperty;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.image.ImageSize;
class ImageSizeParserImpl implements ImageHandler.ImageSizeParser {

View File

@ -1,6 +1,7 @@
package ru.noties.markwon.html.impl.tag;
import android.support.annotation.NonNull;
import android.text.style.StrikethroughSpan;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.SpannableBuilder;
@ -21,7 +22,7 @@ public class StrikeHandler extends TagHandler {
SpannableBuilder.setSpans(
builder,
configuration.factory().strikethrough(),
new StrikethroughSpan(),
tag.start(),
tag.end()
);

View File

@ -13,7 +13,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.renderer.html2.CssInlineStyleParser;
import static org.junit.Assert.assertEquals;

View File

@ -14,12 +14,26 @@ public class Prism4jThemeDefault extends Prism4jThemeBase {
@NonNull
public static Prism4jThemeDefault create() {
return new Prism4jThemeDefault();
return new Prism4jThemeDefault(0xFFf5f2f0);
}
/**
* @since 3.0.0
*/
@NonNull
public static Prism4jThemeDefault create(@ColorInt int background) {
return new Prism4jThemeDefault(background);
}
private final int background;
public Prism4jThemeDefault(@ColorInt int background) {
this.background = background;
}
@Override
public int background() {
return 0xFFf5f2f0;
return background;
}
@Override

View File

@ -18,8 +18,6 @@ dependencies {
deps.with {
api it['support-annotations']
api it['commonmark']
api it['commonmark-strikethrough']
api it['commonmark-table']
}
deps['test'].with {

View File

@ -2,216 +2,46 @@ package ru.noties.markwon;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
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.image.AsyncDrawable;
//import ru.noties.markwon.image.DrawablesScheduler;
import ru.noties.markwon.renderer.SpannableRenderer;
import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.table.TableRowSpan;
import ru.noties.markwon.tasklist.TaskListExtension;
@SuppressWarnings({"WeakerAccess", "unused"})
public abstract class Markwon {
/**
* Helper method to obtain a Parser with registered strike-through &amp; table extensions
* &amp; task lists (added in 1.0.1)
*
* @return a Parser instance that is supported by this library
* @since 1.0.0
*/
@NonNull
public static Parser createParser() {
return new Parser.Builder()
.extensions(Arrays.asList(
StrikethroughExtension.create(),
TablesExtension.create(),
TaskListExtension.create()
))
.build();
public static Builder builder(@NonNull Context context) {
return new MarkwonBuilderImpl(context);
}
/**
* @see #setMarkdown(TextView, MarkwonConfiguration, String)
* @since 1.0.0
*/
public static void setMarkdown(@NonNull TextView view, @NonNull String markdown) {
setMarkdown(view, MarkwonConfiguration.create(view.getContext()), markdown);
}
/**
* Parses submitted raw toMarkdown, converts it to CharSequence (with Spannables)
* and applies it to view
*
* @param view {@link TextView} to set toMarkdown into
* @param configuration a {@link MarkwonConfiguration} instance
* @param markdown raw toMarkdown String (for example: {@code `**Hello**`})
* @see #markdown(MarkwonConfiguration, String)
* @see #setText(TextView, CharSequence)
* @see MarkwonConfiguration
* @since 1.0.0
*/
public static void setMarkdown(
@NonNull TextView view,
@NonNull MarkwonConfiguration configuration,
@NonNull String markdown
) {
setText(view, markdown(configuration, markdown));
}
/**
* Helper method to apply parsed toMarkdown.
* <p>
* Since 1.0.6 redirects it\'s call to {@link #setText(TextView, CharSequence, MovementMethod)}
* with LinkMovementMethod as an argument to preserve current API.
*
* @param view {@link TextView} to set toMarkdown into
* @param text parsed toMarkdown
* @see #setText(TextView, CharSequence, MovementMethod)
* @since 1.0.0
*/
public static void setText(@NonNull TextView view, CharSequence text) {
setText(view, text, LinkMovementMethod.getInstance());
}
/**
* Helper method to apply parsed toMarkdown with additional argument of a MovementMethod. Used
* to workaround problems that occur when using system LinkMovementMethod (for example:
* https://issuetracker.google.com/issues/37068143). As a better alternative to it consider
* using: https://github.com/saket/Better-Link-Movement-Method
*
* @param view TextView to set toMarkdown into
* @param text parsed toMarkdown
* @param movementMethod an implementation if MovementMethod or null
* @see #scheduleDrawables(TextView)
* @see #scheduleTableRows(TextView)
* @since 1.0.6
*/
public static void setText(@NonNull TextView view, CharSequence text, @Nullable MovementMethod movementMethod) {
unscheduleDrawables(view);
unscheduleTableRows(view);
// @since 2.0.1 we must measure ordered-list-item-spans before applying text to a TextView.
// if toMarkdown has a lot of ordered list items (or text size is relatively big, or block-margin
// is relatively small) then this list won't be rendered properly: it will take correct
// layout (width and margin) but will be clipped if margin is not _consistent_ between calls.
OrderedListItemSpan.measure(view, text);
// update movement method (for links to be clickable)
view.setMovementMethod(movementMethod);
view.setText(text);
// schedule drawables (dynamic drawables that can change bounds/animate will be correctly updated)
scheduleDrawables(view);
scheduleTableRows(view);
}
/**
* Returns parsed toMarkdown with default {@link MarkwonConfiguration} obtained from {@link Context}
*
* @param context {@link Context}
* @param markdown raw toMarkdown
* @return parsed toMarkdown
* @since 1.0.0
*/
@NonNull
public static CharSequence markdown(@NonNull Context context, @NonNull String markdown) {
final MarkwonConfiguration configuration = MarkwonConfiguration.create(context);
return markdown(configuration, markdown);
}
public abstract Node parse(@NonNull String input);
/**
* Returns parsed toMarkdown with provided {@link MarkwonConfiguration}
*
* @param configuration a {@link MarkwonConfiguration}
* @param markdown raw toMarkdown
* @return parsed toMarkdown
* @see MarkwonConfiguration
* @since 1.0.0
*/
@NonNull
public static CharSequence markdown(@NonNull MarkwonConfiguration configuration, @NonNull String markdown) {
final Parser parser = createParser();
final Node node = parser.parse(markdown);
final SpannableRenderer renderer = new SpannableRenderer();
return renderer.render(configuration, node);
}
public abstract CharSequence render(@NonNull Node node);
/**
* This method adds support for {@link AsyncDrawable} to be used. As
* textView seems not to support drawables that change bounds (and gives no means
* to update the layout), we create own {@link android.graphics.drawable.Drawable.Callback}
* and apply it. So, textView can display drawables, that are: async (loading from disk, network);
* dynamic (requires `invalidate`) - GIF, animations.
* Please note, that this method should be preceded with {@link #unscheduleDrawables(TextView)}
* in order to avoid keeping drawables in memory after they have been removed from layout
*
* @param view a {@link TextView}
* @see AsyncDrawable
* @see ru.noties.markwon.spans.AsyncDrawableSpan
* @see DrawablesScheduler#schedule(TextView)
* @see DrawablesScheduler#unschedule(TextView)
* @since 1.0.0
*/
public static void scheduleDrawables(@NonNull TextView view) {
// DrawablesScheduler.schedule(view);
}
// parse + render
@NonNull
public abstract CharSequence toMarkdown(@NonNull String input);
/**
* De-references previously scheduled {@link ru.noties.markwon.spans.AsyncDrawableSpan}&#39;s
*
* @param view a {@link TextView}
* @see #scheduleDrawables(TextView)
* @since 1.0.0
*/
public static void unscheduleDrawables(@NonNull TextView view) {
// DrawablesScheduler.unschedule(view);
}
public abstract void setMarkdown(@NonNull TextView textView, @NonNull String markdown);
/**
* This method is required in order to use tables. A bit of background:
* this library uses a {@link android.text.style.ReplacementSpan} to
* render tables, but the flow is not really flexible. We are required
* to return `size` (width) of our replacement, but we are not provided
* with the total one (canvas width). In order to correctly calculate height of our
* table cell text, we must have available width first. This method gives
* ability for {@link TableRowSpan} to invalidate
* `view` when it encounters such a situation (when available width is not known or have changed).
* Precede this call with {@link #unscheduleTableRows(TextView)} in order to
* de-reference previously scheduled {@link TableRowSpan}&#39;s
*
* @param view a {@link TextView}
* @see #unscheduleTableRows(TextView)
* @since 1.0.0
*/
public static void scheduleTableRows(@NonNull TextView view) {
// TableRowsScheduler.schedule(view);
}
public abstract void setParsedMarkdown(@NonNull TextView textView, @NonNull CharSequence markdown);
/**
* De-references previously scheduled {@link TableRowSpan}&#39;s
*
* @param view a {@link TextView}
* @see #scheduleTableRows(TextView)
* @since 1.0.0
*/
public static void unscheduleTableRows(@NonNull TextView view) {
// TableRowsScheduler.unschedule(view);
}
public interface Builder {
private Markwon() {
/**
* Specify bufferType when applying text to a TextView {@code textView.setText(CharSequence,BufferType)}.
* By default `BufferType.SPANNABLE` is used
*
* @param bufferType BufferType
*/
@NonNull
Builder bufferType(@NonNull TextView.BufferType bufferType);
@NonNull
Builder use(@NonNull MarkwonPlugin plugin);
@NonNull
Markwon build();
}
}

View File

@ -1,47 +0,0 @@
package ru.noties.markwon;
import android.content.Context;
import android.support.annotation.NonNull;
import android.widget.TextView;
import org.commonmark.node.Node;
public abstract class Markwon2 {
@NonNull
public static Builder builder(@NonNull Context context) {
return new MarkwonBuilderImpl(context);
}
@NonNull
public abstract Node parse(@NonNull String input);
@NonNull
public abstract CharSequence render(@NonNull Node node);
// parse + render
@NonNull
public abstract CharSequence toMarkdown(@NonNull String input);
public abstract void setMarkdown(@NonNull TextView textView, @NonNull String markdown);
public abstract void setParsedMarkdown(@NonNull TextView textView, @NonNull CharSequence markdown);
public interface Builder {
/**
* Specify bufferType when applying text to a TextView {@code textView.setText(CharSequence,BufferType)}.
* By default `BufferType.SPANNABLE` is used
*
* @param bufferType BufferType
*/
@NonNull
Builder bufferType(@NonNull TextView.BufferType bufferType);
@NonNull
Builder use(@NonNull MarkwonPlugin plugin);
@NonNull
Markwon2 build();
}
}

View File

@ -13,7 +13,7 @@ import java.util.List;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.spans.MarkwonTheme;
class MarkwonBuilderImpl implements Markwon2.Builder {
class MarkwonBuilderImpl implements Markwon.Builder {
private final Context context;
@ -26,21 +26,21 @@ class MarkwonBuilderImpl implements Markwon2.Builder {
@NonNull
@Override
public Markwon2.Builder bufferType(@NonNull TextView.BufferType bufferType) {
public Markwon.Builder bufferType(@NonNull TextView.BufferType bufferType) {
this.bufferType = bufferType;
return this;
}
@NonNull
@Override
public Markwon2.Builder use(@NonNull MarkwonPlugin plugin) {
public Markwon.Builder use(@NonNull MarkwonPlugin plugin) {
plugins.add(plugin);
return this;
}
@NonNull
@Override
public Markwon2 build() {
public Markwon build() {
final Parser.Builder parserBuilder = new Parser.Builder();
final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context);

View File

@ -7,8 +7,8 @@ import ru.noties.markwon.html.MarkwonHtmlParser;
import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.image.AsyncDrawableLoaderNoOp;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.renderer.ImageSizeResolverDef;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.image.ImageSizeResolverDef;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;
@ -39,7 +39,6 @@ public class MarkwonConfiguration {
private final SpannableFactory factory; // @since 1.1.0
private final MarkwonHtmlParser htmlParser; // @since 2.0.0
private final MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0
// private final boolean htmlAllowNonClosedTags; // @since 2.0.0
private MarkwonConfiguration(@NonNull Builder builder) {
this.theme = builder.theme;
@ -51,7 +50,6 @@ public class MarkwonConfiguration {
this.factory = builder.factory;
this.htmlParser = builder.htmlParser;
this.htmlRenderer = builder.htmlRenderer;
// this.htmlAllowNonClosedTags = builder.htmlAllowNonClosedTags;
}
/**
@ -113,13 +111,6 @@ public class MarkwonConfiguration {
return htmlRenderer;
}
// /**
// * @since 2.0.0
// */
// public boolean htmlAllowNonClosedTags() {
// return htmlAllowNonClosedTags;
// }
@SuppressWarnings("unused")
public static class Builder {
@ -134,7 +125,6 @@ public class MarkwonConfiguration {
private SpannableFactory factory; // @since 1.1.0
private MarkwonHtmlParser htmlParser; // @since 2.0.0
private MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0
// private boolean htmlAllowNonClosedTags; // @since 2.0.0
Builder(@NonNull Context context) {
this.context = context;
@ -151,7 +141,6 @@ public class MarkwonConfiguration {
this.factory = configuration.factory;
this.htmlParser = configuration.htmlParser;
this.htmlRenderer = configuration.htmlRenderer;
// this.htmlAllowNonClosedTags = configuration.htmlAllowNonClosedTags;
}
@NonNull
@ -208,19 +197,6 @@ public class MarkwonConfiguration {
return this;
}
// /**
// * @param htmlAllowNonClosedTags that indicates if non-closed html tags should be rendered.
// * If this argument is true then all non-closed HTML tags
// * will be closed at the end of a document. Otherwise they will
// * be delivered non-closed {@code HtmlTag#isClosed()}
// * @since 2.0.0
// */
// @NonNull
// public Builder htmlAllowNonClosedTags(boolean htmlAllowNonClosedTags) {
// this.htmlAllowNonClosedTags = htmlAllowNonClosedTags;
// return this;
// }
@NonNull
public MarkwonConfiguration build(@NonNull MarkwonTheme theme, @NonNull AsyncDrawableLoader asyncDrawableLoader) {

View File

@ -8,7 +8,7 @@ import org.commonmark.parser.Parser;
import java.util.List;
class MarkwonImpl extends Markwon2 {
class MarkwonImpl extends Markwon {
private final TextView.BufferType bufferType;
private final Parser parser;

View File

@ -4,8 +4,8 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;
@ -40,9 +40,6 @@ public interface SpannableFactory {
@Nullable
Object heading(@NonNull MarkwonTheme theme, int level);
@Nullable
Object strikethrough();
/**
* @since 1.1.1
*/

View File

@ -2,13 +2,11 @@ package ru.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.style.StrikethroughSpan;
import android.text.style.UnderlineSpan;
import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.spans.AsyncDrawableSpan;
import ru.noties.markwon.spans.BlockQuoteSpan;
import ru.noties.markwon.spans.BulletListItemSpan;
@ -19,8 +17,6 @@ import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;
import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.spans.StrongEmphasisSpan;
import ru.noties.markwon.spans.SubScriptSpan;
import ru.noties.markwon.spans.SuperScriptSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
/**
@ -82,12 +78,6 @@ public class SpannableFactoryDef implements SpannableFactory {
return new HeadingSpan(theme, level);
}
@Nullable
@Override
public Object strikethrough() {
return new StrikethroughSpan();
}
/**
* @since 1.1.1
*/

View File

@ -10,9 +10,6 @@ import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
public class AsyncDrawable extends Drawable {
private final String destination;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.renderer;
package ru.noties.markwon.image;
import android.support.annotation.Nullable;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.renderer;
package ru.noties.markwon.image;
import android.graphics.Rect;
import android.support.annotation.NonNull;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.renderer;
package ru.noties.markwon.image;
import android.graphics.Rect;
import android.support.annotation.NonNull;

View File

@ -1,529 +0,0 @@
package ru.noties.markwon.renderer;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
import org.commonmark.node.AbstractVisitor;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.BulletList;
import org.commonmark.node.Code;
import org.commonmark.node.CustomBlock;
import org.commonmark.node.CustomNode;
import org.commonmark.node.Emphasis;
import org.commonmark.node.FencedCodeBlock;
import org.commonmark.node.HardLineBreak;
import org.commonmark.node.Heading;
import org.commonmark.node.Image;
import org.commonmark.node.IndentedCodeBlock;
import org.commonmark.node.Link;
import org.commonmark.node.ListBlock;
import org.commonmark.node.ListItem;
import org.commonmark.node.Node;
import org.commonmark.node.OrderedList;
import org.commonmark.node.Paragraph;
import org.commonmark.node.StrongEmphasis;
import org.commonmark.node.Text;
import org.commonmark.node.ThematicBreak;
import java.util.List;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.SpannableBuilder;
import ru.noties.markwon.SpannableFactory;
import ru.noties.markwon.spans.MarkwonTheme;
import ru.noties.markwon.table.TableRowSpan;
import ru.noties.markwon.tasklist.TaskListBlock;
@SuppressWarnings("WeakerAccess")
public class SpannableMarkdownVisitor extends AbstractVisitor {
private final MarkwonConfiguration configuration;
private final SpannableBuilder builder;
// private final MarkwonHtmlParser htmlParser;
private final MarkwonTheme theme;
private final SpannableFactory factory;
private int blockQuoteIndent;
private int listLevel;
private List<TableRowSpan.Cell> pendingTableRow;
private boolean tableRowIsHeader;
private int tableRows;
public SpannableMarkdownVisitor(
@NonNull MarkwonConfiguration configuration,
@NonNull SpannableBuilder builder
) {
this.configuration = configuration;
this.builder = builder;
// this.htmlParser = configuration.htmlParser();
this.theme = configuration.theme();
this.factory = configuration.factory();
}
// @Override
// public void visit(Document document) {
// super.visit(document);
//
// configuration.htmlRenderer().render(configuration, builder, htmlParser);
// }
@Override
public void visit(Text text) {
builder.append(text.getLiteral());
}
@Override
public void visit(StrongEmphasis strongEmphasis) {
final int length = builder.length();
visitChildren(strongEmphasis);
setSpan(length, factory.strongEmphasis());
}
@Override
public void visit(Emphasis emphasis) {
final int length = builder.length();
visitChildren(emphasis);
setSpan(length, factory.emphasis());
}
@Override
public void visit(BlockQuote blockQuote) {
newLine();
if (blockQuoteIndent != 0) {
builder.append('\n');
}
final int length = builder.length();
blockQuoteIndent += 1;
visitChildren(blockQuote);
setSpan(length, factory.blockQuote(theme));
blockQuoteIndent -= 1;
if (hasNext(blockQuote)) {
newLine();
if (blockQuoteIndent == 0) {
builder.append('\n');
}
}
}
@Override
public void visit(Code code) {
final int length = builder.length();
// NB, in order to provide a _padding_ feeling code is wrapped inside two unbreakable spaces
// unfortunately we cannot use this for multiline code as we cannot control where a new line break will be inserted
builder.append('\u00a0');
builder.append(code.getLiteral());
builder.append('\u00a0');
setSpan(length, factory.code(theme, false));
}
@Override
public void visit(FencedCodeBlock fencedCodeBlock) {
// @since 1.0.4
visitCodeBlock(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral(), fencedCodeBlock);
}
/**
* @since 1.0.4
*/
@Override
public void visit(IndentedCodeBlock indentedCodeBlock) {
visitCodeBlock(null, indentedCodeBlock.getLiteral(), indentedCodeBlock);
}
/**
* @param info tag of a code block
* @param code content of a code block
* @since 1.0.4
*/
private void visitCodeBlock(@Nullable String info, @NonNull String code, @NonNull Node node) {
newLine();
final int length = builder.length();
// empty lines on top & bottom
builder.append('\u00a0').append('\n');
builder.append(
configuration.syntaxHighlight()
.highlight(info, code)
);
newLine();
builder.append('\u00a0');
setSpan(length, factory.code(theme, true));
if (hasNext(node)) {
newLine();
builder.append('\n');
}
}
@Override
public void visit(BulletList bulletList) {
visitList(bulletList);
}
@Override
public void visit(OrderedList orderedList) {
visitList(orderedList);
}
private void visitList(Node node) {
newLine();
visitChildren(node);
if (hasNext(node)) {
newLine();
if (listLevel == 0 && blockQuoteIndent == 0) {
builder.append('\n');
}
}
}
@Override
public void visit(ListItem listItem) {
final int length = builder.length();
blockQuoteIndent += 1;
listLevel += 1;
final Node parent = listItem.getParent();
if (parent instanceof OrderedList) {
final int start = ((OrderedList) parent).getStartNumber();
visitChildren(listItem);
setSpan(length, factory.orderedListItem(theme, start));
// after we have visited the children increment start number
final OrderedList orderedList = (OrderedList) parent;
orderedList.setStartNumber(orderedList.getStartNumber() + 1);
} else {
visitChildren(listItem);
setSpan(length, factory.bulletListItem(theme, listLevel - 1));
}
blockQuoteIndent -= 1;
listLevel -= 1;
if (hasNext(listItem)) {
newLine();
}
}
@Override
public void visit(ThematicBreak thematicBreak) {
newLine();
final int length = builder.length();
builder.append('\u00a0'); // without space it won't render
setSpan(length, factory.thematicBreak(theme));
if (hasNext(thematicBreak)) {
newLine();
builder.append('\n');
}
}
@Override
public void visit(Heading heading) {
newLine();
final int length = builder.length();
visitChildren(heading);
setSpan(length, factory.heading(theme, heading.getLevel()));
if (hasNext(heading)) {
newLine();
// after heading we add another line anyway (no additional checks)
builder.append('\n');
}
}
// @Override
// public void visit(SoftLineBreak softLineBreak) {
// // @since 1.1.1 there is an option to treat soft break as a hard break (thus adding new line)
// if (configuration.softBreakAddsNewLine()) {
// newLine();
// } else {
// builder.append(' ');
// }
// }
@Override
public void visit(HardLineBreak hardLineBreak) {
newLine();
}
/**
* @since 1.0.1
*/
@Override
public void visit(CustomBlock customBlock) {
if (customBlock instanceof TaskListBlock) {
blockQuoteIndent += 1;
visitChildren(customBlock);
blockQuoteIndent -= 1;
if (hasNext(customBlock)) {
newLine();
builder.append('\n');
}
} else {
super.visit(customBlock);
}
}
@Override
public void visit(CustomNode customNode) {
if (customNode instanceof Strikethrough) {
final int length = builder.length();
visitChildren(customNode);
setSpan(length, factory.strikethrough());
} else {
super.visit(customNode);
}
}
// private boolean handleTableNodes(CustomNode node) {
//
// final boolean handled;
//
// if (node instanceof TableBody) {
//
// visitChildren(node);
// tableRows = 0;
// handled = true;
//
// if (hasNext(node)) {
// newLine();
// builder.append('\n');
// }
//
// } else if (node instanceof TableRow || node instanceof TableHead) {
//
// final int length = builder.length();
//
// visitChildren(node);
//
// if (pendingTableRow != null) {
//
// // @since 2.0.0
// // we cannot rely on hasNext(TableHead) as it's not reliable
// // we must apply new line manually and then exclude it from tableRow span
// final boolean addNewLine;
// {
// final int builderLength = builder.length();
// addNewLine = builderLength > 0
// && '\n' != builder.charAt(builderLength - 1);
// }
// if (addNewLine) {
// builder.append('\n');
// }
//
// // @since 1.0.4 Replace table char with non-breakable space
// // we need this because if table is at the end of the text, then it will be
// // trimmed from the final result
// builder.append('\u00a0');
//
// final Object span = factory.tableRow(
// theme,
// pendingTableRow,
// tableRowIsHeader,
// tableRows % 2 == 1);
//
// tableRows = tableRowIsHeader
// ? 0
// : tableRows + 1;
//
// setSpan(addNewLine ? length + 1 : length, span);
//
// pendingTableRow = null;
// }
//
// handled = true;
//
// } else if (node instanceof TableCell) {
//
// final TableCell cell = (TableCell) node;
// final int length = builder.length();
// visitChildren(cell);
// if (pendingTableRow == null) {
// pendingTableRow = new ArrayList<>(2);
// }
//
// pendingTableRow.add(new TableRowSpan.Cell(
// tableCellAlignment(cell.getAlignment()),
// builder.removeFromEnd(length)
// ));
//
// tableRowIsHeader = cell.isHeader();
//
// handled = true;
// } else {
// handled = false;
// }
//
// return handled;
// }
@Override
public void visit(Paragraph paragraph) {
final boolean inTightList = isInTightList(paragraph);
if (!inTightList) {
newLine();
}
final int length = builder.length();
visitChildren(paragraph);
// @since 1.1.1 apply paragraph span
setSpan(length, factory.paragraph(inTightList));
if (hasNext(paragraph) && !inTightList) {
newLine();
if (blockQuoteIndent == 0) {
builder.append('\n');
}
}
}
@Override
public void visit(Image image) {
final int length = builder.length();
visitChildren(image);
// we must check if anything _was_ added, as we need at least one char to render
if (length == builder.length()) {
builder.append('\uFFFC');
}
final Node parent = image.getParent();
final boolean link = parent != null && parent instanceof Link;
final String destination = configuration.urlProcessor().process(image.getDestination());
setSpan(
length,
factory.image(
theme,
destination,
configuration.asyncDrawableLoader(),
configuration.imageSizeResolver(),
null,
link
)
);
// todo, maybe, if image is not inside a link, we should make it clickable, so
// user can open it in external viewer?
}
// @Override
// public void visit(HtmlBlock htmlBlock) {
// visitHtml(htmlBlock.getLiteral());
// }
//
// @Override
// public void visit(HtmlInline htmlInline) {
// visitHtml(htmlInline.getLiteral());
// }
//
// private void visitHtml(@Nullable String html) {
// if (html != null) {
// htmlParser.processFragment(builder, html);
// }
// }
@Override
public void visit(Link link) {
final int length = builder.length();
visitChildren(link);
final String destination = configuration.urlProcessor().process(link.getDestination());
setSpan(length, factory.link(theme, destination, configuration.linkResolver()));
}
private void setSpan(int start, @Nullable Object span) {
SpannableBuilder.setSpans(builder, span, start, builder.length());
}
private void newLine() {
if (builder.length() > 0
&& '\n' != builder.lastChar()) {
builder.append('\n');
}
}
private boolean isInTightList(Paragraph paragraph) {
final Node parent = paragraph.getParent();
if (parent != null) {
final Node gramps = parent.getParent();
if (gramps != null && gramps instanceof ListBlock) {
ListBlock list = (ListBlock) gramps;
return list.isTight();
}
}
return false;
}
// @TableRowSpan.Alignment
// private static int tableCellAlignment(TableCell.Alignment alignment) {
// final int out;
// if (alignment != null) {
// switch (alignment) {
// case CENTER:
// out = TableRowSpan.ALIGN_CENTER;
// break;
// case RIGHT:
// out = TableRowSpan.ALIGN_RIGHT;
// break;
// default:
// out = TableRowSpan.ALIGN_LEFT;
// break;
// }
// } else {
// out = TableRowSpan.ALIGN_LEFT;
// }
// return out;
// }
/**
* @since 2.0.0
*/
protected static boolean hasNext(@NonNull Node node) {
return node.getNext() != null;
}
}

View File

@ -1,18 +0,0 @@
package ru.noties.markwon.renderer;
import android.support.annotation.NonNull;
import org.commonmark.node.Node;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.SpannableBuilder;
public class SpannableRenderer {
@NonNull
public CharSequence render(@NonNull MarkwonConfiguration configuration, @NonNull Node node) {
final SpannableBuilder builder = new SpannableBuilder();
node.accept(new SpannableMarkdownVisitor(configuration, builder));
return builder.text();
}
}

View File

@ -1,28 +0,0 @@
package ru.noties.markwon.spans;
import android.support.annotation.NonNull;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
public class SubScriptSpan extends MetricAffectingSpan {
private final MarkwonTheme theme;
public SubScriptSpan(@NonNull MarkwonTheme theme) {
this.theme = theme;
}
@Override
public void updateDrawState(TextPaint tp) {
apply(tp);
}
@Override
public void updateMeasureState(TextPaint tp) {
apply(tp);
}
private void apply(TextPaint paint) {
theme.applySubScriptStyle(paint);
}
}

View File

@ -1,28 +0,0 @@
package ru.noties.markwon.spans;
import android.support.annotation.NonNull;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
public class SuperScriptSpan extends MetricAffectingSpan {
private final MarkwonTheme theme;
public SuperScriptSpan(@NonNull MarkwonTheme theme) {
this.theme = theme;
}
@Override
public void updateDrawState(TextPaint tp) {
apply(tp);
}
@Override
public void updateMeasureState(TextPaint tp) {
apply(tp);
}
private void apply(TextPaint paint) {
theme.applySuperScriptStyle(paint);
}
}

View File

@ -1,22 +0,0 @@
package ru.noties.markwon.tasklist;
import android.support.annotation.NonNull;
import org.commonmark.parser.Parser;
/**
* @since 1.0.1
*/
@Deprecated
public class TaskListExtension implements Parser.ParserExtension {
@NonNull
public static TaskListExtension create() {
return new TaskListExtension();
}
@Override
public void extend(Parser.Builder parserBuilder) {
parserBuilder.customBlockParserFactory(new TaskListBlockParser.Factory());
}
}

View File

@ -8,11 +8,13 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import ru.noties.markwon.renderer.ImageSize.Dimension;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.ImageSize.Dimension;
import ru.noties.markwon.image.ImageSizeResolverDef;
import static org.junit.Assert.assertEquals;
import static ru.noties.markwon.renderer.ImageSizeResolverDef.UNIT_EM;
import static ru.noties.markwon.renderer.ImageSizeResolverDef.UNIT_PERCENT;
import static ru.noties.markwon.image.ImageSizeResolverDef.UNIT_EM;
import static ru.noties.markwon.image.ImageSizeResolverDef.UNIT_PERCENT;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)

View File

@ -9,6 +9,7 @@ import ru.noties.markwon.UrlProcessor;
import ru.noties.markwon.html.api.MarkwonHtmlParser;
import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;

View File

@ -13,7 +13,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.renderer.html2.CssInlineStyleParser;
import static org.junit.Assert.assertEquals;

View File

@ -9,8 +9,8 @@ import java.util.List;
import java.util.Map;
import ru.noties.markwon.SpannableFactory;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.image.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.MarkwonTheme;

View File

@ -13,7 +13,7 @@ import ru.noties.markwon.Markwon;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.SpannableBuilder;
import ru.noties.markwon.il.AsyncDrawableLoader;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
public class MainActivity extends Activity {

View File

@ -1,3 +1,13 @@
rootProject.name = 'MarkwonProject'
include ':app', ':markwon', ':markwon-view', ':sample-custom-extension', ':sample-latex-math', ':markwon-image-svg', ':markwon-image-gif',
':markwon-syntax-highlight', ':markwon-html'
include ':app',
':markwon',
':markwon-ext-strikethrough',
':markwon-ext-tables',
':markwon-ext-tasklist',
':markwon-image-svg',
':markwon-image-gif',
':markwon-syntax-highlight',
':markwon-html',
':markwon-view',
':sample-custom-extension',
':sample-latex-math'