|
|
@ -1767,7 +1767,6 @@ public class HtmlHelper {
|
|
|
|
if (experiments) {
|
|
|
|
if (experiments) {
|
|
|
|
// https://developer.android.com/guide/topics/text/spans
|
|
|
|
// https://developer.android.com/guide/topics/text/spans
|
|
|
|
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
|
|
|
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
|
|
|
List<SpanHolder> holders = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NodeTraversor.traverse(new NodeVisitor() {
|
|
|
|
NodeTraversor.traverse(new NodeVisitor() {
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
@ -1775,17 +1774,42 @@ public class HtmlHelper {
|
|
|
|
if (node instanceof Element) {
|
|
|
|
if (node instanceof Element) {
|
|
|
|
Element element = (Element) node;
|
|
|
|
Element element = (Element) node;
|
|
|
|
element.attr("start-index", Integer.toString(ssb.length()));
|
|
|
|
element.attr("start-index", Integer.toString(ssb.length()));
|
|
|
|
switch (element.tagName()) {
|
|
|
|
|
|
|
|
case "img":
|
|
|
|
boolean pre = false;
|
|
|
|
String src = element.attr("src");
|
|
|
|
Element parent = element.parent();
|
|
|
|
Drawable d = (imageGetter == null
|
|
|
|
while (parent != null) {
|
|
|
|
? context.getDrawable(R.drawable.baseline_broken_image_24)
|
|
|
|
if ("pre".equals(parent.tagName())) {
|
|
|
|
: imageGetter.getDrawable(src));
|
|
|
|
pre = true;
|
|
|
|
ssb.append("\uFFFC"); // Object replacement character
|
|
|
|
|
|
|
|
holders.add(new SpanHolder(new ImageSpan(d, src), ssb.length() - 1, ssb.length()));
|
|
|
|
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
parent = parent.parent();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!pre) {
|
|
|
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
|
|
|
|
|
|
|
|
List<TextNode> tnodes = getTextNodes(element);
|
|
|
|
|
|
|
|
for (int i = 0; i < tnodes.size(); i++) {
|
|
|
|
|
|
|
|
TextNode tnode = tnodes.get(i);
|
|
|
|
|
|
|
|
String text = tnode.getWholeText();
|
|
|
|
|
|
|
|
if (TextUtils.isEmpty(text))
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Remove whitespace before/after newlines
|
|
|
|
|
|
|
|
text = text.replaceAll("\\s+\\r?\\n\\s+", " ");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (i == 0 || (tnodes.get(i - 1).text().endsWith(" ")))
|
|
|
|
|
|
|
|
while (text.startsWith(" "))
|
|
|
|
|
|
|
|
text = text.substring(1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (i == tnodes.size() - 1)
|
|
|
|
|
|
|
|
while (text.endsWith(" "))
|
|
|
|
|
|
|
|
text = text.substring(0, text.length() - 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tnode.text(text);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (node instanceof TextNode) {
|
|
|
|
} else if (node instanceof TextNode) {
|
|
|
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
|
|
|
|
TextNode tnode = (TextNode) node;
|
|
|
|
TextNode tnode = (TextNode) node;
|
|
|
|
ssb.append(tnode.text());
|
|
|
|
ssb.append(tnode.text());
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1799,22 +1823,22 @@ public class HtmlHelper {
|
|
|
|
switch (element.tagName()) {
|
|
|
|
switch (element.tagName()) {
|
|
|
|
case "a":
|
|
|
|
case "a":
|
|
|
|
String href = element.attr("href");
|
|
|
|
String href = element.attr("href");
|
|
|
|
holders.add(new SpanHolder(new URLSpan(href), start, ssb.length()));
|
|
|
|
ssb.setSpan(new URLSpan(href), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "body":
|
|
|
|
case "body":
|
|
|
|
// Do nothing
|
|
|
|
// Do nothing
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "big":
|
|
|
|
case "big":
|
|
|
|
holders.add(new SpanHolder(new RelativeSizeSpan(FONT_LARGE), start, ssb.length()));
|
|
|
|
ssb.setSpan(new RelativeSizeSpan(FONT_LARGE), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "blockquote":
|
|
|
|
case "blockquote":
|
|
|
|
holders.add(new SpanHolder(new QuoteSpan(), start, ssb.length()));
|
|
|
|
ssb.setSpan(new QuoteSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "br":
|
|
|
|
case "br":
|
|
|
|
ssb.append("\n");
|
|
|
|
ssb.append("\n");
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "em":
|
|
|
|
case "em":
|
|
|
|
holders.add(new SpanHolder(new StyleSpan(Typeface.ITALIC), start, ssb.length()));
|
|
|
|
ssb.setSpan(new StyleSpan(Typeface.ITALIC), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "h1":
|
|
|
|
case "h1":
|
|
|
|
case "h2":
|
|
|
|
case "h2":
|
|
|
@ -1823,23 +1847,33 @@ public class HtmlHelper {
|
|
|
|
case "h5":
|
|
|
|
case "h5":
|
|
|
|
case "h6":
|
|
|
|
case "h6":
|
|
|
|
int level = element.tagName().charAt(1) - '1';
|
|
|
|
int level = element.tagName().charAt(1) - '1';
|
|
|
|
holders.add(new SpanHolder(new RelativeSizeSpan(HEADING_SIZES[level]), start, ssb.length()));
|
|
|
|
ssb.setSpan(new RelativeSizeSpan(HEADING_SIZES[level]), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
holders.add(new SpanHolder(new StyleSpan(Typeface.BOLD), start, ssb.length()));
|
|
|
|
ssb.setSpan(new StyleSpan(Typeface.BOLD), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
|
|
|
ssb.append("\n");
|
|
|
|
|
|
|
|
ssb.insert(start, "\n");
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "img":
|
|
|
|
case "img":
|
|
|
|
|
|
|
|
String src = element.attr("src");
|
|
|
|
|
|
|
|
Drawable d = (imageGetter == null
|
|
|
|
|
|
|
|
? context.getDrawable(R.drawable.baseline_broken_image_24)
|
|
|
|
|
|
|
|
: imageGetter.getDrawable(src));
|
|
|
|
|
|
|
|
ssb.insert(start, "\uFFFC"); // Object replacement character
|
|
|
|
|
|
|
|
ssb.setSpan(new ImageSpan(d, src), start, start + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "pre":
|
|
|
|
// Do nothing
|
|
|
|
// Do nothing
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "small":
|
|
|
|
case "small":
|
|
|
|
holders.add(new SpanHolder(new RelativeSizeSpan(FONT_SMALL), start, ssb.length()));
|
|
|
|
ssb.setSpan(new RelativeSizeSpan(FONT_SMALL), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "span":
|
|
|
|
case "span":
|
|
|
|
// Do nothing
|
|
|
|
// Do nothing
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "strong":
|
|
|
|
case "strong":
|
|
|
|
holders.add(new SpanHolder(new StyleSpan(Typeface.BOLD), start, ssb.length()));
|
|
|
|
ssb.setSpan(new StyleSpan(Typeface.BOLD), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "u":
|
|
|
|
case "u":
|
|
|
|
holders.add(new SpanHolder(new UnderlineSpan(), start, ssb.length()));
|
|
|
|
ssb.setSpan(new UnderlineSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
Log.e("Unknown tag=" + element.tagName());
|
|
|
|
Log.e("Unknown tag=" + element.tagName());
|
|
|
@ -1855,24 +1889,32 @@ public class HtmlHelper {
|
|
|
|
switch (key) {
|
|
|
|
switch (key) {
|
|
|
|
case "color":
|
|
|
|
case "color":
|
|
|
|
int color = Integer.parseInt(value.substring(1), 16) | 0xFF000000;
|
|
|
|
int color = Integer.parseInt(value.substring(1), 16) | 0xFF000000;
|
|
|
|
holders.add(new SpanHolder(new ForegroundColorSpan(color), start, ssb.length()));
|
|
|
|
ssb.setSpan(new ForegroundColorSpan(color), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "text-decoration":
|
|
|
|
case "text-decoration":
|
|
|
|
if ("line-through".equals(value))
|
|
|
|
if ("line-through".equals(value))
|
|
|
|
holders.add(new SpanHolder(new StrikethroughSpan(), start, ssb.length()));
|
|
|
|
ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, document.body());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Collections.reverse(holders);
|
|
|
|
List<TextNode> getTextNodes(Element element) {
|
|
|
|
for (SpanHolder holder : holders)
|
|
|
|
List<TextNode> result = new ArrayList<>();
|
|
|
|
ssb.setSpan(holder.span, holder.start, holder.end, holder.flags);
|
|
|
|
|
|
|
|
|
|
|
|
for (Node child : element.childNodes())
|
|
|
|
|
|
|
|
if (child instanceof TextNode)
|
|
|
|
|
|
|
|
result.add((TextNode) child);
|
|
|
|
|
|
|
|
else if (child instanceof Element)
|
|
|
|
|
|
|
|
result.addAll(getTextNodes((Element) child));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, document.body().children());
|
|
|
|
|
|
|
|
|
|
|
|
return ssb;
|
|
|
|
return reverseSpans(ssb);
|
|
|
|
} else
|
|
|
|
} else
|
|
|
|
return fromHtml(document.html(), imageGetter, null);
|
|
|
|
return fromHtml(document.html(), imageGetter, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1930,18 +1972,4 @@ public class HtmlHelper {
|
|
|
|
spanned.getSpanFlags(spans[i]));
|
|
|
|
spanned.getSpanFlags(spans[i]));
|
|
|
|
return reverse;
|
|
|
|
return reverse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static class SpanHolder {
|
|
|
|
|
|
|
|
Object span;
|
|
|
|
|
|
|
|
int start;
|
|
|
|
|
|
|
|
int end;
|
|
|
|
|
|
|
|
int flags;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SpanHolder(Object span, int start, int end) {
|
|
|
|
|
|
|
|
this.span = span;
|
|
|
|
|
|
|
|
this.start = start;
|
|
|
|
|
|
|
|
this.end = end;
|
|
|
|
|
|
|
|
this.flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|