Intermediate commit
This commit is contained in:
parent
18938a1aa2
commit
35dcf079b2
@ -50,8 +50,10 @@ compile 'ru.noties:markwon-view:1.0.0' // optional
|
|||||||
|
|
||||||
Taken with default configuration (except for image loading):
|
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%" />
|
<a href="./art/mw_light_01.png"><img src="./art/mw_light_01.png" width="30%" /></a>
|
||||||
<img src="./art/mw_light_03.png" width="33%" /> <img src="./art/mw_dark_01.png" width="33%" />
|
<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
|
By default configuration uses TextView textColor for styling, so changing textColor changes style
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
minifyEnabled true
|
minifyEnabled false
|
||||||
proguardFile 'proguard.pro'
|
proguardFile 'proguard.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package ru.noties.markwon;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ import java.util.concurrent.Future;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import ru.noties.debug.Debug;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
@ -57,8 +59,14 @@ public class MarkdownRenderer {
|
|||||||
.urlProcessor(urlProcessor)
|
.urlProcessor(urlProcessor)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
final long start = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
final CharSequence text = Markwon.markdown(configuration, markdown);
|
final CharSequence text = Markwon.markdown(configuration, markdown);
|
||||||
|
|
||||||
|
final long end = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
|
Debug.i("markdown rendered: %d ms", end - start);
|
||||||
|
|
||||||
if (!isCancelled()) {
|
if (!isCancelled()) {
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import ru.noties.markwon.UrlProcessor;
|
import ru.noties.markwon.UrlProcessor;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
@ -14,6 +17,9 @@ import ru.noties.markwon.spans.SpannableTheme;
|
|||||||
|
|
||||||
class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
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 SpannableTheme theme;
|
||||||
private final AsyncDrawable.Loader loader;
|
private final AsyncDrawable.Loader loader;
|
||||||
private final UrlProcessor urlProcessor;
|
private final UrlProcessor urlProcessor;
|
||||||
@ -47,7 +53,7 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
|||||||
replacement = "\uFFFC";
|
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 AsyncDrawableSpan span = new AsyncDrawableSpan(theme, drawable);
|
||||||
|
|
||||||
final SpannableString string = new SpannableString(replacement);
|
final SpannableString string = new SpannableString(replacement);
|
||||||
@ -60,4 +66,149 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
|||||||
|
|
||||||
return spanned;
|
return spanned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ImageSize parseImageSize(@NonNull Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -3,15 +3,19 @@ package ru.noties.markwon.spans;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.ColorFilter;
|
import android.graphics.ColorFilter;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.IntRange;
|
import android.support.annotation.IntRange;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import ru.noties.markwon.renderer.html.ImageSize;
|
||||||
|
|
||||||
public class AsyncDrawable extends Drawable {
|
public class AsyncDrawable extends Drawable {
|
||||||
|
|
||||||
public interface Loader {
|
public interface Loader {
|
||||||
|
|
||||||
void load(@NonNull String destination, @NonNull AsyncDrawable drawable);
|
void load(@NonNull String destination, @NonNull AsyncDrawable drawable);
|
||||||
|
|
||||||
void cancel(@NonNull String destination);
|
void cancel(@NonNull String destination);
|
||||||
@ -19,13 +23,24 @@ public class AsyncDrawable extends Drawable {
|
|||||||
|
|
||||||
private final String destination;
|
private final String destination;
|
||||||
private final Loader loader;
|
private final Loader loader;
|
||||||
|
private final ImageSize imageSize;
|
||||||
|
|
||||||
private Drawable result;
|
private Drawable result;
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
|
||||||
|
private int canvasWidth;
|
||||||
|
|
||||||
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
|
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.destination = destination;
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
|
this.imageSize = imageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDestination() {
|
public String getDestination() {
|
||||||
@ -77,13 +92,21 @@ public class AsyncDrawable extends Drawable {
|
|||||||
this.result = result;
|
this.result = result;
|
||||||
this.result.setCallback(callback);
|
this.result.setCallback(callback);
|
||||||
|
|
||||||
// should we copy the data here? like bounds etc?
|
final Rect bounds = resolveBounds();
|
||||||
// if we are async and we load some image from some source
|
result.setBounds(bounds);
|
||||||
// thr bounds might change... so we are better off copy `result` bounds to this instance
|
setBounds(bounds);
|
||||||
setBounds(result.getBounds());
|
|
||||||
invalidateSelf();
|
invalidateSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.0.1
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public void initWithCanvasWidth(int width) {
|
||||||
|
this.canvasWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(@NonNull Canvas canvas) {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
if (hasResult()) {
|
if (hasResult()) {
|
||||||
@ -133,4 +156,76 @@ public class AsyncDrawable extends Drawable {
|
|||||||
}
|
}
|
||||||
return out;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,8 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
|||||||
int bottom,
|
int bottom,
|
||||||
@NonNull Paint paint) {
|
@NonNull Paint paint) {
|
||||||
|
|
||||||
|
drawable.initWithCanvasWidth(canvas.getWidth());
|
||||||
|
|
||||||
this.lastKnownDrawX = (int) (x + .5F);
|
this.lastKnownDrawX = (int) (x + .5F);
|
||||||
this.lastKnownDrawY = y;
|
this.lastKnownDrawY = y;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user