diff --git a/docs/docs/v4/core/configuration.md b/docs/docs/v4/core/configuration.md
index 4c4ad645..23a32fb0 100644
--- a/docs/docs/v4/core/configuration.md
+++ b/docs/docs/v4/core/configuration.md
@@ -4,7 +4,7 @@
These are _configurable_ properties:
* `AsyncDrawableLoader` (back here since )
* `SyntaxHighlight`
-* `LinkSpan.Resolver`
+* `LinkResolver` (since , before — `LinkSpan.Resolver`)
* `UrlProcessor`
* `ImageSizeResolver`
@@ -37,9 +37,9 @@ final Markwon markwon = Markwon.builder(context)
```
Currently `Markwon` provides 3 implementations for loading images:
-* [own implementation](/docs/v4/image.md) with SVG, GIF, data uri and android_assets support
-* [based on Picasso](/docs/v4/image-picasso.md)
-* [based on Glide](/docs/v4/image-glide.md)
+* [markwon implementation](/docs/v4/image/) with SVG, GIF, data uri and android_assets support
+* [based on Picasso](/docs/v4/image-picasso/)
+* [based on Glide](/docs/v4/image-glide/)
## SyntaxHighlight
@@ -59,7 +59,7 @@ Use [syntax-highlight](/docs/v4/syntax-highlight/) to add syntax highlighting
to your application
:::
-## LinkSpan.Resolver
+## LinkResolver
React to a link click event. By default `LinkResolverDef` is used,
which tries to start an Activity given the `link` argument. If no
@@ -70,9 +70,9 @@ final Markwon markwon = Markwon.builder(this)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
- builder.linkResolver(new LinkSpan.Resolver() {
+ builder.linkResolver(new LinkResolver() {
@Override
- public void resolve(View view, @NonNull String link) {
+ public void resolve(@NonNull View view, @NonNull String link) {
// react to link click here
}
});
@@ -119,8 +119,7 @@ will be kept as-is.
## ImageSizeResolver
-`ImageSizeResolver` controls the size of an image to be displayed. Currently it
-handles only HTML images (specified via `img` tag).
+`ImageSizeResolver` controls the size of an image to be displayed.
```java
final Markwon markwon = Markwon.builder(this)
@@ -130,12 +129,9 @@ final Markwon markwon = Markwon.builder(this)
builder.imageSizeResolver(new ImageSizeResolver() {
@NonNull
@Override
- public Rect resolveImageSize(
- @Nullable ImageSize imageSize,
- @NonNull Rect imageBounds,
- int canvasWidth,
- float textSize) {
- return null;
+ public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
+ final ImageSize imageSize = drawable.getImageSize();
+ return drawable.getResult().getBounds();
}
});
}
@@ -168,6 +164,5 @@ so we will have no point-of-reference from which to _calculate_ image height.
`ImageSizeResolverDef` also takes care for an image to **not** exceed
canvas width. If an image has greater width than a TextView Canvas, then
image will be _scaled-down_ to fit the canvas. Please note that this rule
-applies only if image has no absolute sizes (for example width is specified
-in pixels).
+applies only if image has no sizes specified (`ImageSize == null`).
:::
\ No newline at end of file
diff --git a/docs/docs/v4/ext-latex/README.md b/docs/docs/v4/ext-latex/README.md
index bf8a9400..16fde085 100644
--- a/docs/docs/v4/ext-latex/README.md
+++ b/docs/docs/v4/ext-latex/README.md
@@ -35,6 +35,8 @@ final Markwon markwon = Markwon.builder(context)
.align(JLatexMathDrawable.ALIGN_CENTER)
.fitCanvas(true)
.padding(paddingPx)
+ // @since 4.0.0 - horizontal and vertical padding
+ .padding(paddingHorizontalPx, paddingVerticalPx)
// @since 4.0.0 - change to provider
.backgroundProvider(() -> new MyDrawable()))
// @since 4.0.0 - optional, by default cached-thread-pool will be used
diff --git a/docs/docs/v4/html/README.md b/docs/docs/v4/html/README.md
index 44b95c54..cbba91d6 100644
--- a/docs/docs/v4/html/README.md
+++ b/docs/docs/v4/html/README.md
@@ -104,4 +104,132 @@ If you wish to exclude some of them `TagHandlerNoOp` can be used:
## 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 Collection supportedTags() {
+ 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/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index a26c6d72..22ebc86b 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -24,6 +24,7 @@
+
diff --git a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
index a7805f52..8c197ea7 100644
--- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
@@ -21,6 +21,7 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.sample.basicplugins.BasicPluginsActivity;
import io.noties.markwon.sample.core.CoreActivity;
import io.noties.markwon.sample.customextension.CustomExtensionActivity;
+import io.noties.markwon.sample.html.HtmlActivity;
import io.noties.markwon.sample.latex.LatexActivity;
import io.noties.markwon.sample.recycler.RecyclerActivity;
@@ -97,6 +98,10 @@ public class MainActivity extends Activity {
activity = RecyclerActivity.class;
break;
+ case HTML:
+ activity = HtmlActivity.class;
+ break;
+
default:
throw new IllegalStateException("No Activity is associated with sample-item: " + item);
}
diff --git a/sample/src/main/java/io/noties/markwon/sample/Sample.java b/sample/src/main/java/io/noties/markwon/sample/Sample.java
index 459ef4c8..03160a35 100644
--- a/sample/src/main/java/io/noties/markwon/sample/Sample.java
+++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java
@@ -15,7 +15,7 @@ public enum Sample {
RECYCLER(R.string.sample_recycler),
- ;
+ HTML(R.string.sample_html);
private final int textResId;
diff --git a/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java b/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java
new file mode 100644
index 00000000..21613985
--- /dev/null
+++ b/sample/src/main/java/io/noties/markwon/sample/html/HtmlActivity.java
@@ -0,0 +1,190 @@
+package io.noties.markwon.sample.html;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AlignmentSpan;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Random;
+
+import io.noties.markwon.AbstractMarkwonPlugin;
+import io.noties.markwon.Markwon;
+import io.noties.markwon.MarkwonConfiguration;
+import io.noties.markwon.MarkwonVisitor;
+import io.noties.markwon.RenderProps;
+import io.noties.markwon.SpannableBuilder;
+import io.noties.markwon.html.HtmlPlugin;
+import io.noties.markwon.html.HtmlTag;
+import io.noties.markwon.html.MarkwonHtmlRenderer;
+import io.noties.markwon.html.TagHandler;
+import io.noties.markwon.html.tag.SimpleTagHandler;
+import io.noties.markwon.sample.R;
+
+public class HtmlActivity extends Activity {
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_text_view);
+
+ // let's define some custom tag-handlers
+
+ final TextView textView = findViewById(R.id.text_view);
+
+ 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())
+ .addHandler(new RandomCharSize(new Random(42L), textView.getTextSize()))
+ .addHandler(new EnhanceTagHandler((int) (textView.getTextSize() * 2 + .05F))));
+ }
+ })
+ .build();
+
+ final String markdown = "# Hello, HTML\n" +
+ "\n" +
+ "We are centered\n" +
+ "\n" +
+ "We are at the end\n" +
+ "\n" +
+ "We should be at the start\n" +
+ "\n" +
+ "\n" +
+ "This message should have a jumpy feeling because of different sizes of characters\n" +
+ "\n\n" +
+ "This is text that must be enhanced, at least a part of it";
+
+ markwon.setMarkdown(textView, markdown);
+ }
+
+ // we can use `SimpleTagHandler` for _simple_ cases (when the whole tag content
+ // will have spans from start to end)
+ //
+ // we can use any tag name, even not defined in HTML spec
+ private static 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");
+ }
+ }
+
+ // each character will have random size
+ private static class RandomCharSize extends TagHandler {
+
+ private final Random random;
+ private final float base;
+
+ RandomCharSize(@NonNull Random random, float base) {
+ this.random = random;
+ this.base = base;
+ }
+
+ @Override
+ public void handle(
+ @NonNull MarkwonVisitor visitor,
+ @NonNull MarkwonHtmlRenderer renderer,
+ @NonNull HtmlTag tag) {
+
+ final SpannableBuilder builder = visitor.builder();
+
+ // text content is already added, we should only apply spans
+
+ for (int i = tag.start(), end = tag.end(); i < end; i++) {
+ final int size = (int) (base * (random.nextFloat() + 0.5F) + 0.5F);
+ builder.setSpan(new AbsoluteSizeSpan(size, false), i, i + 1);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Collection supportedTags() {
+ return Collections.singleton("random-char-size");
+ }
+ }
+
+ private static 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 Collection supportedTags() {
+ 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;
+ }
+ }
+}
diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml
index 43de11d1..24cab950 100644
--- a/sample/src/main/res/values/strings-samples.xml
+++ b/sample/src/main/res/values/strings-samples.xml
@@ -15,4 +15,6 @@
# \# Recycler\n\nShow how to render markdown in a RecyclerView.
Renders code blocks wrapped in a HorizontalScrollView. Renders tables in a custom view.
+ # \# Html\n\nShows how to define own tag handlers
+
\ No newline at end of file