Failing editor tests
This commit is contained in:
parent
3069432bc2
commit
646e708c82
@ -1,4 +1,16 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"javaClassName": "io.noties.markwon.app.samples.html.InspectHtmlTextSample",
|
||||||
|
"id": "20210201140501",
|
||||||
|
"title": "Inspect text",
|
||||||
|
"description": "Inspect text content of a `HTML` node",
|
||||||
|
"artifacts": [
|
||||||
|
"HTML"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HTML"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"javaClassName": "io.noties.markwon.app.samples.image.HugeImageSample",
|
"javaClassName": "io.noties.markwon.app.samples.image.HugeImageSample",
|
||||||
"id": "20210118165230",
|
"id": "20210118165230",
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package io.noties.markwon.app.samples.html
|
||||||
|
|
||||||
|
import android.text.style.URLSpan
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.MarkwonVisitor
|
||||||
|
import io.noties.markwon.app.sample.Tags
|
||||||
|
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample
|
||||||
|
import io.noties.markwon.html.HtmlPlugin
|
||||||
|
import io.noties.markwon.html.HtmlTag
|
||||||
|
import io.noties.markwon.html.MarkwonHtmlRenderer
|
||||||
|
import io.noties.markwon.html.TagHandler
|
||||||
|
import io.noties.markwon.sample.annotations.MarkwonArtifact
|
||||||
|
import io.noties.markwon.sample.annotations.MarkwonSampleInfo
|
||||||
|
|
||||||
|
@MarkwonSampleInfo(
|
||||||
|
id = "20210201140501",
|
||||||
|
title = "Inspect text",
|
||||||
|
description = "Inspect text content of a `HTML` node",
|
||||||
|
artifacts = [MarkwonArtifact.HTML],
|
||||||
|
tags = [Tags.html]
|
||||||
|
)
|
||||||
|
class InspectHtmlTextSample : MarkwonTextViewSample() {
|
||||||
|
override fun render() {
|
||||||
|
val md = """
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<div class="custom-youtube-player">https://www.youtube.com/watch?v=abcdefgh</div>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(HtmlPlugin.create {
|
||||||
|
it.addHandler(DivHandler())
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
markwon.setMarkdown(textView, md)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DivHandler : TagHandler() {
|
||||||
|
override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
|
||||||
|
val attr = tag.attributes()["class"] ?: return
|
||||||
|
if (attr.contains(CUSTOM_CLASS)) {
|
||||||
|
val text = visitor.builder().substring(tag.start(), tag.end())
|
||||||
|
visitor.builder().setSpan(
|
||||||
|
URLSpan(text),
|
||||||
|
tag.start(),
|
||||||
|
tag.end()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportedTags(): Collection<String> = setOf("div")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CUSTOM_CLASS = "custom-youtube-player"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -94,6 +94,28 @@ public class MarkwonEditorUtilsTest {
|
|||||||
assertMatched(input, strike, "~~", 3, 11);
|
assertMatched(input, strike, "~~", 3, 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void delimited_triple_asterisks() {
|
||||||
|
final String input = "***italic bold bold***";
|
||||||
|
|
||||||
|
final Match bold = findDelimited(input, 0, "**", "__");
|
||||||
|
final Match em = findDelimited(input, 0, "*", "_");
|
||||||
|
|
||||||
|
assertMatched(input, bold, "**", 0, input.length());
|
||||||
|
assertMatched(input, em, "*", 0, input.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void delimited_triple_asterisks_2() {
|
||||||
|
final String input = "***italic bold* bold**";
|
||||||
|
|
||||||
|
final Match bold = findDelimited(input, 0, "**", "__");
|
||||||
|
final Match em = findDelimited(input, 0, "*", "_");
|
||||||
|
|
||||||
|
assertMatched(input, bold, "**", 0, input.length());
|
||||||
|
assertMatched(input, em, "*", 0, 15);
|
||||||
|
}
|
||||||
|
|
||||||
private static void assertMatched(
|
private static void assertMatched(
|
||||||
@NonNull String input,
|
@NonNull String input,
|
||||||
@Nullable Match match,
|
@Nullable Match match,
|
||||||
|
@ -12,6 +12,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@ -77,12 +78,6 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private Future<?> execute(@NonNull final AsyncDrawable asyncDrawable) {
|
private Future<?> execute(@NonNull final AsyncDrawable asyncDrawable) {
|
||||||
|
|
||||||
// todo: more efficient DefaultMediaDecoder... 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() {
|
return executorService.submit(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -113,17 +108,26 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
|
|||||||
|
|
||||||
final ImageItem.WithDecodingNeeded withDecodingNeeded = imageItem.getAsWithDecodingNeeded();
|
final ImageItem.WithDecodingNeeded withDecodingNeeded = imageItem.getAsWithDecodingNeeded();
|
||||||
|
|
||||||
MediaDecoder mediaDecoder = mediaDecoders.get(withDecodingNeeded.contentType());
|
// @since $SNAPSHOT; close input stream
|
||||||
|
try {
|
||||||
|
MediaDecoder mediaDecoder = mediaDecoders.get(withDecodingNeeded.contentType());
|
||||||
|
|
||||||
if (mediaDecoder == null) {
|
if (mediaDecoder == null) {
|
||||||
mediaDecoder = defaultMediaDecoder;
|
mediaDecoder = defaultMediaDecoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaDecoder != null) {
|
if (mediaDecoder != null) {
|
||||||
drawable = mediaDecoder.decode(withDecodingNeeded.contentType(), withDecodingNeeded.inputStream());
|
drawable = mediaDecoder.decode(withDecodingNeeded.contentType(), withDecodingNeeded.inputStream());
|
||||||
} else {
|
} else {
|
||||||
// throw that no media decoder is found
|
// throw that no media decoder is found
|
||||||
throw new IllegalStateException("No media-decoder is found: " + destination);
|
throw new IllegalStateException("No media-decoder is found: " + destination);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
withDecodingNeeded.inputStream().close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("MARKWON-IMAGE", "Error closing inputStream", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
drawable = imageItem.getAsWithResult().result();
|
drawable = imageItem.getAsWithResult().result();
|
||||||
|
@ -33,141 +33,156 @@ import java.util.Collections;
|
|||||||
*/
|
*/
|
||||||
public class DefaultDownScalingMediaDecoder extends MediaDecoder {
|
public class DefaultDownScalingMediaDecoder extends MediaDecoder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Values {@code <= 0} are ignored, a dimension is considered to be not restrained any limit in such case
|
* Values {@code <= 0} are ignored, a dimension is considered to be not restrained any limit in such case
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static DefaultDownScalingMediaDecoder create(int maxWidth, int maxHeight) {
|
public static DefaultDownScalingMediaDecoder create(int maxWidth, int maxHeight) {
|
||||||
return create(Resources.getSystem(), maxWidth, maxHeight);
|
return create(Resources.getSystem(), maxWidth, maxHeight);
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static DefaultDownScalingMediaDecoder create(
|
|
||||||
@NonNull Resources resources,
|
|
||||||
int maxWidth,
|
|
||||||
int maxHeight
|
|
||||||
) {
|
|
||||||
return new DefaultDownScalingMediaDecoder(resources, maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Resources resources;
|
|
||||||
private final int maxWidth;
|
|
||||||
private final int maxHeight;
|
|
||||||
|
|
||||||
private DefaultDownScalingMediaDecoder(@NonNull Resources resources, int maxWidth, int maxHeight) {
|
|
||||||
this.resources = resources;
|
|
||||||
this.maxWidth = maxWidth;
|
|
||||||
this.maxHeight = maxHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://android.jlelse.eu/loading-large-bitmaps-efficiently-in-android-66826cd4ad53
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Drawable decode(@Nullable String contentType, @NonNull InputStream inputStream) {
|
|
||||||
|
|
||||||
final File file = writeToTempFile(inputStream);
|
|
||||||
try {
|
|
||||||
|
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
|
||||||
options.inJustDecodeBounds = true;
|
|
||||||
|
|
||||||
BitmapFactory
|
|
||||||
.decodeStream(readFile(file), null, options);
|
|
||||||
|
|
||||||
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
|
|
||||||
options.inJustDecodeBounds = false;
|
|
||||||
|
|
||||||
final Bitmap bitmap = BitmapFactory
|
|
||||||
.decodeStream(readFile(file), null, options);
|
|
||||||
|
|
||||||
return new BitmapDrawable(resources, bitmap);
|
|
||||||
} finally {
|
|
||||||
// we no longer need the temporary file
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static File writeToTempFile(@NonNull InputStream inputStream) {
|
|
||||||
final File file;
|
|
||||||
try {
|
|
||||||
file = File.createTempFile("markwon", null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final OutputStream outputStream;
|
@NonNull
|
||||||
try {
|
public static DefaultDownScalingMediaDecoder create(
|
||||||
outputStream = new BufferedOutputStream(new FileOutputStream(file, false));
|
@NonNull Resources resources,
|
||||||
} catch (FileNotFoundException e) {
|
int maxWidth,
|
||||||
throw new IllegalStateException(e);
|
int maxHeight
|
||||||
|
) {
|
||||||
|
return new DefaultDownScalingMediaDecoder(resources, maxWidth, maxHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
final byte[] buffer = new byte[1024 * 8];
|
private final Resources resources;
|
||||||
int length;
|
private final int maxWidth;
|
||||||
try {
|
private final int maxHeight;
|
||||||
while ((length = inputStream.read(buffer)) > 0) {
|
|
||||||
outputStream.write(buffer, 0, length);
|
private DefaultDownScalingMediaDecoder(@NonNull Resources resources, int maxWidth, int maxHeight) {
|
||||||
}
|
this.resources = resources;
|
||||||
} catch (IOException e) {
|
this.maxWidth = maxWidth;
|
||||||
throw new IllegalStateException(e);
|
this.maxHeight = maxHeight;
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
outputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return file;
|
// https://android.jlelse.eu/loading-large-bitmaps-efficiently-in-android-66826cd4ad53
|
||||||
}
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Drawable decode(@Nullable String contentType, @NonNull InputStream inputStream) {
|
||||||
|
|
||||||
@NonNull
|
final File file = writeToTempFile(inputStream);
|
||||||
private static InputStream readFile(@NonNull File file) {
|
try {
|
||||||
try {
|
|
||||||
return new BufferedInputStream(new FileInputStream(file));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// see: https://developer.android.com/topic/performance/graphics/load-bitmap.html#load-bitmap
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
private static int calculateInSampleSize(@NonNull BitmapFactory.Options options, int maxWidth, int maxHeight) {
|
options.inJustDecodeBounds = true;
|
||||||
final int w = options.outWidth;
|
|
||||||
final int h = options.outHeight;
|
|
||||||
|
|
||||||
final boolean hasMaxWidth = maxWidth > 0;
|
// initial result when obtaining bounds is discarded
|
||||||
final boolean hasMaxHeight = maxHeight > 0;
|
decode(file, options);
|
||||||
|
|
||||||
final int inSampleSize;
|
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
|
||||||
if (hasMaxWidth && hasMaxHeight) {
|
options.inJustDecodeBounds = false;
|
||||||
// minimum of both
|
|
||||||
inSampleSize = Math.min(calculateInSampleSize(w, maxWidth), calculateInSampleSize(h, maxHeight));
|
final Bitmap bitmap = decode(file, options);
|
||||||
} else if (hasMaxWidth) {
|
|
||||||
inSampleSize = calculateInSampleSize(w, maxWidth);
|
return new BitmapDrawable(resources, bitmap);
|
||||||
} else if (hasMaxHeight) {
|
} finally {
|
||||||
inSampleSize = calculateInSampleSize(h, maxHeight);
|
// we no longer need the temporary file
|
||||||
} else {
|
//noinspection ResultOfMethodCallIgnored
|
||||||
// else no sampling, as we have no dimensions to base our calculations on
|
file.delete();
|
||||||
inSampleSize = 1;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return inSampleSize;
|
@NonNull
|
||||||
}
|
private static File writeToTempFile(@NonNull InputStream inputStream) {
|
||||||
|
final File file;
|
||||||
|
try {
|
||||||
|
file = File.createTempFile("markwon", null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
|
||||||
private static int calculateInSampleSize(int actual, int max) {
|
final OutputStream outputStream;
|
||||||
int inSampleSize = 1;
|
try {
|
||||||
final int half = actual / 2;
|
outputStream = new BufferedOutputStream(new FileOutputStream(file, false));
|
||||||
while ((half / inSampleSize) > max) {
|
} catch (FileNotFoundException e) {
|
||||||
inSampleSize *= 2;
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] buffer = new byte[1024 * 8];
|
||||||
|
int length;
|
||||||
|
try {
|
||||||
|
while ((length = inputStream.read(buffer)) > 0) {
|
||||||
|
outputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
outputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
return inSampleSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@Nullable
|
||||||
@Override
|
private static Bitmap decode(@NonNull File file, @NonNull BitmapFactory.Options options) {
|
||||||
public Collection<String> supportedTypes() {
|
final InputStream is = readFile(file);
|
||||||
return Collections.emptySet();
|
// not yet, still min SDK is 16
|
||||||
}
|
try {
|
||||||
|
return BitmapFactory.decodeStream(is, null, options);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InputStream readFile(@NonNull File file) {
|
||||||
|
try {
|
||||||
|
return new BufferedInputStream(new FileInputStream(file));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: https://developer.android.com/topic/performance/graphics/load-bitmap.html#load-bitmap
|
||||||
|
private static int calculateInSampleSize(@NonNull BitmapFactory.Options options, int maxWidth, int maxHeight) {
|
||||||
|
final int w = options.outWidth;
|
||||||
|
final int h = options.outHeight;
|
||||||
|
|
||||||
|
final boolean hasMaxWidth = maxWidth > 0;
|
||||||
|
final boolean hasMaxHeight = maxHeight > 0;
|
||||||
|
|
||||||
|
final int inSampleSize;
|
||||||
|
if (hasMaxWidth && hasMaxHeight) {
|
||||||
|
// minimum of both
|
||||||
|
inSampleSize = Math.min(calculateInSampleSize(w, maxWidth), calculateInSampleSize(h, maxHeight));
|
||||||
|
} else if (hasMaxWidth) {
|
||||||
|
inSampleSize = calculateInSampleSize(w, maxWidth);
|
||||||
|
} else if (hasMaxHeight) {
|
||||||
|
inSampleSize = calculateInSampleSize(h, maxHeight);
|
||||||
|
} else {
|
||||||
|
// else no sampling, as we have no dimensions to base our calculations on
|
||||||
|
inSampleSize = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inSampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateInSampleSize(int actual, int max) {
|
||||||
|
int inSampleSize = 1;
|
||||||
|
final int half = actual / 2;
|
||||||
|
while ((half / inSampleSize) > max) {
|
||||||
|
inSampleSize *= 2;
|
||||||
|
}
|
||||||
|
return inSampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedTypes() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user