X
` +* `` +* `` +* `, ` +* `,` +* `, ` +* `,
` +* `, , , ` +* `
,
,
,
,
,
` + +:::tip +All predefined tag handlers will use styling spans for native markdown content. +So, if your `Markwon` instance was configured to, for example, render Emphasis +nodes as a red text then HTML tag handler will +use the same span. This includes images, links, UrlResolver, LinkProcessor, etc +::: + +--- + +Staring with
you can exclude all default tag handlers: + +```java +.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() { + @Override + public void configureHtml(@NonNull HtmlPlugin plugin) { + plugin.excludeDefaults(true); + } +})) +``` + +or via plugin: + +```java +.usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(HtmlPlugin.class, new Action () { + @Override + public void apply(@NonNull HtmlPlugin htmlPlugin) { + htmlPlugin.excludeDefaults(true); + } + }); + } +}) +``` + +If you wish to exclude some of them `TagHandlerNoOp` can be used: + +```java +.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() { + @Override + public void configureHtml(@NonNull HtmlPlugin plugin) { + plugin.addHandler(TagHandlerNoOp.create("h4", "h5", "h6", "img")); + } +})) +``` + +## TagHandler + +To define a tag-handler that applies style for the whole tag content (from start to end), +a `SimpleTagHandler` can be used. For example, let's define ` ` tag, which can be used +like this: + +* ` centered text ` +* `this should be aligned at the end (right for LTR locales) ` +* `regular alignment ` + +```java +public class AlignTagHandler extends SimpleTagHandler { + + @Nullable + @Override + public Object getSpans( + @NonNull MarkwonConfiguration configuration, + @NonNull RenderProps renderProps, + @NonNull HtmlTag tag) { + + final Layout.Alignment alignment; + + // html attribute without value,+ if (tag.attributes().containsKey("center")) { + alignment = Layout.Alignment.ALIGN_CENTER; + } else if (tag.attributes().containsKey("end")) { + alignment = Layout.Alignment.ALIGN_OPPOSITE; + } else { + // empty value or any other will make regular alignment + alignment = Layout.Alignment.ALIGN_NORMAL; + } + + return new AlignmentSpan.Standard(alignment); + } + + @NonNull + @Override + public Collection supportedTags() { + return Collections.singleton("align"); + } +} +``` + +:::tip +`SimpleTagHandler` can return an array of spans from `getSpans` method +::: + +Then register `AlignTagHandler`: + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(HtmlPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin + .addHandler(new AlignTagHandler()); + } + }) + .build(); +``` + +or directly on `HtmlPlugin`: + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(HtmlPlugin.create(plugin -> plugin.addHandler(new AlignTagHandler()))) + .build(); +``` + +--- + +If a tag requires special handling `TagHandler` can be used directly. For example +let's define an ` ` tag with `start` and `end` arguments, that will mark +start and end positions of the text that needs to be enlarged: + +```html + This is text that must be enhanced, at least a part of it +``` + + +```java +public class EnhanceTagHandler extends TagHandler { + + private final int enhanceTextSize; + + EnhanceTagHandler(@Px int enhanceTextSize) { + this.enhanceTextSize = enhanceTextSize; + } + + @Override + public void handle( + @NonNull MarkwonVisitor visitor, + @NonNull MarkwonHtmlRenderer renderer, + @NonNull HtmlTag tag) { + + // we require start and end to be present + final int start = parsePosition(tag.attributes().get("start")); + final int end = parsePosition(tag.attributes().get("end")); + + if (start > -1 && end > -1) { + visitor.builder().setSpan( + new AbsoluteSizeSpan(enhanceTextSize), + tag.start() + start, + tag.start() + end + ); + } + } + + @NonNull + @Override + public CollectionsupportedTags() { + return Collections.singleton("enhance"); + } + + private static int parsePosition(@Nullable String value) { + int position; + if (!TextUtils.isEmpty(value)) { + try { + position = Integer.parseInt(value); + } catch (NumberFormatException e) { + e.printStackTrace(); + position = -1; + } + } else { + position = -1; + } + return position; + } +} +``` \ No newline at end of file diff --git a/docs/docs/v4/image-glide/README.md b/docs/docs/v4/image-glide/README.md new file mode 100644 index 00000000..d8b361c6 --- /dev/null +++ b/docs/docs/v4/image-glide/README.md @@ -0,0 +1,27 @@ +# Image Glide + + + +Image loading based on `Glide` library + +```java +final Markwon markwon = Markwon.builder(context) + // automatically create Glide instance + .usePlugin(GlideImagesPlugin.create(context)) + // use supplied Glide instance + .usePlugin(GlideImagesPlugin.create(Glide.with(context))) + // if you need more control + .usePlugin(GlideImagesPlugin.create(new GlideImagesPlugin.GlideStore() { + @NonNull + @Override + public RequestBuilder load(@NonNull AsyncDrawable drawable) { + return Glide.with(context).load(drawable.getDestination()); + } + + @Override + public void cancel(@NonNull Target> target) { + Glide.with(context).clear(target); + } + })) + .build(); +``` \ No newline at end of file diff --git a/docs/docs/v4/image-picasso/README.md b/docs/docs/v4/image-picasso/README.md new file mode 100644 index 00000000..22d5f72d --- /dev/null +++ b/docs/docs/v4/image-picasso/README.md @@ -0,0 +1,32 @@ +# Image Picasso + + + +Image loading based on `Picasso` library + +```java +final Markwon markwon = Markwon.builder(context) + // automatically create Picasso instance + .usePlugin(PicassoImagesPlugin.create(context)) + // use provided picasso instance + .usePlugin(PicassoImagesPlugin.create(Picasso.get())) + // if you need more control + .usePlugin(PicassoImagesPlugin.create(new PicassoImagesPlugin.PicassoStore() { + @NonNull + @Override + public RequestCreator load(@NonNull AsyncDrawable drawable) { + return Picasso.get() + .load(drawable.getDestination()) + // please note that drawable should be used as tag (not a destination) + // otherwise there won't be support for multiple images with the same URL + .tag(drawable); + } + + @Override + public void cancel(@NonNull AsyncDrawable drawable) { + Picasso.get() + .cancelTag(drawable); + } + })) + .build(); +``` \ No newline at end of file diff --git a/docs/docs/v4/image/README.md b/docs/docs/v4/image/README.md new file mode 100644 index 00000000..62cb8e77 --- /dev/null +++ b/docs/docs/v4/image/README.md @@ -0,0 +1,354 @@ +# Image + + + +In order to display images in your markdown `ImagesPlugin` can be used. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) +``` + +:::tip +There are also modules that add image loading capabilities to markdown +based on image-loading libraries: [image-glide](/docs/v4/image-glide/) and +[image-picasso](/docs/v4/image-picasso/) +::: + +`ImagesPlugin` splits the image-loading into 2 parts: scheme-handling and media-decoding. + +## SchemeHandler + +To add a scheme-handler to `ImagesPlugin`: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(ImagesPlugin.class, new Action () { + @Override + public void apply(@NonNull ImagesPlugin imagesPlugin) { + imagesPlugin.addSchemeHandler(DataUriSchemeHandler.create()); + } + }); + } + }) +``` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + plugin.addSchemeHandler(DataUriSchemeHandler.create()); + } + })) +``` + +`ImagesPlugin` comes with a set of predefined scheme-handlers: +* `FileSchemeHandler` - `file://` +* `DataUriSchemeHandler` - `data:` +* `NetworkSchemeHandler` - `http`, `https` +* `OkHttpNetworkSchemeHandler` - `http`, `https` + +### FileSchemeHandler + +Loads images via `file://` scheme. Allows loading images from `assets` folder. + +```java +// default implementation, no assets handling +FileSchemeHandler.create(); + +// assets loading +FileSchemeHandler.createWithAssets(context); +``` + +:::warning +Assets loading will work when your URL will include `android_asset` in the path, +for example: `file:///android_asset/image.png` (mind the 3 slashes `///`). If you wish +to _assume_ all images without proper scheme to point to assets folder, then you can use +[UrlProcessorAndroidAssets](/docs/v4/core/configuration.html#urlprocessorandroidassets) +::: + +By default `ImagesPlugin` includes _plain_ `FileSchemeHandler` (without assets support), +so if you wish to change that you can explicitly specify it: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + plugin.addSchemeHandler(FileSchemeHandler.createWithAssets(context)); + } + })) +``` + +### DataUriSchemeHandler +`DataUriSchemeHandler` allows _inlining_ images with `data:` scheme (`data:image/svg+xml;base64,MTIz`). +This scheme-handler is registered by default, so you do not need to add it explicitly. + +### NetworkSchemeHandler +`NetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris +(internally it uses `HttpURLConnection`). This scheme-handler is registered by default + +### OkHttpNetworkSchemeHandler +`OkHttpNetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris +via [okhttp library](https://github.com/square/okhttp). Please note that in order to use +this scheme-handler you must explicitly add `okhttp` library to your project. + +```java +// default instance +OkHttpNetworkSchemeHandler.create(); + +// specify OkHttpClient to use +OkHttpNetworkSchemeHandler.create(new OkHttpClient()); + +// @since 4.0.0 +OkHttpNetworkSchemeHandler.create(Call.Factory); +``` + +### Custom SchemeHandler + +```java +public abstract class SchemeHandler { + + @NonNull + public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri); + + @NonNull + public abstract Collection supportedSchemes(); +} +``` + +Starting with `SchemeHandler` can return a result (when no +further decoding is required): + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + // for example to return a drawable resource + plugin.addSchemeHandler(new SchemeHandler() { + @NonNull + @Override + public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { + + // will handle URLs like `drawable://ic_account_24dp_white` + final int resourceId = context.getResources().getIdentifier( + raw.substring("drawable://".length()), + "drawable", + context.getPackageName()); + + // it's fine if it throws, async-image-loader will catch exception + final Drawable drawable = context.getDrawable(resourceId); + + return ImageItem.withResult(drawable); + } + + @NonNull + @Override + public Collection supportedSchemes() { + return Collections.singleton("drawable"); + } + }); + } + })) +``` + +Otherwise `SchemeHandler` must return an `InputStream` with proper `content-type` information +for further processing by a `MediaDecoder`: + +```java +imagesPlugin.addSchemeHandler(new SchemeHandler() { + @NonNull + @Override + public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { + return ImageItem.withDecodingNeeded("image/png", load(raw)); + } + + @NonNull + private InputStream load(@NonNull String raw) {...} +}); +``` + +## MediaDecoder + +`ImagesPlugin` comes with predefined media-decoders: +* `GifMediaDecoder` adds support for GIF +* `SvgMediaDecoder` adds support for SVG +* `DefaultMediaDecoder` + +:::warning +If you wish to add support for **SVG** or **GIF** you must explicitly add these dependencies +to your project: +* for `SVG`: `com.caverock:androidsvg:1.4` +* for `GIF`: `pl.droidsonroids.gif:android-gif-drawable:1.2.14` + +You can try more recent versions of these libraries, but make sure that they doesn't +introduce any unexpected behavior. +::: + + +### GifMediaDecoder + +Adds support for GIF media in markdown. If `pl.droidsonroids.gif:android-gif-drawable:*` dependency +is found in the classpath, then registration will happen automatically. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + // autoplayGif controls if GIF should be automatically started + plugin.addMediaDecoder(GifMediaDecoder.create(/*autoplayGif*/false)); + } + })) + .build(); +``` + +### SvgMediaDecoder + +Adds support for SVG media in markdown. If `com.caverock:androidsvg:*` dependency is found +in the classpath, then registration will happen automatically. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + + // uses supplied Resources + plugin.addMediaDecoder(SvgMediaDecoder.create(context.getResources())); + + // uses Resources.getSystem() + plugin.addMediaDecoder(SvgMediaDecoder.create()); + } + })) + .build(); +``` + +### DefaultMediaDecoder + +`DefaultMediaDecoder` _tries_ to decode supplied InputStream +as Bitmap (via `BitmapFactory.decodeStream(inputStream)`). This decoder is registered automatically. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + + // uses supplied Resources + plugin.defaultMediaDecoder(DefaultMediaDecoder.create(context.getResources())); + + // uses Resources.getSystem() + plugin.defaultMediaDecoder(DefaultMediaDecoder.create()); + } + })) + .build(); +``` + +## AsyncDrawableScheduler + +`AsyncDrawableScheduler` is used in order to give `AsyncDrawable` a way to invalidate `TextView` +that is holding it. A plugin that is dealing with `AsyncDrawable` should always call these methods: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { + AsyncDrawableScheduler.unschedule(textView); + } + + @Override + public void afterSetText(@NonNull TextView textView) { + AsyncDrawableScheduler.schedule(textView); + } + }) + .build(); +``` + +:::tip +Starting with multiple plugins can call `AsyncDrawableScheduler#schedule` +method without the penalty to process `AsyncDrawable` callbacks multiple times (internally caches +state which ensures that a `TextView` is processed only once the text has changed). +::: + +## ErrorHandler + +An `ErrorHandler` can be used to receive an error that has happened during image loading +and (optionally) return an error drawable to be displayed instead + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + plugin.errorHandler(new ImagesPlugin.ErrorHandler() { + @Nullable + @Override + public Drawable handleError(@NonNull String url, @NonNull Throwable throwable) { + return null; + } + }); + } + })) + .build(); +``` + +## PlaceholderProvider + +To display a placeholder during image loading `PlaceholderProvider` can be used: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() { + @Nullable + @Override + public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) { + return null; + } + }); + } + })) + .build(); +``` + +:::tip +If your placeholder drawable has _specific_ size which is not the same an image that is being loaded, +you can manually assign bounds to the placeholder: + +```java +plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() { + @Override + public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) { + final ColorDrawable placeholder = new ColorDrawable(Color.BLUE); + // these bounds will be used to display a placeholder, + // so even if loading image has size `width=100%`, placeholder + // bounds won't be affected by it + placeholder.setBounds(0, 0, 48, 48); + return placeholder; + } +}); +``` +::: + +--- + +:::tip +If you are using [html](/docs/v4/html/) you do not have to additionally setup +images displayed via ` ` tag, as `HtmlPlugin` automatically uses configured +image loader. But images referenced in HTML come with additional support for +sizes, which is not supported natively by markdown, allowing absolute or relative sizes: + +```html +
+``` +::: \ No newline at end of file diff --git a/docs/docs/v4/install.md b/docs/docs/v4/install.md new file mode 100644 index 00000000..c6de0e5a --- /dev/null +++ b/docs/docs/v4/install.md @@ -0,0 +1,34 @@ +--- +prev: false +next: /docs/v4/core/getting-started.md +--- + +# Installation + + + + +
+ +## Snapshot + +In order to use latest `SNAPSHOT` version add snapshot repository +to your root project's `build.gradle` file: + +```groovy +allprojects { + repositories { + jcenter() + google() + // this one 👇 + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } // 👈 this one + // this one 👆 + } +} +``` + +:::tip Info +All official artifacts share the same version number and all +are uploaded to **release** and **snapshot** repositories +::: + diff --git a/docs/docs/v4/linkify/README.md b/docs/docs/v4/linkify/README.md new file mode 100644 index 00000000..6140e4bc --- /dev/null +++ b/docs/docs/v4/linkify/README.md @@ -0,0 +1,31 @@ +# Linkify + + + +A plugin to automatically add links to your markdown. Currently autolinking works for: +* email (`me@web.com`) +* phone numbers (`+10000000`) +* web URLS + +:::warning +`Linkify` plugin is based on `android.text.util.Linkify` which can lead to significant performance +drop due to its implementation based on regex. +::: + +:::danger +Do not use `autolink` XML attribute on your `TextView` as it will remove +all links except autolinked ones ¯\\\_(ツ)_/¯ +::: + +```java +final Markwon markwon = Markwon.builder(context) + // will autolink all supported types + .usePlugin(LinkifyPlugin.create()) + // the same as above + .usePlugin(LinkifyPlugin.create( + Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS + )) + // only emails + .usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES)) + .build(); +``` \ No newline at end of file diff --git a/docs/docs/v4/recipes.md b/docs/docs/v4/recipes.md new file mode 100644 index 00000000..70a1aab6 --- /dev/null +++ b/docs/docs/v4/recipes.md @@ -0,0 +1,96 @@ +# Recipes + + +## SpannableFactory + +Consider using `NoCopySpannableFactory` when a `TextView` will be used to display markdown +multiple times (for example in a `RecyclerView`): + +```java +// call after inflation and before setting markdown +textView.setSpannableFactory(NoCopySpannableFactory.getInstance()); +``` + + +## Autolink + +Do not use `autolink` XML attribute on your `TextView` as it will remove all links except autolinked ones. +Consider using [linkify plugin](/docs/v4/linkify/) or commonmark-java [autolink extension](https://github.com/atlassian/commonmark-java) + + +## List item spacing + +If your list items, task list items or paragraphs need special space between them +(increasing spacing between them, but keeping the original line height), +`LastLineSpacingSpan` can be used: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // or Paragraph, or TaskListItem + builder.addFactory(ListItem.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new LastLineSpacingSpan(spacingPx); + } + }); + } + }) + .build(); +``` + +## Softbreak new-line + +If you want to add a new line when a `softbreak` is used: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor () { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { + visitor.forceNewLine(); + } + }); + } + }) + .build(); +``` + + +## Custom typeface + +When using a custom typeface on a `TextView` you might find that **bold** and *italic* nodes +are displayed incorrectly. Consider registering own `SpanFactories` for `StrongEmphasis` and `Emphasis` nodes: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder + .setFactory(StrongEmphasis.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new StyleSpan(Typeface.BOLD); + } + }) + .setFactory(Emphasis.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new StyleSpan(Typeface.ITALIC); + } + }); + } + }) + .build(); +``` + +Please check that `StyleSpan` works for you. If it doesn't consider +using `CustomTypefaceSpan` with your typeface directly. + + diff --git a/docs/docs/v4/recycler-table/README.md b/docs/docs/v4/recycler-table/README.md new file mode 100644 index 00000000..b8d9c193 --- /dev/null +++ b/docs/docs/v4/recycler-table/README.md @@ -0,0 +1,92 @@ +# Recycler Table + + + +Artifact that provides [MarkwonAdapter.Entry](/docs/v4/recycler/) to render `TableBlock` inside +Android-native `TableLayout` widget. + + +
+* It's possible to wrap `TableLayout` inside a `HorizontalScrollView` to include all table content + +--- + +Register instance of `TableEntry` with `MarkwonAdapter` to render TableBlocks: +```java +final MarkwonAdapter adapter = MarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text) + .include(TableBlock.class, TableEntry.create(builder -> builder + .tableLayout(R.layout.adapter_table_block, R.id.table_layout) + .textLayoutIsRoot(R.layout.view_table_entry_cell))) + .build(); +``` + +`TableEntry` requires at least 2 arguments: +* `tableLayout` - layout with `TableLayout` inside +* `textLayout` - layout with `TextView` inside (represents independent table cell) + +In case when required view is the root of layout specific builder methods can be used: +* `tableLayoutIsRoot(int)` +* `textLayoutIsRoot(int)` + +If your layouts have different structure (for example wrap a `TableView` inside a `HorizontalScrollView`) +then you should use methods that accept ID of required view inside layout: +* `tableLayout(int, int)` +* `textLayout(int, int)` + +--- + +To display `TableBlock` as a `TableLayout` specific `MarkwonPlugin` must be used: `TableEntryPlugin`. + +:::warning +Do not use `TablePlugin` if you wish to display markdown tables via `TableEntry`. Use **TableEntryPlugin** instead +::: + +`TableEntryPlugin` can reuse existing `TablePlugin` to make appearance of tables the same in both contexts: +when rendering _natively_ in a TextView and when rendering in RecyclerView with TableEntry. + +* `TableEntryPlugin.create(Context)` - creates plugin with default `TableTheme` +* `TableEntryPlugin.create(TableTheme)` - creates plugin with provided `TableTheme` +* `TableEntryPlugin.create(TablePlugin.ThemeConfigure)` - creates plugin with theme configured by `ThemeConfigure` +* `TableEntryPlugin.create(TablePlugin)` - creates plugin with `TableTheme` used in provided `TablePlugin` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(TableEntryPlugin.create(context)) + // other plugins + .build(); +``` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(TableEntryPlugin.create(builder -> builder + .tableBorderWidth(0) + .tableHeaderRowBackgroundColor(Color.RED))) + // other plugins + .build(); +``` + +## Table with scrollable content + +To stretch table columns to fit the width of screen or to make table scrollable when content exceeds screen width +this layout can be used: + +```xml ++ + +``` \ No newline at end of file diff --git a/docs/docs/v4/recycler/README.md b/docs/docs/v4/recycler/README.md new file mode 100644 index 00000000..175d333c --- /dev/null +++ b/docs/docs/v4/recycler/README.md @@ -0,0 +1,153 @@ +# Recycler+ + + + + +This artifact allows displaying markdown in a set of Android widgets +inside a RecyclerView. Can be useful when displaying lengthy markdown +content or **displaying certain markdown blocks inside specific widgets**. + +```java +// create an adapter that will use a TextView for each block of markdown +// `createTextViewIsRoot` accepts a layout in which TextView is the root view +final MarkwonAdapter adapter = + MarkwonAdapter.createTextViewIsRoot(R.layout.adapter_default_entry); +``` + +```java +// `create` method accepts a layout with TextView and ID of a TextView +// which allows wrapping a TextView inside another widget or combine with other widgets +final MarkwonAdapter adapter = + MarkwonAdapter.create(R.layout.adapter_default_entry, R.id.text_view); + +// initialize RecyclerView (LayoutManager, Decorations, etc) +final RecyclerView recyclerView = obtainRecyclerView(); + +// set adapter +recyclerView.setAdapter(adapter); + +// obtain an instance of Markwon (register all required plugins) +final Markwon markwon = obtainMarkwon(); + +// set markdown to be displayed +adapter.setMarkdown(markwon, "# This is markdown!"); + +// NB, adapter does not handle updates on its own, please use +// whatever method appropriate for you. +adapter.notifyDataSetChanged(); +``` + +Initialized adapter above will use a TextView for each markdown block. +In order to tell adapter to render certain blocks differently a `builder` can be used. +For example, let's render `FencedCodeBlock` inside a `HorizontalScrollView`: + +```java +// we still need to have a _default_ entry +final MarkwonAdapter adapter = + MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry) + .include(FencedCodeBlock.class, new FencedCodeBlockEntry()) + .build(); +``` + +where `FencedCodeBlockEntry` is: + +```java +public class FencedCodeBlockEntry extends MarkwonAdapter.Entry { + + @NonNull + @Override + public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { + return new Holder(inflater.inflate(R.layout.adapter_fenced_code_block, parent, false)); + } + + @Override + public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull FencedCodeBlock node) { + markwon.setParsedMarkdown(holder.textView, markwon.render(node)); + } + + public static class Holder extends MarkwonAdapter.Holder { + + final TextView textView; + + public Holder(@NonNull View itemView) { + super(itemView); + + this.textView = requireView(R.id.text_view); + } + } +} +``` + +and its layout (`R.layout.adapter_fenced_code_block`): + +```xml + + + + +``` + +As we apply styling to `FencedCodeBlock` _manually_, we no longer need +`Markwon` to apply styling spans for us, so `Markwon` initialization could be: + +```java +final Markwon markwon = Markwon.builder(context) + // your other plugins + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> { + // we actually won't be applying code spans here, as our custom view will + // draw background and apply mono typeface + // + // NB the `trim` operation on literal (as code will have a new line at the end) + final CharSequence code = visitor.configuration() + .syntaxHighlight() + .highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim()); + visitor.builder().append(code); + }); + } + }) + .build(); +``` + +Previously we have created a `FencedCodeBlockEntry` but all it does is apply markdown to a TextView. +For such a case there is a `SimpleEntry` that could be used instead: + +```java +final MarkwonAdapter adapter = + MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry) + .include(FencedCodeBlock.class, SimpleEntry.create(R.layout.adapter_fenced_code_block, R.id.text_view)) + .build(); +``` + +:::tip +`SimpleEntry` also takes care of _caching_ parsed markdown. So each node will be +parsed only once and each subsequent adapter binding call will reuse previously cached markdown. +::: + +:::tip Tables +There is a standalone artifact that adds support for displaying markdown tables +natively via `TableLayout`. Please refer to its [documentation](/docs/v4/recycler-table/) +::: \ No newline at end of file diff --git a/docs/docs/v4/simple-ext/README.md b/docs/docs/v4/simple-ext/README.md new file mode 100644 index 00000000..ded84bb6 --- /dev/null +++ b/docs/docs/v4/simple-ext/README.md @@ -0,0 +1,70 @@ +# Simple Extension+ + + + + +`SimpleExtPlugin` allows creating simple _delimited_ extensions, for example: + +```md ++this is text surrounded by `+`+ +``` + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(SimpleExtPlugin.create(plugin -> plugin + // +sometext+ + .addExtension(1, '+', new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new EmphasisSpan(); + } + }) + .build(); +``` + +or + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(SimpleExtPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(SimpleExtPlugin.class, new Action () { + @Override + public void apply(@NonNull SimpleExtPlugin plugin) { + plugin.addExtension(1, '+', new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new EmphasisSpan(); + } + }) + } + }); + } + }) + .build(); +``` + +If opening and closing characters are different another method can be used: + +```java +plugin.addExtension( + /*length*/2, + /*openingCharacter*/'@', + /*closingCharacter*/'$', + /*spanFactory*/(configuration, props) -> new ForegroundColorSpan(Color.RED)))) +``` + +This extension will be applied to a text like this: + +```md +@@we are inside different delimiter characters$$ +``` + +:::warning +Space character cannot be used as a delimiter (from either side). So, +```java +plugin.addExtension(1, '@', ' ', /*spanFactory*/); +``` +won't work for `@some-text ` text +::: diff --git a/docs/docs/v4/syntax-highlight/README.md b/docs/docs/v4/syntax-highlight/README.md new file mode 100644 index 00000000..e1abcca0 --- /dev/null +++ b/docs/docs/v4/syntax-highlight/README.md @@ -0,0 +1,74 @@ +# Syntax highlight + + + +This is a module to add **syntax highlight** functionality to your markdown rendered with `Markwon` library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance. + + + + +
+ +--- + +First, we need to obtain an instance of `Prism4jSyntaxHighlight` which implements Markwon's `SyntaxHighlight`: + +```java +final SyntaxHighlight highlight = + Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme); +``` + +we also can obtain an instance of `Prism4jSyntaxHighlight` that has a _fallback_ option (if a language is not defined in `Prism4j` instance, fallback language can be used): + +```java +final SyntaxHighlight highlight = + Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme, String); +``` + +Generally obtaining a `Prism4j` instance is pretty easy: + +```java +final Prism4j prism4j = new Prism4j(new GrammarLocatorDef()); +``` + +Where `GrammarLocatorDef` is a generated grammar locator (if you use `prism4j-bundler` annotation processor) + +`Prism4jTheme` is a specific type that is defined in this module (`prism4j` doesn't know anything about rendering). It has 2 implementations: + +* `Prism4jThemeDefault` +* `Prism4jThemeDarkula` + +Both of them can be obtained via factory method `create`: + +* `Prism4jThemeDefault.create()` +* `Prism4jThemeDarkula.create()` + +But of cause nothing is stopping you from defining your own theme: + +```java +public interface Prism4jTheme { + + @ColorInt + int background(); + + @ColorInt + int textColor(); + + void apply( + @NonNull String language, + @NonNull Prism4j.Syntax syntax, + @NonNull SpannableStringBuilder builder, + int start, + int end + ); +} +``` + +:::tip +You can extend `Prism4jThemeBase` which has some helper methods +::: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) +``` \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index cb90cb8f..26870ef3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,14 @@ org.gradle.jvmargs=-Xmx5g -Dfile.encoding=UTF-8 #org.gradle.parallel=true org.gradle.configureondemand=true +android.useAndroidX=true +android.enableJetifier=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=3.0.1 +VERSION_NAME=4.0.0 -GROUP=ru.noties.markwon +GROUP=io.noties.markwon POM_DESCRIPTION=Markwon markdown for Android POM_URL=https://github.com/noties/Markwon POM_SCM_URL=https://github.com/noties/Markwon diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 758de960..87b738cb 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d76b502e..c4486d47 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d5..af6708ff 100755 --- a/gradlew +++ b/gradlew @@ -28,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradlew.bat b/gradlew.bat index e95643d6..0f8d5937 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/markwon-core/README.md b/markwon-core/README.md new file mode 100644 index 00000000..1d8653c2 --- /dev/null +++ b/markwon-core/README.md @@ -0,0 +1,3 @@ +# Markwon Core + +https://noties.io/Markwon/docs/v4/core/getting-started.html \ No newline at end of file diff --git a/markwon-core/build.gradle b/markwon-core/build.gradle index 0f6f15db..946d760e 100644 --- a/markwon-core/build.gradle +++ b/markwon-core/build.gradle @@ -16,7 +16,7 @@ android { dependencies { deps.with { - api it['support-annotations'] + api it['x-annotations'] api it['commonmark'] } diff --git a/markwon-core/src/main/AndroidManifest.xml b/markwon-core/src/main/AndroidManifest.xml index c3f0b404..52c9768c 100644 --- a/markwon-core/src/main/AndroidManifest.xml +++ b/markwon-core/src/main/AndroidManifest.xml @@ -1 +1 @@ -
+ diff --git a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java b/markwon-core/src/main/java/io/noties/markwon/AbstractMarkwonPlugin.java similarity index 68% rename from markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java rename to markwon-core/src/main/java/io/noties/markwon/AbstractMarkwonPlugin.java index 5492d4d0..65db542a 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/AbstractMarkwonPlugin.java @@ -1,17 +1,14 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; import android.text.Spanned; import android.widget.TextView; +import androidx.annotation.NonNull; + import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.html.MarkwonHtmlRenderer; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.Priority; +import io.noties.markwon.core.MarkwonTheme; /** * Class that extends {@link MarkwonPlugin} with all methods implemented (empty body) @@ -22,6 +19,11 @@ import ru.noties.markwon.priority.Priority; */ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + + } + @Override public void configureParser(@NonNull Parser.Builder builder) { @@ -32,11 +34,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin { } - @Override - public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { - - } - @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { @@ -52,18 +49,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin { } - @Override - public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) { - - } - - @NonNull - @Override - public Priority priority() { - // by default all come after CorePlugin - return Priority.after(CorePlugin.class); - } - @NonNull @Override public String processMarkdown(@NonNull String markdown) { diff --git a/markwon-core/src/main/java/io/noties/markwon/LinkResolver.java b/markwon-core/src/main/java/io/noties/markwon/LinkResolver.java new file mode 100644 index 00000000..f468f872 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/LinkResolver.java @@ -0,0 +1,14 @@ +package io.noties.markwon; + +import android.view.View; + +import androidx.annotation.NonNull; + +/** + * @see LinkResolverDef + * @see MarkwonConfiguration.Builder#linkResolver(LinkResolver) + * @since 4.0.0 + */ +public interface LinkResolver { + void resolve(@NonNull View view, @NonNull String link); +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/LinkResolverDef.java b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java similarity index 74% rename from markwon-core/src/main/java/ru/noties/markwon/LinkResolverDef.java rename to markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java index 4f893761..999dcee5 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/LinkResolverDef.java +++ b/markwon-core/src/main/java/io/noties/markwon/LinkResolverDef.java @@ -1,19 +1,18 @@ -package ru.noties.markwon; +package io.noties.markwon; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.provider.Browser; -import android.support.annotation.NonNull; import android.util.Log; import android.view.View; -import ru.noties.markwon.core.spans.LinkSpan; +import androidx.annotation.NonNull; -public class LinkResolverDef implements LinkSpan.Resolver { +public class LinkResolverDef implements LinkResolver { @Override - public void resolve(View view, @NonNull String link) { + public void resolve(@NonNull View view, @NonNull String link) { final Uri uri = Uri.parse(link); final Context context = view.getContext(); final Intent intent = new Intent(Intent.ACTION_VIEW, uri); diff --git a/markwon-core/src/main/java/ru/noties/markwon/Markwon.java b/markwon-core/src/main/java/io/noties/markwon/Markwon.java similarity index 87% rename from markwon-core/src/main/java/ru/noties/markwon/Markwon.java rename to markwon-core/src/main/java/io/noties/markwon/Markwon.java index 0ac110ff..ac702599 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/Markwon.java +++ b/markwon-core/src/main/java/io/noties/markwon/Markwon.java @@ -1,14 +1,15 @@ -package ru.noties.markwon; +package io.noties.markwon; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.text.Spanned; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.commonmark.node.Node; -import ru.noties.markwon.core.CorePlugin; +import io.noties.markwon.core.CorePlugin; /** * Class to parse and render markdown. Since version 3.0.0 instance specific (previously consisted @@ -37,13 +38,26 @@ public abstract class Markwon { } /** - * Factory method to obtain an instance of {@link Builder}. + * Factory method to obtain an instance of {@link Builder} with {@link CorePlugin} added. * * @see Builder + * @see #builderNoCore(Context) * @since 3.0.0 */ @NonNull public static Builder builder(@NonNull Context context) { + return new MarkwonBuilderImpl(context) + // @since 4.0.0 add CorePlugin + .usePlugin(CorePlugin.create()); + } + + /** + * Factory method to obtain an instance of {@link Builder} without {@link CorePlugin}. + * + * @since 4.0.0 + */ + @NonNull + public static Builder builderNoCore(@NonNull Context context) { return new MarkwonBuilderImpl(context); } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java new file mode 100644 index 00000000..38ad7573 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java @@ -0,0 +1,110 @@ +package io.noties.markwon; + +import android.content.Context; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import org.commonmark.parser.Parser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import io.noties.markwon.core.MarkwonTheme; + +/** + * @since 3.0.0 + */ +class MarkwonBuilderImpl implements Markwon.Builder { + + private final Context context; + + private final List plugins = new ArrayList<>(3); + + private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE; + + MarkwonBuilderImpl(@NonNull Context context) { + this.context = context; + } + + @NonNull + @Override + public Markwon.Builder bufferType(@NonNull TextView.BufferType bufferType) { + this.bufferType = bufferType; + return this; + } + + @NonNull + @Override + public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) { + plugins.add(plugin); + return this; + } + + @NonNull + @Override + public Markwon.Builder usePlugins(@NonNull Iterable extends MarkwonPlugin> plugins) { + + final Iterator extends MarkwonPlugin> iterator = plugins.iterator(); + + MarkwonPlugin plugin; + + while (iterator.hasNext()) { + plugin = iterator.next(); + if (plugin == null) { + throw new NullPointerException(); + } + this.plugins.add(plugin); + } + + return this; + } + + @NonNull + @Override + public Markwon build() { + + if (plugins.isEmpty()) { + throw new IllegalStateException("No plugins were added to this builder. Use #usePlugin " + + "method to add them"); + } + + // please note that this method must not modify supplied collection + // if nothing should be done -> the same collection can be returned + final List plugins = preparePlugins(this.plugins); + + final Parser.Builder parserBuilder = new Parser.Builder(); + final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context); + final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder(); + final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl(); + final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl(); + + for (MarkwonPlugin plugin : plugins) { + plugin.configureParser(parserBuilder); + plugin.configureTheme(themeBuilder); + plugin.configureConfiguration(configurationBuilder); + plugin.configureVisitor(visitorBuilder); + plugin.configureSpansFactory(spanFactoryBuilder); + } + + final MarkwonConfiguration configuration = configurationBuilder.build( + themeBuilder.build(), + spanFactoryBuilder.build()); + + final RenderProps renderProps = new RenderPropsImpl(); + + return new MarkwonImpl( + bufferType, + parserBuilder.build(), + visitorBuilder.build(configuration, renderProps), + Collections.unmodifiableList(plugins) + ); + } + + @NonNull + private static List preparePlugins(@NonNull List plugins) { + return new RegistryImpl(plugins).process(); + } +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonConfiguration.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonConfiguration.java similarity index 68% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonConfiguration.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonConfiguration.java index af481ee8..5b22623f 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonConfiguration.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonConfiguration.java @@ -1,18 +1,15 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.core.spans.LinkSpan; -import ru.noties.markwon.html.MarkwonHtmlParser; -import ru.noties.markwon.html.MarkwonHtmlRenderer; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImageSizeResolver; -import ru.noties.markwon.image.ImageSizeResolverDef; -import ru.noties.markwon.syntax.SyntaxHighlight; -import ru.noties.markwon.syntax.SyntaxHighlightNoOp; -import ru.noties.markwon.urlprocessor.UrlProcessor; -import ru.noties.markwon.urlprocessor.UrlProcessorNoOp; +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.image.AsyncDrawableLoader; +import io.noties.markwon.image.ImageSizeResolver; +import io.noties.markwon.image.ImageSizeResolverDef; +import io.noties.markwon.syntax.SyntaxHighlight; +import io.noties.markwon.syntax.SyntaxHighlightNoOp; +import io.noties.markwon.urlprocessor.UrlProcessor; +import io.noties.markwon.urlprocessor.UrlProcessorNoOp; /** * since 3.0.0 renamed `SpannableConfiguration` -> `MarkwonConfiguration` @@ -28,11 +25,9 @@ public class MarkwonConfiguration { private final MarkwonTheme theme; private final AsyncDrawableLoader asyncDrawableLoader; private final SyntaxHighlight syntaxHighlight; - private final LinkSpan.Resolver linkResolver; + private final LinkResolver linkResolver; private final UrlProcessor urlProcessor; private final ImageSizeResolver imageSizeResolver; - private final MarkwonHtmlParser htmlParser; - private final MarkwonHtmlRenderer htmlRenderer; // @since 3.0.0 private final MarkwonSpansFactory spansFactory; @@ -45,8 +40,6 @@ public class MarkwonConfiguration { this.urlProcessor = builder.urlProcessor; this.imageSizeResolver = builder.imageSizeResolver; this.spansFactory = builder.spansFactory; - this.htmlParser = builder.htmlParser; - this.htmlRenderer = builder.htmlRenderer; } @NonNull @@ -65,7 +58,7 @@ public class MarkwonConfiguration { } @NonNull - public LinkSpan.Resolver linkResolver() { + public LinkResolver linkResolver() { return linkResolver; } @@ -79,16 +72,6 @@ public class MarkwonConfiguration { return imageSizeResolver; } - @NonNull - public MarkwonHtmlParser htmlParser() { - return htmlParser; - } - - @NonNull - public MarkwonHtmlRenderer htmlRenderer() { - return htmlRenderer; - } - /** * @since 3.0.0 */ @@ -103,16 +86,23 @@ public class MarkwonConfiguration { private MarkwonTheme theme; private AsyncDrawableLoader asyncDrawableLoader; private SyntaxHighlight syntaxHighlight; - private LinkSpan.Resolver linkResolver; + private LinkResolver linkResolver; private UrlProcessor urlProcessor; private ImageSizeResolver imageSizeResolver; - private MarkwonHtmlParser htmlParser; - private MarkwonHtmlRenderer htmlRenderer; private MarkwonSpansFactory spansFactory; Builder() { } + /** + * @since 4.0.0 + */ + @NonNull + public Builder asyncDrawableLoader(@NonNull AsyncDrawableLoader asyncDrawableLoader) { + this.asyncDrawableLoader = asyncDrawableLoader; + return this; + } + @NonNull public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) { this.syntaxHighlight = syntaxHighlight; @@ -120,7 +110,7 @@ public class MarkwonConfiguration { } @NonNull - public Builder linkResolver(@NonNull LinkSpan.Resolver linkResolver) { + public Builder linkResolver(@NonNull LinkResolver linkResolver) { this.linkResolver = linkResolver; return this; } @@ -131,12 +121,6 @@ public class MarkwonConfiguration { return this; } - @NonNull - public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) { - this.htmlParser = htmlParser; - return this; - } - /** * @since 1.0.1 */ @@ -149,15 +133,16 @@ public class MarkwonConfiguration { @NonNull public MarkwonConfiguration build( @NonNull MarkwonTheme theme, - @NonNull AsyncDrawableLoader asyncDrawableLoader, - @NonNull MarkwonHtmlRenderer htmlRenderer, @NonNull MarkwonSpansFactory spansFactory) { this.theme = theme; - this.asyncDrawableLoader = asyncDrawableLoader; - this.htmlRenderer = htmlRenderer; this.spansFactory = spansFactory; + // @since 4.0.0 + if (asyncDrawableLoader == null) { + asyncDrawableLoader = AsyncDrawableLoader.noOp(); + } + if (syntaxHighlight == null) { syntaxHighlight = new SyntaxHighlightNoOp(); } @@ -174,10 +159,6 @@ public class MarkwonConfiguration { imageSizeResolver = new ImageSizeResolverDef(); } - if (htmlParser == null) { - htmlParser = MarkwonHtmlParser.noOp(); - } - return new MarkwonConfiguration(this); } } diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java similarity index 95% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java index e90b7898..199beb0b 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java @@ -1,10 +1,11 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.text.Spanned; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.commonmark.node.Node; import org.commonmark.parser.Parser; diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonPlugin.java similarity index 72% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonPlugin.java index 0804848b..33527a9b 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonPlugin.java @@ -1,30 +1,58 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; import android.text.Spanned; import android.widget.TextView; +import androidx.annotation.NonNull; + import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.html.MarkwonHtmlRenderer; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.MediaDecoder; -import ru.noties.markwon.image.SchemeHandler; -import ru.noties.markwon.priority.Priority; +import io.noties.markwon.core.CorePlugin; +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.image.AsyncDrawableSpan; +import io.noties.markwon.movement.MovementMethodPlugin; /** * Class represents a plugin (extension) to Markwon to configure how parsing and rendering * of markdown is carried on. * * @see AbstractMarkwonPlugin - * @see ru.noties.markwon.core.CorePlugin - * @see ru.noties.markwon.image.ImagesPlugin + * @see CorePlugin + * @see MovementMethodPlugin * @since 3.0.0 */ public interface MarkwonPlugin { + /** + * @see Registry#require(Class, Action) + * @since 4.0.0 + */ + interface Action { + void apply(@NonNull P p); + } + + /** + * @see #configure(Registry) + * @since 4.0.0 + */ + interface Registry { + + @NonNull +
P require(@NonNull Class
plugin); + +
void require( + @NonNull Class
plugin, + @NonNull Action super P> action); + } + + /** + * This method will be called before any other during {@link Markwon} instance construction. + * + * @since 4.0.0 + */ + void configure(@NonNull Registry registry); + /** * Method to configure
org.commonmark.parser.Parser
(for example register custom * extension, etc). @@ -39,17 +67,6 @@ public interface MarkwonPlugin { */ void configureTheme(@NonNull MarkwonTheme.Builder builder); - /** - * Configure image loading functionality. For example add new content-types - * {@link AsyncDrawableLoader.Builder#addMediaDecoder(String, MediaDecoder)}, a transport - * layer (network, file, etc) {@link AsyncDrawableLoader.Builder#addSchemeHandler(String, SchemeHandler)} - * or modify existing properties. - * - * @see AsyncDrawableLoader - * @see AsyncDrawableLoader.Builder - */ - void configureImages(@NonNull AsyncDrawableLoader.Builder builder); - /** * Configure {@link MarkwonConfiguration} * @@ -74,17 +91,6 @@ public interface MarkwonPlugin { */ void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder); - /** - * Configure {@link MarkwonHtmlRenderer} to add or remove HTML {@link ru.noties.markwon.html.TagHandler}s - * - * @see MarkwonHtmlRenderer - * @see MarkwonHtmlRenderer.Builder - */ - void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder); - - @NonNull - Priority priority(); - /** * Process input markdown and return new string to be used in parsing stage further. * Can be described aspre-processing
of markdown String. @@ -117,9 +123,9 @@ public interface MarkwonPlugin { /** * This method will be called before callingTextView#setText
. *- * It can be useful to prepare a TextView for markdown. For example {@link ru.noties.markwon.image.ImagesPlugin} - * uses this method to unregister previously registered {@link ru.noties.markwon.image.AsyncDrawableSpan} - * (if there are such spans in this TextView at this point). Or {@link ru.noties.markwon.core.CorePlugin} + * It can be useful to prepare a TextView for markdown. For example {@code ru.noties.markwon.image.ImagesPlugin} + * uses this method to unregister previously registered {@link AsyncDrawableSpan} + * (if there are such spans in this TextView at this point). Or {@link CorePlugin} * which measures ordered list numbers * * @param textView TextView to which
markdown
will be applied @@ -130,8 +136,8 @@ public interface MarkwonPlugin { /** * This method will be called after markdown was applied. *- * It can be useful to trigger certain action on spans/textView. For example {@link ru.noties.markwon.image.ImagesPlugin} - * uses this method to register {@link ru.noties.markwon.image.AsyncDrawableSpan} and start + * It can be useful to trigger certain action on spans/textView. For example {@code ru.noties.markwon.image.ImagesPlugin} + * uses this method to register {@link AsyncDrawableSpan} and start * asynchronously loading images. *
* Unlike {@link #beforeSetText(TextView, Spanned)} this method does not receive parsed markdown diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonReducer.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonReducer.java similarity index 95% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonReducer.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonReducer.java index a139e3f1..c54f8c2d 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonReducer.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonReducer.java @@ -1,6 +1,6 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.commonmark.node.Node; diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactory.java similarity index 94% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactory.java index 2ef3f47f..c4153075 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactory.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.commonmark.node.Node; diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactoryImpl.java similarity index 97% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactoryImpl.java index 85a4ab33..ab2c1f84 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactoryImpl.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.commonmark.node.Node; diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonVisitor.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java similarity index 97% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonVisitor.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java index e7a83db0..9fd6ffc7 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonVisitor.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.commonmark.node.Node; import org.commonmark.node.Visitor; diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java similarity index 98% rename from markwon-core/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java rename to markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java index d4f0bce6..42db74ac 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.commonmark.node.BlockQuote; import org.commonmark.node.BulletList; diff --git a/markwon-core/src/main/java/ru/noties/markwon/Prop.java b/markwon-core/src/main/java/io/noties/markwon/Prop.java similarity index 93% rename from markwon-core/src/main/java/ru/noties/markwon/Prop.java rename to markwon-core/src/main/java/io/noties/markwon/Prop.java index 91b4515b..bc87c2f2 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/Prop.java +++ b/markwon-core/src/main/java/io/noties/markwon/Prop.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * Class to hold data in {@link RenderProps}. Represents a certain property. diff --git a/markwon-core/src/main/java/io/noties/markwon/RegistryImpl.java b/markwon-core/src/main/java/io/noties/markwon/RegistryImpl.java new file mode 100644 index 00000000..d1d0acdb --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/RegistryImpl.java @@ -0,0 +1,116 @@ +package io.noties.markwon; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.noties.markwon.core.CorePlugin; + +// @since 4.0.0 +class RegistryImpl implements MarkwonPlugin.Registry { + + private final List
origin; + private final List plugins; + private final Set pending; + + RegistryImpl(@NonNull List origin) { + this.origin = origin; + this.plugins = new ArrayList<>(origin.size()); + this.pending = new HashSet<>(3); + } + + @NonNull + @Override + public P require(@NonNull Class
plugin) { + return get(plugin); + } + + @Override + public
void require( + @NonNull Class
plugin, + @NonNull MarkwonPlugin.Action super P> action) { + action.apply(get(plugin)); + } + + @NonNull + List
process() { + for (MarkwonPlugin plugin : origin) { + configure(plugin); + } + return plugins; + } + + private void configure(@NonNull MarkwonPlugin plugin) { + + // important -> check if it's in plugins + // if it is -> no need to configure (already configured) + + if (!plugins.contains(plugin)) { + + if (pending.contains(plugin)) { + throw new IllegalStateException("Cyclic dependency chain found: " + pending); + } + + // start tracking plugins that are pending for configuration + pending.add(plugin); + + plugin.configure(this); + + // stop pending tracking + pending.remove(plugin); + + // check again if it's included (a child might've configured it already) + // add to out-collection if not already present + // this is a bit different from `find` method as it does check for exact instance + // and not a sub-type + if (!plugins.contains(plugin)) { + // core-plugin must always be the first one (if it's present) + if (CorePlugin.class.isAssignableFrom(plugin.getClass())) { + plugins.add(0, plugin); + } else { + plugins.add(plugin); + } + } + } + } + + @NonNull + private P get(@NonNull Class
type) { + + // check if present already in plugins + // find in origin, if not found -> throw, else add to out-plugins + + P plugin = find(plugins, type); + + if (plugin == null) { + + plugin = find(origin, type); + + if (plugin == null) { + throw new IllegalStateException("Requested plugin is not added: " + + "" + type.getName() + ", plugins: " + origin); + } + + configure(plugin); + } + + return plugin; + } + + @Nullable + private static
P find( + @NonNull List
plugins, + @NonNull Class type) { + for (MarkwonPlugin plugin : plugins) { + if (type.isAssignableFrom(plugin.getClass())) { + //noinspection unchecked + return (P) plugin; + } + } + return null; + } +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/RenderProps.java b/markwon-core/src/main/java/io/noties/markwon/RenderProps.java similarity index 73% rename from markwon-core/src/main/java/ru/noties/markwon/RenderProps.java rename to markwon-core/src/main/java/io/noties/markwon/RenderProps.java index e28edbaf..b7b0a4e2 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/RenderProps.java +++ b/markwon-core/src/main/java/io/noties/markwon/RenderProps.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * @since 3.0.0 diff --git a/markwon-core/src/main/java/ru/noties/markwon/RenderPropsImpl.java b/markwon-core/src/main/java/io/noties/markwon/RenderPropsImpl.java similarity index 89% rename from markwon-core/src/main/java/ru/noties/markwon/RenderPropsImpl.java rename to markwon-core/src/main/java/io/noties/markwon/RenderPropsImpl.java index 24557975..e400e528 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/RenderPropsImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/RenderPropsImpl.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.HashMap; import java.util.Map; diff --git a/markwon-core/src/main/java/ru/noties/markwon/SpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/SpanFactory.java similarity index 62% rename from markwon-core/src/main/java/ru/noties/markwon/SpanFactory.java rename to markwon-core/src/main/java/io/noties/markwon/SpanFactory.java index 76e251e7..a9f7418b 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/SpanFactory.java +++ b/markwon-core/src/main/java/io/noties/markwon/SpanFactory.java @@ -1,7 +1,7 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * @since 3.0.0 diff --git a/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java b/markwon-core/src/main/java/io/noties/markwon/SpannableBuilder.java similarity index 98% rename from markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java rename to markwon-core/src/main/java/io/noties/markwon/SpannableBuilder.java index 3c10f779..6a15a9a8 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java +++ b/markwon-core/src/main/java/io/noties/markwon/SpannableBuilder.java @@ -1,11 +1,12 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import android.text.SpannableStringBuilder; import android.text.Spanned; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/CorePlugin.java b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java similarity index 72% rename from markwon-core/src/main/java/ru/noties/markwon/core/CorePlugin.java rename to markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java index 77fa3653..194882e3 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/CorePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java @@ -1,12 +1,13 @@ -package ru.noties.markwon.core; +package io.noties.markwon.core; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import org.commonmark.node.BlockQuote; import org.commonmark.node.BulletList; import org.commonmark.node.Code; @@ -14,6 +15,7 @@ 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; @@ -26,21 +28,27 @@ import org.commonmark.node.StrongEmphasis; import org.commonmark.node.Text; import org.commonmark.node.ThematicBreak; -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.core.factory.BlockQuoteSpanFactory; -import ru.noties.markwon.core.factory.CodeBlockSpanFactory; -import ru.noties.markwon.core.factory.CodeSpanFactory; -import ru.noties.markwon.core.factory.EmphasisSpanFactory; -import ru.noties.markwon.core.factory.HeadingSpanFactory; -import ru.noties.markwon.core.factory.LinkSpanFactory; -import ru.noties.markwon.core.factory.ListItemSpanFactory; -import ru.noties.markwon.core.factory.StrongEmphasisSpanFactory; -import ru.noties.markwon.core.factory.ThematicBreakSpanFactory; -import ru.noties.markwon.core.spans.OrderedListItemSpan; -import ru.noties.markwon.priority.Priority; +import java.util.ArrayList; +import java.util.List; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.SpannableBuilder; +import io.noties.markwon.core.factory.BlockQuoteSpanFactory; +import io.noties.markwon.core.factory.CodeBlockSpanFactory; +import io.noties.markwon.core.factory.CodeSpanFactory; +import io.noties.markwon.core.factory.EmphasisSpanFactory; +import io.noties.markwon.core.factory.HeadingSpanFactory; +import io.noties.markwon.core.factory.LinkSpanFactory; +import io.noties.markwon.core.factory.ListItemSpanFactory; +import io.noties.markwon.core.factory.StrongEmphasisSpanFactory; +import io.noties.markwon.core.factory.ThematicBreakSpanFactory; +import io.noties.markwon.core.spans.OrderedListItemSpan; +import io.noties.markwon.image.ImageProps; /** * @see CoreProps @@ -48,14 +56,57 @@ import ru.noties.markwon.priority.Priority; */ public class CorePlugin extends AbstractMarkwonPlugin { + /** + * @see #addOnTextAddedListener(OnTextAddedListener) + * @since 4.0.0 + */ + public interface OnTextAddedListener { + + /** + * Will be called when new text is added to resulting {@link SpannableBuilder}. + * Please note that only text represented by {@link Text} node will trigger this callback + * (text inside code and code-blocks won\'t trigger it). + *
+ * Please note that if you wish to add spans you must use {@code start} parameter + * in order to place spans correctly ({@code start} represents the index at which {@code text} + * was added). So, to set a span for the whole length of the text added one should use: + *
+ * {@code + * visitor.builder().setSpan(new MySpan(), start, start + text.length(), 0); + * } + * + * @param visitor {@link MarkwonVisitor} + * @param text literal that had been added + * @param start index in {@code visitor} as which text had been added + * @see #addOnTextAddedListener(OnTextAddedListener) + */ + void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start); + } + @NonNull public static CorePlugin create() { return new CorePlugin(); } + // @since 4.0.0 + private final List
onTextAddedListeners = new ArrayList<>(0); + protected CorePlugin() { } + /** + * Can be useful to post-process text added. For example for auto-linking capabilities. + * + * @see OnTextAddedListener + * @since 4.0.0 + */ + @SuppressWarnings("UnusedReturnValue") + @NonNull + public CorePlugin addOnTextAddedListener(@NonNull OnTextAddedListener onTextAddedListener) { + onTextAddedListeners.add(onTextAddedListener); + return this; + } + @Override public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { text(builder); @@ -65,6 +116,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { code(builder); fencedCodeBlock(builder); indentedCodeBlock(builder); + image(builder); bulletList(builder); orderedList(builder); listItem(builder); @@ -95,12 +147,6 @@ public class CorePlugin extends AbstractMarkwonPlugin { .setFactory(ThematicBreak.class, new ThematicBreakSpanFactory()); } - @NonNull - @Override - public Priority priority() { - return Priority.none(); - } - @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { OrderedListItemSpan.measure(textView, markdown); @@ -116,11 +162,23 @@ public class CorePlugin extends AbstractMarkwonPlugin { } } - private static void text(@NonNull MarkwonVisitor.Builder builder) { + private void text(@NonNull MarkwonVisitor.Builder builder) { builder.on(Text.class, new MarkwonVisitor.NodeVisitor () { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) { - visitor.builder().append(text.getLiteral()); + + final String literal = text.getLiteral(); + + visitor.builder().append(literal); + + // @since 4.0.0 + if (!onTextAddedListeners.isEmpty()) { + // calculate the start position + final int length = visitor.length() - literal.length(); + for (OnTextAddedListener onTextAddedListener : onTextAddedListeners) { + onTextAddedListener.onTextAdded(visitor, literal, length); + } + } } }); } @@ -204,6 +262,53 @@ public class CorePlugin extends AbstractMarkwonPlugin { }); } + // @since 4.0.0 + // his method is moved from ImagesPlugin. Alternative implementations must set SpanFactory + // for Image node in order for this visitor to function + private static void image(MarkwonVisitor.Builder builder) { + builder.on(Image.class, new MarkwonVisitor.NodeVisitor () { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) { + + // if there is no image spanFactory, ignore + final SpanFactory spanFactory = visitor.configuration().spansFactory().get(Image.class); + if (spanFactory == null) { + visitor.visitChildren(image); + return; + } + + final int length = visitor.length(); + + visitor.visitChildren(image); + + // we must check if anything _was_ added, as we need at least one char to render + if (length == visitor.length()) { + visitor.builder().append('\uFFFC'); + } + + final MarkwonConfiguration configuration = visitor.configuration(); + + final Node parent = image.getParent(); + final boolean link = parent instanceof Link; + + final String destination = configuration + .urlProcessor() + .process(image.getDestination()); + + final RenderProps props = visitor.renderProps(); + + // apply image properties + // Please note that we explicitly set IMAGE_SIZE to null as we do not clear + // properties after we applied span (we could though) + ImageProps.DESTINATION.set(props, destination); + ImageProps.REPLACEMENT_TEXT_IS_LINK.set(props, link); + ImageProps.IMAGE_SIZE.set(props, null); + + visitor.setSpans(length, spanFactory.getSpans(configuration, props)); + } + }); + } + @VisibleForTesting static void visitCodeBlock( @NonNull MarkwonVisitor visitor, diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/CoreProps.java b/markwon-core/src/main/java/io/noties/markwon/core/CoreProps.java similarity index 92% rename from markwon-core/src/main/java/ru/noties/markwon/core/CoreProps.java rename to markwon-core/src/main/java/io/noties/markwon/core/CoreProps.java index 4a722b0f..1da57e24 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/CoreProps.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/CoreProps.java @@ -1,6 +1,6 @@ -package ru.noties.markwon.core; +package io.noties.markwon.core; -import ru.noties.markwon.Prop; +import io.noties.markwon.Prop; /** * @since 3.0.0 diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/MarkwonTheme.java b/markwon-core/src/main/java/io/noties/markwon/core/MarkwonTheme.java similarity index 97% rename from markwon-core/src/main/java/ru/noties/markwon/core/MarkwonTheme.java rename to markwon-core/src/main/java/io/noties/markwon/core/MarkwonTheme.java index f01df083..7c8142de 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/MarkwonTheme.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/MarkwonTheme.java @@ -1,20 +1,22 @@ -package ru.noties.markwon.core; +package io.noties.markwon.core; import android.content.Context; import android.graphics.Paint; import android.graphics.Typeface; -import android.support.annotation.ColorInt; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.annotation.Px; -import android.support.annotation.Size; import android.text.TextPaint; +import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Px; +import androidx.annotation.Size; + import java.util.Arrays; import java.util.Locale; -import ru.noties.markwon.utils.ColorUtils; -import ru.noties.markwon.utils.Dip; +import io.noties.markwon.MarkwonPlugin; +import io.noties.markwon.utils.ColorUtils; +import io.noties.markwon.utils.Dip; /** * Class to hold theming information for rending of markdown. @@ -23,14 +25,14 @@ import ru.noties.markwon.utils.Dip; * information holds data for core features only. But based on this other components can still use it * to display markdown consistently. * - * Since version 3.0.0 this class should not be instantiated manually. Instead a {@link ru.noties.markwon.MarkwonPlugin} - * should be used: {@link ru.noties.markwon.MarkwonPlugin#configureTheme(Builder)} + * Since version 3.0.0 this class should not be instantiated manually. Instead a {@link MarkwonPlugin} + * should be used: {@link MarkwonPlugin#configureTheme(Builder)} *
* Since version 3.0.0 properties related to strike-through, tables and HTML * are moved to specific plugins in independent artifacts * * @see CorePlugin - * @see ru.noties.markwon.MarkwonPlugin#configureTheme(Builder) + * @see MarkwonPlugin#configureTheme(Builder) */ @SuppressWarnings("WeakerAccess") public class MarkwonTheme { @@ -52,7 +54,7 @@ public class MarkwonTheme { * Create an empty instance of {@link Builder} with no default values applied *
* Since version 3.0.0 manual construction of {@link MarkwonTheme} is not required, instead a - * {@link ru.noties.markwon.MarkwonPlugin#configureTheme(Builder)} should be used in order + * {@link MarkwonPlugin#configureTheme(Builder)} should be used in order * to change certain theme properties * * @since 3.0.0 diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java b/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java similarity index 80% rename from markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java rename to markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java index 31f04081..91742773 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java @@ -1,13 +1,13 @@ -package ru.noties.markwon.core; +package io.noties.markwon.core; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.commonmark.node.Node; -import ru.noties.markwon.MarkwonVisitor; +import io.noties.markwon.MarkwonVisitor; /** - * A {@link ru.noties.markwon.MarkwonVisitor.NodeVisitor} that ensures that a markdown + * A {@link MarkwonVisitor.NodeVisitor} that ensures that a markdown * block starts with a new line, all children are visited and if further content available * ensures a new line after self. Does not render any spans * diff --git a/markwon-core/src/main/java/io/noties/markwon/core/factory/BlockQuoteSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/BlockQuoteSpanFactory.java new file mode 100644 index 00000000..4541169c --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/BlockQuoteSpanFactory.java @@ -0,0 +1,17 @@ +package io.noties.markwon.core.factory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.spans.BlockQuoteSpan; + +public class BlockQuoteSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new BlockQuoteSpan(configuration.theme()); + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/core/factory/CodeBlockSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/CodeBlockSpanFactory.java new file mode 100644 index 00000000..656b1ade --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/CodeBlockSpanFactory.java @@ -0,0 +1,17 @@ +package io.noties.markwon.core.factory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.spans.CodeBlockSpan; + +public class CodeBlockSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new CodeBlockSpan(configuration.theme()); + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/core/factory/CodeSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/CodeSpanFactory.java new file mode 100644 index 00000000..6c3615b1 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/CodeSpanFactory.java @@ -0,0 +1,17 @@ +package io.noties.markwon.core.factory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.spans.CodeSpan; + +public class CodeSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new CodeSpan(configuration.theme()); + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/core/factory/EmphasisSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/EmphasisSpanFactory.java new file mode 100644 index 00000000..9ea0c609 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/EmphasisSpanFactory.java @@ -0,0 +1,17 @@ +package io.noties.markwon.core.factory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.spans.EmphasisSpan; + +public class EmphasisSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new EmphasisSpan(); + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/core/factory/HeadingSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/HeadingSpanFactory.java new file mode 100644 index 00000000..d0f7df78 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/HeadingSpanFactory.java @@ -0,0 +1,21 @@ +package io.noties.markwon.core.factory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.core.spans.HeadingSpan; + +public class HeadingSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new HeadingSpan( + configuration.theme(), + CoreProps.HEADING_LEVEL.require(props) + ); + } +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/LinkSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/LinkSpanFactory.java similarity index 52% rename from markwon-core/src/main/java/ru/noties/markwon/core/factory/LinkSpanFactory.java rename to markwon-core/src/main/java/io/noties/markwon/core/factory/LinkSpanFactory.java index ee97ba34..fe93ef77 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/LinkSpanFactory.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/LinkSpanFactory.java @@ -1,13 +1,13 @@ -package ru.noties.markwon.core.factory; +package io.noties.markwon.core.factory; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.CoreProps; -import ru.noties.markwon.core.spans.LinkSpan; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.core.spans.LinkSpan; public class LinkSpanFactory implements SpanFactory { @Nullable diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/ListItemSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/ListItemSpanFactory.java similarity index 71% rename from markwon-core/src/main/java/ru/noties/markwon/core/factory/ListItemSpanFactory.java rename to markwon-core/src/main/java/io/noties/markwon/core/factory/ListItemSpanFactory.java index 06d73d2e..24cac063 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/ListItemSpanFactory.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/ListItemSpanFactory.java @@ -1,14 +1,14 @@ -package ru.noties.markwon.core.factory; +package io.noties.markwon.core.factory; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.CoreProps; -import ru.noties.markwon.core.spans.BulletListItemSpan; -import ru.noties.markwon.core.spans.OrderedListItemSpan; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.core.spans.BulletListItemSpan; +import io.noties.markwon.core.spans.OrderedListItemSpan; public class ListItemSpanFactory implements SpanFactory { diff --git a/markwon-core/src/main/java/io/noties/markwon/core/factory/StrongEmphasisSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/StrongEmphasisSpanFactory.java new file mode 100644 index 00000000..46cadf17 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/StrongEmphasisSpanFactory.java @@ -0,0 +1,17 @@ +package io.noties.markwon.core.factory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.spans.StrongEmphasisSpan; + +public class StrongEmphasisSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new StrongEmphasisSpan(); + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/core/factory/ThematicBreakSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/core/factory/ThematicBreakSpanFactory.java new file mode 100644 index 00000000..2b141a38 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/factory/ThematicBreakSpanFactory.java @@ -0,0 +1,17 @@ +package io.noties.markwon.core.factory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.spans.ThematicBreakSpan; + +public class ThematicBreakSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new ThematicBreakSpan(configuration.theme()); + } +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/BlockQuoteSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/BlockQuoteSpan.java similarity index 91% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/BlockQuoteSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/BlockQuoteSpan.java index ea4e353c..9f649d55 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/BlockQuoteSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/BlockQuoteSpan.java @@ -1,13 +1,14 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.support.annotation.NonNull; import android.text.Layout; import android.text.style.LeadingMarginSpan; -import ru.noties.markwon.core.MarkwonTheme; +import androidx.annotation.NonNull; + +import io.noties.markwon.core.MarkwonTheme; public class BlockQuoteSpan implements LeadingMarginSpan { diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/BulletListItemSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/BulletListItemSpan.java similarity index 94% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/BulletListItemSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/BulletListItemSpan.java index 86a6c81b..6d8da746 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/BulletListItemSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/BulletListItemSpan.java @@ -1,16 +1,17 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; import android.text.Layout; import android.text.style.LeadingMarginSpan; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.utils.LeadingMarginUtils; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; + +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.utils.LeadingMarginUtils; public class BulletListItemSpan implements LeadingMarginSpan { diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/CodeBlockSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/CodeBlockSpan.java similarity index 92% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/CodeBlockSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/CodeBlockSpan.java index 00109766..c358a27e 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/CodeBlockSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/CodeBlockSpan.java @@ -1,15 +1,16 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.support.annotation.NonNull; import android.text.Layout; import android.text.TextPaint; import android.text.style.LeadingMarginSpan; import android.text.style.MetricAffectingSpan; -import ru.noties.markwon.core.MarkwonTheme; +import androidx.annotation.NonNull; + +import io.noties.markwon.core.MarkwonTheme; /** * @since 3.0.0 split inline and block spans diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/CodeSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/CodeSpan.java similarity index 83% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/CodeSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/CodeSpan.java index 856d5807..455fe34b 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/CodeSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/CodeSpan.java @@ -1,10 +1,11 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; -import android.support.annotation.NonNull; import android.text.TextPaint; import android.text.style.MetricAffectingSpan; -import ru.noties.markwon.core.MarkwonTheme; +import androidx.annotation.NonNull; + +import io.noties.markwon.core.MarkwonTheme; /** * @since 3.0.0 split inline and block spans diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/CustomTypefaceSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/CustomTypefaceSpan.java similarity index 92% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/CustomTypefaceSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/CustomTypefaceSpan.java index 99ae2111..b12a80d6 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/CustomTypefaceSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/CustomTypefaceSpan.java @@ -1,10 +1,11 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Typeface; -import android.support.annotation.NonNull; import android.text.TextPaint; import android.text.style.MetricAffectingSpan; +import androidx.annotation.NonNull; + /** * A span implementation that allow applying custom Typeface. Although it is * not used directly by the library, it\'s helpful for customizations. diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/EmphasisSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/EmphasisSpan.java similarity index 90% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/EmphasisSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/EmphasisSpan.java index cb7f9ac6..f0275228 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/EmphasisSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/EmphasisSpan.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.text.TextPaint; import android.text.style.MetricAffectingSpan; diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/HeadingSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/HeadingSpan.java similarity index 90% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/HeadingSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/HeadingSpan.java index a942cc64..cb723a30 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/HeadingSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/HeadingSpan.java @@ -1,17 +1,18 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; import android.text.Layout; import android.text.TextPaint; import android.text.style.LeadingMarginSpan; import android.text.style.MetricAffectingSpan; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.utils.LeadingMarginUtils; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; + +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.utils.LeadingMarginUtils; public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan { diff --git a/markwon-core/src/main/java/io/noties/markwon/core/spans/LastLineSpacingSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/LastLineSpacingSpan.java new file mode 100644 index 00000000..6ef71dea --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/LastLineSpacingSpan.java @@ -0,0 +1,43 @@ +package io.noties.markwon.core.spans; + +import android.graphics.Paint; +import android.text.Spanned; +import android.text.style.LineHeightSpan; + +import androidx.annotation.NonNull; +import androidx.annotation.Px; + +/** + * @since 4.0.0 + */ +public class LastLineSpacingSpan implements LineHeightSpan { + + @NonNull + public static LastLineSpacingSpan create(@Px int spacing) { + return new LastLineSpacingSpan(spacing); + } + + private final int spacing; + + public LastLineSpacingSpan(@Px int spacing) { + this.spacing = spacing; + } + + @Override + public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { + if (selfEnd(end, text, this)) { + // let's just add what we want + fm.descent += spacing; + fm.bottom += spacing; + } + } + + private static boolean selfEnd(int end, CharSequence text, Object span) { + // this is some kind of interesting magic here... only the last + // span will receive correct _end_ argument, but previous spans + // receive it tilted by one (1). Most likely it's just a new-line character... and + // if needed we could check for that + final int spanEnd = ((Spanned) text).getSpanEnd(span); + return spanEnd == end || spanEnd == end - 1; + } +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/LinkSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/LinkSpan.java similarity index 59% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/LinkSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/LinkSpan.java index e8f7d8f7..f8483423 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/LinkSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/LinkSpan.java @@ -1,23 +1,24 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; -import android.support.annotation.NonNull; import android.text.TextPaint; import android.text.style.URLSpan; import android.view.View; -import ru.noties.markwon.core.MarkwonTheme; +import androidx.annotation.NonNull; + +import io.noties.markwon.LinkResolver; +import io.noties.markwon.core.MarkwonTheme; public class LinkSpan extends URLSpan { - public interface Resolver { - void resolve(View view, @NonNull String link); - } - private final MarkwonTheme theme; private final String link; - private final Resolver resolver; + private final LinkResolver resolver; - public LinkSpan(@NonNull MarkwonTheme theme, @NonNull String link, @NonNull Resolver resolver) { + public LinkSpan( + @NonNull MarkwonTheme theme, + @NonNull String link, + @NonNull LinkResolver resolver) { super(link); this.theme = theme; this.link = link; diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/ObjectsPool.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/ObjectsPool.java similarity index 95% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/ObjectsPool.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/ObjectsPool.java index de6f0671..aa1b0818 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/ObjectsPool.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/ObjectsPool.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Paint; import android.graphics.Rect; diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/OrderedListItemSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/OrderedListItemSpan.java similarity index 95% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/OrderedListItemSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/OrderedListItemSpan.java index 6be46fd5..abcd3287 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/OrderedListItemSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/OrderedListItemSpan.java @@ -1,16 +1,17 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Canvas; import android.graphics.Paint; -import android.support.annotation.NonNull; import android.text.Layout; import android.text.Spanned; import android.text.TextPaint; import android.text.style.LeadingMarginSpan; import android.widget.TextView; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.utils.LeadingMarginUtils; +import androidx.annotation.NonNull; + +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.utils.LeadingMarginUtils; public class OrderedListItemSpan implements LeadingMarginSpan { diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/StrongEmphasisSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/StrongEmphasisSpan.java similarity index 90% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/StrongEmphasisSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/StrongEmphasisSpan.java index d74ee63e..6b837fea 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/StrongEmphasisSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/StrongEmphasisSpan.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.text.TextPaint; import android.text.style.MetricAffectingSpan; diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/ThematicBreakSpan.java b/markwon-core/src/main/java/io/noties/markwon/core/spans/ThematicBreakSpan.java similarity index 91% rename from markwon-core/src/main/java/ru/noties/markwon/core/spans/ThematicBreakSpan.java rename to markwon-core/src/main/java/io/noties/markwon/core/spans/ThematicBreakSpan.java index 0e06537b..6b3bcc5c 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/ThematicBreakSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/spans/ThematicBreakSpan.java @@ -1,13 +1,14 @@ -package ru.noties.markwon.core.spans; +package io.noties.markwon.core.spans; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.support.annotation.NonNull; import android.text.Layout; import android.text.style.LeadingMarginSpan; -import ru.noties.markwon.core.MarkwonTheme; +import androidx.annotation.NonNull; + +import io.noties.markwon.core.MarkwonTheme; public class ThematicBreakSpan implements LeadingMarginSpan { diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java similarity index 78% rename from markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java rename to markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java index 25cf6363..62ea138a 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawable.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.graphics.Canvas; import android.graphics.ColorFilter; @@ -6,9 +6,10 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; public class AsyncDrawable extends Drawable { @@ -32,7 +33,7 @@ public class AsyncDrawable extends Drawable { public AsyncDrawable( @NonNull String destination, @NonNull AsyncDrawableLoader loader, - @Nullable ImageSizeResolver imageSizeResolver, + @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize ) { this.destination = destination; @@ -40,7 +41,7 @@ public class AsyncDrawable extends Drawable { this.imageSizeResolver = imageSizeResolver; this.imageSize = imageSize; - final Drawable placeholder = loader.placeholder(); + final Drawable placeholder = loader.placeholder(this); if (placeholder != null) { setPlaceholderResult(placeholder); } @@ -51,6 +52,45 @@ public class AsyncDrawable extends Drawable { return destination; } + /** + * @since 4.0.0 + */ + @Nullable + public ImageSize getImageSize() { + return imageSize; + } + + /** + * @since 4.0.0 + */ + @NonNull + public ImageSizeResolver getImageSizeResolver() { + return imageSizeResolver; + } + + /** + * @since 4.0.0 + */ + public boolean hasKnownDimentions() { + return canvasWidth > 0; + } + + /** + * @see #hasKnownDimentions() + * @since 4.0.0 + */ + public int getLastKnownCanvasWidth() { + return canvasWidth; + } + + /** + * @see #hasKnownDimentions() + * @since 4.0.0 + */ + public float getLastKnowTextSize() { + return textSize; + } + public Drawable getResult() { return result; } @@ -80,7 +120,7 @@ public class AsyncDrawable extends Drawable { result.setCallback(callback); } - loader.load(destination, this); + loader.load(this); } else { if (result != null) { @@ -91,7 +131,7 @@ public class AsyncDrawable extends Drawable { ((Animatable) result).stop(); } } - loader.cancel(destination); + loader.cancel(this); } } @@ -222,7 +262,8 @@ public class AsyncDrawable extends Drawable { if (hasResult()) { out = result.getIntrinsicWidth(); } else { - out = 0; + // @since 4.0.0, must not be zero in order to receive canvas dimensions + out = 1; } return out; } @@ -233,7 +274,8 @@ public class AsyncDrawable extends Drawable { if (hasResult()) { out = result.getIntrinsicHeight(); } else { - out = 0; + // @since 4.0.0, must not be zero in order to receive canvas dimensions + out = 1; } return out; } @@ -243,12 +285,21 @@ public class AsyncDrawable extends Drawable { */ @NonNull private Rect resolveBounds() { - // @since 2.0.0 previously we were checking if image is greater than canvas width here // but as imageSizeResolver won't be null anymore, we should transfer this logic // there - return imageSizeResolver != null - ? imageSizeResolver.resolveImageSize(imageSize, result.getBounds(), canvasWidth, textSize) - : result.getBounds(); + return imageSizeResolver.resolveImageSize(this); + } + + @Override + public String toString() { + return "AsyncDrawable{" + + "destination='" + destination + '\'' + + ", imageSize=" + imageSize + + ", result=" + result + + ", canvasWidth=" + canvasWidth + + ", textSize=" + textSize + + ", waitingForDimensions=" + waitingForDimensions + + '}'; } } diff --git a/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableLoader.java b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableLoader.java new file mode 100644 index 00000000..9907202f --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableLoader.java @@ -0,0 +1,31 @@ +package io.noties.markwon.image; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public abstract class AsyncDrawableLoader { + + /** + * @since 3.0.0 + */ + @NonNull + public static AsyncDrawableLoader noOp() { + return new AsyncDrawableLoaderNoOp(); + } + + /** + * @since 4.0.0 + */ + public abstract void load(@NonNull AsyncDrawable drawable); + + /** + * @since 4.0.0 + */ + public abstract void cancel(@NonNull AsyncDrawable drawable); + + @Nullable + public abstract Drawable placeholder(@NonNull AsyncDrawable drawable); + +} diff --git a/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableLoaderNoOp.java b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableLoaderNoOp.java new file mode 100644 index 00000000..2c5c57d0 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableLoaderNoOp.java @@ -0,0 +1,24 @@ +package io.noties.markwon.image; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader { + @Override + public void load(@NonNull AsyncDrawable drawable) { + + } + + @Override + public void cancel(@NonNull AsyncDrawable drawable) { + + } + + @Nullable + @Override + public Drawable placeholder(@NonNull AsyncDrawable drawable) { + return null; + } +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableScheduler.java b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableScheduler.java similarity index 59% rename from markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableScheduler.java rename to markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableScheduler.java index 0093abed..a7d664e3 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableScheduler.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableScheduler.java @@ -1,27 +1,43 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Looper; import android.os.SystemClock; -import android.support.annotation.NonNull; import android.text.Spanned; -import android.text.style.DynamicDrawableSpan; import android.view.View; import android.widget.TextView; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import ru.noties.markwon.renderer.R; +import io.noties.markwon.R; public abstract class AsyncDrawableScheduler { public static void schedule(@NonNull final TextView textView) { - final List
list = extract(textView); - if (list.size() > 0) { + // we need a simple check if current text has already scheduled drawables + // we need this in order to allow multiple calls to schedule (different plugins + // might use AsyncDrawable), but we do not want to repeat the task + // + // hm... we need the same thing for unschedule then... we can check if last hash is !null, + // if it's not -> unschedule, else ignore + + // @since 4.0.0 + final Integer lastTextHashCode = + (Integer) textView.getTag(R.id.markwon_drawables_scheduler_last_text_hashcode); + final int textHashCode = textView.getText().hashCode(); + if (lastTextHashCode != null + && lastTextHashCode == textHashCode) { + return; + } + textView.setTag(R.id.markwon_drawables_scheduler_last_text_hashcode, textHashCode); + + + final AsyncDrawableSpan[] spans = extractSpans(textView); + if (spans != null + && spans.length > 0) { if (textView.getTag(R.id.markwon_drawables_scheduler) == null) { final View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() { @@ -41,7 +57,10 @@ public abstract class AsyncDrawableScheduler { textView.setTag(R.id.markwon_drawables_scheduler, listener); } - for (AsyncDrawable drawable : list) { + AsyncDrawable drawable; + + for (AsyncDrawableSpan span : spans) { + drawable = span.getDrawable(); drawable.setCallback2(new DrawableCallbackImpl(textView, drawable.getBounds())); } } @@ -49,57 +68,40 @@ public abstract class AsyncDrawableScheduler { // must be called when text manually changed in TextView public static void unschedule(@NonNull TextView view) { - for (AsyncDrawable drawable : extract(view)) { - drawable.setCallback2(null); + + // @since 4.0.0 + if (view.getTag(R.id.markwon_drawables_scheduler_last_text_hashcode) == null) { + return; + } + view.setTag(R.id.markwon_drawables_scheduler_last_text_hashcode, null); + + + final AsyncDrawableSpan[] spans = extractSpans(view); + if (spans != null + && spans.length > 0) { + for (AsyncDrawableSpan span : spans) { + span.getDrawable().setCallback2(null); + } } } - private static List extract(@NonNull TextView view) { + @Nullable + private static AsyncDrawableSpan[] extractSpans(@NonNull TextView textView) { - final List list; - - final CharSequence cs = view.getText(); + final CharSequence cs = textView.getText(); final int length = cs != null ? cs.length() : 0; - if (length == 0 || !(cs instanceof Spanned)) { - //noinspection unchecked - list = Collections.EMPTY_LIST; - } else { - - final List drawables = new ArrayList<>(2); - - final Spanned spanned = (Spanned) cs; - final AsyncDrawableSpan[] asyncDrawableSpans = spanned.getSpans(0, length, AsyncDrawableSpan.class); - if (asyncDrawableSpans != null - && asyncDrawableSpans.length > 0) { - for (AsyncDrawableSpan span : asyncDrawableSpans) { - drawables.add(span.getDrawable()); - } - } - - final DynamicDrawableSpan[] dynamicDrawableSpans = spanned.getSpans(0, length, DynamicDrawableSpan.class); - if (dynamicDrawableSpans != null - && dynamicDrawableSpans.length > 0) { - for (DynamicDrawableSpan span : dynamicDrawableSpans) { - final Drawable d = span.getDrawable(); - if (d != null - && d instanceof AsyncDrawable) { - drawables.add((AsyncDrawable) d); - } - } - } - - if (drawables.size() == 0) { - //noinspection unchecked - list = Collections.EMPTY_LIST; - } else { - list = drawables; - } + if (length == 0 + || !(cs instanceof Spanned)) { + return null; } - return list; + // we also could've tried the `nextSpanTransition`, but strangely it leads to worse performance + // than direct getSpans + + return ((Spanned) cs).getSpans(0, length, AsyncDrawableSpan.class); } private AsyncDrawableScheduler() { diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableSpan.java b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableSpan.java similarity index 94% rename from markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableSpan.java rename to markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableSpan.java index a268ef19..0d70b62a 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableSpan.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/AsyncDrawableSpan.java @@ -1,18 +1,19 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.support.annotation.IntDef; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.text.style.ReplacementSpan; +import androidx.annotation.IntDef; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import ru.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.core.MarkwonTheme; @SuppressWarnings("WeakerAccess") public class AsyncDrawableSpan extends ReplacementSpan { diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java b/markwon-core/src/main/java/io/noties/markwon/image/DrawableUtils.java similarity index 85% rename from markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java rename to markwon-core/src/main/java/io/noties/markwon/image/DrawableUtils.java index a5134d52..4017a669 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/DrawableUtils.java @@ -1,9 +1,10 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.support.annotation.CheckResult; -import android.support.annotation.NonNull; + +import androidx.annotation.CheckResult; +import androidx.annotation.NonNull; /** * @since 3.0.1 diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageProps.java b/markwon-core/src/main/java/io/noties/markwon/image/ImageProps.java similarity index 85% rename from markwon-core/src/main/java/ru/noties/markwon/image/ImageProps.java rename to markwon-core/src/main/java/io/noties/markwon/image/ImageProps.java index 7bb9bfd1..2c1343c3 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageProps.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/ImageProps.java @@ -1,6 +1,6 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; -import ru.noties.markwon.Prop; +import io.noties.markwon.Prop; /** * @since 3.0.0 diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSize.java b/markwon-core/src/main/java/io/noties/markwon/image/ImageSize.java similarity index 92% rename from markwon-core/src/main/java/ru/noties/markwon/image/ImageSize.java rename to markwon-core/src/main/java/io/noties/markwon/image/ImageSize.java index ec44ed76..62b34292 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSize.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/ImageSize.java @@ -1,6 +1,6 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; /** * @since 1.0.1 diff --git a/markwon-core/src/main/java/io/noties/markwon/image/ImageSizeResolver.java b/markwon-core/src/main/java/io/noties/markwon/image/ImageSizeResolver.java new file mode 100644 index 00000000..cc7e7060 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/image/ImageSizeResolver.java @@ -0,0 +1,19 @@ +package io.noties.markwon.image; + +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +/** + * @see ImageSizeResolverDef + * @see io.noties.markwon.MarkwonConfiguration.Builder#imageSizeResolver(ImageSizeResolver) + * @since 1.0.1 + */ +public abstract class ImageSizeResolver { + + /** + * @since 4.0.0 + */ + @NonNull + public abstract Rect resolveImageSize(@NonNull AsyncDrawable drawable); +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSizeResolverDef.java b/markwon-core/src/main/java/io/noties/markwon/image/ImageSizeResolverDef.java similarity index 86% rename from markwon-core/src/main/java/ru/noties/markwon/image/ImageSizeResolverDef.java rename to markwon-core/src/main/java/io/noties/markwon/image/ImageSizeResolverDef.java index bdf7ae48..52d6abac 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSizeResolverDef.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/ImageSizeResolverDef.java @@ -1,8 +1,9 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.graphics.Rect; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * @since 1.0.1 @@ -16,7 +17,16 @@ public class ImageSizeResolverDef extends ImageSizeResolver { @NonNull @Override - public Rect resolveImageSize( + public Rect resolveImageSize(@NonNull AsyncDrawable drawable) { + return resolveImageSize( + drawable.getImageSize(), + drawable.getResult().getBounds(), + drawable.getLastKnownCanvasWidth(), + drawable.getLastKnowTextSize()); + } + + @NonNull + protected Rect resolveImageSize( @Nullable ImageSize imageSize, @NonNull Rect imageBounds, int canvasWidth, diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java b/markwon-core/src/main/java/io/noties/markwon/image/ImageSpanFactory.java similarity index 74% rename from markwon-core/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java rename to markwon-core/src/main/java/io/noties/markwon/image/ImageSpanFactory.java index 5af5b0ec..b1e38362 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java +++ b/markwon-core/src/main/java/io/noties/markwon/image/ImageSpanFactory.java @@ -1,11 +1,11 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; public class ImageSpanFactory implements SpanFactory { @Nullable diff --git a/markwon-core/src/main/java/ru/noties/markwon/movement/MovementMethodPlugin.java b/markwon-core/src/main/java/io/noties/markwon/movement/MovementMethodPlugin.java similarity index 89% rename from markwon-core/src/main/java/ru/noties/markwon/movement/MovementMethodPlugin.java rename to markwon-core/src/main/java/io/noties/markwon/movement/MovementMethodPlugin.java index 59dd04ab..0f9a7e10 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/movement/MovementMethodPlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/movement/MovementMethodPlugin.java @@ -1,12 +1,13 @@ -package ru.noties.markwon.movement; +package io.noties.markwon.movement; -import android.support.annotation.NonNull; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.method.MovementMethod; import android.widget.TextView; -import ru.noties.markwon.AbstractMarkwonPlugin; +import androidx.annotation.NonNull; + +import io.noties.markwon.AbstractMarkwonPlugin; /** * @since 3.0.0 diff --git a/markwon-core/src/main/java/ru/noties/markwon/syntax/SyntaxHighlight.java b/markwon-core/src/main/java/io/noties/markwon/syntax/SyntaxHighlight.java similarity index 56% rename from markwon-core/src/main/java/ru/noties/markwon/syntax/SyntaxHighlight.java rename to markwon-core/src/main/java/io/noties/markwon/syntax/SyntaxHighlight.java index cf6921ee..5a7dd839 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/syntax/SyntaxHighlight.java +++ b/markwon-core/src/main/java/io/noties/markwon/syntax/SyntaxHighlight.java @@ -1,7 +1,7 @@ -package ru.noties.markwon.syntax; +package io.noties.markwon.syntax; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; @SuppressWarnings("WeakerAccess") public interface SyntaxHighlight { diff --git a/markwon-core/src/main/java/ru/noties/markwon/syntax/SyntaxHighlightNoOp.java b/markwon-core/src/main/java/io/noties/markwon/syntax/SyntaxHighlightNoOp.java similarity index 62% rename from markwon-core/src/main/java/ru/noties/markwon/syntax/SyntaxHighlightNoOp.java rename to markwon-core/src/main/java/io/noties/markwon/syntax/SyntaxHighlightNoOp.java index 48a80ee2..c3787bbc 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/syntax/SyntaxHighlightNoOp.java +++ b/markwon-core/src/main/java/io/noties/markwon/syntax/SyntaxHighlightNoOp.java @@ -1,7 +1,7 @@ -package ru.noties.markwon.syntax; +package io.noties.markwon.syntax; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; public class SyntaxHighlightNoOp implements SyntaxHighlight { @NonNull diff --git a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessor.java b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessor.java similarity index 54% rename from markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessor.java rename to markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessor.java index 9ea7919e..b49585e5 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessor.java +++ b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessor.java @@ -1,6 +1,6 @@ -package ru.noties.markwon.urlprocessor; +package io.noties.markwon.urlprocessor; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; public interface UrlProcessor { @NonNull diff --git a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java similarity index 91% rename from markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java rename to markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java index 439d7f12..bd3c74cb 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java +++ b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java @@ -1,10 +1,11 @@ -package ru.noties.markwon.urlprocessor; +package io.noties.markwon.urlprocessor; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * Processor that will assume that an URL without scheme points to android assets folder. * URL with a scheme will be processed by {@link #processor} (if it is specified) or returned `as-is`. diff --git a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorNoOp.java b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorNoOp.java similarity index 68% rename from markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorNoOp.java rename to markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorNoOp.java index 9d8560d6..1bc15a88 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorNoOp.java +++ b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorNoOp.java @@ -1,6 +1,6 @@ -package ru.noties.markwon.urlprocessor; +package io.noties.markwon.urlprocessor; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; public class UrlProcessorNoOp implements UrlProcessor { @NonNull diff --git a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsolute.java b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsolute.java similarity index 87% rename from markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsolute.java rename to markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsolute.java index d99aaf4f..99a19226 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsolute.java +++ b/markwon-core/src/main/java/io/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsolute.java @@ -1,7 +1,7 @@ -package ru.noties.markwon.urlprocessor; +package io.noties.markwon.urlprocessor; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.net.MalformedURLException; import java.net.URL; diff --git a/markwon-core/src/main/java/ru/noties/markwon/utils/ColorUtils.java b/markwon-core/src/main/java/io/noties/markwon/utils/ColorUtils.java similarity index 85% rename from markwon-core/src/main/java/ru/noties/markwon/utils/ColorUtils.java rename to markwon-core/src/main/java/io/noties/markwon/utils/ColorUtils.java index d6305132..d2a8bc6e 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/utils/ColorUtils.java +++ b/markwon-core/src/main/java/io/noties/markwon/utils/ColorUtils.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.utils; +package io.noties.markwon.utils; public abstract class ColorUtils { diff --git a/markwon-core/src/main/java/ru/noties/markwon/utils/Dip.java b/markwon-core/src/main/java/io/noties/markwon/utils/Dip.java similarity index 86% rename from markwon-core/src/main/java/ru/noties/markwon/utils/Dip.java rename to markwon-core/src/main/java/io/noties/markwon/utils/Dip.java index 51d098e0..8b579356 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/utils/Dip.java +++ b/markwon-core/src/main/java/io/noties/markwon/utils/Dip.java @@ -1,7 +1,8 @@ -package ru.noties.markwon.utils; +package io.noties.markwon.utils; import android.content.Context; -import android.support.annotation.NonNull; + +import androidx.annotation.NonNull; public class Dip { diff --git a/markwon-core/src/main/java/ru/noties/markwon/utils/DrawableUtils.java b/markwon-core/src/main/java/io/noties/markwon/utils/DrawableUtils.java similarity index 69% rename from markwon-core/src/main/java/ru/noties/markwon/utils/DrawableUtils.java rename to markwon-core/src/main/java/io/noties/markwon/utils/DrawableUtils.java index 0f719339..f50a8b06 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/utils/DrawableUtils.java +++ b/markwon-core/src/main/java/io/noties/markwon/utils/DrawableUtils.java @@ -1,10 +1,11 @@ -package ru.noties.markwon.utils; +package io.noties.markwon.utils; import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; + +import androidx.annotation.NonNull; /** - * @deprecated Please use {@link ru.noties.markwon.image.DrawableUtils} + * @deprecated Please use {@link io.noties.markwon.image.DrawableUtils} */ @Deprecated public abstract class DrawableUtils { diff --git a/markwon-core/src/main/java/ru/noties/markwon/utils/DumpNodes.java b/markwon-core/src/main/java/io/noties/markwon/utils/DumpNodes.java similarity index 96% rename from markwon-core/src/main/java/ru/noties/markwon/utils/DumpNodes.java rename to markwon-core/src/main/java/io/noties/markwon/utils/DumpNodes.java index 3b45d238..ffa2bf86 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/utils/DumpNodes.java +++ b/markwon-core/src/main/java/io/noties/markwon/utils/DumpNodes.java @@ -1,7 +1,7 @@ -package ru.noties.markwon.utils; +package io.noties.markwon.utils; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.commonmark.node.Block; import org.commonmark.node.Node; diff --git a/markwon-core/src/main/java/ru/noties/markwon/utils/LeadingMarginUtils.java b/markwon-core/src/main/java/io/noties/markwon/utils/LeadingMarginUtils.java similarity index 94% rename from markwon-core/src/main/java/ru/noties/markwon/utils/LeadingMarginUtils.java rename to markwon-core/src/main/java/io/noties/markwon/utils/LeadingMarginUtils.java index bc18a140..072d2ccf 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/utils/LeadingMarginUtils.java +++ b/markwon-core/src/main/java/io/noties/markwon/utils/LeadingMarginUtils.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.utils; +package io.noties.markwon.utils; import android.text.Spanned; diff --git a/markwon-core/src/main/java/ru/noties/markwon/utils/NoCopySpannableFactory.java b/markwon-core/src/main/java/io/noties/markwon/utils/NoCopySpannableFactory.java similarity index 90% rename from markwon-core/src/main/java/ru/noties/markwon/utils/NoCopySpannableFactory.java rename to markwon-core/src/main/java/io/noties/markwon/utils/NoCopySpannableFactory.java index f5eedc2a..dcb49813 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/utils/NoCopySpannableFactory.java +++ b/markwon-core/src/main/java/io/noties/markwon/utils/NoCopySpannableFactory.java @@ -1,9 +1,10 @@ -package ru.noties.markwon.utils; +package io.noties.markwon.utils; -import android.support.annotation.NonNull; import android.text.Spannable; import android.text.SpannableString; +import androidx.annotation.NonNull; + /** * Utility SpannableFactory that re-uses Spannable instance between multiple * `TextView#setText` calls. diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java deleted file mode 100644 index a390c46e..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java +++ /dev/null @@ -1,194 +0,0 @@ -package ru.noties.markwon; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import android.widget.TextView; - -import org.commonmark.parser.Parser; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.html.MarkwonHtmlRenderer; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.PriorityProcessor; - -/** - * @since 3.0.0 - */ -@SuppressWarnings("WeakerAccess") -class MarkwonBuilderImpl implements Markwon.Builder { - - private final Context context; - - private final List plugins = new ArrayList<>(3); - - private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE; - - private PriorityProcessor priorityProcessor; - - MarkwonBuilderImpl(@NonNull Context context) { - this.context = context; - } - - @NonNull - @Override - public Markwon.Builder bufferType(@NonNull TextView.BufferType bufferType) { - this.bufferType = bufferType; - return this; - } - - @NonNull - @Override - public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) { - plugins.add(plugin); - return this; - } - - @NonNull - @Override - public Markwon.Builder usePlugins(@NonNull Iterable extends MarkwonPlugin> plugins) { - - final Iterator extends MarkwonPlugin> iterator = plugins.iterator(); - - MarkwonPlugin plugin; - - while (iterator.hasNext()) { - plugin = iterator.next(); - if (plugin == null) { - throw new NullPointerException(); - } - this.plugins.add(plugin); - } - - return this; - } - - @SuppressWarnings("UnusedReturnValue") - @NonNull - public MarkwonBuilderImpl priorityProcessor(@NonNull PriorityProcessor priorityProcessor) { - this.priorityProcessor = priorityProcessor; - return this; - } - - @NonNull - @Override - public Markwon build() { - - if (plugins.isEmpty()) { - throw new IllegalStateException("No plugins were added to this builder. Use #usePlugin " + - "method to add them"); - } - - // this class will sort plugins to match a priority/dependency graph that we have - PriorityProcessor priorityProcessor = this.priorityProcessor; - if (priorityProcessor == null) { - // strictly speaking we do not need updating this field - // as we are not building this class to be reused between multiple `build` calls - priorityProcessor = this.priorityProcessor = PriorityProcessor.create(); - } - - // please note that this method must not modify supplied collection - // if nothing should be done -> the same collection can be returned - final List plugins = preparePlugins(priorityProcessor, this.plugins); - - final Parser.Builder parserBuilder = new Parser.Builder(); - final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context); - final AsyncDrawableLoader.Builder asyncDrawableLoaderBuilder = new AsyncDrawableLoader.Builder(); - final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder(); - final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl(); - final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl(); - final MarkwonHtmlRenderer.Builder htmlRendererBuilder = MarkwonHtmlRenderer.builder(); - - for (MarkwonPlugin plugin : plugins) { - plugin.configureParser(parserBuilder); - plugin.configureTheme(themeBuilder); - plugin.configureImages(asyncDrawableLoaderBuilder); - plugin.configureConfiguration(configurationBuilder); - plugin.configureVisitor(visitorBuilder); - plugin.configureSpansFactory(spanFactoryBuilder); - plugin.configureHtmlRenderer(htmlRendererBuilder); - } - - final MarkwonConfiguration configuration = configurationBuilder.build( - themeBuilder.build(), - asyncDrawableLoaderBuilder.build(), - htmlRendererBuilder.build(), - spanFactoryBuilder.build()); - - final RenderProps renderProps = new RenderPropsImpl(); - - return new MarkwonImpl( - bufferType, - parserBuilder.build(), - visitorBuilder.build(configuration, renderProps), - Collections.unmodifiableList(plugins) - ); - } - - @VisibleForTesting - @NonNull - static List preparePlugins( - @NonNull PriorityProcessor priorityProcessor, - @NonNull List plugins) { - - // with this method we will ensure that CorePlugin is added IF and ONLY IF - // there are plugins that depend on it. If CorePlugin is added, or there are - // no plugins that require it, CorePlugin won't be added - final List out = ensureImplicitCoreIfHasDependents(plugins); - - return priorityProcessor.process(out); - } - - // this method will _implicitly_ add CorePlugin if there is at least one plugin - // that depends on CorePlugin - @VisibleForTesting - @NonNull - static List ensureImplicitCoreIfHasDependents(@NonNull List plugins) { - // loop over plugins -> if CorePlugin is found -> break; - // iterate over all plugins and check if CorePlugin is requested - - boolean hasCore = false; - boolean hasCoreDependents = false; - - for (MarkwonPlugin plugin : plugins) { - - // here we do not check for exact match (a user could've subclasses CorePlugin - // and supplied it. In this case we DO NOT implicitly add CorePlugin - // - // if core is present already we do not need to iterate anymore -> as nothing - // will be changed (and we actually do not care if there are any dependents of Core - // as it's present anyway) - if (CorePlugin.class.isAssignableFrom(plugin.getClass())) { - hasCore = true; - break; - } - - // if plugin has CorePlugin in dependencies -> mark for addition - if (!hasCoreDependents) { - // here we check for direct CorePlugin, if it's not CorePlugin (exact, not a subclass - // or something -> ignore) - if (plugin.priority().after().contains(CorePlugin.class)) { - hasCoreDependents = true; - } - } - } - - // important thing here is to check if corePlugin is added - // add it _only_ if it's not present - if (hasCoreDependents && !hasCore) { - final List out = new ArrayList<>(plugins.size() + 1); - // add default instance of CorePlugin - out.add(CorePlugin.create()); - out.addAll(plugins); - return out; - } - - return plugins; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonNodeRenderer.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonNodeRenderer.java deleted file mode 100644 index 94de3c20..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonNodeRenderer.java +++ /dev/null @@ -1,184 +0,0 @@ -package ru.noties.markwon; - -import android.content.Context; -import android.support.annotation.IdRes; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import org.commonmark.node.Node; - -import java.util.HashMap; -import java.util.Map; - -/** - * @since 3.0.0 - */ -public abstract class MarkwonNodeRenderer { - - public interface ViewProvider { - - /** - * Please note that you should not attach created View to specified group. It will be done - * automatically. - */ - @NonNull - View provide( - @NonNull LayoutInflater inflater, - @NonNull ViewGroup group, - @NonNull Markwon markwon, - @NonNull N n); - } - - @NonNull - public static Builder builder(@NonNull ViewProvider defaultViewProvider) { - return new Builder(defaultViewProvider); - } - - /** - * @param defaultViewProviderLayoutResId layout resource id to be used in default view provider - * @param defaultViewProviderTextViewId id of a TextView in specified layout - * @return Builder - * @see SimpleTextViewProvider - */ - @NonNull - public static Builder builder( - @LayoutRes int defaultViewProviderLayoutResId, - @IdRes int defaultViewProviderTextViewId) { - return new Builder(new SimpleTextViewProvider( - defaultViewProviderLayoutResId, - defaultViewProviderTextViewId)); - } - - public abstract void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull String markdown); - - public abstract void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull Node root); - - - public static class Builder { - - private final ViewProvider defaultViewProvider; - - private MarkwonReducer reducer; - private Map , ViewProvider > viewProviders; - private LayoutInflater inflater; - - public Builder(@NonNull ViewProvider defaultViewProvider) { - this.defaultViewProvider = defaultViewProvider; - this.viewProviders = new HashMap<>(3); - } - - @NonNull - public Builder reducer(@NonNull MarkwonReducer reducer) { - this.reducer = reducer; - return this; - } - - @NonNull - public Builder viewProvider( - @NonNull Class type, - @NonNull ViewProvider super N> viewProvider) { - //noinspection unchecked - viewProviders.put(type, (ViewProvider ) viewProvider); - return this; - } - - @NonNull - public Builder inflater(@NonNull LayoutInflater inflater) { - this.inflater = inflater; - return this; - } - - @NonNull - public MarkwonNodeRenderer build() { - if (reducer == null) { - reducer = MarkwonReducer.directChildren(); - } - return new Impl(this); - } - } - - public static class SimpleTextViewProvider implements ViewProvider { - - private final int layoutResId; - private final int textViewId; - - public SimpleTextViewProvider(@LayoutRes int layoutResId, @IdRes int textViewId) { - this.layoutResId = layoutResId; - this.textViewId = textViewId; - } - - @NonNull - @Override - public View provide( - @NonNull LayoutInflater inflater, - @NonNull ViewGroup group, - @NonNull Markwon markwon, - @NonNull Node node) { - final View view = inflater.inflate(layoutResId, group, false); - final TextView textView = view.findViewById(textViewId); - markwon.setParsedMarkdown(textView, markwon.render(node)); - return view; - } - } - - static class Impl extends MarkwonNodeRenderer { - - private final MarkwonReducer reducer; - private final Map , ViewProvider > viewProviders; - private final ViewProvider defaultViewProvider; - - private LayoutInflater inflater; - - Impl(@NonNull Builder builder) { - this.reducer = builder.reducer; - this.viewProviders = builder.viewProviders; - this.defaultViewProvider = builder.defaultViewProvider; - this.inflater = builder.inflater; - } - - @Override - public void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull String markdown) { - render(group, markwon, markwon.parse(markdown)); - } - - @Override - public void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull Node root) { - - final LayoutInflater inflater = ensureLayoutInflater(group.getContext()); - - ViewProvider viewProvider; - - for (Node node : reducer.reduce(root)) { - viewProvider = viewProvider(node); - group.addView(viewProvider.provide(inflater, group, markwon, node)); - } - } - - @NonNull - private LayoutInflater ensureLayoutInflater(@NonNull Context context) { - LayoutInflater inflater = this.inflater; - if (inflater == null) { - inflater = this.inflater = LayoutInflater.from(context); - } - return inflater; - } - - @NonNull - private ViewProvider viewProvider(@NonNull Node node) { - - // check for specific node view provider - final ViewProvider provider = viewProviders.get(node.getClass()); - if (provider != null) { - return provider; - } - - // if it's not present, then we can return a default one - return defaultViewProvider; - } - } - -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/BlockQuoteSpanFactory.java b/markwon-core/src/main/java/ru/noties/markwon/core/factory/BlockQuoteSpanFactory.java deleted file mode 100644 index cbe86db6..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/BlockQuoteSpanFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.core.factory; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.spans.BlockQuoteSpan; - -public class BlockQuoteSpanFactory implements SpanFactory { - @Nullable - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - return new BlockQuoteSpan(configuration.theme()); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/CodeBlockSpanFactory.java b/markwon-core/src/main/java/ru/noties/markwon/core/factory/CodeBlockSpanFactory.java deleted file mode 100644 index 2bf9383e..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/CodeBlockSpanFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.core.factory; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.spans.CodeBlockSpan; - -public class CodeBlockSpanFactory implements SpanFactory { - @Nullable - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - return new CodeBlockSpan(configuration.theme()); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/CodeSpanFactory.java b/markwon-core/src/main/java/ru/noties/markwon/core/factory/CodeSpanFactory.java deleted file mode 100644 index 944556fb..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/CodeSpanFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.core.factory; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.spans.CodeSpan; - -public class CodeSpanFactory implements SpanFactory { - @Nullable - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - return new CodeSpan(configuration.theme()); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/EmphasisSpanFactory.java b/markwon-core/src/main/java/ru/noties/markwon/core/factory/EmphasisSpanFactory.java deleted file mode 100644 index d9d8331d..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/EmphasisSpanFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.core.factory; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.spans.EmphasisSpan; - -public class EmphasisSpanFactory implements SpanFactory { - @Nullable - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - return new EmphasisSpan(); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/HeadingSpanFactory.java b/markwon-core/src/main/java/ru/noties/markwon/core/factory/HeadingSpanFactory.java deleted file mode 100644 index bd675edd..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/HeadingSpanFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.noties.markwon.core.factory; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.CoreProps; -import ru.noties.markwon.core.spans.HeadingSpan; - -public class HeadingSpanFactory implements SpanFactory { - @Nullable - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - return new HeadingSpan( - configuration.theme(), - CoreProps.HEADING_LEVEL.require(props) - ); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/StrongEmphasisSpanFactory.java b/markwon-core/src/main/java/ru/noties/markwon/core/factory/StrongEmphasisSpanFactory.java deleted file mode 100644 index c81d6121..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/StrongEmphasisSpanFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.core.factory; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.spans.StrongEmphasisSpan; - -public class StrongEmphasisSpanFactory implements SpanFactory { - @Nullable - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - return new StrongEmphasisSpan(); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/factory/ThematicBreakSpanFactory.java b/markwon-core/src/main/java/ru/noties/markwon/core/factory/ThematicBreakSpanFactory.java deleted file mode 100644 index ecd20f32..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/core/factory/ThematicBreakSpanFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.core.factory; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.spans.ThematicBreakSpan; - -public class ThematicBreakSpanFactory implements SpanFactory { - @Nullable - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - return new ThematicBreakSpan(configuration.theme()); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java deleted file mode 100644 index ecf6e423..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.noties.markwon.html; - -import android.support.annotation.NonNull; - -import java.util.Collections; - -class MarkwonHtmlParserNoOp extends MarkwonHtmlParser { - @Override - public void processFragment(@NonNull T output, @NonNull String htmlFragment) { - // no op - } - - @Override - public void flushInlineTags(int documentLength, @NonNull FlushAction action) { - action.apply(Collections. emptyList()); - } - - @Override - public void flushBlockTags(int documentLength, @NonNull FlushAction action) { - action.apply(Collections. emptyList()); - } - - @Override - public void reset() { - // no op - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java deleted file mode 100644 index 93d0411d..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.noties.markwon.html; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.Collection; - -import ru.noties.markwon.MarkwonVisitor; - -/** - * @since 2.0.0 - */ -public abstract class MarkwonHtmlRenderer { - - @NonNull - public static Builder builder() { - return new MarkwonHtmlRendererImpl.BuilderImpl(); - } - - public abstract void render( - @NonNull MarkwonVisitor visitor, - @NonNull MarkwonHtmlParser parser - ); - - @Nullable - public abstract TagHandler tagHandler(@NonNull String tagName); - - - /** - * @since 3.0.0 - */ - public interface Builder { - - /** - * @param allowNonClosedTags parameter to indicate that all non-closed HTML tags should be - * closed at the end of a document. if {@code true} all non-closed - * tags will be force-closed at the end. Otherwise these tags will be - * ignored and thus not rendered. - * @return self - */ - @NonNull - Builder allowNonClosedTags(boolean allowNonClosedTags); - - @NonNull - Builder setHandler(@NonNull String tagName, @Nullable TagHandler tagHandler); - - @NonNull - Builder setHandler(@NonNull Collection tagNames, @Nullable TagHandler tagHandler); - - @Nullable - TagHandler getHandler(@NonNull String tagName); - - @NonNull - MarkwonHtmlRenderer build(); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java deleted file mode 100644 index 8b76acc5..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java +++ /dev/null @@ -1,165 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public abstract class AsyncDrawableLoader { - - /** - * @since 3.0.0 - */ - public interface DrawableProvider { - @Nullable - Drawable provide(); - } - - /** - * @since 3.0.0 - */ - @NonNull - public static Builder builder() { - return new Builder(); - } - - /** - * @since 3.0.0 - */ - @NonNull - public static AsyncDrawableLoader noOp() { - return new AsyncDrawableLoaderNoOp(); - } - - - public abstract void load(@NonNull String destination, @NonNull AsyncDrawable drawable); - - public abstract void cancel(@NonNull String destination); - - @Nullable - public abstract Drawable placeholder(); - - public static class Builder { - - ExecutorService executorService; - final Map schemeHandlers = new HashMap<>(3); - final Map mediaDecoders = new HashMap<>(3); - MediaDecoder defaultMediaDecoder; - DrawableProvider placeholderDrawableProvider; - DrawableProvider errorDrawableProvider; - - AsyncDrawableLoader implementation; - - @NonNull - public Builder executorService(@NonNull ExecutorService executorService) { - this.executorService = executorService; - return this; - } - - @NonNull - public Builder addSchemeHandler(@NonNull String scheme, @NonNull SchemeHandler schemeHandler) { - schemeHandlers.put(scheme, schemeHandler); - return this; - } - - @NonNull - public Builder addSchemeHandler(@NonNull Collection schemes, @NonNull SchemeHandler schemeHandler) { - for (String scheme : schemes) { - schemeHandlers.put(scheme, schemeHandler); - } - return this; - } - - @NonNull - public Builder addMediaDecoder(@NonNull String contentType, @NonNull MediaDecoder mediaDecoder) { - mediaDecoders.put(contentType, mediaDecoder); - return this; - } - - @NonNull - public Builder addMediaDecoder(@NonNull Collection contentTypes, @NonNull MediaDecoder mediaDecoder) { - for (String contentType : contentTypes) { - mediaDecoders.put(contentType, mediaDecoder); - } - return this; - } - - @NonNull - public Builder removeSchemeHandler(@NonNull String scheme) { - schemeHandlers.remove(scheme); - return this; - } - - @NonNull - public Builder removeMediaDecoder(@NonNull String contentType) { - mediaDecoders.remove(contentType); - return this; - } - - @NonNull - public Builder defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) { - this.defaultMediaDecoder = mediaDecoder; - return this; - } - - /** - * @since 3.0.0 - */ - @NonNull - public Builder placeholderDrawableProvider(@NonNull DrawableProvider placeholderDrawableProvider) { - this.placeholderDrawableProvider = placeholderDrawableProvider; - return this; - } - - /** - * @since 3.0.0 - */ - @NonNull - public Builder errorDrawableProvider(@NonNull DrawableProvider errorDrawableProvider) { - this.errorDrawableProvider = errorDrawableProvider; - return this; - } - - /** - * Please note that if implementation is supplied, all configuration properties - * (scheme-handlers, media-decoders, placeholder, etc) of this builder instance - * will be ignored. - * - * @param implementation {@link AsyncDrawableLoader} implementation to be used. - * @since 3.0.1 - */ - @NonNull - public Builder implementation(@NonNull AsyncDrawableLoader implementation) { - this.implementation = implementation; - return this; - } - - @NonNull - public AsyncDrawableLoader build() { - - // NB, all other configuration properties will be ignored if - // implementation is specified - if (implementation != null) { - return implementation; - } - - // if we have no schemeHandlers -> we cannot show anything - // OR if we have no media decoders - if (schemeHandlers.size() == 0 - || (mediaDecoders.size() == 0 && defaultMediaDecoder == null)) { - return new AsyncDrawableLoaderNoOp(); - } - - if (executorService == null) { - executorService = Executors.newCachedThreadPool(); - } - - return new AsyncDrawableLoaderImpl(this); - } - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java deleted file mode 100644 index fd7c3ad1..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java +++ /dev/null @@ -1,148 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -class AsyncDrawableLoaderImpl extends AsyncDrawableLoader { - - private final ExecutorService executorService; - private final Map schemeHandlers; - private final Map mediaDecoders; - private final MediaDecoder defaultMediaDecoder; - private final DrawableProvider placeholderDrawableProvider; - private final DrawableProvider errorDrawableProvider; - - private final Handler mainThread; - - private final Map > requests = new HashMap<>(2); - - AsyncDrawableLoaderImpl(@NonNull Builder builder) { - this.executorService = builder.executorService; - this.schemeHandlers = builder.schemeHandlers; - this.mediaDecoders = builder.mediaDecoders; - this.defaultMediaDecoder = builder.defaultMediaDecoder; - this.placeholderDrawableProvider = builder.placeholderDrawableProvider; - this.errorDrawableProvider = builder.errorDrawableProvider; - this.mainThread = new Handler(Looper.getMainLooper()); - } - - @Override - public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { - // if drawable is not a link -> show loading placeholder... - requests.put(destination, execute(destination, drawable)); - } - - @Override - public void cancel(@NonNull String destination) { - final Future> request = requests.remove(destination); - if (request != null) { - request.cancel(true); - } - } - - @Nullable - @Override - public Drawable placeholder() { - return placeholderDrawableProvider != null - ? placeholderDrawableProvider.provide() - : null; - } - - private Future> execute(@NonNull final String destination, @NonNull AsyncDrawable drawable) { - - final WeakReference reference = new WeakReference (drawable); - - // todo: should we cancel pending request for the same destination? - // we _could_ but there is possibility that one resource is request in multiple places - - // todo: error handing (simply applying errorDrawable is not a good solution - // as reason for an error is unclear (no scheme handler, no input data, error decoding, etc) - - // todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal - // for big images for sure. We _could_ introduce internal Drawable that will check for - // image bounds (but we will need to cache inputStream in order to inspect and optimize - // input image...) - - return executorService.submit(new Runnable() { - @Override - public void run() { - - final ImageItem item; - - final Uri uri = Uri.parse(destination); - - final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme()); - - if (schemeHandler != null) { - item = schemeHandler.handle(destination, uri); - } else { - item = null; - } - - final InputStream inputStream = item != null - ? item.inputStream() - : null; - - Drawable result = null; - - if (inputStream != null) { - try { - - MediaDecoder mediaDecoder = mediaDecoders.get(item.contentType()); - if (mediaDecoder == null) { - mediaDecoder = defaultMediaDecoder; - } - - if (mediaDecoder != null) { - result = mediaDecoder.decode(inputStream); - } - - } finally { - try { - inputStream.close(); - } catch (IOException e) { - // ignored - } - } - } - - // if result is null, we assume it's an error - if (result == null) { - result = errorDrawableProvider != null - ? errorDrawableProvider.provide() - : null; - } - - if (result != null) { - final Drawable out = result; - mainThread.post(new Runnable() { - @Override - public void run() { - final boolean canDeliver = requests.remove(destination) != null; - if (canDeliver) { - final AsyncDrawable asyncDrawable = reference.get(); - if (asyncDrawable != null && asyncDrawable.isAttached()) { - asyncDrawable.setResult(out); - } - } - } - }); - } else { - requests.remove(destination); - } - } - }); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java deleted file mode 100644 index 74520fb2..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader { - @Override - public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { - - } - - @Override - public void cancel(@NonNull String destination) { - - } - - @Nullable - @Override - public Drawable placeholder() { - return null; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageItem.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImageItem.java deleted file mode 100644 index 2dc4b729..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageItem.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.noties.markwon.image; - -import android.support.annotation.Nullable; - -import java.io.InputStream; - -/** - * @since 2.0.0 - */ -public class ImageItem { - - private final String contentType; - private final InputStream inputStream; - - public ImageItem( - @Nullable String contentType, - @Nullable InputStream inputStream) { - this.contentType = contentType; - this.inputStream = inputStream; - } - - @Nullable - public String contentType() { - return contentType; - } - - @Nullable - public InputStream inputStream() { - return inputStream; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java deleted file mode 100644 index c510c4d8..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.noties.markwon.image; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.InputStream; - -/** - * This class can be used as the last {@link MediaDecoder} to _try_ to handle all rest cases. - * Here we just assume that supplied InputStream is of image type and try to decode it. - * - * @since 1.1.0 - */ -public class ImageMediaDecoder extends MediaDecoder { - - @NonNull - public static ImageMediaDecoder create(@NonNull Resources resources) { - return new ImageMediaDecoder(resources); - } - - private final Resources resources; - - @SuppressWarnings("WeakerAccess") - ImageMediaDecoder(Resources resources) { - this.resources = resources; - } - - @Nullable - @Override - public Drawable decode(@NonNull InputStream inputStream) { - - final Drawable out; - - // absolutely not optimal... thing - final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); - if (bitmap != null) { - out = new BitmapDrawable(resources, bitmap); - DrawableUtils.applyIntrinsicBounds(out); - } else { - out = null; - } - - return out; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSizeResolver.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImageSizeResolver.java deleted file mode 100644 index 57284a41..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSizeResolver.java +++ /dev/null @@ -1,29 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.Rect; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -/** - * @since 1.0.1 - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -public abstract class ImageSizeResolver { - - /** - * We do not expose canvas height deliberately. As we cannot rely on this value very much - * - * @param imageSize {@link ImageSize} parsed from HTML - * @param imageBounds original image bounds - * @param canvasWidth width of the canvas - * @param textSize current font size - * @return resolved image bounds - */ - @NonNull - public abstract Rect resolveImageSize( - @Nullable ImageSize imageSize, - @NonNull Rect imageBounds, - int canvasWidth, - float textSize - ); -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImagesPlugin.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImagesPlugin.java deleted file mode 100644 index 2f6e8117..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImagesPlugin.java +++ /dev/null @@ -1,130 +0,0 @@ -package ru.noties.markwon.image; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.text.Spanned; -import android.widget.TextView; - -import org.commonmark.node.Image; -import org.commonmark.node.Link; -import org.commonmark.node.Node; - -import java.util.Arrays; - -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.image.data.DataUriSchemeHandler; -import ru.noties.markwon.image.file.FileSchemeHandler; -import ru.noties.markwon.image.network.NetworkSchemeHandler; - -/** - * @since 3.0.0 - */ -public class ImagesPlugin extends AbstractMarkwonPlugin { - - @NonNull - public static ImagesPlugin create(@NonNull Context context) { - return new ImagesPlugin(context, false); - } - - /** - * Special scheme that is used {@code file:///android_asset/} - * - * @param context - * @return - */ - @NonNull - public static ImagesPlugin createWithAssets(@NonNull Context context) { - return new ImagesPlugin(context, true); - } - - private final Context context; - private final boolean useAssets; - - protected ImagesPlugin(Context context, boolean useAssets) { - this.context = context; - this.useAssets = useAssets; - } - - @Override - public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { - - final FileSchemeHandler fileSchemeHandler = useAssets - ? FileSchemeHandler.createWithAssets(context.getAssets()) - : FileSchemeHandler.create(); - - builder - .addSchemeHandler(DataUriSchemeHandler.SCHEME, DataUriSchemeHandler.create()) - .addSchemeHandler(FileSchemeHandler.SCHEME, fileSchemeHandler) - .addSchemeHandler( - Arrays.asList( - NetworkSchemeHandler.SCHEME_HTTP, - NetworkSchemeHandler.SCHEME_HTTPS), - NetworkSchemeHandler.create()) - .defaultMediaDecoder(ImageMediaDecoder.create(context.getResources())); - } - - @Override - public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { - builder.setFactory(Image.class, new ImageSpanFactory()); - } - - @Override - public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Image.class, new MarkwonVisitor.NodeVisitor () { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) { - - // if there is no image spanFactory, ignore - final SpanFactory spanFactory = visitor.configuration().spansFactory().get(Image.class); - if (spanFactory == null) { - visitor.visitChildren(image); - return; - } - - final int length = visitor.length(); - - visitor.visitChildren(image); - - // we must check if anything _was_ added, as we need at least one char to render - if (length == visitor.length()) { - visitor.builder().append('\uFFFC'); - } - - final MarkwonConfiguration configuration = visitor.configuration(); - - final Node parent = image.getParent(); - final boolean link = parent instanceof Link; - - final String destination = configuration - .urlProcessor() - .process(image.getDestination()); - - final RenderProps props = visitor.renderProps(); - - // apply image properties - // Please note that we explicitly set IMAGE_SIZE to null as we do not clear - // properties after we applied span (we could though) - ImageProps.DESTINATION.set(props, destination); - ImageProps.REPLACEMENT_TEXT_IS_LINK.set(props, link); - ImageProps.IMAGE_SIZE.set(props, null); - - visitor.setSpans(length, spanFactory.getSpans(configuration, props)); - } - }); - } - - @Override - public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { - AsyncDrawableScheduler.unschedule(textView); - } - - @Override - public void afterSetText(@NonNull TextView textView) { - AsyncDrawableScheduler.schedule(textView); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/MediaDecoder.java b/markwon-core/src/main/java/ru/noties/markwon/image/MediaDecoder.java deleted file mode 100644 index 68d0ff33..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/MediaDecoder.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.InputStream; - -/** - * @since 3.0.0 - */ -public abstract class MediaDecoder { - - @Nullable - public abstract Drawable decode(@NonNull InputStream inputStream); -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/SchemeHandler.java b/markwon-core/src/main/java/ru/noties/markwon/image/SchemeHandler.java deleted file mode 100644 index cac1c801..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/SchemeHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.noties.markwon.image; - -import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -/** - * @since 3.0.0 - */ -public abstract class SchemeHandler { - - @Nullable - public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri); -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java b/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java deleted file mode 100644 index 7e3d4f73..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.noties.markwon.image.data; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.util.Base64; - -public abstract class DataUriDecoder { - - @Nullable - public abstract byte[] decode(@NonNull DataUri dataUri); - - @NonNull - public static DataUriDecoder create() { - return new Impl(); - } - - static class Impl extends DataUriDecoder { - - @Nullable - @Override - public byte[] decode(@NonNull DataUri dataUri) { - - final String data = dataUri.data(); - - if (!TextUtils.isEmpty(data)) { - try { - if (dataUri.base64()) { - return Base64.decode(data.getBytes("UTF-8"), Base64.DEFAULT); - } else { - return data.getBytes("UTF-8"); - } - } catch (Throwable t) { - return null; - } - } else { - return null; - } - } - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/priority/Priority.java b/markwon-core/src/main/java/ru/noties/markwon/priority/Priority.java deleted file mode 100644 index 5582ff72..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/priority/Priority.java +++ /dev/null @@ -1,96 +0,0 @@ -package ru.noties.markwon.priority; - -import android.support.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import ru.noties.markwon.MarkwonPlugin; - -// a small dependency graph also -// what if plugins cannot be constructed into a graph? for example they depend on something -// but not overlap? then it would be hard to sort them (but this doesn't make sense, if -// they do not care about other components, just put them in whatever order, no?) - -/** - * @see MarkwonPlugin#priority() - * @since 3.0.0 - */ -public abstract class Priority { - - @NonNull - public static Priority none() { - return builder().build(); - } - - @NonNull - public static Priority after(@NonNull Class extends MarkwonPlugin> plugin) { - return builder().after(plugin).build(); - } - - @NonNull - public static Priority after( - @NonNull Class extends MarkwonPlugin> plugin1, - @NonNull Class extends MarkwonPlugin> plugin2) { - return builder().after(plugin1).after(plugin2).build(); - } - - @NonNull - public static Builder builder() { - return new Impl.BuilderImpl(); - } - - public interface Builder { - - @NonNull - Builder after(@NonNull Class extends MarkwonPlugin> plugin); - - @NonNull - Priority build(); - } - - @NonNull - public abstract List > after(); - - - static class Impl extends Priority { - - private final List > after; - - Impl(@NonNull List > after) { - this.after = after; - } - - @NonNull - @Override - public List > after() { - return after; - } - - @Override - public String toString() { - return "Priority{" + - "after=" + after + - '}'; - } - - static class BuilderImpl implements Builder { - - private final List > after = new ArrayList<>(0); - - @NonNull - @Override - public Builder after(@NonNull Class extends MarkwonPlugin> plugin) { - after.add(plugin); - return this; - } - - @NonNull - @Override - public Priority build() { - return new Impl(Collections.unmodifiableList(after)); - } - } - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessor.java b/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessor.java deleted file mode 100644 index 1ba1353d..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessor.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.noties.markwon.priority; - -import android.support.annotation.NonNull; - -import java.util.List; - -import ru.noties.markwon.MarkwonPlugin; - -public abstract class PriorityProcessor { - - @NonNull - public static PriorityProcessor create() { - return new PriorityProcessorImpl(); - } - - @NonNull - public abstract List process(@NonNull List plugins); -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessorImpl.java b/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessorImpl.java deleted file mode 100644 index e676a9c9..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessorImpl.java +++ /dev/null @@ -1,132 +0,0 @@ -package ru.noties.markwon.priority; - -import android.support.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import ru.noties.markwon.MarkwonPlugin; - -import static java.lang.Math.max; - -class PriorityProcessorImpl extends PriorityProcessor { - - @NonNull - @Override - public List process(@NonNull List in) { - - // create new collection based on supplied argument - final List plugins = new ArrayList<>(in); - - final int size = plugins.size(); - - final Map , Set >> map = - new HashMap<>(size); - - for (MarkwonPlugin plugin : plugins) { - if (map.put(plugin.getClass(), new HashSet<>(plugin.priority().after())) != null) { - throw new IllegalStateException(String.format("Markwon duplicate plugin " + - "found `%s`: %s", plugin.getClass().getName(), plugin)); - } - } - - final Map cache = new HashMap<>(size); - for (MarkwonPlugin plugin : plugins) { - cache.put(plugin, eval(plugin, map)); - } - - Collections.sort(plugins, new PriorityComparator(cache)); - - return plugins; - } - - private static int eval( - @NonNull MarkwonPlugin plugin, - @NonNull Map , Set >> map) { - - final Set > set = map.get(plugin.getClass()); - - // no dependencies - if (set.isEmpty()) { - return 0; - } - - final Class extends MarkwonPlugin> who = plugin.getClass(); - - int max = 0; - - for (Class extends MarkwonPlugin> dependency : set) { - max = max(max, eval(who, dependency, map)); - } - - return 1 + max; - } - - // we need to count the number of steps to a root node (which has no parents) - private static int eval( - @NonNull Class extends MarkwonPlugin> who, - @NonNull Class extends MarkwonPlugin> plugin, - @NonNull Map , Set >> map) { - - // exact match - Set > set = map.get(plugin); - - if (set == null) { - - // let's try to find inexact type (overridden/subclassed) - for (Map.Entry , Set >> entry : map.entrySet()) { - if (plugin.isAssignableFrom(entry.getKey())) { - set = entry.getValue(); - break; - } - } - - if (set == null) { - // unsatisfied dependency - throw new IllegalStateException(String.format("Markwon unsatisfied dependency found. " + - "Plugin `%s` comes after `%s` but it is not added.", - who.getName(), plugin.getName())); - } - } - - if (set.isEmpty()) { - return 0; - } - - int value = 1; - - for (Class extends MarkwonPlugin> dependency : set) { - - // a case when a plugin defines `Priority.after(getClass)` or being - // referenced by own dependency (even indirect) - if (who.equals(dependency)) { - throw new IllegalStateException(String.format("Markwon plugin `%s` defined self " + - "as a dependency or being referenced by own dependency (cycle)", who.getName())); - } - - value += eval(who, dependency, map); - } - - return value; - } - - private static class PriorityComparator implements Comparator { - - private final Map map; - - PriorityComparator(@NonNull Map map) { - this.map = map; - } - - @Override - public int compare(MarkwonPlugin o1, MarkwonPlugin o2) { - return map.get(o1).compareTo(map.get(o2)); - } - } -} diff --git a/markwon-core/src/main/res/values/ids.xml b/markwon-core/src/main/res/values/ids.xml index 90fb8322..31a4445d 100644 --- a/markwon-core/src/main/res/values/ids.xml +++ b/markwon-core/src/main/res/values/ids.xml @@ -2,5 +2,6 @@ \ No newline at end of file diff --git a/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonPluginTest.java similarity index 57% rename from markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java rename to markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonPluginTest.java index 1af2b513..3587fdca 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonPluginTest.java @@ -1,32 +1,16 @@ -package ru.noties.markwon; +package io.noties.markwon; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import java.util.List; - -import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.priority.Priority; - import static org.junit.Assert.assertEquals; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class AbstractMarkwonPluginTest { - @Test - public void priority() { - // returns CorePlugin dependency - - final Priority priority = new AbstractMarkwonPlugin() { - }.priority(); - final List - +
> after = priority.after(); - assertEquals(1, after.size()); - assertEquals(CorePlugin.class, after.get(0)); - } - @Test public void process_markdown() { // returns supplied argument (no-op) diff --git a/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonVisitorImpl.java b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java similarity index 87% rename from markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonVisitorImpl.java rename to markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java index ea099248..ed4dae6b 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonVisitorImpl.java +++ b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java @@ -1,6 +1,6 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.commonmark.node.Node; diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonAssert.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonAssert.java new file mode 100644 index 00000000..85f686c8 --- /dev/null +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonAssert.java @@ -0,0 +1,15 @@ +package io.noties.markwon; + +import androidx.annotation.NonNull; + +import org.junit.Assert; + +public abstract class MarkwonAssert { + + public static void assertMessageContains(@NonNull Throwable t, @NonNull String contains) { + Assert.assertTrue(t.getMessage(), t.getMessage().contains(contains)); + } + + private MarkwonAssert() { + } +} diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonBuilderImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonBuilderImplTest.java new file mode 100644 index 00000000..bc0fd409 --- /dev/null +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonBuilderImplTest.java @@ -0,0 +1,65 @@ +package io.noties.markwon; + +import android.text.Spanned; +import android.widget.TextView; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import io.noties.markwon.core.MarkwonTheme; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class MarkwonBuilderImplTest { + + @Test + public void no_plugins_added_throws() { + // there is no sense in having an instance with no plugins registered + + try { + new MarkwonBuilderImpl(RuntimeEnvironment.application).build(); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), e.getMessage(), containsString("No plugins were added")); + } + } + + @Test + public void plugin_configured() { + // verify that all configuration methods (applicable) are called + + final MarkwonPlugin plugin = mock(MarkwonPlugin.class); + + final MarkwonBuilderImpl impl = new MarkwonBuilderImpl(RuntimeEnvironment.application); + impl.usePlugin(plugin).build(); + + verify(plugin, times(1)).configure(any(MarkwonPlugin.Registry.class)); + + verify(plugin, times(1)).configureParser(any(Parser.Builder.class)); + verify(plugin, times(1)).configureTheme(any(MarkwonTheme.Builder.class)); + verify(plugin, times(1)).configureConfiguration(any(MarkwonConfiguration.Builder.class)); + verify(plugin, times(1)).configureVisitor(any(MarkwonVisitor.Builder.class)); + verify(plugin, times(1)).configureSpansFactory(any(MarkwonSpansFactory.Builder.class)); + + // note, no render props -> they must be configured on render stage + verify(plugin, times(0)).processMarkdown(anyString()); + verify(plugin, times(0)).beforeRender(any(Node.class)); + verify(plugin, times(0)).afterRender(any(Node.class), any(MarkwonVisitor.class)); + verify(plugin, times(0)).beforeSetText(any(TextView.class), any(Spanned.class)); + verify(plugin, times(0)).afterSetText(any(TextView.class)); + } +} \ No newline at end of file diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java similarity index 99% rename from markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java rename to markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java index d80cb765..60f022ef 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java @@ -1,4 +1,4 @@ -package ru.noties.markwon; +package io.noties.markwon; import android.text.Spanned; import android.widget.TextView; diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryImplTest.java similarity index 99% rename from markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java rename to markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryImplTest.java index b66cfd0c..ad43e6e6 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryImplTest.java @@ -1,4 +1,4 @@ -package ru.noties.markwon; +package io.noties.markwon; import org.commonmark.node.Block; import org.commonmark.node.Emphasis; diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryTest.java similarity index 98% rename from markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryTest.java rename to markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryTest.java index a2cb36ff..cdb631e7 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryTest.java @@ -1,4 +1,4 @@ -package ru.noties.markwon; +package io.noties.markwon; import org.commonmark.node.BlockQuote; import org.commonmark.node.Image; diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonVisitorImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java similarity index 99% rename from markwon-core/src/test/java/ru/noties/markwon/MarkwonVisitorImplTest.java rename to markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java index 1a444d70..45387afb 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonVisitorImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java @@ -1,4 +1,4 @@ -package ru.noties.markwon; +package io.noties.markwon; import org.commonmark.node.BlockQuote; import org.commonmark.node.Node; diff --git a/markwon-core/src/test/java/ru/noties/markwon/PropTest.java b/markwon-core/src/test/java/io/noties/markwon/PropTest.java similarity index 98% rename from markwon-core/src/test/java/ru/noties/markwon/PropTest.java rename to markwon-core/src/test/java/io/noties/markwon/PropTest.java index 85a81fe1..3b90b7fb 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/PropTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/PropTest.java @@ -1,4 +1,4 @@ -package ru.noties.markwon; +package io.noties.markwon; import org.junit.Before; import org.junit.Test; diff --git a/markwon-core/src/test/java/io/noties/markwon/RegistryImplTest.java b/markwon-core/src/test/java/io/noties/markwon/RegistryImplTest.java new file mode 100644 index 00000000..4456bc47 --- /dev/null +++ b/markwon-core/src/test/java/io/noties/markwon/RegistryImplTest.java @@ -0,0 +1,194 @@ +package io.noties.markwon; + +import androidx.annotation.NonNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.noties.markwon.core.CorePlugin; + +import static io.noties.markwon.MarkwonAssert.assertMessageContains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class RegistryImplTest { + + @Test + public void single_plugin_requires_self() { + // detect recursive require + + final class Plugin extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + registry.require(Plugin.class); + } + } + final MarkwonPlugin plugin = new Plugin(); + + final RegistryImpl impl = new RegistryImpl(Collections.singletonList(plugin)); + + try { + impl.process(); + } catch (Throwable t) { + assertMessageContains(t, "Cyclic dependency chain found"); + } + } + + @Test + public void plugins_dependency_cycle() { + + final Map > map = new HashMap<>(); + + final class A extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + //noinspection ConstantConditions + registry.require(map.get("A")); + } + } + + final class B extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + //noinspection ConstantConditions + registry.require(map.get("B")); + } + } + + final class C extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + //noinspection ConstantConditions + registry.require(map.get("C")); + } + } + + map.put("A", B.class); + map.put("B", C.class); + map.put("C", A.class); + + final RegistryImpl impl = + new RegistryImpl(Arrays.asList((MarkwonPlugin) new A(), new B(), new C())); + + try { + impl.process(); + fail(); + } catch (Throwable t) { + assertMessageContains(t, "Cyclic dependency chain found"); + } + } + + @Test + public void plugins_no_dependency_cycle() { + + final class C extends AbstractMarkwonPlugin { + } + + final class B extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + registry.require(C.class); + } + } + + final class A extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + registry.require(B.class); + } + } + + final RegistryImpl impl = + new RegistryImpl(Arrays.asList((MarkwonPlugin) new A(), new B(), new C())); + + impl.process(); + } + + @Test + public void dependency_not_satisfied() { + // when require is called for plugin not added + + final class A extends AbstractMarkwonPlugin { + } + + final class B extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + registry.require(A.class); + } + } + + final RegistryImpl impl = + new RegistryImpl(Collections.singletonList((MarkwonPlugin) new B())); + + try { + impl.process(); + fail(); + } catch (Throwable t) { + assertMessageContains(t, "Requested plugin is not added"); + assertMessageContains(t, A.class.getName()); // ? if it's null for local class? + } + } + + @Test + public void core_plugin_first() { + // if core-plugin is present, hen it should be the first one + + final CorePlugin plugin = CorePlugin.create(); + + final RegistryImpl impl = new RegistryImpl(Arrays.asList( + mock(MarkwonPlugin.class), + mock(MarkwonPlugin.class), + plugin + )); + + final List plugins = impl.process(); + assertEquals(3, plugins.size()); + assertEquals(plugin, plugins.get(0)); + } + + @Test + public void correct_order() { + + final class A extends AbstractMarkwonPlugin { + } + + final class B extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + registry.require(A.class); + } + } + + final class C extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + registry.require(B.class); + } + } + + final A a = new A(); + final B b = new B(); + final C c = new C(); + + final RegistryImpl impl = new RegistryImpl(Arrays.asList( + (MarkwonPlugin) c, b, a)); + + final List plugins = impl.process(); + assertEquals(3, plugins.size()); + assertEquals(a, plugins.get(0)); + assertEquals(b, plugins.get(1)); + assertEquals(c, plugins.get(2)); + } +} \ No newline at end of file diff --git a/markwon-core/src/test/java/ru/noties/markwon/RenderPropsImplTest.java b/markwon-core/src/test/java/io/noties/markwon/RenderPropsImplTest.java similarity index 99% rename from markwon-core/src/test/java/ru/noties/markwon/RenderPropsImplTest.java rename to markwon-core/src/test/java/io/noties/markwon/RenderPropsImplTest.java index 36a6315b..3f185bcf 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/RenderPropsImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/RenderPropsImplTest.java @@ -1,4 +1,4 @@ -package ru.noties.markwon; +package io.noties.markwon; import org.junit.Before; import org.junit.Test; diff --git a/markwon-core/src/test/java/ru/noties/markwon/SpannableBuilderTest.java b/markwon-core/src/test/java/io/noties/markwon/SpannableBuilderTest.java similarity index 98% rename from markwon-core/src/test/java/ru/noties/markwon/SpannableBuilderTest.java rename to markwon-core/src/test/java/io/noties/markwon/SpannableBuilderTest.java index 3535e19b..8a0ee7e4 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/SpannableBuilderTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/SpannableBuilderTest.java @@ -1,6 +1,6 @@ -package ru.noties.markwon; +package io.noties.markwon; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -19,8 +19,8 @@ import ix.IxFunction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static ru.noties.markwon.SpannableBuilder.isPositionValid; -import static ru.noties.markwon.SpannableBuilder.setSpans; +import static io.noties.markwon.SpannableBuilder.isPositionValid; +import static io.noties.markwon.SpannableBuilder.setSpans; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginBridge.java b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginBridge.java similarity index 70% rename from markwon-core/src/test/java/ru/noties/markwon/core/CorePluginBridge.java rename to markwon-core/src/test/java/io/noties/markwon/core/CorePluginBridge.java index 6517873b..d1bd2dbf 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginBridge.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginBridge.java @@ -1,11 +1,11 @@ -package ru.noties.markwon.core; +package io.noties.markwon.core; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.commonmark.node.Node; -import ru.noties.markwon.MarkwonVisitor; +import io.noties.markwon.MarkwonVisitor; public abstract class CorePluginBridge { diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java similarity index 94% rename from markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java index dd1d43cc..e21a8ded 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java @@ -1,8 +1,9 @@ -package ru.noties.markwon.core; +package io.noties.markwon.core; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.text.method.MovementMethod; +import android.widget.ImageView; import android.widget.TextView; import org.commonmark.node.BlockQuote; @@ -12,6 +13,7 @@ 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.ListItem; @@ -39,12 +41,12 @@ import java.util.Set; import ix.Ix; import ix.IxFunction; import ix.IxPredicate; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.SpannableBuilder; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.SpannableBuilder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -84,7 +86,8 @@ public class CorePluginTest { SoftLineBreak.class, StrongEmphasis.class, Text.class, - ThematicBreak.class + ThematicBreak.class, + Image.class }; final CorePlugin plugin = CorePlugin.create(); @@ -191,13 +194,6 @@ public class CorePluginTest { assertEquals(impl.map.toString(), 0, impl.map.size()); } - @Test - public void priority_none() { - // CorePlugin returns none as priority (thus 0) - - assertEquals(0, CorePlugin.create().priority().after().size()); - } - @Test public void plugin_methods() { // checks that only expected plugin methods are overridden @@ -209,6 +205,7 @@ public class CorePluginTest { add("beforeSetText"); add("afterSetText"); add("priority"); + add("addOnTextAddedListener"); }}; // we will use declaredMethods because it won't return inherited ones diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CoreTest.java b/markwon-core/src/test/java/io/noties/markwon/core/CoreTest.java similarity index 77% rename from markwon-core/src/test/java/ru/noties/markwon/core/CoreTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/CoreTest.java index de7c0dd0..b0c4556d 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/CoreTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/CoreTest.java @@ -1,6 +1,6 @@ -package ru.noties.markwon.core; +package io.noties.markwon.core; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.Spanned; import org.commonmark.node.Emphasis; @@ -11,18 +11,18 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.Markwon; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.test.TestSpan; -import ru.noties.markwon.test.TestSpanMatcher; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.test.TestSpan; +import io.noties.markwon.test.TestSpanMatcher; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/.editorconfig b/markwon-core/src/test/java/io/noties/markwon/core/suite/.editorconfig similarity index 100% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/.editorconfig rename to markwon-core/src/test/java/io/noties/markwon/core/suite/.editorconfig diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/BaseSuiteTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/BaseSuiteTest.java similarity index 89% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/BaseSuiteTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/BaseSuiteTest.java index d434ed99..09d086fb 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/BaseSuiteTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/BaseSuiteTest.java @@ -1,7 +1,7 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.text.Spanned; import org.apache.commons.io.IOUtils; @@ -24,19 +24,19 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.Markwon; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.core.CoreProps; -import ru.noties.markwon.test.TestSpan; -import ru.noties.markwon.test.TestSpanMatcher; +import io.noties.markwon.core.CorePlugin; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.test.TestSpan; +import io.noties.markwon.test.TestSpanMatcher; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.span; abstract class BaseSuiteTest { diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/BlockquoteTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/BlockquoteTest.java similarity index 76% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/BlockquoteTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/BlockquoteTest.java index 21d2c852..11d4aa1f 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/BlockquoteTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/BlockquoteTest.java @@ -1,15 +1,15 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/BoldItalicTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/BoldItalicTest.java similarity index 66% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/BoldItalicTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/BoldItalicTest.java index 1315136b..ceece2f5 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/BoldItalicTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/BoldItalicTest.java @@ -1,15 +1,15 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan; +import io.noties.markwon.test.TestSpan; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/CodeTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/CodeTest.java similarity index 80% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/CodeTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/CodeTest.java index 52a8f614..3fc22a3d 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/CodeTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/CodeTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/DeeplyNestedTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/DeeplyNestedTest.java similarity index 71% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/DeeplyNestedTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/DeeplyNestedTest.java index e0dcda38..86c948ba 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/DeeplyNestedTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/DeeplyNestedTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/EmphasisTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/EmphasisTest.java similarity index 64% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/EmphasisTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/EmphasisTest.java index 057acc18..697075ac 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/EmphasisTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/EmphasisTest.java @@ -1,15 +1,15 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/FirstTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/FirstTest.java similarity index 71% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/FirstTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/FirstTest.java index f67a5062..32c8f17e 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/FirstTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/FirstTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/HeadingTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/HeadingTest.java similarity index 71% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/HeadingTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/HeadingTest.java index 10fa5f9b..4aef1e52 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/HeadingTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/HeadingTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/LinkTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/LinkTest.java similarity index 59% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/LinkTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/LinkTest.java index ff70f82c..a3e6f2af 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/LinkTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/LinkTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/NoParagraphsTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/NoParagraphsTest.java similarity index 75% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/NoParagraphsTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/NoParagraphsTest.java index 32ea306b..634b6d16 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/NoParagraphsTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/NoParagraphsTest.java @@ -1,14 +1,14 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/OrderedListTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/OrderedListTest.java similarity index 86% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/OrderedListTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/OrderedListTest.java index 9efb46b8..afd98aeb 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/OrderedListTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/OrderedListTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/ParagraphTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/ParagraphTest.java similarity index 73% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/ParagraphTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/ParagraphTest.java index 877a8818..c866ebe9 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/ParagraphTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/ParagraphTest.java @@ -1,15 +1,15 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/SecondTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/SecondTest.java similarity index 78% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/SecondTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/SecondTest.java index 340df08d..fef1f3f4 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/SecondTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/SecondTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/SoftBreakTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/SoftBreakTest.java similarity index 72% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/SoftBreakTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/SoftBreakTest.java index b94ab45a..ed282908 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/SoftBreakTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/SoftBreakTest.java @@ -1,14 +1,14 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/StrongEmphasisTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/StrongEmphasisTest.java similarity index 65% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/StrongEmphasisTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/StrongEmphasisTest.java index d08377a1..97ccfa2e 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/StrongEmphasisTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/StrongEmphasisTest.java @@ -1,15 +1,15 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/ThematicBreakTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/ThematicBreakTest.java similarity index 66% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/ThematicBreakTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/ThematicBreakTest.java index 1e0159bc..53d8f234 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/ThematicBreakTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/ThematicBreakTest.java @@ -1,15 +1,15 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/suite/UnOrderedListTest.java b/markwon-core/src/test/java/io/noties/markwon/core/suite/UnOrderedListTest.java similarity index 83% rename from markwon-core/src/test/java/ru/noties/markwon/core/suite/UnOrderedListTest.java rename to markwon-core/src/test/java/io/noties/markwon/core/suite/UnOrderedListTest.java index 9b1dfb85..41503788 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/suite/UnOrderedListTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/suite/UnOrderedListTest.java @@ -1,16 +1,16 @@ -package ru.noties.markwon.core.suite; +package io.noties.markwon.core.suite; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpan.Document; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/image/AsyncDrawableTest.java b/markwon-core/src/test/java/io/noties/markwon/image/AsyncDrawableTest.java similarity index 95% rename from markwon-core/src/test/java/ru/noties/markwon/image/AsyncDrawableTest.java rename to markwon-core/src/test/java/io/noties/markwon/image/AsyncDrawableTest.java index 7dad4514..809cfd5f 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/image/AsyncDrawableTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/image/AsyncDrawableTest.java @@ -1,11 +1,11 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.junit.Before; import org.junit.Test; diff --git a/markwon-core/src/test/java/ru/noties/markwon/image/ImageSizeResolverDefTest.java b/markwon-core/src/test/java/io/noties/markwon/image/ImageSizeResolverDefTest.java similarity index 56% rename from markwon-core/src/test/java/ru/noties/markwon/image/ImageSizeResolverDefTest.java rename to markwon-core/src/test/java/io/noties/markwon/image/ImageSizeResolverDefTest.java index f8649603..455ccb74 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/image/ImageSizeResolverDefTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/image/ImageSizeResolverDefTest.java @@ -1,20 +1,22 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.image.ImageSize; -import ru.noties.markwon.image.ImageSize.Dimension; -import ru.noties.markwon.image.ImageSizeResolverDef; - +import static io.noties.markwon.image.ImageSizeResolverDef.UNIT_EM; +import static io.noties.markwon.image.ImageSizeResolverDef.UNIT_PERCENT; import static org.junit.Assert.assertEquals; -import static ru.noties.markwon.image.ImageSizeResolverDef.UNIT_EM; -import static ru.noties.markwon.image.ImageSizeResolverDef.UNIT_PERCENT; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -27,6 +29,42 @@ public class ImageSizeResolverDefTest { def = new ImageSizeResolverDef(); } + @Test + public void correct_redirect() { + // @since 4.0.0 the main method is changed to accept AsyncDrawable + + final ImageSizeResolverDef def = mock(ImageSizeResolverDef.class, Mockito.CALLS_REAL_METHODS); + final AsyncDrawable drawable = mock(AsyncDrawable.class); + + final ImageSize imageSize = mock(ImageSize.class); + final Drawable result = mock(Drawable.class); + final Rect rect = mock(Rect.class); + when(result.getBounds()).thenReturn(rect); + + when(drawable.getImageSize()).thenReturn(imageSize); + when(drawable.getResult()).thenReturn(result); + when(drawable.getLastKnownCanvasWidth()).thenReturn(111); + when(drawable.getLastKnowTextSize()).thenReturn(24.0F); + + def.resolveImageSize(drawable); + + final ArgumentCaptor imageSizeArgumentCaptor = ArgumentCaptor.forClass(ImageSize.class); + final ArgumentCaptor rectArgumentCaptor = ArgumentCaptor.forClass(Rect.class); + final ArgumentCaptor integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor floatArgumentCaptor = ArgumentCaptor.forClass(Float.class); + + verify(def).resolveImageSize( + imageSizeArgumentCaptor.capture(), + rectArgumentCaptor.capture(), + integerArgumentCaptor.capture(), + floatArgumentCaptor.capture()); + + assertEquals(imageSize, imageSizeArgumentCaptor.getValue()); + assertEquals(rect, rectArgumentCaptor.getValue()); + assertEquals((Integer) 111, integerArgumentCaptor.getValue()); + assertEquals((Float) 24.0F, floatArgumentCaptor.getValue()); + } + @Test public void no_image_size() { // no image size returns image original bounds @@ -55,7 +93,7 @@ public class ImageSizeResolverDefTest { assertEquals( rect, def.resolveImageSize( - new ImageSize(null, new Dimension(100.F, UNIT_PERCENT)), + new ImageSize(null, new ImageSize.Dimension(100.F, UNIT_PERCENT)), rect, -1, Float.NaN @@ -69,7 +107,7 @@ public class ImageSizeResolverDefTest { assertEquals( new Rect(0, 0, 50, 100), def.resolveImageSize( - new ImageSize(new Dimension(50.F, UNIT_PERCENT), null), + new ImageSize(new ImageSize.Dimension(50.F, UNIT_PERCENT), null), rect, 100, Float.NaN @@ -83,7 +121,7 @@ public class ImageSizeResolverDefTest { assertEquals( new Rect(0, 0, 7, 9), def.resolveImageSize( - new ImageSize(new Dimension(7, "width"), new Dimension(9, "height")), + new ImageSize(new ImageSize.Dimension(7, "width"), new ImageSize.Dimension(9, "height")), rect, 90, Float.NaN @@ -97,7 +135,7 @@ public class ImageSizeResolverDefTest { assertEquals( new Rect(0, 0, 20, 40), def.resolveImageSize( - new ImageSize(new Dimension(2.f, UNIT_EM), new Dimension(4.F, UNIT_EM)), + new ImageSize(new ImageSize.Dimension(2.f, UNIT_EM), new ImageSize.Dimension(4.F, UNIT_EM)), rect, 999, 10.F @@ -111,7 +149,7 @@ public class ImageSizeResolverDefTest { assertEquals( new Rect(0, 0, 10, 20), def.resolveImageSize( - new ImageSize(new Dimension(1.F, UNIT_EM), null), + new ImageSize(new ImageSize.Dimension(1.F, UNIT_EM), null), rect, 42, 10.F @@ -125,7 +163,7 @@ public class ImageSizeResolverDefTest { assertEquals( new Rect(0, 0, 100, 50), def.resolveImageSize( - new ImageSize(null, new Dimension(50, "px")), + new ImageSize(null, new ImageSize.Dimension(50, "px")), rect, 200, Float.NaN @@ -139,7 +177,7 @@ public class ImageSizeResolverDefTest { assertEquals( new Rect(0, 0, 10, 30), def.resolveImageSize( - new ImageSize(null, new Dimension(3.F, UNIT_EM)), + new ImageSize(null, new ImageSize.Dimension(3.F, UNIT_EM)), rect, 40, 10.F diff --git a/markwon-core/src/test/java/ru/noties/markwon/image/ImageTest.java b/markwon-core/src/test/java/io/noties/markwon/image/ImageTest.java similarity index 64% rename from markwon-core/src/test/java/ru/noties/markwon/image/ImageTest.java rename to markwon-core/src/test/java/io/noties/markwon/image/ImageTest.java index 69391899..61721d16 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/image/ImageTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/image/ImageTest.java @@ -1,8 +1,7 @@ -package ru.noties.markwon.image; +package io.noties.markwon.image; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; import org.commonmark.node.Image; import org.junit.Test; @@ -11,21 +10,20 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.Markwon; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.test.TestSpan.Document; -import ru.noties.markwon.test.TestSpanMatcher; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.core.CorePlugin; +import io.noties.markwon.test.TestSpan.Document; +import io.noties.markwon.test.TestSpanMatcher; -import static ru.noties.markwon.test.TestSpan.args; -import static ru.noties.markwon.test.TestSpan.document; -import static ru.noties.markwon.test.TestSpan.span; -import static ru.noties.markwon.test.TestSpan.text; +import static io.noties.markwon.test.TestSpan.args; +import static io.noties.markwon.test.TestSpan.document; +import static io.noties.markwon.test.TestSpan.span; +import static io.noties.markwon.test.TestSpan.text; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -39,7 +37,6 @@ public class ImageTest { final Context context = RuntimeEnvironment.application; final Markwon markwon = Markwon.builder(context) .usePlugin(CorePlugin.create()) - .usePlugin(new ImagesPlugin(context, false)) .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { diff --git a/markwon-core/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java b/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java similarity index 85% rename from markwon-core/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java rename to markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java index 9c0262bb..fdde1888 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java @@ -1,11 +1,12 @@ -package ru.noties.markwon.syntax; +package io.noties.markwon.syntax; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.text.SpannableStringBuilder; import android.text.Spanned; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.Node; import org.junit.Test; @@ -17,17 +18,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.Map; -import ru.noties.markwon.AbstractMarkwonVisitorImpl; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.SpannableBuilder; -import ru.noties.markwon.core.CorePluginBridge; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.html.MarkwonHtmlRenderer; -import ru.noties.markwon.image.AsyncDrawableLoader; +import io.noties.markwon.AbstractMarkwonVisitorImpl; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.SpannableBuilder; +import io.noties.markwon.core.CorePluginBridge; +import io.noties.markwon.core.MarkwonTheme; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -84,7 +83,7 @@ public class SyntaxHighlightTest { final MarkwonConfiguration configuration = MarkwonConfiguration.builder() .syntaxHighlight(highlight) - .build(mock(MarkwonTheme.class), mock(AsyncDrawableLoader.class), mock(MarkwonHtmlRenderer.class), spansFactory); + .build(mock(MarkwonTheme.class), spansFactory); final Map , MarkwonVisitor.NodeVisitor extends Node>> visitorMap = Collections.emptyMap(); diff --git a/markwon-core/src/test/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssetsTest.java b/markwon-core/src/test/java/io/noties/markwon/urlprocessor/UrlProcessorAndroidAssetsTest.java similarity index 87% rename from markwon-core/src/test/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssetsTest.java rename to markwon-core/src/test/java/io/noties/markwon/urlprocessor/UrlProcessorAndroidAssetsTest.java index 7a5cbf1a..4129bfb8 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssetsTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/urlprocessor/UrlProcessorAndroidAssetsTest.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.urlprocessor; +package io.noties.markwon.urlprocessor; import org.junit.Before; import org.junit.Test; @@ -6,10 +6,8 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.urlprocessor.UrlProcessorAndroidAssets; - import static org.junit.Assert.assertEquals; -import static ru.noties.markwon.urlprocessor.UrlProcessorAndroidAssets.BASE; +import static io.noties.markwon.urlprocessor.UrlProcessorAndroidAssets.BASE; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) diff --git a/markwon-core/src/test/java/ru/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsoluteTest.java b/markwon-core/src/test/java/io/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsoluteTest.java similarity index 95% rename from markwon-core/src/test/java/ru/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsoluteTest.java rename to markwon-core/src/test/java/io/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsoluteTest.java index a8bf2d01..afa55b33 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsoluteTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/urlprocessor/UrlProcessorRelativeToAbsoluteTest.java @@ -1,12 +1,10 @@ -package ru.noties.markwon.urlprocessor; +package io.noties.markwon.urlprocessor; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute; - import static org.junit.Assert.*; @RunWith(RobolectricTestRunner.class) diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java deleted file mode 100644 index af3d00b1..00000000 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java +++ /dev/null @@ -1,232 +0,0 @@ -package ru.noties.markwon; - -import android.support.annotation.NonNull; -import android.text.Spanned; -import android.widget.TextView; - -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.html.MarkwonHtmlRenderer; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.Priority; -import ru.noties.markwon.priority.PriorityProcessor; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.isA; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static ru.noties.markwon.MarkwonBuilderImpl.ensureImplicitCoreIfHasDependents; -import static ru.noties.markwon.MarkwonBuilderImpl.preparePlugins; - -@RunWith(RobolectricTestRunner.class) -@Config(manifest = Config.NONE) -public class MarkwonBuilderImplTest { - - @Test - public void implicit_core_created() { - // a plugin explicitly requests CorePlugin, but CorePlugin is not added manually by user - // we validate that default CorePlugin instance is added - - final MarkwonPlugin plugin = new AbstractMarkwonPlugin() { - @NonNull - @Override - public Priority priority() { - // strictly speaking we do not need to override this method - // as all children of AbstractMarkwonPlugin specify CorePlugin as a dependency. - // but we still add it to make things explicit and future proof, if this - // behavior changes - return Priority.after(CorePlugin.class); - } - }; - - final List plugins = ensureImplicitCoreIfHasDependents(Collections.singletonList(plugin)); - - assertThat(plugins, hasSize(2)); - assertThat(plugins, hasItem(isA(CorePlugin.class))); - } - - @Test - public void implicit_core_no_dependents_not_added() { - final MarkwonPlugin a = new AbstractMarkwonPlugin() { - @NonNull - @Override - public Priority priority() { - return Priority.none(); - } - }; - - final MarkwonPlugin b = new AbstractMarkwonPlugin() { - @NonNull - @Override - public Priority priority() { - return Priority.after(a.getClass()); - } - }; - - final List plugins = ensureImplicitCoreIfHasDependents(Arrays.asList(a, b)); - assertThat(plugins, hasSize(2)); - assertThat(plugins, not(hasItem(isA(CorePlugin.class)))); - } - - @Test - public void implicit_core_present() { - // if core is present it won't be added (whether or not there are dependents) - - final MarkwonPlugin plugin = new AbstractMarkwonPlugin() { - @NonNull - @Override - public Priority priority() { - return Priority.after(CorePlugin.class); - } - }; - - final CorePlugin corePlugin = CorePlugin.create(); - - final List plugins = ensureImplicitCoreIfHasDependents(Arrays.asList(plugin, corePlugin)); - assertThat(plugins, hasSize(2)); - assertThat(plugins, hasItem(plugin)); - assertThat(plugins, hasItem(corePlugin)); - } - - @Test - public void implicit_core_subclass_present() { - // core was subclassed by a user and provided (implicit core won't be added) - - final MarkwonPlugin plugin = new AbstractMarkwonPlugin() { - @NonNull - @Override - public Priority priority() { - return Priority.after(CorePlugin.class); - } - }; - - // our subclass - final CorePlugin corePlugin = new CorePlugin() { - - }; - - final List plugins = ensureImplicitCoreIfHasDependents(Arrays.asList(plugin, corePlugin)); - assertThat(plugins, hasSize(2)); - assertThat(plugins, hasItem(plugin)); - assertThat(plugins, hasItem(corePlugin)); - } - - @Test - public void prepare_plugins() { - // validate that prepare plugins is calling `ensureImplicitCoreIfHasDependents` and - // priority processor - - final PriorityProcessor priorityProcessor = mock(PriorityProcessor.class); - when(priorityProcessor.process(ArgumentMatchers. anyList())) - .thenAnswer(new Answer