Move all html entities to markwon-html module
This commit is contained in:
parent
2efd12f020
commit
27ed17aaff
app/src/main/java/ru/noties/markwon
markwon-html-parser-api
markwon-html
build.gradlegradle.properties
src
main
AndroidManifest.xml
java/ru/noties/markwon/html/impl
test/java/ru/noties/markwon/html/impl
markwon-image-loader
build.gradlegradle.properties
src
markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax
markwon
build.gradle
settings.gradlesrc
main/java/ru/noties/markwon
MarkwonConfiguration.javaSpannableFactory.javaSpannableFactoryDef.java
html
HtmlTag.javaMarkwonHtmlParser.javaMarkwonHtmlParserNoOp.javaMarkwonHtmlRenderer.javaMarkwonHtmlRendererNoOp.javaTagHandler.java
renderer/html2
test/java/ru/noties/markwon/renderer
@ -77,10 +77,6 @@ public class MarkdownRenderer {
|
||||
? prism4jThemeDefault
|
||||
: prism4JThemeDarkula;
|
||||
|
||||
// final int background = isLightTheme
|
||||
// ? prism4jTheme.background()
|
||||
// : 0x0Fffffff;
|
||||
|
||||
final Markwon2 markwon2 = Markwon2.builder(context)
|
||||
.use(CorePlugin.create())
|
||||
.use(ImagesPlugin.createWithAssets(context))
|
||||
|
@ -1,23 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion config['compile-sdk']
|
||||
buildToolsVersion config['build-tools']
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion config['min-sdk']
|
||||
targetSdkVersion config['target-sdk']
|
||||
versionCode 1
|
||||
versionName version
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
deps.with {
|
||||
api it['support-annotations']
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
@ -1,3 +0,0 @@
|
||||
POM_NAME=Markwon
|
||||
POM_ARTIFACT_ID=markwon-html-parser-api
|
||||
POM_PACKAGING=aar
|
@ -1 +0,0 @@
|
||||
<manifest package="ru.noties.markwon.html.api" />
|
@ -15,7 +15,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-html-parser-api')
|
||||
api project(':markwon')
|
||||
|
||||
deps.with {
|
||||
api it['support-annotations']
|
||||
@ -25,6 +25,7 @@ dependencies {
|
||||
deps.test.with {
|
||||
testImplementation it['junit']
|
||||
testImplementation it['robolectric']
|
||||
testImplementation it['ix-java']
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package ru.noties.markwon.renderer.html2;
|
||||
package ru.noties.markwon.html.impl;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
@ -1,4 +1,4 @@
|
||||
package ru.noties.markwon.renderer.html2;
|
||||
package ru.noties.markwon.html.impl;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
@ -3,7 +3,7 @@ package ru.noties.markwon.html.impl;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
|
||||
/**
|
||||
* This class will be used to append some text to output in order to
|
@ -7,7 +7,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
|
||||
abstract class HtmlTagImpl implements HtmlTag {
|
||||
|
@ -14,10 +14,10 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.api.HtmlTag.Block;
|
||||
import ru.noties.markwon.html.api.HtmlTag.Inline;
|
||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag.Block;
|
||||
import ru.noties.markwon.html.HtmlTag.Inline;
|
||||
import ru.noties.markwon.html.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.html.impl.jsoup.nodes.Attribute;
|
||||
import ru.noties.markwon.html.impl.jsoup.nodes.Attributes;
|
||||
import ru.noties.markwon.html.impl.jsoup.parser.CharacterReader;
|
@ -0,0 +1,167 @@
|
||||
package ru.noties.markwon.html.impl;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||
import ru.noties.markwon.html.TagHandler;
|
||||
import ru.noties.markwon.html.impl.tag.BlockquoteHandler;
|
||||
import ru.noties.markwon.html.impl.tag.EmphasisHandler;
|
||||
import ru.noties.markwon.html.impl.tag.HeadingHandler;
|
||||
import ru.noties.markwon.html.impl.tag.ImageHandler;
|
||||
import ru.noties.markwon.html.impl.tag.LinkHandler;
|
||||
import ru.noties.markwon.html.impl.tag.ListHandler;
|
||||
import ru.noties.markwon.html.impl.tag.StrikeHandler;
|
||||
import ru.noties.markwon.html.impl.tag.StrongEmphasisHandler;
|
||||
import ru.noties.markwon.html.impl.tag.SubScriptHandler;
|
||||
import ru.noties.markwon.html.impl.tag.SuperScriptHandler;
|
||||
import ru.noties.markwon.html.impl.tag.UnderlineHandler;
|
||||
|
||||
public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
|
||||
|
||||
@NonNull
|
||||
public static MarkwonHtmlRendererImpl create() {
|
||||
return builderWithDefaults().build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder builderWithDefaults() {
|
||||
|
||||
final EmphasisHandler emphasisHandler = new EmphasisHandler();
|
||||
final StrongEmphasisHandler strongEmphasisHandler = new StrongEmphasisHandler();
|
||||
final StrikeHandler strikeHandler = new StrikeHandler();
|
||||
final UnderlineHandler underlineHandler = new UnderlineHandler();
|
||||
final ListHandler listHandler = new ListHandler();
|
||||
|
||||
return builder()
|
||||
.handler("i", emphasisHandler)
|
||||
.handler("em", emphasisHandler)
|
||||
.handler("cite", emphasisHandler)
|
||||
.handler("dfn", emphasisHandler)
|
||||
.handler("b", strongEmphasisHandler)
|
||||
.handler("strong", strongEmphasisHandler)
|
||||
.handler("sup", new SuperScriptHandler())
|
||||
.handler("sub", new SubScriptHandler())
|
||||
.handler("u", underlineHandler)
|
||||
.handler("ins", underlineHandler)
|
||||
.handler("del", strikeHandler)
|
||||
.handler("s", strikeHandler)
|
||||
.handler("strike", strikeHandler)
|
||||
.handler("a", new LinkHandler())
|
||||
.handler("ul", listHandler)
|
||||
.handler("ol", listHandler)
|
||||
.handler("img", ImageHandler.create())
|
||||
.handler("blockquote", new BlockquoteHandler())
|
||||
.handler("h1", new HeadingHandler(1))
|
||||
.handler("h2", new HeadingHandler(2))
|
||||
.handler("h3", new HeadingHandler(3))
|
||||
.handler("h4", new HeadingHandler(4))
|
||||
.handler("h5", new HeadingHandler(5))
|
||||
.handler("h6", new HeadingHandler(6));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
|
||||
|
||||
private final Map<String, TagHandler> tagHandlers;
|
||||
|
||||
private MarkwonHtmlRendererImpl(@NonNull Map<String, TagHandler> tagHandlers) {
|
||||
this.tagHandlers = tagHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
@NonNull final MarkwonConfiguration configuration,
|
||||
@NonNull final SpannableBuilder builder,
|
||||
@NonNull MarkwonHtmlParser parser) {
|
||||
|
||||
final int end;
|
||||
if (!configuration.htmlAllowNonClosedTags()) {
|
||||
end = HtmlTag.NO_END;
|
||||
} else {
|
||||
end = builder.length();
|
||||
}
|
||||
|
||||
parser.flushInlineTags(end, new MarkwonHtmlParser.FlushAction<HtmlTag.Inline>() {
|
||||
@Override
|
||||
public void apply(@NonNull List<HtmlTag.Inline> tags) {
|
||||
|
||||
TagHandler handler;
|
||||
|
||||
for (HtmlTag.Inline inline : tags) {
|
||||
|
||||
// if tag is not closed -> do not render
|
||||
if (!inline.isClosed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handler = tagHandler(inline.name());
|
||||
if (handler != null) {
|
||||
handler.handle(configuration, builder, inline);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
parser.flushBlockTags(end, new MarkwonHtmlParser.FlushAction<HtmlTag.Block>() {
|
||||
@Override
|
||||
public void apply(@NonNull List<HtmlTag.Block> tags) {
|
||||
|
||||
TagHandler handler;
|
||||
|
||||
for (HtmlTag.Block block : tags) {
|
||||
|
||||
if (!block.isClosed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handler = tagHandler(block.name());
|
||||
if (handler != null) {
|
||||
handler.handle(configuration, builder, block);
|
||||
} else {
|
||||
// see if any of children can be handled
|
||||
apply(block.children());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TagHandler tagHandler(@NonNull String tagName) {
|
||||
return tagHandlers.get(tagName);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Map<String, TagHandler> tagHandlers = new HashMap<>(2);
|
||||
|
||||
@NonNull
|
||||
public Builder handler(@NonNull String tagName, @NonNull TagHandler tagHandler) {
|
||||
tagHandlers.put(tagName.toLowerCase(Locale.US), tagHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public MarkwonHtmlRendererImpl build() {
|
||||
return new MarkwonHtmlRendererImpl(Collections.unmodifiableMap(tagHandlers));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package ru.noties.markwon.html.impl.span;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.MetricAffectingSpan;
|
||||
|
||||
import ru.noties.markwon.html.impl.MarkwonHtmlRendererImpl;
|
||||
|
||||
public class SubScriptSpan extends MetricAffectingSpan {
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint tp) {
|
||||
apply(tp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasureState(@NonNull TextPaint tp) {
|
||||
apply(tp);
|
||||
}
|
||||
|
||||
private void apply(TextPaint paint) {
|
||||
paint.setTextSize(paint.getTextSize() * MarkwonHtmlRendererImpl.SCRIPT_DEF_TEXT_SIZE_RATIO);
|
||||
paint.baselineShift -= (int) (paint.ascent() / 2);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package ru.noties.markwon.html.impl.span;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.MetricAffectingSpan;
|
||||
|
||||
import ru.noties.markwon.html.impl.MarkwonHtmlRendererImpl;
|
||||
|
||||
public class SuperScriptSpan extends MetricAffectingSpan {
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint tp) {
|
||||
apply(tp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasureState(@NonNull TextPaint tp) {
|
||||
apply(tp);
|
||||
}
|
||||
|
||||
private void apply(TextPaint paint) {
|
||||
paint.setTextSize(paint.getTextSize() * MarkwonHtmlRendererImpl.SCRIPT_DEF_TEXT_SIZE_RATIO);
|
||||
paint.baselineShift += (int) (paint.ascent() / 2);
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.TagHandler;
|
||||
|
||||
public class BlockquoteHandler extends TagHandler {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
|
||||
public class EmphasisHandler extends SimpleTagHandler {
|
||||
@Nullable
|
@ -1,10 +1,10 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
|
||||
public class HeadingHandler extends SimpleTagHandler {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@ -7,9 +7,9 @@ import android.text.TextUtils;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.impl.CssInlineStyleParser;
|
||||
import ru.noties.markwon.renderer.ImageSize;
|
||||
import ru.noties.markwon.renderer.html2.CssInlineStyleParser;
|
||||
|
||||
public class ImageHandler extends SimpleTagHandler {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@ -7,9 +7,9 @@ import android.text.TextUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.html.impl.CssInlineStyleParser;
|
||||
import ru.noties.markwon.html.impl.CssProperty;
|
||||
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 {
|
||||
|
@ -1,11 +1,11 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
|
||||
public class LinkHandler extends SimpleTagHandler {
|
||||
@Nullable
|
@ -1,10 +1,11 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.TagHandler;
|
||||
|
||||
public class ListHandler extends TagHandler {
|
||||
|
@ -1,11 +1,12 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.TagHandler;
|
||||
|
||||
public abstract class SimpleTagHandler extends TagHandler {
|
||||
|
@ -1,10 +1,11 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.TagHandler;
|
||||
|
||||
public class StrikeHandler extends TagHandler {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
|
||||
public class StrongEmphasisHandler extends SimpleTagHandler {
|
||||
@Nullable
|
@ -1,15 +1,16 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.impl.span.SubScriptSpan;
|
||||
|
||||
public class SubScriptHandler extends SimpleTagHandler {
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull HtmlTag tag) {
|
||||
return configuration.factory().subScript(configuration.theme());
|
||||
return new SubScriptSpan();
|
||||
}
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.impl.span.SuperScriptSpan;
|
||||
|
||||
public class SuperScriptHandler extends SimpleTagHandler {
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull HtmlTag tag) {
|
||||
return configuration.factory().superScript(configuration.theme());
|
||||
return new SuperScriptSpan();
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html.impl.tag;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.style.UnderlineSpan;
|
||||
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.html.HtmlTag;
|
||||
import ru.noties.markwon.html.TagHandler;
|
||||
|
||||
public class UnderlineHandler extends TagHandler {
|
||||
|
||||
@ -23,7 +25,7 @@ public class UnderlineHandler extends TagHandler {
|
||||
|
||||
SpannableBuilder.setSpans(
|
||||
builder,
|
||||
configuration.factory().underline(),
|
||||
new UnderlineSpan(),
|
||||
tag.start(),
|
||||
tag.end()
|
||||
);
|
239
markwon-html/src/test/java/ru/noties/markwon/html/impl/CssInlineStyleParserTest.java
Normal file
239
markwon-html/src/test/java/ru/noties/markwon/html/impl/CssInlineStyleParserTest.java
Normal file
@ -0,0 +1,239 @@
|
||||
package ru.noties.markwon.html.impl;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ix.Ix;
|
||||
import ix.IxFunction;
|
||||
import ru.noties.markwon.test.TestUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static ru.noties.markwon.test.TestUtils.with;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class CssInlineStyleParserTest {
|
||||
|
||||
private CssInlineStyleParser.Impl impl;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
impl = new CssInlineStyleParser.Impl();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_single_pair() {
|
||||
|
||||
final String input = "key: value;";
|
||||
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key", cssProperty.key());
|
||||
assertEquals("value", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_two_pairs() {
|
||||
|
||||
final String input = "key1: value1; key2: value2;";
|
||||
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(2, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key1", cssProperty.key());
|
||||
assertEquals("value1", cssProperty.value());
|
||||
}
|
||||
});
|
||||
|
||||
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key2", cssProperty.key());
|
||||
assertEquals("value2", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void one_pair_eof() {
|
||||
|
||||
final String input = "key: value";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key", cssProperty.key());
|
||||
assertEquals("value", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void one_pair_eof_whitespaces() {
|
||||
|
||||
final String input = "key: value \n\n\t";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key", cssProperty.key());
|
||||
assertEquals("value", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void white_spaces() {
|
||||
|
||||
final String input = "\n\n\n\t \t key1 \n\n\n\t : \n\n\n\n \t value1 \n\n\n\n ; \n key2\n : \n value2 \n ; ";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(2, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key1", cssProperty.key());
|
||||
assertEquals("value1", cssProperty.value());
|
||||
}
|
||||
});
|
||||
|
||||
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key2", cssProperty.key());
|
||||
assertEquals("value2", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list_of_keys() {
|
||||
|
||||
final String input = "key1 key2 key3 key4";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list_of_keys_and_value() {
|
||||
|
||||
final String input = "key1 key2 key3 key4: value4";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key4", cssProperty.key());
|
||||
assertEquals("value4", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list_of_keys_separated_by_semi_colon() {
|
||||
|
||||
final String input = "key1;key2;key3;key4;";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void key_value_with_invalid_between() {
|
||||
|
||||
final String input = "key1: value1; key2 key3: value3;";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(2, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key1", cssProperty.key());
|
||||
assertEquals("value1", cssProperty.value());
|
||||
}
|
||||
});
|
||||
|
||||
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key3", cssProperty.key());
|
||||
assertEquals("value3", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void css_functions() {
|
||||
|
||||
final Map<String, String> map = new HashMap<String, String>() {{
|
||||
put("attr", "\" (\" attr(href) \")\"");
|
||||
put("calc", "calc(100% - 100px)");
|
||||
put("cubic-bezier", "cubic-bezier(0.1, 0.7, 1.0, 0.1)");
|
||||
put("hsl", "hsl(120,100%,50%)");
|
||||
put("hsla", "hsla(120,100%,50%,0.3)");
|
||||
put("linear-gradient", "linear-gradient(red, yellow, blue)");
|
||||
put("radial-gradient", "radial-gradient(red, green, blue)");
|
||||
put("repeating-linear-gradient", "repeating-linear-gradient(red, yellow 10%, green 20%)");
|
||||
put("repeating-radial-gradient", "repeating-radial-gradient(red, yellow 10%, green 15%)");
|
||||
put("rgb", "rgb(255,0,0)");
|
||||
put("rgba", "rgba(255,0,0,0.3)");
|
||||
put("var", "var(--some-variable)");
|
||||
put("url", "url(\"url.gif\")");
|
||||
}};
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry: map.entrySet()) {
|
||||
builder.append(entry.getKey())
|
||||
.append(':')
|
||||
.append(entry.getValue())
|
||||
.append(';');
|
||||
}
|
||||
|
||||
for (CssProperty cssProperty: impl.parse(builder.toString())) {
|
||||
final String value = map.remove(cssProperty.key());
|
||||
assertNotNull(cssProperty.key(), value);
|
||||
assertEquals(cssProperty.key(), value, cssProperty.value());
|
||||
}
|
||||
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<CssProperty> listProperties(@NonNull String input) {
|
||||
return Ix.from(impl.parse(input))
|
||||
.map(new IxFunction<CssProperty, CssProperty>() {
|
||||
@Override
|
||||
public CssProperty apply(CssProperty cssProperty) {
|
||||
return cssProperty.mutate();
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
186
markwon-html/src/test/java/ru/noties/markwon/html/impl/tag/ImageSizeParserImplTest.java
Normal file
186
markwon-html/src/test/java/ru/noties/markwon/html/impl/tag/ImageSizeParserImplTest.java
Normal file
@ -0,0 +1,186 @@
|
||||
package ru.noties.markwon.html.impl.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);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion config['compile-sdk']
|
||||
buildToolsVersion config['build-tools']
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion config['min-sdk']
|
||||
targetSdkVersion config['target-sdk']
|
||||
versionCode 1
|
||||
versionName version
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
// okio....
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon')
|
||||
|
||||
deps.with {
|
||||
api it['android-svg']
|
||||
api it['android-gif']
|
||||
api it['okhttp']
|
||||
}
|
||||
|
||||
deps['test'].with {
|
||||
testImplementation it['junit']
|
||||
testImplementation it['robolectric']
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
@ -1,3 +0,0 @@
|
||||
POM_NAME=Markwon-Image-Loader
|
||||
POM_ARTIFACT_ID=markwon-image-loader
|
||||
POM_PACKAGING=aar
|
@ -1 +0,0 @@
|
||||
<manifest package="ru.noties.markwon.il" />
|
@ -1,405 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import ru.noties.markwon.image.AsyncDrawable;
|
||||
|
||||
public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
|
||||
@NonNull
|
||||
public static AsyncDrawableLoader create() {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AsyncDrawableLoader.Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final Handler mainThread;
|
||||
private final Drawable errorDrawable;
|
||||
private final Map<String, SchemeHandler> schemeHandlers;
|
||||
private final List<MediaDecoder> mediaDecoders;
|
||||
|
||||
private final Map<String, Future<?>> requests;
|
||||
|
||||
AsyncDrawableLoader(Builder builder) {
|
||||
this.executorService = builder.executorService;
|
||||
this.mainThread = new Handler(Looper.getMainLooper());
|
||||
this.errorDrawable = builder.errorDrawable;
|
||||
this.schemeHandlers = builder.schemeHandlers;
|
||||
this.mediaDecoders = builder.mediaDecoders;
|
||||
this.requests = new HashMap<>(3);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
|
||||
// if drawable is not a link -> show loading placeholder...
|
||||
requests.put(destination, execute(destination, drawable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(@NonNull String destination) {
|
||||
|
||||
final Future<?> request = requests.remove(destination);
|
||||
if (request != null) {
|
||||
request.cancel(true);
|
||||
}
|
||||
|
||||
for (SchemeHandler schemeHandler : schemeHandlers.values()) {
|
||||
schemeHandler.cancel(destination);
|
||||
}
|
||||
}
|
||||
|
||||
private Future<?> execute(@NonNull final String destination, @NonNull AsyncDrawable drawable) {
|
||||
|
||||
final WeakReference<AsyncDrawable> reference = new WeakReference<AsyncDrawable>(drawable);
|
||||
|
||||
// todo: should we cancel pending request for the same destination?
|
||||
// we _could_ but there is possibility that one resource is request in multiple places
|
||||
|
||||
// todo: error handing (simply applying errorDrawable is not a good solution
|
||||
// as reason for an error is unclear (no scheme handler, no input data, error decoding, etc)
|
||||
|
||||
// todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal
|
||||
// for big images for sure. We _could_ introduce internal Drawable that will check for
|
||||
// image bounds (but we will need to cache inputStream in order to inspect and optimize
|
||||
// input image...)
|
||||
|
||||
return executorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
final ImageItem item;
|
||||
|
||||
final Uri uri = Uri.parse(destination);
|
||||
|
||||
final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme());
|
||||
if (schemeHandler != null) {
|
||||
item = schemeHandler.handle(destination, uri);
|
||||
} else {
|
||||
item = null;
|
||||
}
|
||||
|
||||
final InputStream inputStream = item != null
|
||||
? item.inputStream()
|
||||
: null;
|
||||
|
||||
Drawable result = null;
|
||||
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
|
||||
final String fileName = item.fileName();
|
||||
final MediaDecoder mediaDecoder = fileName != null
|
||||
? mediaDecoderFromFile(fileName)
|
||||
: mediaDecoderFromContentType(item.contentType());
|
||||
|
||||
if (mediaDecoder != null) {
|
||||
result = mediaDecoder.decode(inputStream);
|
||||
}
|
||||
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if result is null, we assume it's an error
|
||||
if (result == null) {
|
||||
result = errorDrawable;
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
final Drawable out = result;
|
||||
mainThread.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AsyncDrawable asyncDrawable = reference.get();
|
||||
if (asyncDrawable != null && asyncDrawable.isAttached()) {
|
||||
asyncDrawable.setResult(out);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
requests.remove(destination);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MediaDecoder mediaDecoderFromFile(@NonNull String fileName) {
|
||||
|
||||
MediaDecoder out = null;
|
||||
|
||||
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||
if (mediaDecoder.canDecodeByFileName(fileName)) {
|
||||
out = mediaDecoder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MediaDecoder mediaDecoderFromContentType(@Nullable String contentType) {
|
||||
|
||||
MediaDecoder out = null;
|
||||
|
||||
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||
if (mediaDecoder.canDecodeByContentType(contentType)) {
|
||||
out = mediaDecoder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// todo: as now we have different layers of abstraction (for scheme handling and media decoding)
|
||||
// we no longer should add dependencies implicitly, it would be way better to allow adding
|
||||
// multiple artifacts (file, data, network, svg, gif)... at least, maybe we can extract API
|
||||
// for this module (without implementations), but keep _all-in_ (fat) artifact with all of these.
|
||||
public static class Builder {
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 add {@link NetworkSchemeHandler} directly
|
||||
*/
|
||||
@Deprecated
|
||||
private OkHttpClient client;
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 construct {@link MediaDecoder} and {@link SchemeHandler} appropriately
|
||||
*/
|
||||
@Deprecated
|
||||
private Resources resources;
|
||||
|
||||
private ExecutorService executorService;
|
||||
private Drawable errorDrawable;
|
||||
|
||||
// @since 1.1.0
|
||||
private final List<MediaDecoder> mediaDecoders = new ArrayList<>(3);
|
||||
|
||||
// @since 2.0.0
|
||||
private final Map<String, SchemeHandler> schemeHandlers = new HashMap<>(3);
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 add {@link NetworkSchemeHandler} directly
|
||||
*/
|
||||
@NonNull
|
||||
@Deprecated
|
||||
public Builder client(@NonNull OkHttpClient client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplied resources argument will be used to open files from assets directory
|
||||
* and to create default {@link MediaDecoder}\'s which require resources instance
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
@NonNull
|
||||
public Builder resources(@NonNull Resources resources) {
|
||||
this.resources = resources;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder executorService(@NonNull ExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder errorDrawable(@NonNull Drawable errorDrawable) {
|
||||
this.errorDrawable = errorDrawable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
@NonNull
|
||||
public Builder addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
||||
|
||||
SchemeHandler previous;
|
||||
|
||||
for (String scheme : schemeHandler.schemes()) {
|
||||
previous = schemeHandlers.put(scheme, schemeHandler);
|
||||
if (previous != null) {
|
||||
throw new IllegalStateException(String.format("Multiple scheme handlers handle " +
|
||||
"the same scheme: `%s`, %s %s", scheme, previous, schemeHandler));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #addMediaDecoder(MediaDecoder)
|
||||
* @see #addMediaDecoders(MediaDecoder...)
|
||||
* @see #addMediaDecoders(Iterable)
|
||||
* @since 1.1.0
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder mediaDecoders(@NonNull List<MediaDecoder> mediaDecoders) {
|
||||
|
||||
// previously it was clearing before adding
|
||||
|
||||
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||
this.mediaDecoders.add(requireNonNull(mediaDecoder));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #addMediaDecoder(MediaDecoder)
|
||||
* @see #addMediaDecoders(MediaDecoder...)
|
||||
* @see #addMediaDecoders(Iterable)
|
||||
* @since 1.1.0
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
@NonNull
|
||||
@Deprecated
|
||||
public Builder mediaDecoders(MediaDecoder... mediaDecoders) {
|
||||
|
||||
// previously it was clearing before adding
|
||||
|
||||
final int length = mediaDecoders != null
|
||||
? mediaDecoders.length
|
||||
: 0;
|
||||
|
||||
if (length > 0) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
this.mediaDecoders.add(requireNonNull(mediaDecoders[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SvgMediaDecoder
|
||||
* @see GifMediaDecoder
|
||||
* @see ImageMediaDecoder
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||
mediaDecoders.add(mediaDecoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SvgMediaDecoder
|
||||
* @see GifMediaDecoder
|
||||
* @see ImageMediaDecoder
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addMediaDecoders(@NonNull Iterable<MediaDecoder> mediaDecoders) {
|
||||
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||
this.mediaDecoders.add(requireNonNull(mediaDecoder));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SvgMediaDecoder
|
||||
* @see GifMediaDecoder
|
||||
* @see ImageMediaDecoder
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addMediaDecoders(MediaDecoder... mediaDecoders) {
|
||||
|
||||
final int length = mediaDecoders != null
|
||||
? mediaDecoders.length
|
||||
: 0;
|
||||
|
||||
if (length > 0) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
this.mediaDecoders.add(requireNonNull(mediaDecoders[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AsyncDrawableLoader build() {
|
||||
|
||||
// I think we should deprecate this...
|
||||
if (resources == null) {
|
||||
resources = Resources.getSystem();
|
||||
}
|
||||
|
||||
if (executorService == null) {
|
||||
// @since 2.0.0 we are using newCachedThreadPool instead
|
||||
// of `okHttpClient.dispatcher().executorService()`
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
// @since 2.0.0
|
||||
// put default scheme handlers (to mimic previous behavior)
|
||||
// remove in 3.0.0 with plugins
|
||||
if (schemeHandlers.size() == 0) {
|
||||
if (client == null) {
|
||||
client = new OkHttpClient();
|
||||
}
|
||||
addSchemeHandler(NetworkSchemeHandler.create(client));
|
||||
addSchemeHandler(FileSchemeHandler.createWithAssets(resources.getAssets()));
|
||||
addSchemeHandler(DataUriSchemeHandler.create());
|
||||
}
|
||||
|
||||
// add default media decoders if not specified
|
||||
// remove in 3.0.0 with plugins
|
||||
if (mediaDecoders.size() == 0) {
|
||||
mediaDecoders.add(SvgMediaDecoder.create(resources));
|
||||
mediaDecoders.add(GifMediaDecoder.create(true));
|
||||
mediaDecoders.add(ImageMediaDecoder.create(resources));
|
||||
}
|
||||
|
||||
return new AsyncDrawableLoader(this);
|
||||
}
|
||||
}
|
||||
|
||||
// @since 2.0.0
|
||||
@NonNull
|
||||
private static <T> T requireNonNull(@Nullable T t) {
|
||||
if (t == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public class DataUri {
|
||||
|
||||
private final String contentType;
|
||||
private final boolean base64;
|
||||
private final String data;
|
||||
|
||||
public DataUri(@Nullable String contentType, boolean base64, @Nullable String data) {
|
||||
this.contentType = contentType;
|
||||
this.base64 = base64;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public boolean base64() {
|
||||
return base64;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String data() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DataUri{" +
|
||||
"contentType='" + contentType + '\'' +
|
||||
", base64=" + base64 +
|
||||
", data='" + data + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
DataUri dataUri = (DataUri) o;
|
||||
|
||||
if (base64 != dataUri.base64) return false;
|
||||
if (contentType != null ? !contentType.equals(dataUri.contentType) : dataUri.contentType != null)
|
||||
return false;
|
||||
return data != null ? data.equals(dataUri.data) : dataUri.data == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = contentType != null ? contentType.hashCode() : 0;
|
||||
result = 31 * result + (base64 ? 1 : 0);
|
||||
result = 31 * result + (data != null ? data.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
|
||||
public abstract class DataUriDecoder {
|
||||
|
||||
@Nullable
|
||||
public abstract byte[] decode(@NonNull DataUri dataUri);
|
||||
|
||||
@NonNull
|
||||
public static DataUriDecoder create() {
|
||||
return new Impl();
|
||||
}
|
||||
|
||||
static class Impl extends DataUriDecoder {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] decode(@NonNull DataUri dataUri) {
|
||||
|
||||
final String data = dataUri.data();
|
||||
|
||||
if (!TextUtils.isEmpty(data)) {
|
||||
try {
|
||||
if (dataUri.base64()) {
|
||||
return Base64.decode(data.getBytes("UTF-8"), Base64.DEFAULT);
|
||||
} else {
|
||||
return data.getBytes("UTF-8");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public abstract class DataUriParser {
|
||||
|
||||
@Nullable
|
||||
public abstract DataUri parse(@NonNull String input);
|
||||
|
||||
|
||||
@NonNull
|
||||
public static DataUriParser create() {
|
||||
return new Impl();
|
||||
}
|
||||
|
||||
static class Impl extends DataUriParser {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DataUri parse(@NonNull String input) {
|
||||
|
||||
final int index = input.indexOf(',');
|
||||
// we expect exactly one comma
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String contentType;
|
||||
final boolean base64;
|
||||
|
||||
if (index > 0) {
|
||||
final String part = input.substring(0, index);
|
||||
final String[] parts = part.split(";");
|
||||
final int length = parts.length;
|
||||
if (length > 0) {
|
||||
// if one: either content-type or base64
|
||||
if (length == 1) {
|
||||
final String value = parts[0];
|
||||
if ("base64".equals(value)) {
|
||||
contentType = null;
|
||||
base64 = true;
|
||||
} else {
|
||||
contentType = value.indexOf('/') > -1
|
||||
? value
|
||||
: null;
|
||||
base64 = false;
|
||||
}
|
||||
} else {
|
||||
contentType = parts[0].indexOf('/') > -1
|
||||
? parts[0]
|
||||
: null;
|
||||
base64 = "base64".equals(parts[length - 1]);
|
||||
}
|
||||
} else {
|
||||
contentType = null;
|
||||
base64 = false;
|
||||
}
|
||||
} else {
|
||||
contentType = null;
|
||||
base64 = false;
|
||||
}
|
||||
|
||||
final String data;
|
||||
if (index < input.length()) {
|
||||
final String value = input.substring(index + 1, input.length()).replaceAll("\n", "");
|
||||
if (value.length() == 0) {
|
||||
data = null;
|
||||
} else {
|
||||
data = value;
|
||||
}
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
|
||||
return new DataUri(contentType, base64, data);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class DataUriSchemeHandler extends SchemeHandler {
|
||||
|
||||
@NonNull
|
||||
public static DataUriSchemeHandler create() {
|
||||
return new DataUriSchemeHandler(DataUriParser.create(), DataUriDecoder.create());
|
||||
}
|
||||
|
||||
private static final String START = "data:";
|
||||
|
||||
private final DataUriParser uriParser;
|
||||
private final DataUriDecoder uriDecoder;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
DataUriSchemeHandler(@NonNull DataUriParser uriParser, @NonNull DataUriDecoder uriDecoder) {
|
||||
this.uriParser = uriParser;
|
||||
this.uriDecoder = uriDecoder;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||
|
||||
if (!raw.startsWith(START)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String part = raw.substring(START.length());
|
||||
|
||||
// this part is added to support `data://` with which this functionality was released
|
||||
if (part.startsWith("//")) {
|
||||
part = part.substring(2);
|
||||
}
|
||||
|
||||
final DataUri dataUri = uriParser.parse(part);
|
||||
if (dataUri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte[] bytes = uriDecoder.decode(dataUri);
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ImageItem(
|
||||
dataUri.contentType(),
|
||||
new ByteArrayInputStream(bytes),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(@NonNull String raw) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Collection<String> schemes() {
|
||||
return Collections.singleton("data");
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
abstract class DrawableUtils {
|
||||
|
||||
static void intrinsicBounds(@NonNull Drawable drawable) {
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
}
|
||||
|
||||
private DrawableUtils() {}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class FileSchemeHandler extends SchemeHandler {
|
||||
|
||||
@NonNull
|
||||
public static FileSchemeHandler createWithAssets(@NonNull AssetManager assetManager) {
|
||||
return new FileSchemeHandler(assetManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FileSchemeHandler create() {
|
||||
return new FileSchemeHandler(null);
|
||||
}
|
||||
|
||||
private static final String FILE_ANDROID_ASSETS = "android_asset";
|
||||
|
||||
@Nullable
|
||||
private final AssetManager assetManager;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
FileSchemeHandler(@Nullable AssetManager assetManager) {
|
||||
this.assetManager = assetManager;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||
|
||||
final List<String> segments = uri.getPathSegments();
|
||||
if (segments == null
|
||||
|| segments.size() == 0) {
|
||||
// pointing to file & having no path segments is no use
|
||||
return null;
|
||||
}
|
||||
|
||||
final ImageItem out;
|
||||
|
||||
InputStream inputStream = null;
|
||||
|
||||
final boolean assets = FILE_ANDROID_ASSETS.equals(segments.get(0));
|
||||
final String fileName = uri.getLastPathSegment();
|
||||
|
||||
if (assets) {
|
||||
|
||||
// no handling of assets here if we have no assetsManager
|
||||
if (assetManager != null) {
|
||||
|
||||
final StringBuilder path = new StringBuilder();
|
||||
for (int i = 1, size = segments.size(); i < size; i++) {
|
||||
if (i != 1) {
|
||||
path.append('/');
|
||||
}
|
||||
path.append(segments.get(i));
|
||||
}
|
||||
// load assets
|
||||
|
||||
try {
|
||||
inputStream = assetManager.open(path.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
inputStream = new BufferedInputStream(new FileInputStream(new File(uri.getPath())));
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
out = new ImageItem(fileName, inputStream, fileName);
|
||||
} else {
|
||||
out = null;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(@NonNull String raw) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Collection<String> schemes() {
|
||||
return Collections.singleton("file");
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import pl.droidsonroids.gif.GifDrawable;
|
||||
|
||||
/**
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class GifMediaDecoder extends MediaDecoder {
|
||||
|
||||
protected static final String CONTENT_TYPE_GIF = "image/gif";
|
||||
protected static final String FILE_EXTENSION_GIF = ".gif";
|
||||
|
||||
@NonNull
|
||||
public static GifMediaDecoder create(boolean autoPlayGif) {
|
||||
return new GifMediaDecoder(autoPlayGif);
|
||||
}
|
||||
|
||||
private final boolean autoPlayGif;
|
||||
|
||||
protected GifMediaDecoder(boolean autoPlayGif) {
|
||||
this.autoPlayGif = autoPlayGif;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeByContentType(@Nullable String contentType) {
|
||||
return CONTENT_TYPE_GIF.equals(contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeByFileName(@NonNull String fileName) {
|
||||
return fileName.endsWith(FILE_EXTENSION_GIF);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable decode(@NonNull InputStream inputStream) {
|
||||
|
||||
Drawable out = null;
|
||||
|
||||
final byte[] bytes = readBytes(inputStream);
|
||||
if (bytes != null) {
|
||||
try {
|
||||
out = newGifDrawable(bytes);
|
||||
DrawableUtils.intrinsicBounds(out);
|
||||
|
||||
if (!autoPlayGif) {
|
||||
((GifDrawable) out).pause();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Drawable newGifDrawable(@NonNull byte[] bytes) throws IOException {
|
||||
return new GifDrawable(bytes);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static byte[] readBytes(@NonNull InputStream stream) {
|
||||
|
||||
byte[] out = null;
|
||||
|
||||
try {
|
||||
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
final int length = 1024 * 8;
|
||||
final byte[] buffer = new byte[length];
|
||||
int read;
|
||||
while ((read = stream.read(buffer, 0, length)) != -1) {
|
||||
outputStream.write(buffer, 0, read);
|
||||
}
|
||||
out = outputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ImageItem {
|
||||
|
||||
private final String contentType;
|
||||
private final InputStream inputStream;
|
||||
private final String fileName;
|
||||
|
||||
public ImageItem(
|
||||
@Nullable String contentType,
|
||||
@Nullable InputStream inputStream,
|
||||
@Nullable String fileName) {
|
||||
this.contentType = contentType;
|
||||
this.inputStream = inputStream;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InputStream inputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String fileName() {
|
||||
return fileName;
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This class can be used as the last {@link MediaDecoder} to _try_ to handle all rest cases.
|
||||
* Here we just assume that supplied InputStream is of image type and try to decode it.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class ImageMediaDecoder extends MediaDecoder {
|
||||
|
||||
@NonNull
|
||||
public static ImageMediaDecoder create(@NonNull Resources resources) {
|
||||
return new ImageMediaDecoder(resources);
|
||||
}
|
||||
|
||||
private final Resources resources;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
ImageMediaDecoder(Resources resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeByContentType(@Nullable String contentType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeByFileName(@NonNull String fileName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable decode(@NonNull InputStream inputStream) {
|
||||
|
||||
final Drawable out;
|
||||
|
||||
// absolutely not optimal... thing
|
||||
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
||||
if (bitmap != null) {
|
||||
out = new BitmapDrawable(resources, bitmap);
|
||||
DrawableUtils.intrinsicBounds(out);
|
||||
} else {
|
||||
out = null;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class MediaDecoder {
|
||||
|
||||
public abstract boolean canDecodeByContentType(@Nullable String contentType);
|
||||
|
||||
public abstract boolean canDecodeByFileName(@NonNull String fileName);
|
||||
|
||||
@Nullable
|
||||
public abstract Drawable decode(@NonNull InputStream inputStream);
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class NetworkSchemeHandler extends SchemeHandler {
|
||||
|
||||
@NonNull
|
||||
public static NetworkSchemeHandler create(@NonNull OkHttpClient client) {
|
||||
return new NetworkSchemeHandler(client);
|
||||
}
|
||||
|
||||
private static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private final OkHttpClient client;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
NetworkSchemeHandler(@NonNull OkHttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||
|
||||
ImageItem out = null;
|
||||
|
||||
final Request request = new Request.Builder()
|
||||
.url(raw)
|
||||
.tag(raw)
|
||||
.build();
|
||||
|
||||
Response response = null;
|
||||
try {
|
||||
response = client.newCall(request).execute();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
final ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
final InputStream inputStream = body.byteStream();
|
||||
if (inputStream != null) {
|
||||
final String contentType = response.header(HEADER_CONTENT_TYPE);
|
||||
out = new ImageItem(contentType, inputStream, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(@NonNull String raw) {
|
||||
final List<Call> calls = client.dispatcher().queuedCalls();
|
||||
if (calls != null) {
|
||||
for (Call call : calls) {
|
||||
if (!call.isCanceled()) {
|
||||
if (raw.equals(call.request().tag())) {
|
||||
call.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Collection<String> schemes() {
|
||||
return Arrays.asList("http", "https");
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class SchemeHandler {
|
||||
|
||||
@Nullable
|
||||
public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);
|
||||
|
||||
public abstract void cancel(@NonNull String raw);
|
||||
|
||||
/**
|
||||
* Will be called only once during initialization, should return schemes that are
|
||||
* handled by this handler
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Collection<String> schemes();
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.caverock.androidsvg.SVG;
|
||||
import com.caverock.androidsvg.SVGParseException;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class SvgMediaDecoder extends MediaDecoder {
|
||||
|
||||
private static final String CONTENT_TYPE_SVG = "image/svg+xml";
|
||||
private static final String FILE_EXTENSION_SVG = ".svg";
|
||||
|
||||
@NonNull
|
||||
public static SvgMediaDecoder create(@NonNull Resources resources) {
|
||||
return new SvgMediaDecoder(resources);
|
||||
}
|
||||
|
||||
private final Resources resources;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
SvgMediaDecoder(Resources resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeByContentType(@Nullable String contentType) {
|
||||
return contentType != null && contentType.startsWith(CONTENT_TYPE_SVG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeByFileName(@NonNull String fileName) {
|
||||
return fileName.endsWith(FILE_EXTENSION_SVG);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable decode(@NonNull InputStream inputStream) {
|
||||
|
||||
final Drawable out;
|
||||
|
||||
SVG svg = null;
|
||||
try {
|
||||
svg = SVG.getFromInputStream(inputStream);
|
||||
} catch (SVGParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (svg == null) {
|
||||
out = null;
|
||||
} else {
|
||||
|
||||
final float w = svg.getDocumentWidth();
|
||||
final float h = svg.getDocumentHeight();
|
||||
final float density = resources.getDisplayMetrics().density;
|
||||
|
||||
final int width = (int) (w * density + .5F);
|
||||
final int height = (int) (h * density + .5F);
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
canvas.scale(density, density);
|
||||
svg.renderToCanvas(canvas);
|
||||
|
||||
out = new BitmapDrawable(resources, bitmap);
|
||||
DrawableUtils.intrinsicBounds(out);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
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.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class DataUriParserTest {
|
||||
|
||||
private DataUriParser.Impl impl;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
impl = new DataUriParser.Impl();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
final Map<String, DataUri> data = new LinkedHashMap<String, DataUri>() {{
|
||||
put(",", new DataUri(null, false, null));
|
||||
put("image/svg+xml;base64,!@#$%^&*(", new DataUri("image/svg+xml", true, "!@#$%^&*("));
|
||||
put("text/vnd-example+xyz;foo=bar;base64,R0lGODdh", new DataUri("text/vnd-example+xyz", true, "R0lGODdh"));
|
||||
put("text/plain;charset=UTF-8;page=21,the%20data:1234,5678", new DataUri("text/plain", false, "the%20data:1234,5678"));
|
||||
}};
|
||||
|
||||
for (Map.Entry<String, DataUri> entry : data.entrySet()) {
|
||||
assertEquals(entry.getKey(), entry.getValue(), impl.parse(entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_new_lines_are_ignored() {
|
||||
|
||||
final String input = "image/png;base64,iVBORw0KGgoAAA\n" +
|
||||
"ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4\n" +
|
||||
"//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU\n" +
|
||||
"5ErkJggg==";
|
||||
|
||||
assertEquals(
|
||||
new DataUri("image/png", true, "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="),
|
||||
impl.parse(input)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void no_comma_returns_null() {
|
||||
|
||||
final String[] inputs = {
|
||||
"",
|
||||
"what-ever",
|
||||
";;;;;;;",
|
||||
"some crazy data"
|
||||
};
|
||||
|
||||
for (String input : inputs) {
|
||||
assertNull(input, impl.parse(input));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void two_commas() {
|
||||
final String input = ",,"; // <- second one would be considered data...
|
||||
assertEquals(
|
||||
input,
|
||||
new DataUri(null, false, ","),
|
||||
impl.parse(input)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void more_commas() {
|
||||
final String input = "first,second,third"; // <- first is just a value (will be ignored)
|
||||
assertEquals(
|
||||
input,
|
||||
new DataUri(null, false, "second,third"),
|
||||
impl.parse(input)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void base64_no_content_type() {
|
||||
final String input = ";base64,12345";
|
||||
assertEquals(
|
||||
input,
|
||||
new DataUri(null, true, "12345"),
|
||||
impl.parse(input)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_base64_no_content_type() {
|
||||
final String input = ",qweRTY";
|
||||
assertEquals(
|
||||
input,
|
||||
new DataUri(null, false, "qweRTY"),
|
||||
impl.parse(input)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void content_type_data_no_base64() {
|
||||
final String input = "image/png,aSdFg";
|
||||
assertEquals(
|
||||
input,
|
||||
new DataUri("image/png", false, "aSdFg"),
|
||||
impl.parse(input)
|
||||
);
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package ru.noties.markwon.il;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
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 DataUriSchemeHandlerTest {
|
||||
|
||||
private DataUriSchemeHandler handler;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
handler = DataUriSchemeHandler.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheme_specific_part_is_empty() {
|
||||
assertNull(handler.handle("data:", Uri.parse("data:")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_uri_is_empty() {
|
||||
assertNull(handler.handle("data://whatever", Uri.parse("data://whatever")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void no_data() {
|
||||
assertNull(handler.handle("data://,", Uri.parse("data://,")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correct() {
|
||||
|
||||
final class Item {
|
||||
|
||||
final String contentType;
|
||||
final String data;
|
||||
|
||||
Item(String contentType, String data) {
|
||||
this.contentType = contentType;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, Item> expected = new HashMap<String, Item>() {{
|
||||
put("data://text/plain;,123", new Item("text/plain", "123"));
|
||||
put("data://image/svg+xml;base64,MTIz", new Item("image/svg+xml", "123"));
|
||||
}};
|
||||
|
||||
for (Map.Entry<String, Item> entry : expected.entrySet()) {
|
||||
final ImageItem item = handler.handle(entry.getKey(), Uri.parse(entry.getKey()));
|
||||
assertNotNull(entry.getKey(), item);
|
||||
assertEquals(entry.getKey(), entry.getValue().contentType, item.contentType());
|
||||
assertEquals(entry.getKey(), entry.getValue().data, readStream(item.inputStream()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correct_real() {
|
||||
|
||||
final class Item {
|
||||
|
||||
final String contentType;
|
||||
final String data;
|
||||
|
||||
Item(String contentType, String data) {
|
||||
this.contentType = contentType;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, Item> expected = new HashMap<String, Item>() {{
|
||||
put("data:text/plain;,123", new Item("text/plain", "123"));
|
||||
put("data:image/svg+xml;base64,MTIz", new Item("image/svg+xml", "123"));
|
||||
}};
|
||||
|
||||
for (Map.Entry<String, Item> entry : expected.entrySet()) {
|
||||
final ImageItem item = handler.handle(entry.getKey(), Uri.parse(entry.getKey()));
|
||||
assertNotNull(entry.getKey(), item);
|
||||
assertEquals(entry.getKey(), entry.getValue().contentType, item.contentType());
|
||||
assertEquals(entry.getKey(), entry.getValue().data, readStream(item.inputStream()));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String readStream(@NonNull InputStream stream) {
|
||||
try {
|
||||
final Scanner scanner = new Scanner(stream, "UTF-8").useDelimiter("\\A");
|
||||
return scanner.hasNext()
|
||||
? scanner.next()
|
||||
: "";
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,10 @@ public class Prism4jThemeDarkula extends Prism4jThemeBase {
|
||||
return new Prism4jThemeDarkula(0xFF2d2d2d);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param background color
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@NonNull
|
||||
public static Prism4jThemeDarkula create(@ColorInt int background) {
|
||||
return new Prism4jThemeDarkula(background);
|
||||
|
@ -15,9 +15,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-html-parser-api')
|
||||
api project(':markwon-html-parser-impl')
|
||||
|
||||
deps.with {
|
||||
api it['support-annotations']
|
||||
api it['commonmark']
|
||||
|
@ -3,12 +3,12 @@ package ru.noties.markwon;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.html.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||
import ru.noties.markwon.image.AsyncDrawableLoaderNoOp;
|
||||
import ru.noties.markwon.renderer.ImageSizeResolver;
|
||||
import ru.noties.markwon.renderer.ImageSizeResolverDef;
|
||||
import ru.noties.markwon.renderer.html2.MarkwonHtmlRenderer;
|
||||
import ru.noties.markwon.spans.LinkSpan;
|
||||
import ru.noties.markwon.spans.MarkwonTheme;
|
||||
|
||||
@ -37,7 +37,6 @@ public class MarkwonConfiguration {
|
||||
private final UrlProcessor urlProcessor;
|
||||
private final ImageSizeResolver imageSizeResolver;
|
||||
private final SpannableFactory factory; // @since 1.1.0
|
||||
private final boolean softBreakAddsNewLine; // @since 1.1.1
|
||||
private final MarkwonHtmlParser htmlParser; // @since 2.0.0
|
||||
private final MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0
|
||||
private final boolean htmlAllowNonClosedTags; // @since 2.0.0
|
||||
@ -50,7 +49,6 @@ public class MarkwonConfiguration {
|
||||
this.urlProcessor = builder.urlProcessor;
|
||||
this.imageSizeResolver = builder.imageSizeResolver;
|
||||
this.factory = builder.factory;
|
||||
this.softBreakAddsNewLine = builder.softBreakAddsNewLine;
|
||||
this.htmlParser = builder.htmlParser;
|
||||
this.htmlRenderer = builder.htmlRenderer;
|
||||
this.htmlAllowNonClosedTags = builder.htmlAllowNonClosedTags;
|
||||
@ -99,15 +97,6 @@ public class MarkwonConfiguration {
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a flag indicating if soft break should be treated as a hard
|
||||
* break and thus adding a new line instead of adding a white space
|
||||
* @since 1.1.1
|
||||
*/
|
||||
public boolean softBreakAddsNewLine() {
|
||||
return softBreakAddsNewLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ -143,7 +132,6 @@ public class MarkwonConfiguration {
|
||||
private UrlProcessor urlProcessor;
|
||||
private ImageSizeResolver imageSizeResolver;
|
||||
private SpannableFactory factory; // @since 1.1.0
|
||||
private boolean softBreakAddsNewLine; // @since 1.1.1
|
||||
private MarkwonHtmlParser htmlParser; // @since 2.0.0
|
||||
private MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0
|
||||
private boolean htmlAllowNonClosedTags; // @since 2.0.0
|
||||
@ -161,7 +149,6 @@ public class MarkwonConfiguration {
|
||||
this.urlProcessor = configuration.urlProcessor;
|
||||
this.imageSizeResolver = configuration.imageSizeResolver;
|
||||
this.factory = configuration.factory;
|
||||
this.softBreakAddsNewLine = configuration.softBreakAddsNewLine;
|
||||
this.htmlParser = configuration.htmlParser;
|
||||
this.htmlRenderer = configuration.htmlRenderer;
|
||||
this.htmlAllowNonClosedTags = configuration.htmlAllowNonClosedTags;
|
||||
@ -203,19 +190,6 @@ public class MarkwonConfiguration {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param softBreakAddsNewLine a flag indicating if soft break should be treated as a hard
|
||||
* break and thus adding a new line instead of adding a white space
|
||||
* @return self
|
||||
* @see <a href="https://spec.commonmark.org/0.28/#soft-line-breaks" > spec </a >
|
||||
* @since 1.1.1
|
||||
*/
|
||||
@NonNull
|
||||
public Builder softBreakAddsNewLine(boolean softBreakAddsNewLine) {
|
||||
this.softBreakAddsNewLine = softBreakAddsNewLine;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ -276,17 +250,12 @@ public class MarkwonConfiguration {
|
||||
|
||||
// @since 2.0.0
|
||||
if (htmlParser == null) {
|
||||
try {
|
||||
// if impl artifact was excluded -> fallback to no-op implementation
|
||||
htmlParser = ru.noties.markwon.html.impl.MarkwonHtmlParserImpl.create();
|
||||
} catch (Throwable t) {
|
||||
htmlParser = MarkwonHtmlParser.noOp();
|
||||
}
|
||||
htmlParser = MarkwonHtmlParser.noOp();
|
||||
}
|
||||
|
||||
// @since 2.0.0
|
||||
if (htmlRenderer == null) {
|
||||
htmlRenderer = MarkwonHtmlRenderer.create();
|
||||
htmlRenderer = MarkwonHtmlRenderer.noOp();
|
||||
}
|
||||
|
||||
return new MarkwonConfiguration(this);
|
||||
|
@ -63,16 +63,4 @@ public interface SpannableFactory {
|
||||
@NonNull MarkwonTheme theme,
|
||||
@NonNull String destination,
|
||||
@NonNull LinkSpan.Resolver resolver);
|
||||
|
||||
// Currently used by HTML parser
|
||||
@Nullable
|
||||
Object superScript(@NonNull MarkwonTheme theme);
|
||||
|
||||
// Currently used by HTML parser
|
||||
@Nullable
|
||||
Object subScript(@NonNull MarkwonTheme theme);
|
||||
|
||||
// Currently used by HTML parser
|
||||
@Nullable
|
||||
Object underline();
|
||||
}
|
||||
|
@ -118,21 +118,4 @@ public class SpannableFactoryDef implements SpannableFactory {
|
||||
public Object link(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull LinkSpan.Resolver resolver) {
|
||||
return new LinkSpan(theme, destination, resolver);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object superScript(@NonNull MarkwonTheme theme) {
|
||||
return new SuperScriptSpan(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object subScript(@NonNull MarkwonTheme theme) {
|
||||
return new SubScriptSpan(theme);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object underline() {
|
||||
return new UnderlineSpan();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package ru.noties.markwon.html.api;
|
||||
package ru.noties.markwon.html;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
@ -1,4 +1,4 @@
|
||||
package ru.noties.markwon.html.api;
|
||||
package ru.noties.markwon.html;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@ -34,7 +34,7 @@ public abstract class MarkwonHtmlParser {
|
||||
* If you wish to keep them open (do not force close at the end of a
|
||||
* document pass here {@link HtmlTag#NO_END}. Later non-closed tags
|
||||
* can be detected by calling {@link HtmlTag#isClosed()}
|
||||
* @param action {@link FlushAction} to be called with resulting tags ({@link ru.noties.markwon.html.api.HtmlTag.Inline})
|
||||
* @param action {@link FlushAction} to be called with resulting tags ({@link HtmlTag.Inline})
|
||||
*/
|
||||
public abstract void flushInlineTags(
|
||||
int documentLength,
|
||||
@ -49,7 +49,7 @@ public abstract class MarkwonHtmlParser {
|
||||
* If you wish to keep them open (do not force close at the end of a
|
||||
* document pass here {@link HtmlTag#NO_END}. Later non-closed tags
|
||||
* can be detected by calling {@link HtmlTag#isClosed()}
|
||||
* @param action {@link FlushAction} to be called with resulting tags ({@link ru.noties.markwon.html.api.HtmlTag.Block})
|
||||
* @param action {@link FlushAction} to be called with resulting tags ({@link HtmlTag.Block})
|
||||
*/
|
||||
public abstract void flushBlockTags(
|
||||
int documentLength,
|
@ -1,4 +1,4 @@
|
||||
package ru.noties.markwon.html.api;
|
||||
package ru.noties.markwon.html;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
@ -0,0 +1,30 @@
|
||||
package ru.noties.markwon.html;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class MarkwonHtmlRenderer {
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@NonNull
|
||||
public static MarkwonHtmlRenderer noOp() {
|
||||
return new MarkwonHtmlRendererNoOp();
|
||||
}
|
||||
|
||||
public abstract void render(
|
||||
@NonNull MarkwonConfiguration configuration,
|
||||
@NonNull SpannableBuilder builder,
|
||||
@NonNull MarkwonHtmlParser parser
|
||||
);
|
||||
|
||||
@Nullable
|
||||
public abstract TagHandler tagHandler(@NonNull String tagName);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package ru.noties.markwon.html;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
|
||||
class MarkwonHtmlRendererNoOp extends MarkwonHtmlRenderer {
|
||||
|
||||
@Override
|
||||
public void render(@NonNull MarkwonConfiguration configuration, @NonNull SpannableBuilder builder, @NonNull MarkwonHtmlParser parser) {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TagHandler tagHandler(@NonNull String tagName) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
package ru.noties.markwon.renderer.html2.tag;
|
||||
package ru.noties.markwon.html;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
|
||||
public abstract class TagHandler {
|
||||
|
@ -1,101 +0,0 @@
|
||||
package ru.noties.markwon.renderer.html2;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.renderer.html2.tag.BlockquoteHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.EmphasisHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.HeadingHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.ImageHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.LinkHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.ListHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.StrikeHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.StrongEmphasisHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.SubScriptHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.SuperScriptHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.TagHandler;
|
||||
import ru.noties.markwon.renderer.html2.tag.UnderlineHandler;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class MarkwonHtmlRenderer {
|
||||
|
||||
public abstract void render(
|
||||
@NonNull MarkwonConfiguration configuration,
|
||||
@NonNull SpannableBuilder builder,
|
||||
@NonNull MarkwonHtmlParser parser
|
||||
);
|
||||
|
||||
@Nullable
|
||||
public abstract TagHandler tagHandler(@NonNull String tagName);
|
||||
|
||||
@NonNull
|
||||
public static MarkwonHtmlRenderer create() {
|
||||
return builderWithDefaults().build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder builderWithDefaults() {
|
||||
|
||||
final EmphasisHandler emphasisHandler = new EmphasisHandler();
|
||||
final StrongEmphasisHandler strongEmphasisHandler = new StrongEmphasisHandler();
|
||||
final StrikeHandler strikeHandler = new StrikeHandler();
|
||||
final UnderlineHandler underlineHandler = new UnderlineHandler();
|
||||
final ListHandler listHandler = new ListHandler();
|
||||
|
||||
return builder()
|
||||
.handler("i", emphasisHandler)
|
||||
.handler("em", emphasisHandler)
|
||||
.handler("cite", emphasisHandler)
|
||||
.handler("dfn", emphasisHandler)
|
||||
.handler("b", strongEmphasisHandler)
|
||||
.handler("strong", strongEmphasisHandler)
|
||||
.handler("sup", new SuperScriptHandler())
|
||||
.handler("sub", new SubScriptHandler())
|
||||
.handler("u", underlineHandler)
|
||||
.handler("ins", underlineHandler)
|
||||
.handler("del", strikeHandler)
|
||||
.handler("s", strikeHandler)
|
||||
.handler("strike", strikeHandler)
|
||||
.handler("a", new LinkHandler())
|
||||
.handler("ul", listHandler)
|
||||
.handler("ol", listHandler)
|
||||
.handler("img", ImageHandler.create())
|
||||
.handler("blockquote", new BlockquoteHandler())
|
||||
.handler("h1", new HeadingHandler(1))
|
||||
.handler("h2", new HeadingHandler(2))
|
||||
.handler("h3", new HeadingHandler(3))
|
||||
.handler("h4", new HeadingHandler(4))
|
||||
.handler("h5", new HeadingHandler(5))
|
||||
.handler("h6", new HeadingHandler(6));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Map<String, TagHandler> tagHandlers = new HashMap<>(2);
|
||||
|
||||
public Builder handler(@NonNull String tagName, @NonNull TagHandler tagHandler) {
|
||||
tagHandlers.put(tagName.toLowerCase(Locale.US), tagHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public MarkwonHtmlRenderer build() {
|
||||
return new MarkwonHtmlRendererImpl(Collections.unmodifiableMap(tagHandlers));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package ru.noties.markwon.renderer.html2;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.SpannableBuilder;
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.html.api.HtmlTag;
|
||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.renderer.html2.tag.TagHandler;
|
||||
|
||||
class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
|
||||
|
||||
private final Map<String, TagHandler> tagHandlers;
|
||||
|
||||
MarkwonHtmlRendererImpl(@NonNull Map<String, TagHandler> tagHandlers) {
|
||||
this.tagHandlers = tagHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
@NonNull final MarkwonConfiguration configuration,
|
||||
@NonNull final SpannableBuilder builder,
|
||||
@NonNull MarkwonHtmlParser parser) {
|
||||
|
||||
final int end;
|
||||
if (!configuration.htmlAllowNonClosedTags()) {
|
||||
end = HtmlTag.NO_END;
|
||||
} else {
|
||||
end = builder.length();
|
||||
}
|
||||
|
||||
parser.flushInlineTags(end, new MarkwonHtmlParser.FlushAction<HtmlTag.Inline>() {
|
||||
@Override
|
||||
public void apply(@NonNull List<HtmlTag.Inline> tags) {
|
||||
|
||||
TagHandler handler;
|
||||
|
||||
for (HtmlTag.Inline inline : tags) {
|
||||
|
||||
// if tag is not closed -> do not render
|
||||
if (!inline.isClosed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handler = tagHandler(inline.name());
|
||||
if (handler != null) {
|
||||
handler.handle(configuration, builder, inline);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
parser.flushBlockTags(end, new MarkwonHtmlParser.FlushAction<HtmlTag.Block>() {
|
||||
@Override
|
||||
public void apply(@NonNull List<HtmlTag.Block> tags) {
|
||||
|
||||
TagHandler handler;
|
||||
|
||||
for (HtmlTag.Block block : tags) {
|
||||
|
||||
if (!block.isClosed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handler = tagHandler(block.name());
|
||||
if (handler != null) {
|
||||
handler.handle(configuration, builder, block);
|
||||
} else {
|
||||
// see if any of children can be handled
|
||||
apply(block.children());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TagHandler tagHandler(@NonNull String tagName) {
|
||||
return tagHandlers.get(tagName);
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import ru.noties.markwon.SpannableFactory;
|
||||
import ru.noties.markwon.SyntaxHighlight;
|
||||
import ru.noties.markwon.UrlProcessor;
|
||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
||||
import ru.noties.markwon.renderer.html2.MarkwonHtmlRenderer;
|
||||
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||
import ru.noties.markwon.image.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.LinkSpan;
|
||||
import ru.noties.markwon.spans.MarkwonTheme;
|
||||
|
@ -1,3 +1,3 @@
|
||||
rootProject.name = 'MarkwonProject'
|
||||
include ':app', ':markwon', ':markwon-view', ':sample-custom-extension', ':sample-latex-math', ':markwon-image-svg', ':markwon-image-gif',
|
||||
':markwon-syntax-highlight', ':markwon-html-parser-api', ':markwon-html-parser-impl'
|
||||
':markwon-syntax-highlight', ':markwon-html'
|
||||
|
Loading…
x
Reference in New Issue
Block a user