From 602621f36ea7631126a63a972cc7aac594267b92 Mon Sep 17 00:00:00 2001 From: M66B Date: Mon, 27 Apr 2020 17:50:58 +0200 Subject: [PATCH] Removed custom rendering as experiment --- FAQ.md | 2 +- .../eu/faircode/email/AdapterMessage.java | 13 - .../java/eu/faircode/email/ApplicationEx.java | 2 + .../java/eu/faircode/email/HtmlHelper.java | 530 +++++++++--------- 4 files changed, 265 insertions(+), 282 deletions(-) diff --git a/FAQ.md b/FAQ.md index 869ae54d95..2497dc521c 100644 --- a/FAQ.md +++ b/FAQ.md @@ -2540,7 +2540,7 @@ Reformatting and displaying such messages will take too long. You can try to use **(125) What are the current experimental features?** -* Custom rendering of reformatted messages +* ~~Custom rendering of reformatted messages~~
diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index eec798f016..3dbb30d888 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -245,7 +245,6 @@ public class AdapterMessage extends RecyclerView.Adapter", 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 callback = new DiffUtil.ItemCallback() { @Override diff --git a/app/src/main/java/eu/faircode/email/ApplicationEx.java b/app/src/main/java/eu/faircode/email/ApplicationEx.java index 5e5f848df8..bf3ab46669 100644 --- a/app/src/main/java/eu/faircode/email/ApplicationEx.java +++ b/app/src/main/java/eu/faircode/email/ApplicationEx.java @@ -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) diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index 14b2b58bda..f83fae95ab 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -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 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 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 BLOCK_START = Collections.unmodifiableList(Arrays.asList( - "body", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ol", "ul", "pre" - )); - private List BLOCK_END = Collections.unmodifiableList(Arrays.asList( - "body", "blockquote", "br", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ol", "ul", "pre" - )); + private List BLOCK_START = Collections.unmodifiableList(Arrays.asList( + "body", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ol", "ul", "pre" + )); + private List 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 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 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) {