Intermediate commit

This commit is contained in:
Dimitry Ivanov 2017-11-11 12:46:13 +03:00
parent 18938a1aa2
commit 35dcf079b2
7 changed files with 338 additions and 8 deletions

View File

@ -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

View File

@ -19,7 +19,7 @@ android {
buildTypes {
debug {
minifyEnabled true
minifyEnabled false
proguardFile 'proguard.pro'
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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;
}
}

View File

@ -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;