parent
d70a4b7b91
commit
9251a608fe
@ -50,8 +50,10 @@ compile 'ru.noties:markwon-view:1.0.0' // optional
|
||||
|
||||
Taken with default configuration (except for image loading):
|
||||
|
||||
<img src="./art/mw_light_01.png" width="33%" /> <img src="./art/mw_light_02.png" width="33%" />
|
||||
<img src="./art/mw_light_03.png" width="33%" /> <img src="./art/mw_dark_01.png" width="33%" />
|
||||
<a href="./art/mw_light_01.png"><img src="./art/mw_light_01.png" width="30%" /></a>
|
||||
<a href="./art/mw_light_02.png"><img src="./art/mw_light_02.png" width="30%" /></a>
|
||||
<a href="./art/mw_light_03.png"><img src="./art/mw_light_03.png" width="30%" /></a>
|
||||
<a href="./art/mw_dark_01.png"><img src="./art/mw_dark_01.png" width="30%" /></a>
|
||||
|
||||
By default configuration uses TextView textColor for styling, so changing textColor changes style
|
||||
|
||||
|
@ -3,6 +3,7 @@ package ru.noties.markwon;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@ -11,6 +12,7 @@ import java.util.concurrent.Future;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import ru.noties.debug.Debug;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
|
||||
@ActivityScope
|
||||
@ -57,8 +59,14 @@ public class MarkdownRenderer {
|
||||
.urlProcessor(urlProcessor)
|
||||
.build();
|
||||
|
||||
final long start = SystemClock.uptimeMillis();
|
||||
|
||||
final CharSequence text = Markwon.markdown(configuration, markdown);
|
||||
|
||||
final long end = SystemClock.uptimeMillis();
|
||||
|
||||
Debug.i("markdown rendered: %d ms", end - start);
|
||||
|
||||
if (!isCancelled()) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
|
@ -3,6 +3,8 @@ package ru.noties.markwon;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.renderer.html.ImageSizeResolver;
|
||||
import ru.noties.markwon.renderer.html.ImageSizeResolverDef;
|
||||
import ru.noties.markwon.renderer.html.SpannableHtmlParser;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.LinkSpan;
|
||||
@ -12,10 +14,12 @@ import ru.noties.markwon.spans.SpannableTheme;
|
||||
public class SpannableConfiguration {
|
||||
|
||||
// creates default configuration
|
||||
@NonNull
|
||||
public static SpannableConfiguration create(@NonNull Context context) {
|
||||
return new Builder(context).build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder builder(@NonNull Context context) {
|
||||
return new Builder(context);
|
||||
}
|
||||
@ -27,7 +31,7 @@ public class SpannableConfiguration {
|
||||
private final UrlProcessor urlProcessor;
|
||||
private final SpannableHtmlParser htmlParser;
|
||||
|
||||
private SpannableConfiguration(Builder builder) {
|
||||
private SpannableConfiguration(@NonNull Builder builder) {
|
||||
this.theme = builder.theme;
|
||||
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||
this.syntaxHighlight = builder.syntaxHighlight;
|
||||
@ -36,26 +40,32 @@ public class SpannableConfiguration {
|
||||
this.htmlParser = builder.htmlParser;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public SpannableTheme theme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AsyncDrawable.Loader asyncDrawableLoader() {
|
||||
return asyncDrawableLoader;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public SyntaxHighlight syntaxHighlight() {
|
||||
return syntaxHighlight;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public LinkSpan.Resolver linkResolver() {
|
||||
return linkResolver;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public UrlProcessor urlProcessor() {
|
||||
return urlProcessor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public SpannableHtmlParser htmlParser() {
|
||||
return htmlParser;
|
||||
}
|
||||
@ -70,60 +80,89 @@ public class SpannableConfiguration {
|
||||
private LinkSpan.Resolver linkResolver;
|
||||
private UrlProcessor urlProcessor;
|
||||
private SpannableHtmlParser htmlParser;
|
||||
private ImageSizeResolver imageSizeResolver;
|
||||
|
||||
Builder(Context context) {
|
||||
Builder(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public Builder theme(SpannableTheme theme) {
|
||||
@NonNull
|
||||
public Builder theme(@NonNull SpannableTheme theme) {
|
||||
this.theme = theme;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder asyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader) {
|
||||
@NonNull
|
||||
public Builder asyncDrawableLoader(@NonNull AsyncDrawable.Loader asyncDrawableLoader) {
|
||||
this.asyncDrawableLoader = asyncDrawableLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder syntaxHighlight(SyntaxHighlight syntaxHighlight) {
|
||||
@NonNull
|
||||
public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
|
||||
this.syntaxHighlight = syntaxHighlight;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder linkResolver(LinkSpan.Resolver linkResolver) {
|
||||
@NonNull
|
||||
public Builder linkResolver(@NonNull LinkSpan.Resolver linkResolver) {
|
||||
this.linkResolver = linkResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder urlProcessor(UrlProcessor urlProcessor) {
|
||||
@NonNull
|
||||
public Builder urlProcessor(@NonNull UrlProcessor urlProcessor) {
|
||||
this.urlProcessor = urlProcessor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder htmlParser(SpannableHtmlParser htmlParser) {
|
||||
@NonNull
|
||||
public Builder htmlParser(@NonNull SpannableHtmlParser htmlParser) {
|
||||
this.htmlParser = htmlParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@NonNull
|
||||
public Builder imageSizeResolver(@NonNull ImageSizeResolver imageSizeResolver) {
|
||||
this.imageSizeResolver = imageSizeResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public SpannableConfiguration build() {
|
||||
|
||||
if (theme == null) {
|
||||
theme = SpannableTheme.create(context);
|
||||
}
|
||||
|
||||
if (asyncDrawableLoader == null) {
|
||||
asyncDrawableLoader = new AsyncDrawableLoaderNoOp();
|
||||
}
|
||||
|
||||
if (syntaxHighlight == null) {
|
||||
syntaxHighlight = new SyntaxHighlightNoOp();
|
||||
}
|
||||
|
||||
if (linkResolver == null) {
|
||||
linkResolver = new LinkResolverDef();
|
||||
}
|
||||
|
||||
if (urlProcessor == null) {
|
||||
urlProcessor = new UrlProcessorNoOp();
|
||||
}
|
||||
|
||||
if (htmlParser == null) {
|
||||
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver);
|
||||
|
||||
if (imageSizeResolver == null) {
|
||||
imageSizeResolver = new ImageSizeResolverDef();
|
||||
}
|
||||
|
||||
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver, imageSizeResolver);
|
||||
}
|
||||
|
||||
return new SpannableConfiguration(this);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
package ru.noties.markwon.renderer.html;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.UrlProcessor;
|
||||
@ -17,14 +20,18 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
||||
private final SpannableTheme theme;
|
||||
private final AsyncDrawable.Loader loader;
|
||||
private final UrlProcessor urlProcessor;
|
||||
private final ImageSizeResolver imageSizeResolver;
|
||||
|
||||
ImageProviderImpl(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader,
|
||||
@NonNull UrlProcessor urlProcessor) {
|
||||
@NonNull UrlProcessor urlProcessor,
|
||||
@NonNull ImageSizeResolver imageSizeResolver
|
||||
) {
|
||||
this.theme = theme;
|
||||
this.loader = loader;
|
||||
this.urlProcessor = urlProcessor;
|
||||
this.imageSizeResolver = imageSizeResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -47,7 +54,7 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
||||
replacement = "\uFFFC";
|
||||
}
|
||||
|
||||
final AsyncDrawable drawable = new AsyncDrawable(destination, loader);
|
||||
final AsyncDrawable drawable = new AsyncDrawable(destination, loader, imageSizeResolver, parseImageSize(attributes));
|
||||
final AsyncDrawableSpan span = new AsyncDrawableSpan(theme, drawable);
|
||||
|
||||
final SpannableString string = new SpannableString(replacement);
|
||||
@ -60,4 +67,138 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
||||
|
||||
return spanned;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ImageSize parseImageSize(@NonNull Map<String, String> attributes) {
|
||||
|
||||
final ImageSize imageSize;
|
||||
|
||||
final StyleProvider styleProvider = new StyleProvider(attributes.get("style"));
|
||||
|
||||
final ImageSize.Dimension width = parseDimension(extractDimension("width", attributes, styleProvider));
|
||||
final ImageSize.Dimension height = parseDimension(extractDimension("height", attributes, styleProvider));
|
||||
|
||||
if (width == null
|
||||
&& height == null) {
|
||||
imageSize = null;
|
||||
} else {
|
||||
imageSize = new ImageSize(width, height);
|
||||
}
|
||||
|
||||
return imageSize;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String extractDimension(@NonNull String name, @NonNull Map<String, String> attributes, @NonNull StyleProvider styleProvider) {
|
||||
|
||||
final String out;
|
||||
|
||||
final String inline = attributes.get(name);
|
||||
if (!TextUtils.isEmpty(inline)) {
|
||||
out = inline;
|
||||
} else {
|
||||
out = extractDimensionFromStyle(name, styleProvider);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String extractDimensionFromStyle(@NonNull String name, @NonNull StyleProvider styleProvider) {
|
||||
return styleProvider.attributes().get(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ImageSize.Dimension parseDimension(@Nullable String raw) {
|
||||
|
||||
// a set of digits, then dimension unit (allow floating)
|
||||
|
||||
final ImageSize.Dimension dimension;
|
||||
|
||||
final int length = raw != null
|
||||
? raw.length()
|
||||
: 0;
|
||||
|
||||
if (length == 0) {
|
||||
dimension = null;
|
||||
} else {
|
||||
|
||||
// first digit to find -> unit is finished (can be null)
|
||||
|
||||
int index = -1;
|
||||
|
||||
for (int i = length - 1; i >= 0; i--) {
|
||||
if (Character.isDigit(raw.charAt(i))) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no digits -> no dimension
|
||||
if (index == -1) {
|
||||
dimension = null;
|
||||
} else {
|
||||
|
||||
final String value;
|
||||
final String unit;
|
||||
|
||||
// no unit is specified
|
||||
if (index == length - 1) {
|
||||
value = raw;
|
||||
unit = null;
|
||||
} else {
|
||||
value = raw.substring(0, index + 1);
|
||||
unit = raw.substring(index + 1);
|
||||
}
|
||||
|
||||
ImageSize.Dimension inner;
|
||||
try {
|
||||
final float floatValue = Float.parseFloat(value);
|
||||
inner = new ImageSize.Dimension(floatValue, unit);
|
||||
} catch (NumberFormatException e) {
|
||||
inner = null;
|
||||
}
|
||||
|
||||
dimension = inner;
|
||||
}
|
||||
}
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private static class StyleProvider {
|
||||
|
||||
private final String style;
|
||||
private Map<String, String> attributes;
|
||||
|
||||
StyleProvider(@Nullable String style) {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Map<String, String> attributes() {
|
||||
final Map<String, String> out;
|
||||
if (attributes != null) {
|
||||
out = attributes;
|
||||
} else {
|
||||
if (TextUtils.isEmpty(style)) {
|
||||
out = attributes = Collections.emptyMap();
|
||||
} else {
|
||||
final String[] split = style.split(";");
|
||||
final Map<String, String> map = new HashMap<>(split.length);
|
||||
String[] parts;
|
||||
for (String s : split) {
|
||||
if (!TextUtils.isEmpty(s)) {
|
||||
parts = s.split(":");
|
||||
if (parts.length == 2) {
|
||||
map.put(parts[0].trim(), parts[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
out = attributes = map;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package ru.noties.markwon.renderer.html;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class ImageSize {
|
||||
|
||||
public static class Dimension {
|
||||
|
||||
public final float value;
|
||||
public final String unit;
|
||||
|
||||
public Dimension(float value, @Nullable String unit) {
|
||||
this.value = value;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Dimension{" +
|
||||
"value=" + value +
|
||||
", unit='" + unit + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public final Dimension width;
|
||||
public final Dimension height;
|
||||
|
||||
public ImageSize(@Nullable Dimension width, @Nullable Dimension height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package ru.noties.markwon.renderer.html;
|
||||
|
||||
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
|
||||
);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package ru.noties.markwon.renderer.html;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public class ImageSizeResolverDef extends ImageSizeResolver {
|
||||
|
||||
// we track these two, others are considered to be pixels
|
||||
protected static final String UNIT_PERCENT = "%";
|
||||
protected static final String UNIT_EM = "em";
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Rect resolveImageSize(
|
||||
@Nullable ImageSize imageSize,
|
||||
@NonNull Rect imageBounds,
|
||||
int canvasWidth,
|
||||
float textSize
|
||||
) {
|
||||
|
||||
if (imageSize == null) {
|
||||
return imageBounds;
|
||||
}
|
||||
|
||||
final Rect rect;
|
||||
|
||||
final ImageSize.Dimension width = imageSize.width;
|
||||
final ImageSize.Dimension height = imageSize.height;
|
||||
|
||||
final int imageWidth = imageBounds.width();
|
||||
final int imageHeight = imageBounds.height();
|
||||
|
||||
final float ratio = (float) imageWidth / imageHeight;
|
||||
|
||||
if (width != null) {
|
||||
|
||||
final int w;
|
||||
final int h;
|
||||
|
||||
if (UNIT_PERCENT.equals(width.unit)) {
|
||||
w = (int) (canvasWidth * (width.value / 100.F) + .5F);
|
||||
} else {
|
||||
w = resolveAbsolute(width, imageWidth, textSize);
|
||||
}
|
||||
|
||||
if (height == null
|
||||
|| UNIT_PERCENT.equals(height.unit)) {
|
||||
h = (int) (w / ratio + .5F);
|
||||
} else {
|
||||
h = resolveAbsolute(height, imageHeight, textSize);
|
||||
}
|
||||
|
||||
rect = new Rect(0, 0, w, h);
|
||||
|
||||
} else if (height != null) {
|
||||
|
||||
if (!UNIT_PERCENT.equals(height.unit)) {
|
||||
final int h = resolveAbsolute(height, imageHeight, textSize);
|
||||
final int w = (int) (h * ratio + .5F);
|
||||
rect = new Rect(0, 0, w, h);
|
||||
} else {
|
||||
rect = imageBounds;
|
||||
}
|
||||
} else {
|
||||
rect = imageBounds;
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
protected int resolveAbsolute(@NonNull ImageSize.Dimension dimension, int original, float textSize) {
|
||||
final int out;
|
||||
if (UNIT_EM.equals(dimension.unit)) {
|
||||
out = (int) (dimension.value * textSize + .5F);
|
||||
} else {
|
||||
out = original;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
@ -21,37 +21,74 @@ import ru.noties.markwon.spans.SpannableTheme;
|
||||
public class SpannableHtmlParser {
|
||||
|
||||
// creates default parser
|
||||
@NonNull
|
||||
public static SpannableHtmlParser create(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, null, null)
|
||||
return builderWithDefaults(theme, loader, null, null, null)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@NonNull
|
||||
public static SpannableHtmlParser create(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader,
|
||||
@NonNull ImageSizeResolver imageSizeResolver
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, null, null, imageSizeResolver)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SpannableHtmlParser create(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader,
|
||||
@NonNull UrlProcessor urlProcessor,
|
||||
@NonNull LinkSpan.Resolver resolver
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, urlProcessor, resolver)
|
||||
return builderWithDefaults(theme, loader, urlProcessor, resolver, null)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@NonNull
|
||||
public static SpannableHtmlParser create(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader,
|
||||
@NonNull UrlProcessor urlProcessor,
|
||||
@NonNull LinkSpan.Resolver resolver,
|
||||
@NonNull ImageSizeResolver imageSizeResolver
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, urlProcessor, resolver, imageSizeResolver)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder builderWithDefaults(@NonNull SpannableTheme theme) {
|
||||
return builderWithDefaults(theme, null, null, null);
|
||||
return builderWithDefaults(theme, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated in 1.0.1: added imageSizeResolverArgument
|
||||
*/
|
||||
@NonNull
|
||||
public static Builder builderWithDefaults(
|
||||
@NonNull SpannableTheme theme,
|
||||
@Nullable AsyncDrawable.Loader asyncDrawableLoader,
|
||||
@Nullable UrlProcessor urlProcessor,
|
||||
@Nullable LinkSpan.Resolver resolver
|
||||
@Nullable LinkSpan.Resolver resolver,
|
||||
@Nullable ImageSizeResolver imageSizeResolver
|
||||
) {
|
||||
|
||||
if (urlProcessor == null) {
|
||||
@ -68,7 +105,12 @@ public class SpannableHtmlParser {
|
||||
|
||||
final ImageProvider imageProvider;
|
||||
if (asyncDrawableLoader != null) {
|
||||
imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor);
|
||||
|
||||
if (imageSizeResolver == null) {
|
||||
imageSizeResolver = new ImageSizeResolverDef();
|
||||
}
|
||||
|
||||
imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor, imageSizeResolver);
|
||||
} else {
|
||||
imageProvider = null;
|
||||
}
|
||||
@ -163,21 +205,25 @@ public class SpannableHtmlParser {
|
||||
private ImageProvider imageProvider;
|
||||
private HtmlParser parser;
|
||||
|
||||
@NonNull
|
||||
Builder simpleTag(@NonNull String tag, @NonNull SpanProvider provider) {
|
||||
simpleTags.put(tag, provider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder imageProvider(ImageProvider imageProvider) {
|
||||
@NonNull
|
||||
public Builder imageProvider(@Nullable ImageProvider imageProvider) {
|
||||
this.imageProvider = imageProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder parser(@NonNull HtmlParser parser) {
|
||||
this.parser = parser;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public SpannableHtmlParser build() {
|
||||
if (parser == null) {
|
||||
parser = DefaultHtmlParser.create();
|
||||
|
@ -3,15 +3,20 @@ package ru.noties.markwon.spans;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
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 ru.noties.markwon.renderer.html.ImageSize;
|
||||
import ru.noties.markwon.renderer.html.ImageSizeResolver;
|
||||
|
||||
public class AsyncDrawable extends Drawable {
|
||||
|
||||
public interface Loader {
|
||||
|
||||
void load(@NonNull String destination, @NonNull AsyncDrawable drawable);
|
||||
|
||||
void cancel(@NonNull String destination);
|
||||
@ -19,13 +24,32 @@ public class AsyncDrawable extends Drawable {
|
||||
|
||||
private final String destination;
|
||||
private final Loader loader;
|
||||
private final ImageSize imageSize;
|
||||
private final ImageSizeResolver imageSizeResolver;
|
||||
|
||||
private Drawable result;
|
||||
private Callback callback;
|
||||
|
||||
private int canvasWidth;
|
||||
private float textSize;
|
||||
|
||||
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
|
||||
this(destination, loader, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public AsyncDrawable(
|
||||
@NonNull String destination,
|
||||
@NonNull Loader loader,
|
||||
@Nullable ImageSizeResolver imageSizeResolver,
|
||||
@Nullable ImageSize imageSize
|
||||
) {
|
||||
this.destination = destination;
|
||||
this.loader = loader;
|
||||
this.imageSizeResolver = imageSizeResolver;
|
||||
this.imageSize = imageSize;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
@ -77,13 +101,22 @@ public class AsyncDrawable extends Drawable {
|
||||
this.result = result;
|
||||
this.result.setCallback(callback);
|
||||
|
||||
// should we copy the data here? like bounds etc?
|
||||
// if we are async and we load some image from some source
|
||||
// thr bounds might change... so we are better off copy `result` bounds to this instance
|
||||
setBounds(result.getBounds());
|
||||
final Rect bounds = resolveBounds();
|
||||
result.setBounds(bounds);
|
||||
setBounds(bounds);
|
||||
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public void initWithKnownDimensions(int width, float textSize) {
|
||||
this.canvasWidth = width;
|
||||
this.textSize = textSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (hasResult()) {
|
||||
@ -133,4 +166,19 @@ public class AsyncDrawable extends Drawable {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@NonNull
|
||||
private Rect resolveBounds() {
|
||||
final Rect rect;
|
||||
if (imageSizeResolver == null
|
||||
|| imageSize == null) {
|
||||
rect = result.getBounds();
|
||||
} else {
|
||||
rect = imageSizeResolver.resolveImageSize(imageSize, result.getBounds(), canvasWidth, textSize);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,8 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
int bottom,
|
||||
@NonNull Paint paint) {
|
||||
|
||||
drawable.initWithKnownDimensions(canvas.getWidth(), paint.getTextSize());
|
||||
|
||||
final AsyncDrawable drawable = this.drawable;
|
||||
|
||||
if (drawable.hasResult()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user