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