Removed custom rendering as experiment

This commit is contained in:
M66B 2020-04-27 17:50:58 +02:00
parent 86e589a904
commit 602621f36e
4 changed files with 265 additions and 282 deletions

2
FAQ.md
View File

@ -2540,7 +2540,7 @@ Reformatting and displaying such messages will take too long. You can try to use
<a name="faq125"></a>
**(125) What are the current experimental features?**
* Custom rendering of reformatted messages
* ~~Custom rendering of reformatted messages~~
<br />

View File

@ -245,7 +245,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private boolean authentication;
private boolean language_detection;
private static boolean debug;
private boolean experiments;
private boolean gotoTop = false;
private boolean firstClick = false;
@ -1913,17 +1912,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
quote.html("&#8230;");
}
// Add debug info
if (debug && !experiments) {
document.outputSettings().prettyPrint(true).outline(true).indentAmount(1);
String[] lines = document.html().split("\\r?\\n");
for (int i = 0; i < lines.length; i++)
lines[i] = Html.escapeHtml(lines[i]);
Element pre = document.createElement("pre");
pre.html(TextUtils.join("<br>", lines));
document.body().appendChild(pre);
}
// Draw images
Spanned spanned = HtmlHelper.fromDocument(context, document, new Html.ImageGetter() {
@Override
@ -4647,7 +4635,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
this.language_detection = prefs.getBoolean("language_detection", false);
debug = prefs.getBoolean("debug", false);
this.experiments = prefs.getBoolean("experiments", false);
DiffUtil.ItemCallback<TupleMessageEx> callback = new DiffUtil.ItemCallback<TupleMessageEx>() {
@Override

View File

@ -272,6 +272,8 @@ public class ApplicationEx extends Application {
} else if (version < 1121) {
if (!Helper.isPlayStoreInstall())
editor.putBoolean("experiments", true);
} else if (version < 1124) {
editor.remove("experiments");
}
if (version < BuildConfig.VERSION_CODE)

View File

@ -314,8 +314,6 @@ public class HtmlHelper {
boolean display_hidden = prefs.getBoolean("display_hidden", false);
boolean disable_tracking = prefs.getBoolean("disable_tracking", true);
boolean parse_classes = prefs.getBoolean("parse_classes", false);
boolean experiments = prefs.getBoolean("experiments", false);
// https://chromium.googlesource.com/chromium/blink/+/master/Source/core/css/html.css
@ -754,13 +752,13 @@ public class HtmlHelper {
// Subscript/Superscript
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup
if (!experiments || !view)
if (!view)
for (Element subp : document.select("sub,sup"))
subp.tagName("small");
// Lists
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li
if (!experiments || !view) {
if (!view) {
for (Element li : document.select("li")) {
li.tagName("span");
Element parent = li.parent();
@ -1779,295 +1777,291 @@ public class HtmlHelper {
static Spanned fromDocument(Context context, @NonNull Document document, @Nullable Html.ImageGetter imageGetter, @Nullable Html.TagHandler tagHandler) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean experiments = prefs.getBoolean("experiments", false);
boolean debug = prefs.getBoolean("debug", false);
int colorAccent = Helper.resolveColor(context, R.attr.colorAccent);
int dp3 = Helper.dp2pixels(context, 3);
int dp6 = Helper.dp2pixels(context, 6);
int dp24 = Helper.dp2pixels(context, 24);
if (experiments) {
// https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
NodeTraversor.traverse(new NodeVisitor() {
private int pre = 0;
private Element element;
private List<TextNode> block = new ArrayList<>();
// https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
NodeTraversor.traverse(new NodeVisitor() {
private int pre = 0;
private Element element;
private List<TextNode> block = new ArrayList<>();
private String WHITESPACE = " \t\f\u00A0";
private String WHITESPACE_NL = WHITESPACE + "\r\n";
private Pattern TRIM_WHITESPACE_NL =
Pattern.compile("[" + WHITESPACE + "]*\\r?\\n[" + WHITESPACE + "]*");
private String WHITESPACE = " \t\f\u00A0";
private String WHITESPACE_NL = WHITESPACE + "\r\n";
private Pattern TRIM_WHITESPACE_NL =
Pattern.compile("[" + WHITESPACE + "]*\\r?\\n[" + WHITESPACE + "]*");
private List<String> BLOCK_START = Collections.unmodifiableList(Arrays.asList(
"body", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ol", "ul", "pre"
));
private List<String> BLOCK_END = Collections.unmodifiableList(Arrays.asList(
"body", "blockquote", "br", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ol", "ul", "pre"
));
private List<String> BLOCK_START = Collections.unmodifiableList(Arrays.asList(
"body", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ol", "ul", "pre"
));
private List<String> BLOCK_END = Collections.unmodifiableList(Arrays.asList(
"body", "blockquote", "br", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ol", "ul", "pre"
));
@Override
public void head(Node node, int depth) {
if (node instanceof TextNode) {
if (pre == 0)
block.add((TextNode) node);
} else if (node instanceof Element) {
element = (Element) node;
if (BLOCK_START.contains(element.tagName())) {
normalizeText(block);
block.clear();
}
if ("pre".equals(element.tagName()))
pre++;
@Override
public void head(Node node, int depth) {
if (node instanceof TextNode) {
if (pre == 0)
block.add((TextNode) node);
} else if (node instanceof Element) {
element = (Element) node;
if (BLOCK_START.contains(element.tagName())) {
normalizeText(block);
block.clear();
}
if ("pre".equals(element.tagName()))
pre++;
}
}
@Override
public void tail(Node node, int depth) {
if (node instanceof Element) {
element = (Element) node;
if (BLOCK_END.contains(element.tagName())) {
normalizeText(block);
block.clear();
}
if ("pre".equals(element.tagName()))
pre--;
}
}
private void normalizeText(List<TextNode> block) {
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
TextNode tnode;
String text;
for (int i = 0; i < block.size(); ) {
tnode = block.get(i);
text = tnode.getWholeText();
// Remove whitespace before/after newlines
TRIM_WHITESPACE_NL.matcher(text).replaceAll(" ");
if (i == 0 || endsWithWhitespace(block.get(i - 1).text()))
while (startsWithWhiteSpace(text))
text = text.substring(1);
if (i == block.size() - 1)
while (endsWithWhitespace(text))
text = text.substring(0, text.length() - 1);
tnode.text(text);
if (TextUtils.isEmpty(text))
block.remove(i);
else
i++;
}
if (debug) {
if (block.size() > 0) {
block.get(0).text("(" + block.get(0));
block.get(block.size() - 1).text(block.get(block.size() - 1) + ")");
}
}
}
@Override
public void tail(Node node, int depth) {
if (node instanceof Element) {
element = (Element) node;
if (BLOCK_END.contains(element.tagName())) {
normalizeText(block);
block.clear();
}
if ("pre".equals(element.tagName()))
pre--;
}
boolean startsWithWhiteSpace(String text) {
int len = text.length();
if (len == 0)
return false;
return WHITESPACE_NL.contains(text.substring(0, 1));
}
boolean endsWithWhitespace(String text) {
int len = text.length();
if (len == 0)
return false;
return WHITESPACE_NL.contains(text.substring(len - 1));
}
}, document.body());
// https://developer.android.com/guide/topics/text/spans
SpannableStringBuilder ssb = new SpannableStringBuilder();
NodeTraversor.traverse(new NodeVisitor() {
private Element element;
private TextNode tnode;
@Override
public void head(Node node, int depth) {
if (node instanceof Element) {
element = (Element) node;
element.attr("start-index", Integer.toString(ssb.length()));
if (debug)
ssb.append("[" + element.tagName() + "]");
} else if (node instanceof TextNode) {
tnode = (TextNode) node;
ssb.append(tnode.text());
}
}
private void normalizeText(List<TextNode> block) {
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
TextNode tnode;
String text;
for (int i = 0; i < block.size(); ) {
tnode = block.get(i);
text = tnode.getWholeText();
// Remove whitespace before/after newlines
TRIM_WHITESPACE_NL.matcher(text).replaceAll(" ");
if (i == 0 || endsWithWhitespace(block.get(i - 1).text()))
while (startsWithWhiteSpace(text))
text = text.substring(1);
if (i == block.size() - 1)
while (endsWithWhitespace(text))
text = text.substring(0, text.length() - 1);
tnode.text(text);
if (TextUtils.isEmpty(text))
block.remove(i);
else
i++;
}
if (debug) {
if (block.size() > 0) {
block.get(0).text("(" + block.get(0));
block.get(block.size() - 1).text(block.get(block.size() - 1) + ")");
}
}
}
boolean startsWithWhiteSpace(String text) {
int len = text.length();
if (len == 0)
return false;
return WHITESPACE_NL.contains(text.substring(0, 1));
}
boolean endsWithWhitespace(String text) {
int len = text.length();
if (len == 0)
return false;
return WHITESPACE_NL.contains(text.substring(len - 1));
}
}, document.body());
// https://developer.android.com/guide/topics/text/spans
SpannableStringBuilder ssb = new SpannableStringBuilder();
NodeTraversor.traverse(new NodeVisitor() {
private Element element;
private TextNode tnode;
@Override
public void head(Node node, int depth) {
if (node instanceof Element) {
element = (Element) node;
element.attr("start-index", Integer.toString(ssb.length()));
if (debug)
ssb.append("[" + element.tagName() + "]");
} else if (node instanceof TextNode) {
tnode = (TextNode) node;
ssb.append(tnode.text());
}
}
@Override
public void tail(Node node, int depth) {
if (node instanceof Element) {
element = (Element) node;
int start = Integer.parseInt(element.attr("start-index"));
if (debug)
ssb.append("[/" + element.tagName() + "]");
switch (element.tagName()) {
case "a":
String href = element.attr("href");
ssb.setSpan(new URLSpan(href), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "body":
// Do nothing
break;
case "big":
ssb.setSpan(new RelativeSizeSpan(FONT_LARGE), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "blockquote":
ssb.setSpan(new QuoteSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "br":
newline(ssb.length());
break;
case "i":
case "em":
ssb.setSpan(new StyleSpan(Typeface.ITALIC), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "font":
// Do nothing
break;
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
int level = element.tagName().charAt(1) - '1';
ssb.setSpan(new RelativeSizeSpan(HEADING_SIZES[level]), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new StyleSpan(Typeface.BOLD), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
newline(start);
newline(ssb.length());
break;
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 "li":
newline(ssb.length());
Element parent = element.parent();
if (parent == null || "ul".equals(parent.tagName()))
// TODO BulletSpanCompat
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
ssb.setSpan(new BulletSpan(dp6, colorAccent), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
else
ssb.setSpan(new BulletSpan(dp6, colorAccent, dp3), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
else {
int index = 0;
for (Node child : parent.childNodes()) {
if (child instanceof Element) {
index++;
if (child == element)
break;
}
@Override
public void tail(Node node, int depth) {
if (node instanceof Element) {
element = (Element) node;
int start = Integer.parseInt(element.attr("start-index"));
if (debug)
ssb.append("[/" + element.tagName() + "]");
switch (element.tagName()) {
case "a":
String href = element.attr("href");
ssb.setSpan(new URLSpan(href), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "body":
// Do nothing
break;
case "big":
ssb.setSpan(new RelativeSizeSpan(FONT_LARGE), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "blockquote":
ssb.setSpan(new QuoteSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "br":
newline(ssb.length());
break;
case "i":
case "em":
ssb.setSpan(new StyleSpan(Typeface.ITALIC), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "font":
// Do nothing
break;
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
int level = element.tagName().charAt(1) - '1';
ssb.setSpan(new RelativeSizeSpan(HEADING_SIZES[level]), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new StyleSpan(Typeface.BOLD), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
newline(start);
newline(ssb.length());
break;
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 "li":
newline(ssb.length());
Element parent = element.parent();
if (parent == null || "ul".equals(parent.tagName()))
// TODO BulletSpanCompat
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
ssb.setSpan(new BulletSpan(dp6, colorAccent), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
else
ssb.setSpan(new BulletSpan(dp6, colorAccent, dp3), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
else {
int index = 0;
for (Node child : parent.childNodes()) {
if (child instanceof Element) {
index++;
if (child == element)
break;
}
ssb.setSpan(new NumberSpan(dp6, colorAccent, index), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
break;
case "ol":
case "ul":
int llevel = 0;
Element lparent = element.parent();
while (lparent != null) {
if (lparent.tagName().equals(element.tagName()))
llevel++;
lparent = lparent.parent();
}
if (llevel > 0)
ssb.setSpan(new LeadingMarginSpan.Standard(llevel * dp24), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
newline(start);
newline(ssb.length());
break;
case "pre":
// Do nothing
break;
case "small":
ssb.setSpan(new RelativeSizeSpan(FONT_SMALL), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "span":
// Do nothing
break;
case "sub":
ssb.setSpan(new SubscriptSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new RelativeSizeSpan(FONT_SMALL), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "sup":
ssb.setSpan(new SuperscriptSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new RelativeSizeSpan(FONT_SMALL), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "b":
case "strong":
ssb.setSpan(new StyleSpan(Typeface.BOLD), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "s":
case "del":
ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "tt":
ssb.setSpan(new TypefaceSpan("monospace"), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "u":
ssb.setSpan(new UnderlineSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
default:
Log.e("Unknown tag=" + element.tagName());
}
ssb.setSpan(new NumberSpan(dp6, colorAccent, index), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
break;
case "ol":
case "ul":
int llevel = 0;
Element lparent = element.parent();
while (lparent != null) {
if (lparent.tagName().equals(element.tagName()))
llevel++;
lparent = lparent.parent();
}
if (llevel > 0)
ssb.setSpan(new LeadingMarginSpan.Standard(llevel * dp24), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
newline(start);
newline(ssb.length());
break;
case "pre":
// Do nothing
break;
case "small":
ssb.setSpan(new RelativeSizeSpan(FONT_SMALL), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "span":
// Do nothing
break;
case "sub":
ssb.setSpan(new SubscriptSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new RelativeSizeSpan(FONT_SMALL), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "sup":
ssb.setSpan(new SuperscriptSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new RelativeSizeSpan(FONT_SMALL), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "b":
case "strong":
ssb.setSpan(new StyleSpan(Typeface.BOLD), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "s":
case "del":
ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "tt":
ssb.setSpan(new TypefaceSpan("monospace"), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "u":
ssb.setSpan(new UnderlineSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
default:
Log.e("Unknown tag=" + element.tagName());
}
String style = element.attr("style");
if (!TextUtils.isEmpty(style)) {
String[] params = style.split(";");
for (String param : params) {
int semi = param.indexOf(":");
if (semi < 0)
continue;
String key = param.substring(0, semi);
String value = param.substring(semi + 1);
switch (key) {
case "color":
int color = Integer.parseInt(value.substring(1), 16) | 0xFF000000;
ssb.setSpan(new ForegroundColorSpan(color), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "text-decoration":
if ("line-through".equals(value))
ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
}
String style = element.attr("style");
if (!TextUtils.isEmpty(style)) {
String[] params = style.split(";");
for (String param : params) {
int semi = param.indexOf(":");
if (semi < 0)
continue;
String key = param.substring(0, semi);
String value = param.substring(semi + 1);
switch (key) {
case "color":
int color = Integer.parseInt(value.substring(1), 16) | 0xFF000000;
ssb.setSpan(new ForegroundColorSpan(color), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "text-decoration":
if ("line-through".equals(value))
ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
}
}
}
}
}
private void newline(int index) {
int len = ssb.length();
if (len > 2 &&
ssb.charAt(len - 1) == '\n' &&
ssb.charAt(len - 2) == '\n')
return;
ssb.insert(index, "\n");
}
private void newline(int index) {
int len = ssb.length();
if (len > 2 &&
ssb.charAt(len - 1) == '\n' &&
ssb.charAt(len - 2) == '\n')
return;
ssb.insert(index, "\n");
}
}, document.body());
}, document.body());
if (debug)
for (int i = ssb.length() - 1; i >= 0; i--)
if (ssb.charAt(i) == '\n')
ssb.insert(i, "|");
if (debug)
for (int i = ssb.length() - 1; i >= 0; i--)
if (ssb.charAt(i) == '\n')
ssb.insert(i, "|");
return reverseSpans(ssb);
} else
return fromHtml(document.html(), imageGetter, null);
return reverseSpans(ssb);
}
static Spanned fromHtml(@NonNull String html) {