Image sizes specified in HTML
This commit is contained in:
parent
35dcf079b2
commit
3332a722d4
@ -3,6 +3,8 @@ package ru.noties.markwon;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
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.renderer.html.SpannableHtmlParser;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.spans.LinkSpan;
|
import ru.noties.markwon.spans.LinkSpan;
|
||||||
@ -12,10 +14,12 @@ import ru.noties.markwon.spans.SpannableTheme;
|
|||||||
public class SpannableConfiguration {
|
public class SpannableConfiguration {
|
||||||
|
|
||||||
// creates default configuration
|
// creates default configuration
|
||||||
|
@NonNull
|
||||||
public static SpannableConfiguration create(@NonNull Context context) {
|
public static SpannableConfiguration create(@NonNull Context context) {
|
||||||
return new Builder(context).build();
|
return new Builder(context).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static Builder builder(@NonNull Context context) {
|
public static Builder builder(@NonNull Context context) {
|
||||||
return new Builder(context);
|
return new Builder(context);
|
||||||
}
|
}
|
||||||
@ -27,7 +31,7 @@ public class SpannableConfiguration {
|
|||||||
private final UrlProcessor urlProcessor;
|
private final UrlProcessor urlProcessor;
|
||||||
private final SpannableHtmlParser htmlParser;
|
private final SpannableHtmlParser htmlParser;
|
||||||
|
|
||||||
private SpannableConfiguration(Builder builder) {
|
private SpannableConfiguration(@NonNull Builder builder) {
|
||||||
this.theme = builder.theme;
|
this.theme = builder.theme;
|
||||||
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||||
this.syntaxHighlight = builder.syntaxHighlight;
|
this.syntaxHighlight = builder.syntaxHighlight;
|
||||||
@ -36,26 +40,32 @@ public class SpannableConfiguration {
|
|||||||
this.htmlParser = builder.htmlParser;
|
this.htmlParser = builder.htmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public SpannableTheme theme() {
|
public SpannableTheme theme() {
|
||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public AsyncDrawable.Loader asyncDrawableLoader() {
|
public AsyncDrawable.Loader asyncDrawableLoader() {
|
||||||
return asyncDrawableLoader;
|
return asyncDrawableLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public SyntaxHighlight syntaxHighlight() {
|
public SyntaxHighlight syntaxHighlight() {
|
||||||
return syntaxHighlight;
|
return syntaxHighlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public LinkSpan.Resolver linkResolver() {
|
public LinkSpan.Resolver linkResolver() {
|
||||||
return linkResolver;
|
return linkResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public UrlProcessor urlProcessor() {
|
public UrlProcessor urlProcessor() {
|
||||||
return urlProcessor;
|
return urlProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public SpannableHtmlParser htmlParser() {
|
public SpannableHtmlParser htmlParser() {
|
||||||
return htmlParser;
|
return htmlParser;
|
||||||
}
|
}
|
||||||
@ -69,60 +79,89 @@ public class SpannableConfiguration {
|
|||||||
private LinkSpan.Resolver linkResolver;
|
private LinkSpan.Resolver linkResolver;
|
||||||
private UrlProcessor urlProcessor;
|
private UrlProcessor urlProcessor;
|
||||||
private SpannableHtmlParser htmlParser;
|
private SpannableHtmlParser htmlParser;
|
||||||
|
private ImageSizeResolver imageSizeResolver;
|
||||||
|
|
||||||
Builder(Context context) {
|
Builder(@NonNull Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder theme(SpannableTheme theme) {
|
@NonNull
|
||||||
|
public Builder theme(@NonNull SpannableTheme theme) {
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder asyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader) {
|
@NonNull
|
||||||
|
public Builder asyncDrawableLoader(@NonNull AsyncDrawable.Loader asyncDrawableLoader) {
|
||||||
this.asyncDrawableLoader = asyncDrawableLoader;
|
this.asyncDrawableLoader = asyncDrawableLoader;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder syntaxHighlight(SyntaxHighlight syntaxHighlight) {
|
@NonNull
|
||||||
|
public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
|
||||||
this.syntaxHighlight = syntaxHighlight;
|
this.syntaxHighlight = syntaxHighlight;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder linkResolver(LinkSpan.Resolver linkResolver) {
|
@NonNull
|
||||||
|
public Builder linkResolver(@NonNull LinkSpan.Resolver linkResolver) {
|
||||||
this.linkResolver = linkResolver;
|
this.linkResolver = linkResolver;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder urlProcessor(UrlProcessor urlProcessor) {
|
@NonNull
|
||||||
|
public Builder urlProcessor(@NonNull UrlProcessor urlProcessor) {
|
||||||
this.urlProcessor = urlProcessor;
|
this.urlProcessor = urlProcessor;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder htmlParser(SpannableHtmlParser htmlParser) {
|
@NonNull
|
||||||
|
public Builder htmlParser(@NonNull SpannableHtmlParser htmlParser) {
|
||||||
this.htmlParser = htmlParser;
|
this.htmlParser = htmlParser;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.0.1
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Builder imageSizeResolver(@NonNull ImageSizeResolver imageSizeResolver) {
|
||||||
|
this.imageSizeResolver = imageSizeResolver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public SpannableConfiguration build() {
|
public SpannableConfiguration build() {
|
||||||
|
|
||||||
if (theme == null) {
|
if (theme == null) {
|
||||||
theme = SpannableTheme.create(context);
|
theme = SpannableTheme.create(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asyncDrawableLoader == null) {
|
if (asyncDrawableLoader == null) {
|
||||||
asyncDrawableLoader = new AsyncDrawableLoaderNoOp();
|
asyncDrawableLoader = new AsyncDrawableLoaderNoOp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syntaxHighlight == null) {
|
if (syntaxHighlight == null) {
|
||||||
syntaxHighlight = new SyntaxHighlightNoOp();
|
syntaxHighlight = new SyntaxHighlightNoOp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linkResolver == null) {
|
if (linkResolver == null) {
|
||||||
linkResolver = new LinkResolverDef();
|
linkResolver = new LinkResolverDef();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlProcessor == null) {
|
if (urlProcessor == null) {
|
||||||
urlProcessor = new UrlProcessorNoOp();
|
urlProcessor = new UrlProcessorNoOp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (htmlParser == null) {
|
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);
|
return new SpannableConfiguration(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import android.text.SpannableString;
|
|||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
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;
|
||||||
@ -17,20 +17,21 @@ 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;
|
||||||
|
private final ImageSizeResolver imageSizeResolver;
|
||||||
|
|
||||||
ImageProviderImpl(
|
ImageProviderImpl(
|
||||||
@NonNull SpannableTheme theme,
|
@NonNull SpannableTheme theme,
|
||||||
@NonNull AsyncDrawable.Loader loader,
|
@NonNull AsyncDrawable.Loader loader,
|
||||||
@NonNull UrlProcessor urlProcessor) {
|
@NonNull UrlProcessor urlProcessor,
|
||||||
|
@NonNull ImageSizeResolver imageSizeResolver
|
||||||
|
) {
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
this.urlProcessor = urlProcessor;
|
this.urlProcessor = urlProcessor;
|
||||||
|
this.imageSizeResolver = imageSizeResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -53,7 +54,7 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
|||||||
replacement = "\uFFFC";
|
replacement = "\uFFFC";
|
||||||
}
|
}
|
||||||
|
|
||||||
final AsyncDrawable drawable = new AsyncDrawable(destination, loader, parseImageSize(attributes));
|
final AsyncDrawable drawable = new AsyncDrawable(destination, loader, imageSizeResolver, 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);
|
||||||
@ -70,145 +71,134 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private static ImageSize parseImageSize(@NonNull Map<String, String> attributes) {
|
private static ImageSize parseImageSize(@NonNull Map<String, String> attributes) {
|
||||||
|
|
||||||
return null;
|
final ImageSize imageSize;
|
||||||
|
|
||||||
// final String width = attributes.get("width");
|
final StyleProvider styleProvider = new StyleProvider(attributes.get("style"));
|
||||||
// final String height = attributes.get("height");
|
|
||||||
//
|
final ImageSize.Dimension width = parseDimension(extractDimension("width", attributes, styleProvider));
|
||||||
// final ImageSize imageSize;
|
final ImageSize.Dimension height = parseDimension(extractDimension("height", attributes, styleProvider));
|
||||||
//
|
|
||||||
// final String width = extractWidth(attributes);
|
if (width == null
|
||||||
// final String height = extractHeight(attributes);
|
&& height == null) {
|
||||||
//
|
imageSize = null;
|
||||||
// if (TextUtils.isEmpty(width)
|
} else {
|
||||||
// && TextUtils.isEmpty(height)) {
|
imageSize = new ImageSize(width, 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
|
return imageSize;
|
||||||
// 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
|
@Nullable
|
||||||
private static String extractHeight(@NonNull Map<String, String> attributes) {
|
private static String extractDimension(@NonNull String name, @NonNull Map<String, String> attributes, @NonNull StyleProvider styleProvider) {
|
||||||
|
|
||||||
String height = attributes.get("height");
|
final String out;
|
||||||
|
|
||||||
if (height == null) {
|
final String inline = attributes.get(name);
|
||||||
final String style = attributes.get("style");
|
if (!TextUtils.isEmpty(inline)) {
|
||||||
if (!TextUtils.isEmpty(style)) {
|
out = inline;
|
||||||
final Matcher matcher = STYLE_HEIGHT.matcher(style);
|
} else {
|
||||||
if (matcher.matches()) {
|
out = extractDimensionFromStyle(name, styleProvider);
|
||||||
height = matcher.group(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return height;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static ImageSize.Dimension extractFromStyle(@Nullable String style, @NonNull Pattern pattern) {
|
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 ImageSize.Dimension dimension;
|
||||||
if (style == null) {
|
|
||||||
|
final int length = raw != null
|
||||||
|
? raw.length()
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
dimension = null;
|
dimension = null;
|
||||||
} else {
|
} else {
|
||||||
final Matcher matcher = pattern.matcher(style);
|
|
||||||
if (matcher.matches()) {
|
// first digit to find -> unit is finished (can be null)
|
||||||
dimension = new ImageSize.Dimension(
|
|
||||||
parseUnit(matcher.group(2)),
|
int index = -1;
|
||||||
parseInt(matcher.group(1))
|
|
||||||
);
|
for (int i = length - 1; i >= 0; i--) {
|
||||||
} else {
|
if (Character.isDigit(raw.charAt(i))) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no digits -> no dimension
|
||||||
|
if (index == -1) {
|
||||||
dimension = null;
|
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;
|
return dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class StyleProvider {
|
||||||
|
|
||||||
|
private final String style;
|
||||||
|
private Map<String, String> attributes;
|
||||||
|
|
||||||
|
StyleProvider(@Nullable String style) {
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static ImageSize.Unit parseUnit(@Nullable String s) {
|
Map<String, String> attributes() {
|
||||||
|
final Map<String, String> out;
|
||||||
final ImageSize.Unit unit;
|
if (attributes != null) {
|
||||||
|
out = attributes;
|
||||||
if (TextUtils.isEmpty(s)) {
|
|
||||||
|
|
||||||
unit = ImageSize.Unit.PIXELS;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
if (TextUtils.isEmpty(style)) {
|
||||||
final int length = s.length();
|
out = attributes = Collections.emptyMap();
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
unit = ImageSize.Unit.PIXELS;
|
final String[] split = style.split(";");
|
||||||
}
|
final Map<String, String> map = new HashMap<>(split.length);
|
||||||
}
|
String[] parts;
|
||||||
|
for (String s : split) {
|
||||||
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)) {
|
if (!TextUtils.isEmpty(s)) {
|
||||||
try {
|
parts = s.split(":");
|
||||||
result = Integer.parseInt(s.replaceAll("[^\\d]", ""));
|
if (parts.length == 2) {
|
||||||
} catch (NumberFormatException e) {
|
map.put(parts[0].trim(), parts[1].trim());
|
||||||
result = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
}
|
||||||
|
out = attributes = map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,72 +1,37 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.0.1
|
* @since 1.0.1
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class ImageSize {
|
public class ImageSize {
|
||||||
|
|
||||||
public enum Unit {
|
|
||||||
PERCENT, FONT_SIZE, PIXELS
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Dimension {
|
public static class Dimension {
|
||||||
|
|
||||||
private final Unit unit;
|
public final float value;
|
||||||
private final int value;
|
public final String unit;
|
||||||
|
|
||||||
public Dimension(@NonNull Unit unit, int value) {
|
public Dimension(float value, @Nullable String unit) {
|
||||||
this.unit = unit;
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
this.unit = unit;
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Unit unit() {
|
|
||||||
return unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int value() {
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Dimension{" +
|
return "Dimension{" +
|
||||||
"unit=" + unit +
|
"value=" + value +
|
||||||
", value=" + value +
|
", unit='" + unit + '\'' +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// width can be relative (in percent)
|
public final Dimension width;
|
||||||
// height CANNOT be relative (endless loop)
|
public final Dimension height;
|
||||||
// both can be absolute
|
|
||||||
|
|
||||||
private final Dimension width;
|
|
||||||
private final Dimension height;
|
|
||||||
|
|
||||||
public ImageSize(@Nullable Dimension width, @Nullable Dimension height) {
|
public ImageSize(@Nullable Dimension width, @Nullable Dimension height) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
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 +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
public class SpannableHtmlParser {
|
||||||
|
|
||||||
// creates default parser
|
// creates default parser
|
||||||
|
@NonNull
|
||||||
public static SpannableHtmlParser create(
|
public static SpannableHtmlParser create(
|
||||||
@NonNull SpannableTheme theme,
|
@NonNull SpannableTheme theme,
|
||||||
@NonNull AsyncDrawable.Loader loader
|
@NonNull AsyncDrawable.Loader loader
|
||||||
) {
|
) {
|
||||||
return builderWithDefaults(theme, loader, null, null)
|
return builderWithDefaults(theme, loader, null, null, null)
|
||||||
.build();
|
.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(
|
public static SpannableHtmlParser create(
|
||||||
@NonNull SpannableTheme theme,
|
@NonNull SpannableTheme theme,
|
||||||
@NonNull AsyncDrawable.Loader loader,
|
@NonNull AsyncDrawable.Loader loader,
|
||||||
@NonNull UrlProcessor urlProcessor,
|
@NonNull UrlProcessor urlProcessor,
|
||||||
@NonNull LinkSpan.Resolver resolver
|
@NonNull LinkSpan.Resolver resolver
|
||||||
) {
|
) {
|
||||||
return builderWithDefaults(theme, loader, urlProcessor, resolver)
|
return builderWithDefaults(theme, loader, urlProcessor, resolver, null)
|
||||||
.build();
|
.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() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static Builder builderWithDefaults(@NonNull SpannableTheme theme) {
|
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(
|
public static Builder builderWithDefaults(
|
||||||
@NonNull SpannableTheme theme,
|
@NonNull SpannableTheme theme,
|
||||||
@Nullable AsyncDrawable.Loader asyncDrawableLoader,
|
@Nullable AsyncDrawable.Loader asyncDrawableLoader,
|
||||||
@Nullable UrlProcessor urlProcessor,
|
@Nullable UrlProcessor urlProcessor,
|
||||||
@Nullable LinkSpan.Resolver resolver
|
@Nullable LinkSpan.Resolver resolver,
|
||||||
|
@Nullable ImageSizeResolver imageSizeResolver
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (urlProcessor == null) {
|
if (urlProcessor == null) {
|
||||||
@ -68,7 +105,12 @@ public class SpannableHtmlParser {
|
|||||||
|
|
||||||
final ImageProvider imageProvider;
|
final ImageProvider imageProvider;
|
||||||
if (asyncDrawableLoader != null) {
|
if (asyncDrawableLoader != null) {
|
||||||
imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor);
|
|
||||||
|
if (imageSizeResolver == null) {
|
||||||
|
imageSizeResolver = new ImageSizeResolverDef();
|
||||||
|
}
|
||||||
|
|
||||||
|
imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor, imageSizeResolver);
|
||||||
} else {
|
} else {
|
||||||
imageProvider = null;
|
imageProvider = null;
|
||||||
}
|
}
|
||||||
@ -163,21 +205,25 @@ public class SpannableHtmlParser {
|
|||||||
private ImageProvider imageProvider;
|
private ImageProvider imageProvider;
|
||||||
private HtmlParser parser;
|
private HtmlParser parser;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
Builder simpleTag(@NonNull String tag, @NonNull SpanProvider provider) {
|
Builder simpleTag(@NonNull String tag, @NonNull SpanProvider provider) {
|
||||||
simpleTags.put(tag, provider);
|
simpleTags.put(tag, provider);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder imageProvider(ImageProvider imageProvider) {
|
@NonNull
|
||||||
|
public Builder imageProvider(@Nullable ImageProvider imageProvider) {
|
||||||
this.imageProvider = imageProvider;
|
this.imageProvider = imageProvider;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public Builder parser(@NonNull HtmlParser parser) {
|
public Builder parser(@NonNull HtmlParser parser) {
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public SpannableHtmlParser build() {
|
public SpannableHtmlParser build() {
|
||||||
if (parser == null) {
|
if (parser == null) {
|
||||||
parser = DefaultHtmlParser.create();
|
parser = DefaultHtmlParser.create();
|
||||||
|
@ -11,6 +11,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import ru.noties.markwon.renderer.html.ImageSize;
|
import ru.noties.markwon.renderer.html.ImageSize;
|
||||||
|
import ru.noties.markwon.renderer.html.ImageSizeResolver;
|
||||||
|
|
||||||
public class AsyncDrawable extends Drawable {
|
public class AsyncDrawable extends Drawable {
|
||||||
|
|
||||||
@ -24,22 +25,30 @@ 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 final ImageSize imageSize;
|
||||||
|
private final ImageSizeResolver imageSizeResolver;
|
||||||
|
|
||||||
private Drawable result;
|
private Drawable result;
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
|
||||||
private int canvasWidth;
|
private int canvasWidth;
|
||||||
|
private float textSize;
|
||||||
|
|
||||||
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
|
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
|
||||||
this(destination, loader, null);
|
this(destination, loader, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.0.1
|
* @since 1.0.1
|
||||||
*/
|
*/
|
||||||
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader, @Nullable ImageSize imageSize) {
|
public AsyncDrawable(
|
||||||
|
@NonNull String destination,
|
||||||
|
@NonNull Loader loader,
|
||||||
|
@Nullable ImageSizeResolver imageSizeResolver,
|
||||||
|
@Nullable ImageSize imageSize
|
||||||
|
) {
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
|
this.imageSizeResolver = imageSizeResolver;
|
||||||
this.imageSize = imageSize;
|
this.imageSize = imageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,8 +112,9 @@ public class AsyncDrawable extends Drawable {
|
|||||||
* @since 1.0.1
|
* @since 1.0.1
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public void initWithCanvasWidth(int width) {
|
public void initWithKnownDimensions(int width, float textSize) {
|
||||||
this.canvasWidth = width;
|
this.canvasWidth = width;
|
||||||
|
this.textSize = textSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -162,70 +172,13 @@ public class AsyncDrawable extends Drawable {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
private Rect resolveBounds() {
|
private Rect resolveBounds() {
|
||||||
|
final Rect rect;
|
||||||
return result.getBounds();
|
if (imageSizeResolver == null
|
||||||
|
|| imageSize == null) {
|
||||||
// final Rect rect;
|
rect = result.getBounds();
|
||||||
//
|
} else {
|
||||||
// if (canvasWidth == 0
|
rect = imageSizeResolver.resolveImageSize(imageSize, result.getBounds(), canvasWidth, textSize);
|
||||||
// || imageSize == null) {
|
}
|
||||||
//
|
return rect;
|
||||||
// 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,7 +112,9 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
|||||||
int bottom,
|
int bottom,
|
||||||
@NonNull Paint paint) {
|
@NonNull Paint paint) {
|
||||||
|
|
||||||
drawable.initWithCanvasWidth(canvas.getWidth());
|
// we will pass this value, so when image is obtained drawable will have dimensions
|
||||||
|
// to resolve image size
|
||||||
|
drawable.initWithKnownDimensions(canvas.getWidth(), paint.getTextSize());
|
||||||
|
|
||||||
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