Add ImageHandler (img tag)
This commit is contained in:
parent
f1a38fca0f
commit
2d9c80d519
@ -67,7 +67,7 @@ public abstract class MarkwonHtmlRenderer {
|
|||||||
.handler("a", new LinkHandler())
|
.handler("a", new LinkHandler())
|
||||||
.handler("ul", listHandler)
|
.handler("ul", listHandler)
|
||||||
.handler("ol", listHandler)
|
.handler("ol", listHandler)
|
||||||
.handler("img", new ImageHandler());
|
.handler("img", ImageHandler.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -9,9 +9,26 @@ import java.util.Map;
|
|||||||
import ru.noties.markwon.SpannableConfiguration;
|
import ru.noties.markwon.SpannableConfiguration;
|
||||||
import ru.noties.markwon.html.api.HtmlTag;
|
import ru.noties.markwon.html.api.HtmlTag;
|
||||||
import ru.noties.markwon.renderer.ImageSize;
|
import ru.noties.markwon.renderer.ImageSize;
|
||||||
|
import ru.noties.markwon.renderer.html2.CssInlineStyleParser;
|
||||||
|
|
||||||
public class ImageHandler extends SimpleTagHandler {
|
public class ImageHandler extends SimpleTagHandler {
|
||||||
|
|
||||||
|
interface ImageSizeParser {
|
||||||
|
@Nullable
|
||||||
|
ImageSize parse(@NonNull Map<String, String> attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static ImageHandler create() {
|
||||||
|
return new ImageHandler(new ImageSizeParserImpl(CssInlineStyleParser.create()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ImageSizeParser imageSizeParser;
|
||||||
|
|
||||||
|
ImageHandler(@NonNull ImageSizeParser imageSizeParser) {
|
||||||
|
this.imageSizeParser = imageSizeParser;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
|
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
|
||||||
@ -26,21 +43,17 @@ public class ImageHandler extends SimpleTagHandler {
|
|||||||
|
|
||||||
// todo: replacement text is link... as we are not at block level
|
// todo: replacement text is link... as we are not at block level
|
||||||
// and cannot inspect the parent of this node... (img and a are both inlines)
|
// and cannot inspect the parent of this node... (img and a are both inlines)
|
||||||
|
//
|
||||||
|
// but we can look and see if we are inside a LinkSpan (will have to extend TagHandler
|
||||||
|
// to obtain an instance SpannableBuilder for inspection)
|
||||||
|
|
||||||
return configuration.factory().image(
|
return configuration.factory().image(
|
||||||
configuration.theme(),
|
configuration.theme(),
|
||||||
destination,
|
destination,
|
||||||
configuration.asyncDrawableLoader(),
|
configuration.asyncDrawableLoader(),
|
||||||
configuration.imageSizeResolver(),
|
configuration.imageSizeResolver(),
|
||||||
parseImageSize(attributes),
|
imageSizeParser.parse(tag.attributes()),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static ImageSize parseImageSize(@NonNull Map<String, String> attributes) {
|
|
||||||
// strictly speaking percents when specified directly on an attribute
|
|
||||||
// are not part of the HTML spec (I couldn't find any reference)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
package ru.noties.markwon.renderer.html2.tag;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ru.noties.markwon.renderer.ImageSize;
|
||||||
|
import ru.noties.markwon.renderer.html2.CssInlineStyleParser;
|
||||||
|
import ru.noties.markwon.renderer.html2.CssProperty;
|
||||||
|
|
||||||
|
class ImageSizeParserImpl implements ImageHandler.ImageSizeParser {
|
||||||
|
|
||||||
|
private final CssInlineStyleParser inlineStyleParser;
|
||||||
|
|
||||||
|
ImageSizeParserImpl(@NonNull CssInlineStyleParser inlineStyleParser) {
|
||||||
|
this.inlineStyleParser = inlineStyleParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageSize parse(@NonNull Map<String, String> attributes) {
|
||||||
|
|
||||||
|
// strictly speaking percents when specified directly on an attribute
|
||||||
|
// are not part of the HTML spec (I couldn't find any reference)
|
||||||
|
|
||||||
|
ImageSize.Dimension width = null;
|
||||||
|
ImageSize.Dimension height = null;
|
||||||
|
|
||||||
|
// okay, let's first check styles
|
||||||
|
final String style = attributes.get("style");
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(style)) {
|
||||||
|
|
||||||
|
String key;
|
||||||
|
|
||||||
|
for (CssProperty cssProperty : inlineStyleParser.parse(style)) {
|
||||||
|
|
||||||
|
key = cssProperty.key();
|
||||||
|
|
||||||
|
if ("width".equals(key)) {
|
||||||
|
width = dimension(cssProperty.value());
|
||||||
|
} else if ("height".equals(key)) {
|
||||||
|
height = dimension(cssProperty.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width != null
|
||||||
|
&& height != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width != null
|
||||||
|
&& height != null) {
|
||||||
|
return new ImageSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check tag attributes
|
||||||
|
if (width == null) {
|
||||||
|
width = dimension(attributes.get("width"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height == null) {
|
||||||
|
height = dimension(attributes.get("height"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width == null
|
||||||
|
&& height == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ImageSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@VisibleForTesting
|
||||||
|
ImageSize.Dimension dimension(@Nullable String value) {
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int length = value.length();
|
||||||
|
|
||||||
|
for (int i = length - 1; i > -1; i--) {
|
||||||
|
|
||||||
|
if (Character.isDigit(value.charAt(i))) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
final float val = Float.parseFloat(value.substring(0, i + 1));
|
||||||
|
final String unit;
|
||||||
|
if (i == length - 1) {
|
||||||
|
// no unit info
|
||||||
|
unit = null;
|
||||||
|
} else {
|
||||||
|
unit = value.substring(i + 1, length);
|
||||||
|
}
|
||||||
|
return new ImageSize.Dimension(val, unit);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// value cannot not be represented as a float
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
package ru.noties.markwon.renderer.html2.tag;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ru.noties.markwon.renderer.ImageSize;
|
||||||
|
import ru.noties.markwon.renderer.html2.CssInlineStyleParser;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(manifest = Config.NONE)
|
||||||
|
public class ImageSizeParserImplTest {
|
||||||
|
|
||||||
|
private static final float DELTA = 1e-7F;
|
||||||
|
|
||||||
|
private ImageSizeParserImpl impl;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
impl = new ImageSizeParserImpl(CssInlineStyleParser.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nothing() {
|
||||||
|
assertNull(impl.parse(Collections.<String, String>emptyMap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void width_height_from_style() {
|
||||||
|
|
||||||
|
final String style = "width: 123; height: 321";
|
||||||
|
|
||||||
|
assertImageSize(
|
||||||
|
new ImageSize(dimension(123, null), dimension(321, null)),
|
||||||
|
impl.parse(Collections.singletonMap("style", style))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void style_has_higher_priority_width() {
|
||||||
|
|
||||||
|
// if property is found in styles, do not lookup raw attribute
|
||||||
|
final Map<String, String> attributes = new HashMap<String, String>() {{
|
||||||
|
put("style", "width: 43");
|
||||||
|
put("width", "991");
|
||||||
|
}};
|
||||||
|
|
||||||
|
assertImageSize(
|
||||||
|
new ImageSize(dimension(43, null), null),
|
||||||
|
impl.parse(attributes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void style_has_higher_priority_height() {
|
||||||
|
|
||||||
|
// if property is found in styles, do not lookup raw attribute
|
||||||
|
final Map<String, String> attributes = new HashMap<String, String>() {{
|
||||||
|
put("style", "height: 177");
|
||||||
|
put("height", "8");
|
||||||
|
}};
|
||||||
|
|
||||||
|
assertImageSize(
|
||||||
|
new ImageSize(null, dimension(177, null)),
|
||||||
|
impl.parse(attributes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void width_style_height_attributes() {
|
||||||
|
|
||||||
|
final Map<String, String> attributes = new HashMap<String, String>() {{
|
||||||
|
put("style", "width: 99");
|
||||||
|
put("height", "7");
|
||||||
|
}};
|
||||||
|
|
||||||
|
assertImageSize(
|
||||||
|
new ImageSize(dimension(99, null), dimension(7, null)),
|
||||||
|
impl.parse(attributes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void height_style_width_attributes() {
|
||||||
|
|
||||||
|
final Map<String, String> attributes = new HashMap<String, String>() {{
|
||||||
|
put("style", "height: 15");
|
||||||
|
put("width", "88");
|
||||||
|
}};
|
||||||
|
|
||||||
|
assertImageSize(
|
||||||
|
new ImageSize(dimension(88, null), dimension(15, null)),
|
||||||
|
impl.parse(attributes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void non_empty_styles_width_height_attributes() {
|
||||||
|
|
||||||
|
final Map<String, String> attributes = new HashMap<String, String>() {{
|
||||||
|
put("style", "key1: value1; width0: 123; height0: 99");
|
||||||
|
put("width", "40");
|
||||||
|
put("height", "77");
|
||||||
|
}};
|
||||||
|
|
||||||
|
assertImageSize(
|
||||||
|
new ImageSize(dimension(40, null), dimension(77, null)),
|
||||||
|
impl.parse(attributes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dimension_units() {
|
||||||
|
|
||||||
|
final Map<String, ImageSize.Dimension> map = new HashMap<String, ImageSize.Dimension>() {{
|
||||||
|
put("100", dimension(100, null));
|
||||||
|
put("100%", dimension(100, "%"));
|
||||||
|
put("1%", dimension(1, "%"));
|
||||||
|
put("0.2em", dimension(0.2F, "em"));
|
||||||
|
put("155px", dimension(155, "px"));
|
||||||
|
put("67blah", dimension(67, "blah"));
|
||||||
|
put("-1", dimension(-1, null));
|
||||||
|
put("-0.01pt", dimension(-0.01F, "pt"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
for (Map.Entry<String, ImageSize.Dimension> entry : map.entrySet()) {
|
||||||
|
assertDimension(entry.getKey(), entry.getValue(), impl.dimension(entry.getKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bad_dimension() {
|
||||||
|
|
||||||
|
final String[] dimensions = {
|
||||||
|
"calc(5px + 10rem)",
|
||||||
|
"whataver6",
|
||||||
|
"165 165",
|
||||||
|
"!@#$%^&*(%"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String dimension: dimensions) {
|
||||||
|
assertNull(dimension, impl.dimension(dimension));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertImageSize(@Nullable ImageSize expected, @Nullable ImageSize actual) {
|
||||||
|
if (expected == null) {
|
||||||
|
assertNull(actual);
|
||||||
|
} else {
|
||||||
|
assertNotNull(actual);
|
||||||
|
assertDimension("width", expected.width, actual.width);
|
||||||
|
assertDimension("height", expected.height, actual.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertDimension(
|
||||||
|
@NonNull String name,
|
||||||
|
@Nullable ImageSize.Dimension expected,
|
||||||
|
@Nullable ImageSize.Dimension actual) {
|
||||||
|
if (expected == null) {
|
||||||
|
assertNull(name, actual);
|
||||||
|
} else {
|
||||||
|
assertNotNull(name, actual);
|
||||||
|
assertEquals(name, expected.value, actual.value, DELTA);
|
||||||
|
assertEquals(name, expected.unit, actual.unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ImageSize.Dimension dimension(float value, @Nullable String unit) {
|
||||||
|
return new ImageSize.Dimension(value, unit);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user