From 35dcf079b24d0a2a5a7992db4fa77c875d405923 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sat, 11 Nov 2017 12:46:13 +0300 Subject: [PATCH] Intermediate commit --- README.md | 6 +- app/build.gradle | 2 +- .../ru/noties/markwon/MarkdownRenderer.java | 8 + .../renderer/html/ImageProviderImpl.java | 153 +++++++++++++++++- .../markwon/renderer/html/ImageSize.java | 72 +++++++++ .../noties/markwon/spans/AsyncDrawable.java | 103 +++++++++++- .../markwon/spans/AsyncDrawableSpan.java | 2 + 7 files changed, 338 insertions(+), 8 deletions(-) create mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/ImageSize.java diff --git a/README.md b/README.md index 1e33a9f4..5fe29ccf 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,10 @@ compile 'ru.noties:markwon-view:1.0.0' // optional Taken with default configuration (except for image loading): - - + + + + By default configuration uses TextView textColor for styling, so changing textColor changes style diff --git a/app/build.gradle b/app/build.gradle index fab6b399..bc2bca96 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { buildTypes { debug { - minifyEnabled true + minifyEnabled false proguardFile 'proguard.pro' } } diff --git a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java index 21b0ae7a..f276dfe2 100644 --- a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java +++ b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java @@ -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 diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java b/library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java index 3e45caee..d9f589cc 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java +++ b/library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java @@ -1,11 +1,14 @@ 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.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import ru.noties.markwon.UrlProcessor; import ru.noties.markwon.spans.AsyncDrawable; @@ -14,6 +17,9 @@ import ru.noties.markwon.spans.SpannableTheme; class ImageProviderImpl implements SpannableHtmlParser.ImageProvider { + private static final Pattern STYLE_WIDTH = Pattern.compile(".*width:\\s*(\\d+)(%|em|px)*.*"); + private static final Pattern STYLE_HEIGHT = Pattern.compile(".*height:\\s*(\\d+)(%|em|px)*.*"); + private final SpannableTheme theme; private final AsyncDrawable.Loader loader; private final UrlProcessor urlProcessor; @@ -47,7 +53,7 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider { replacement = "\uFFFC"; } - final AsyncDrawable drawable = new AsyncDrawable(destination, loader); + final AsyncDrawable drawable = new AsyncDrawable(destination, loader, parseImageSize(attributes)); final AsyncDrawableSpan span = new AsyncDrawableSpan(theme, drawable); final SpannableString string = new SpannableString(replacement); @@ -60,4 +66,149 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider { return spanned; } + + @Nullable + private static ImageSize parseImageSize(@NonNull Map attributes) { + + return null; + +// final String width = attributes.get("width"); +// final String height = attributes.get("height"); +// +// final ImageSize imageSize; +// +// final String width = extractWidth(attributes); +// final String height = extractHeight(attributes); +// +// if (TextUtils.isEmpty(width) +// && TextUtils.isEmpty(height)) { +// imageSize = null; +// } else { +// if (isRelative(width)) { +// // check if height is NOT relative, if it is -> ignore +// final int h = isRelative(height) +// ? 0 +// : parseInt(height); +// imageSize = new ImageSize(, parseInt(width), h); +// } else { +// imageSize = new ImageSize(false, parseInt(width), parseInt(height)); +// } +// } +// +// return imageSize; + } + +// @Nullable +// private static ImageSize.Dimension parseWidth(@NonNull Map attributes) { +// +// // so, we can have raw value specified via direct attribute +// +// final ImageSize.Dimension dimension; +// +// final String width = attributes.get("width"); +// if (!TextUtils.isEmpty(width)) { +// final Matcher matcher = +// } +// } + + @Nullable + private static String extractWidth(@NonNull Map attributes) { + + // let's check style first + + String width = attributes.get("width"); + + if (width == null) { + final String style = attributes.get("style"); + if (!TextUtils.isEmpty(style)) { + final Matcher matcher = STYLE_WIDTH.matcher(style); + if (matcher.matches()) { + width = matcher.group(1); + } + } + } + + return width; + } + + @Nullable + private static String extractHeight(@NonNull Map attributes) { + + String height = attributes.get("height"); + + if (height == null) { + final String style = attributes.get("style"); + if (!TextUtils.isEmpty(style)) { + final Matcher matcher = STYLE_HEIGHT.matcher(style); + if (matcher.matches()) { + height = matcher.group(1); + } + } + } + + return height; + } + + @Nullable + private static ImageSize.Dimension extractFromStyle(@Nullable String style, @NonNull Pattern pattern) { + final ImageSize.Dimension dimension; + if (style == null) { + dimension = null; + } else { + final Matcher matcher = pattern.matcher(style); + if (matcher.matches()) { + dimension = new ImageSize.Dimension( + parseUnit(matcher.group(2)), + parseInt(matcher.group(1)) + ); + } else { + dimension = null; + } + } + return dimension; + } + + @NonNull + private static ImageSize.Unit parseUnit(@Nullable String s) { + + final ImageSize.Unit unit; + + if (TextUtils.isEmpty(s)) { + + unit = ImageSize.Unit.PIXELS; + + } else { + + final int length = s.length(); + + if (length == 1 + && '%' == s.charAt(length - 1)) { + unit = ImageSize.Unit.PERCENT; + } else if (length == 2 + && 'e' == s.charAt(length - 2) + && 'm' == s.charAt(length - 1)) { + unit = ImageSize.Unit.FONT_SIZE; + } else { + unit = ImageSize.Unit.PIXELS; + } + } + + return unit; + } + + private static boolean isRelative(@Nullable String attr) { + return attr != null && attr.charAt(attr.length() - 1) == '%'; + } + + private static int parseInt(@Nullable String s) { + int result = 0; + if (!TextUtils.isEmpty(s)) { + try { + result = Integer.parseInt(s.replaceAll("[^\\d]", "")); + } catch (NumberFormatException e) { + result = 0; + } + } + return result; + } } diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/ImageSize.java b/library/src/main/java/ru/noties/markwon/renderer/html/ImageSize.java new file mode 100644 index 00000000..0f916716 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/renderer/html/ImageSize.java @@ -0,0 +1,72 @@ +package ru.noties.markwon.renderer.html; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * @since 1.0.1 + */ +public class ImageSize { + + public enum Unit { + PERCENT, FONT_SIZE, PIXELS + } + + public static class Dimension { + + private final Unit unit; + private final int value; + + public Dimension(@NonNull Unit unit, int value) { + this.unit = unit; + this.value = value; + } + + @NonNull + public Unit unit() { + return unit; + } + + public int value() { + return value; + } + + @Override + public String toString() { + return "Dimension{" + + "unit=" + unit + + ", value=" + value + + '}'; + } + } + + // width can be relative (in percent) + // height CANNOT be relative (endless loop) + // both can be absolute + + private final Dimension width; + private final Dimension height; + + public ImageSize(@Nullable Dimension width, @Nullable Dimension height) { + this.width = width; + this.height = height; + } + + @Nullable + public Dimension width() { + return width; + } + + @Nullable + public Dimension height() { + return height; + } + + @Override + public String toString() { + return "ImageSize{" + + "width=" + width + + ", height=" + height + + '}'; + } +} diff --git a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java b/library/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java index b466e194..4471e699 100644 --- a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java +++ b/library/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java @@ -3,15 +3,19 @@ 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; + public class AsyncDrawable extends Drawable { public interface Loader { + void load(@NonNull String destination, @NonNull AsyncDrawable drawable); void cancel(@NonNull String destination); @@ -19,13 +23,24 @@ public class AsyncDrawable extends Drawable { private final String destination; private final Loader loader; + private final ImageSize imageSize; private Drawable result; private Callback callback; + private int canvasWidth; + public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) { + this(destination, loader, null); + } + + /** + * @since 1.0.1 + */ + public AsyncDrawable(@NonNull String destination, @NonNull Loader loader, @Nullable ImageSize imageSize) { this.destination = destination; this.loader = loader; + this.imageSize = imageSize; } public String getDestination() { @@ -77,13 +92,21 @@ 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 initWithCanvasWidth(int width) { + this.canvasWidth = width; + } + @Override public void draw(@NonNull Canvas canvas) { if (hasResult()) { @@ -133,4 +156,76 @@ public class AsyncDrawable extends Drawable { } return out; } + + /** + * @since 1.0.1 + */ + @NonNull + private Rect resolveBounds() { + + return result.getBounds(); + +// final Rect rect; +// +// if (canvasWidth == 0 +// || imageSize == null) { +// +// rect = result.getBounds(); +// +// } else { +// +// final Rect bounds = result.getBounds(); +// final float ratio = (float) bounds.width() / bounds.height(); +// +// if (imageSize.widthIsRelative()) { +// +// final int w = (int) (canvasWidth * ((float) imageSize.width() / 100.F) + .5F); +// final int h; +// +// // we still should allow absolute height +// if (imageSize.height() > 0) { +// h = imageSize.height(); +// } else { +// h = (int) (w / ratio); +// } +// +// rect = new Rect(0, 0, w, h); +// +// } else { +// +// // if width is specified, but height not -> calculate by ratio (and vice versa) +// // else +// +// final int w; +// final int h; +// +// final int width = imageSize.width(); +// final int height = imageSize.height(); +// +// if (width > 0 +// && height > 0) { +// w = width; +// h = height; +// } else if (width > 0) { +// w = width; +// h = (int) (w / ratio + .5F); +// } else if (height > 0) { +// h = height; +// w = (int) (h * ratio + .5F); +// } else { +// w = 0; +// h = 0; +// } +// +// if (w == 0 +// || h == 0) { +// rect = bounds; +// } else { +// rect = new Rect(0, 0, w, h); +// } +// } +// } +// +// return rect; + } } diff --git a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java b/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java index 25d01b00..216203d1 100644 --- a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java @@ -112,6 +112,8 @@ public class AsyncDrawableSpan extends ReplacementSpan { int bottom, @NonNull Paint paint) { + drawable.initWithCanvasWidth(canvas.getWidth()); + this.lastKnownDrawX = (int) (x + .5F); this.lastKnownDrawY = y;