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):
|
||||
|
||||
<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
|
||||
|
||||
|
@ -19,7 +19,7 @@ android {
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled true
|
||||
minifyEnabled false
|
||||
proguardFile 'proguard.pro'
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<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.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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user