diff --git a/patches/Html.patch b/patches/Html.patch index aa21d7bc91..61ee5d359e 100644 --- a/patches/Html.patch +++ b/patches/Html.patch @@ -1,272 +1,1203 @@ -diff --git a/app/src/main/java/eu/faircode/email/HtmlEx.java b/app/src/main/java/eu/faircode/email/HtmlEx.java -index af06a672b..bbfcdc5fc 100644 ---- a/app/src/main/java/eu/faircode/email/HtmlEx.java -+++ b/app/src/main/java/eu/faircode/email/HtmlEx.java -@@ -55,7 +55,7 @@ public class HtmlEx { +--- /home/marcel/Android/Sdk/sources/android-30/android/text/Html.java 2020-09-10 08:18:47.009487012 +0200 ++++ ./app/src/main/java/eu/faircode/email/HtmlEx.java 2020-10-03 11:19:59.338719036 +0200 +@@ -1,3 +1,5 @@ ++package eu.faircode.email; ++ + /* + * Copyright (C) 2007 The Android Open Source Project + * +@@ -14,15 +16,12 @@ + * limitations under the License. + */ + +-package android.text; +- +-import android.app.ActivityThread; +-import android.app.Application; +-import android.compat.annotation.UnsupportedAppUsage; +-import android.content.res.Resources; +-import android.graphics.Color; ++import android.content.Context; + import android.graphics.Typeface; +-import android.graphics.drawable.Drawable; ++import android.text.Layout; ++import android.text.Spanned; ++import android.text.TextDirectionHeuristics; ++import android.text.TextUtils; + import android.text.style.AbsoluteSizeSpan; + import android.text.style.AlignmentSpan; + import android.text.style.BackgroundColorSpan; +@@ -41,213 +40,22 @@ import android.text.style.TypefaceSpan; + import android.text.style.URLSpan; + import android.text.style.UnderlineSpan; + +-import org.ccil.cowan.tagsoup.HTMLSchema; +-import org.ccil.cowan.tagsoup.Parser; +-import org.xml.sax.Attributes; +-import org.xml.sax.ContentHandler; +-import org.xml.sax.InputSource; +-import org.xml.sax.Locator; +-import org.xml.sax.SAXException; +-import org.xml.sax.XMLReader; +- +-import java.io.IOException; +-import java.io.StringReader; +-import java.util.HashMap; +-import java.util.Locale; +-import java.util.Map; +-import java.util.regex.Matcher; +-import java.util.regex.Pattern; +- +-/** +- * This class processes HTML strings into displayable styled text. +- * Not all HTML tags are supported. +- */ +-public class Html { +- /** +- * Retrieves images for HTML <img> tags. +- */ +- public static interface ImageGetter { +- /** +- * This method is called when the HTML parser encounters an +- * <img> tag. The source argument is the +- * string from the "src" attribute; the return value should be +- * a Drawable representation of the image or null +- * for a generic replacement image. Make sure you call +- * setBounds() on your Drawable if it doesn't already have +- * its bounds set. +- */ +- public Drawable getDrawable(String source); +- } +- +- /** +- * Is notified when HTML tags are encountered that the parser does +- * not know how to interpret. +- */ +- public static interface TagHandler { +- /** +- * This method will be called whenn the HTML parser encounters +- * a tag that it does not know how to interpret. +- */ +- public void handleTag(boolean opening, String tag, +- Editable output, XMLReader xmlReader); +- } +- +- /** +- * Option for {@link #toHtml(Spanned, int)}: Wrap consecutive lines of text delimited by '\n' +- * inside <p> elements. {@link BulletSpan}s are ignored. +- */ +- public static final int TO_HTML_PARAGRAPH_LINES_CONSECUTIVE = 0x00000000; +- +- /** +- * Option for {@link #toHtml(Spanned, int)}: Wrap each line of text delimited by '\n' inside a +- * <p> or a <li> element. This allows {@link ParagraphStyle}s attached to be +- * encoded as CSS styles within the corresponding <p> or <li> element. +- */ +- public static final int TO_HTML_PARAGRAPH_LINES_INDIVIDUAL = 0x00000001; +- +- /** +- * Flag indicating that texts inside <p> elements will be separated from other texts with +- * one newline character by default. +- */ +- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH = 0x00000001; +- +- /** +- * Flag indicating that texts inside <h1>~<h6> elements will be separated from +- * other texts with one newline character by default. +- */ +- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_HEADING = 0x00000002; +- +- /** +- * Flag indicating that texts inside <li> elements will be separated from other texts +- * with one newline character by default. +- */ +- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM = 0x00000004; +- +- /** +- * Flag indicating that texts inside <ul> elements will be separated from other texts +- * with one newline character by default. +- */ +- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST = 0x00000008; +- +- /** +- * Flag indicating that texts inside <div> elements will be separated from other texts +- * with one newline character by default. +- */ +- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_DIV = 0x00000010; +- +- /** +- * Flag indicating that texts inside <blockquote> elements will be separated from other +- * texts with one newline character by default. +- */ +- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE = 0x00000020; +- +- /** +- * Flag indicating that CSS color values should be used instead of those defined in +- * {@link Color}. +- */ +- public static final int FROM_HTML_OPTION_USE_CSS_COLORS = 0x00000100; +- +- /** +- * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level +- * elements with blank lines (two newline characters) in between. This is the legacy behavior +- * prior to N. +- */ +- public static final int FROM_HTML_MODE_LEGACY = 0x00000000; ++import static android.text.Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE; + +- /** +- * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level +- * elements with line breaks (single newline character) in between. This inverts the +- * {@link Spanned} to HTML string conversion done with the option +- * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}. +- */ +- public static final int FROM_HTML_MODE_COMPACT = +- FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH +- | FROM_HTML_SEPARATOR_LINE_BREAK_HEADING +- | FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM +- | FROM_HTML_SEPARATOR_LINE_BREAK_LIST +- | FROM_HTML_SEPARATOR_LINE_BREAK_DIV +- | FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE; ++public class HtmlEx { ++ private Context context; + +- /** +- * The bit which indicates if lines delimited by '\n' will be grouped into <p> elements. +- */ + private static final int TO_HTML_PARAGRAPH_FLAG = 0x00000001; + +- private Html() { } +- +- /** +- * Returns displayable styled text from the provided HTML string with the legacy flags +- * {@link #FROM_HTML_MODE_LEGACY}. +- * +- * @deprecated use {@link #fromHtml(String, int)} instead. +- */ +- @Deprecated +- public static Spanned fromHtml(String source) { +- return fromHtml(source, FROM_HTML_MODE_LEGACY, null, null); +- } +- +- /** +- * Returns displayable styled text from the provided HTML string. Any <img> tags in the +- * HTML will display as a generic replacement image which your program can then go through and +- * replace with real images. +- * +- *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. +- */ +- public static Spanned fromHtml(String source, int flags) { +- return fromHtml(source, flags, null, null); +- } +- +- /** +- * Lazy initialization holder for HTML parser. This class will +- * a) be preloaded by the zygote, or b) not loaded until absolutely +- * necessary. +- */ +- private static class HtmlParser { +- private static final HTMLSchema schema = new HTMLSchema(); +- } +- +- /** +- * Returns displayable styled text from the provided HTML string with the legacy flags +- * {@link #FROM_HTML_MODE_LEGACY}. +- * +- * @deprecated use {@link #fromHtml(String, int, ImageGetter, TagHandler)} instead. +- */ +- @Deprecated +- public static Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler) { +- return fromHtml(source, FROM_HTML_MODE_LEGACY, imageGetter, tagHandler); +- } +- +- /** +- * Returns displayable styled text from the provided HTML string. Any <img> tags in the +- * HTML will use the specified ImageGetter to request a representation of the image (use null +- * if you don't want this) and the specified TagHandler to handle unknown tags (specify null if +- * you don't want this). +- * +- *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. +- */ +- public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter, +- TagHandler tagHandler) { +- Parser parser = new Parser(); +- try { +- parser.setProperty(Parser.schemaProperty, HtmlParser.schema); +- } catch (org.xml.sax.SAXNotRecognizedException e) { +- // Should not happen. +- throw new RuntimeException(e); +- } catch (org.xml.sax.SAXNotSupportedException e) { +- // Should not happen. +- throw new RuntimeException(e); +- } +- +- HtmlToSpannedConverter converter = +- new HtmlToSpannedConverter(source, imageGetter, tagHandler, parser, flags); +- return converter.convert(); ++ public HtmlEx(Context context){ ++ this.context = context; + } + + /** * @deprecated use {@link #toHtml(Spanned, int)} instead. */ @Deprecated -- public /* static */ String toHtml(Spanned text) { -+ public static String toHtml(Spanned text) { +- public static String toHtml(Spanned text) { ++ public /* static */ String toHtml(Spanned text) { return toHtml(text, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE); } -@@ -69,7 +69,7 @@ public class HtmlEx { +@@ -261,7 +69,7 @@ public class Html { * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL} * @return string containing input converted to HTML */ -- public /* static */ String toHtml(Spanned text, int option) { -+ public static String toHtml(Spanned text, int option) { +- public static String toHtml(Spanned text, int option) { ++ public /* static */ String toHtml(Spanned text, int option) { StringBuilder out = new StringBuilder(); withinHtml(out, text, option); return out.toString(); -@@ -78,13 +78,13 @@ public class HtmlEx { +@@ -270,13 +78,13 @@ public class Html { /** * Returns an HTML escaped representation of the given plain text. */ -- public /* static */ String escapeHtml(CharSequence text) { -+ public static String escapeHtml(CharSequence text) { +- public static String escapeHtml(CharSequence text) { ++ public /* static */ String escapeHtml(CharSequence text) { StringBuilder out = new StringBuilder(); withinStyle(out, text, 0, text.length()); return out.toString(); } -- private /* static */ void withinHtml(StringBuilder out, Spanned text, int option) { -+ private static void withinHtml(StringBuilder out, Spanned text, int option) { +- private static void withinHtml(StringBuilder out, Spanned text, int option) { ++ private /* static */ void withinHtml(StringBuilder out, Spanned text, int option) { if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) { encodeTextAlignmentByDiv(out, text, option); return; -@@ -93,7 +93,7 @@ public class HtmlEx { +@@ -285,7 +93,7 @@ public class Html { withinDiv(out, text, 0, text.length(), option); } -- private /* static */ void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) { -+ private static void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) { +- private static void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) { ++ private /* static */ void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) { int len = text.length(); int next; -@@ -129,7 +129,7 @@ public class HtmlEx { +@@ -298,7 +106,7 @@ public class Html { + for(int j = 0; j < style.length; j++) { + if (style[j] instanceof AlignmentSpan) { + Layout.Alignment align = +- ((AlignmentSpan) style[j]).getAlignment(); ++ ((AlignmentSpan) style[j]).getAlignment(); + needDiv = true; + if (align == Layout.Alignment.ALIGN_CENTER) { + elements = "align=\"center\" " + elements; +@@ -321,8 +129,8 @@ public class Html { } } -- private /* static */ void withinDiv(StringBuilder out, Spanned text, int start, int end, -+ private static void withinDiv(StringBuilder out, Spanned text, int start, int end, - int option) { +- private static void withinDiv(StringBuilder out, Spanned text, int start, int end, +- int option) { ++ private /* static */ void withinDiv(StringBuilder out, Spanned text, int start, int end, ++ int option) { int next; for (int i = start; i < end; i = next) { -@@ -148,7 +148,7 @@ public class HtmlEx { + next = text.nextSpanTransition(i, end, QuoteSpan.class); +@@ -340,7 +148,7 @@ public class Html { } } -- private /* static */ String getTextDirection(Spanned text, int start, int end) { -+ private static String getTextDirection(Spanned text, int start, int end) { +- private static String getTextDirection(Spanned text, int start, int end) { ++ private /* static */ String getTextDirection(Spanned text, int start, int end) { if (TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(text, start, end - start)) { return " dir=\"rtl\""; } else { -@@ -156,7 +156,7 @@ public class HtmlEx { +@@ -348,8 +156,8 @@ public class Html { } } -- private /* static */ String getTextStyles(Spanned text, int start, int end, -+ private static String getTextStyles(Spanned text, int start, int end, - boolean forceNoVerticalMargin, boolean includeTextAlign) { +- private static String getTextStyles(Spanned text, int start, int end, +- boolean forceNoVerticalMargin, boolean includeTextAlign) { ++ private /* static */ String getTextStyles(Spanned text, int start, int end, ++ boolean forceNoVerticalMargin, boolean includeTextAlign) { String margin = null; String textAlign = null; -@@ -200,7 +200,7 @@ public class HtmlEx { + +@@ -362,7 +170,7 @@ public class Html { + // Only use the last AlignmentSpan with flag SPAN_PARAGRAPH + for (int i = alignmentSpans.length - 1; i >= 0; i--) { + AlignmentSpan s = alignmentSpans[i]; +- if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH) { ++ if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH || true) { + final Layout.Alignment alignment = s.getAlignment(); + if (alignment == Layout.Alignment.ALIGN_NORMAL) { + textAlign = "text-align:start;"; +@@ -392,8 +200,8 @@ public class Html { return style.append("\"").toString(); } -- private /* static */ void withinBlockquote(StringBuilder out, Spanned text, int start, int end, -+ private static void withinBlockquote(StringBuilder out, Spanned text, int start, int end, - int option) { +- private static void withinBlockquote(StringBuilder out, Spanned text, int start, int end, +- int option) { ++ private /* static */ void withinBlockquote(StringBuilder out, Spanned text, int start, int end, ++ int option) { if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) { withinBlockquoteConsecutive(out, text, start, end); -@@ -209,9 +209,9 @@ public class HtmlEx { + } else { +@@ -401,9 +209,9 @@ public class Html { } } -- private /* static */ void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start, -+ private static void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start, - int end) { -- Boolean isInBulletList = null; -+ boolean isInList = false; +- private static void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start, +- int end) { +- boolean isInList = false; ++ private /* static */ void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start, ++ int end) { ++ Boolean isInBulletList = null; int next; for (int i = start; i <= end; i = next) { next = TextUtils.indexOf(text, '\n', i, end); -@@ -220,48 +220,42 @@ public class HtmlEx { +@@ -412,42 +220,48 @@ public class Html { } if (next == i) { -- if (isInBulletList != null) { -+ if (isInList) { +- if (isInList) { ++ if (isInBulletList != null) { // Current paragraph is no longer a list item; close the previously opened list -- out.append(isInBulletList ? "\n" : "\n"); -- isInBulletList = null; -+ isInList = false; -+ out.append("\n"); +- isInList = false; +- out.append("\n"); ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; } -- if (i != text.length()) -- out.append("
\n"); -+ out.append("
\n"); +- out.append("
\n"); ++ if (i != text.length()) ++ out.append("
\n"); } else { -- Boolean isBulletListItem = null; -+ boolean isListItem = false; +- boolean isListItem = false; ++ Boolean isBulletListItem = null; ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class); for (ParagraphStyle paragraphStyle : paragraphStyles) { final int spanFlags = text.getSpanFlags(paragraphStyle); if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH && paragraphStyle instanceof BulletSpan) { -- isBulletListItem = !(paragraphStyle instanceof eu.faircode.email.NumberSpan); -+ isListItem = true; +- isListItem = true; ++ isBulletListItem = !(paragraphStyle instanceof eu.faircode.email.NumberSpan); break; } } -- if (isBulletListItem != null && isInBulletList != null && isBulletListItem != isInBulletList) { -- out.append(isInBulletList ? "\n" : "\n"); -- isInBulletList = null; -- } -- -- if (isBulletListItem != null && isInBulletList == null) { -+ if (isListItem && !isInList) { +- if (isListItem && !isInList) { ++ if (isBulletListItem != null && isInBulletList != null && isBulletListItem != isInBulletList) { ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; ++ } ++ ++ if (isBulletListItem != null && isInBulletList == null) { // Current paragraph is the first item in a list -- isInBulletList = isBulletListItem; -- out.append(isInBulletList ? "\n"); } -- if (isInBulletList != null && isBulletListItem == null) { -+ if (isInList && !isListItem) { +- if (isInList && !isListItem) { ++ if (isInBulletList != null && isBulletListItem == null) { // Current paragraph is no longer a list item; close the previously opened list -- out.append(isInBulletList ? "\n" : "\n"); -- isInBulletList = null; -+ isInList = false; -+ out.append("\n"); +- isInList = false; +- out.append("\n"); ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; } -- String tagType = isBulletListItem != null ? "li" : "span"; -+ String tagType = isListItem ? "li" : "p"; +- String tagType = isListItem ? "li" : "p"; ++ String tagType = isBulletListItem != null ? "li" : "span"; out.append("<").append(tagType) .append(getTextDirection(text, i, next)) -- .append(getTextStyles(text, i, next, isBulletListItem == null, true)) -+ .append(getTextStyles(text, i, next, !isListItem, true)) +- .append(getTextStyles(text, i, next, !isListItem, true)) ++ .append(getTextStyles(text, i, next, isBulletListItem == null, true)) .append(">"); withinParagraph(out, text, i, next); -@@ -269,12 +263,10 @@ public class HtmlEx { +@@ -455,10 +269,12 @@ public class Html { out.append("\n"); -- if (isBulletListItem == null) -- out.append("
\n"); ++ if (isBulletListItem == null) ++ out.append("
\n"); -- if (next == end && isInBulletList != null) { -- out.append(isInBulletList ? "\n" : "\n"); -- isInBulletList = null; -+ if (next == end && isInList) { -+ isInList = false; -+ out.append("\n"); +- if (next == end && isInList) { +- isInList = false; +- out.append("\n"); ++ if (next == end && isInBulletList != null) { ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; } } -@@ -282,9 +274,9 @@ public class HtmlEx { +@@ -466,9 +282,9 @@ public class Html { } } -- private /* static */ void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start, -+ private static void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start, - int end) { -- out.append(""); -+ out.append(""); +- private static void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start, +- int end) { +- out.append(""); ++ private /* static */ void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start, ++ int end) { ++ out.append(""); int next; for (int i = start; i < end; i = next) { -@@ -302,24 +294,24 @@ public class HtmlEx { +@@ -486,24 +302,24 @@ public class Html { withinParagraph(out, text, i, next - nl); -- if (nl == 0) { -+ if (nl == 1) { +- if (nl == 1) { ++ if (nl == 0) { out.append("
\n"); } else { -- for (int j = 0; j < nl; j++) { -+ for (int j = 2; j < nl; j++) { +- for (int j = 2; j < nl; j++) { ++ for (int j = 0; j < nl; j++) { out.append("
"); } if (next != end) { /* Paragraph should be closed and reopened */ -- out.append("\n"); -- out.append(""); -+ out.append("

\n"); -+ out.append(""); +- out.append("

\n"); +- out.append(""); ++ out.append("\n"); ++ out.append(""); } } } -- out.append("\n"); -+ out.append("

\n"); +- out.append("

\n"); ++ out.append("\n"); } -- private /* static */ void withinParagraph(StringBuilder out, Spanned text, int start, int end) { -+ private static void withinParagraph(StringBuilder out, Spanned text, int start, int end) { +- private static void withinParagraph(StringBuilder out, Spanned text, int start, int end) { ++ private /* static */ void withinParagraph(StringBuilder out, Spanned text, int start, int end) { int next; for (int i = start; i < end; i = next) { next = text.nextSpanTransition(i, end, CharacterStyle.class); -@@ -339,11 +331,9 @@ public class HtmlEx { +@@ -523,9 +339,11 @@ public class Html { if (style[j] instanceof TypefaceSpan) { String s = ((TypefaceSpan) style[j]).getFamily(); -- //if ("monospace".equals(s)) { -- // out.append(""); -- //} -- -- out.append(""); -+ if ("monospace".equals(s)) { -+ out.append(""); -+ } +- if ("monospace".equals(s)) { +- out.append(""); +- } ++ //if ("monospace".equals(s)) { ++ // out.append(""); ++ //} ++ ++ out.append(""); } if (style[j] instanceof SuperscriptSpan) { out.append(""); -@@ -374,8 +364,8 @@ public class HtmlEx { +@@ -556,8 +374,8 @@ public class Html { AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]); float sizeDip = s.getSize(); if (!s.getDip()) { -- //Application application = ActivityThread.currentApplication(); -- sizeDip /= context.getResources().getDisplayMetrics().density; -+ Application application = ActivityThread.currentApplication(); -+ sizeDip /= application.getResources().getDisplayMetrics().density; +- Application application = ActivityThread.currentApplication(); +- sizeDip /= application.getResources().getDisplayMetrics().density; ++ //Application application = ActivityThread.currentApplication(); ++ sizeDip /= context.getResources().getDisplayMetrics().density; } // px in CSS is the equivalance of dip in Android -@@ -427,13 +417,11 @@ public class HtmlEx { +@@ -609,11 +427,13 @@ public class Html { out.append(""); } if (style[j] instanceof TypefaceSpan) { -- //String s = ((TypefaceSpan) style[j]).getFamily(); -- -- //if (s.equals("monospace")) { -- // out.append(""); -- //} -+ String s = ((TypefaceSpan) style[j]).getFamily(); +- String s = ((TypefaceSpan) style[j]).getFamily(); ++ //String s = ((TypefaceSpan) style[j]).getFamily(); -- out.append(""); -+ if (s.equals("monospace")) { -+ out.append(""); -+ } +- if (s.equals("monospace")) { +- out.append(""); +- } ++ //if (s.equals("monospace")) { ++ // out.append(""); ++ //} ++ ++ out.append(""); } if (style[j] instanceof StyleSpan) { int s = ((StyleSpan) style[j]).getStyle(); -@@ -449,8 +437,8 @@ public class HtmlEx { +@@ -629,8 +449,8 @@ public class Html { } } -- //@UnsupportedAppUsage -- private /* static */ void withinStyle(StringBuilder out, CharSequence text, -+ @UnsupportedAppUsage -+ private static void withinStyle(StringBuilder out, CharSequence text, +- @UnsupportedAppUsage +- private static void withinStyle(StringBuilder out, CharSequence text, ++ //@UnsupportedAppUsage ++ private /* static */ void withinStyle(StringBuilder out, CharSequence text, int start, int end) { for (int i = start; i < end; i++) { char c = text.charAt(i); +@@ -665,668 +485,3 @@ public class Html { + } + } + } +- +-class HtmlToSpannedConverter implements ContentHandler { +- +- private static final float[] HEADING_SIZES = { +- 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f, +- }; +- +- private String mSource; +- private XMLReader mReader; +- private SpannableStringBuilder mSpannableStringBuilder; +- private Html.ImageGetter mImageGetter; +- private Html.TagHandler mTagHandler; +- private int mFlags; +- +- private static Pattern sTextAlignPattern; +- private static Pattern sForegroundColorPattern; +- private static Pattern sBackgroundColorPattern; +- private static Pattern sTextDecorationPattern; +- +- /** +- * Name-value mapping of HTML/CSS colors which have different values in {@link Color}. +- */ +- private static final Map sColorMap; +- +- static { +- sColorMap = new HashMap<>(); +- sColorMap.put("darkgray", 0xFFA9A9A9); +- sColorMap.put("gray", 0xFF808080); +- sColorMap.put("lightgray", 0xFFD3D3D3); +- sColorMap.put("darkgrey", 0xFFA9A9A9); +- sColorMap.put("grey", 0xFF808080); +- sColorMap.put("lightgrey", 0xFFD3D3D3); +- sColorMap.put("green", 0xFF008000); +- } +- +- private static Pattern getTextAlignPattern() { +- if (sTextAlignPattern == null) { +- sTextAlignPattern = Pattern.compile("(?:\\s+|\\A)text-align\\s*:\\s*(\\S*)\\b"); +- } +- return sTextAlignPattern; +- } +- +- private static Pattern getForegroundColorPattern() { +- if (sForegroundColorPattern == null) { +- sForegroundColorPattern = Pattern.compile( +- "(?:\\s+|\\A)color\\s*:\\s*(\\S*)\\b"); +- } +- return sForegroundColorPattern; +- } +- +- private static Pattern getBackgroundColorPattern() { +- if (sBackgroundColorPattern == null) { +- sBackgroundColorPattern = Pattern.compile( +- "(?:\\s+|\\A)background(?:-color)?\\s*:\\s*(\\S*)\\b"); +- } +- return sBackgroundColorPattern; +- } +- +- private static Pattern getTextDecorationPattern() { +- if (sTextDecorationPattern == null) { +- sTextDecorationPattern = Pattern.compile( +- "(?:\\s+|\\A)text-decoration\\s*:\\s*(\\S*)\\b"); +- } +- return sTextDecorationPattern; +- } +- +- public HtmlToSpannedConverter( String source, Html.ImageGetter imageGetter, +- Html.TagHandler tagHandler, Parser parser, int flags) { +- mSource = source; +- mSpannableStringBuilder = new SpannableStringBuilder(); +- mImageGetter = imageGetter; +- mTagHandler = tagHandler; +- mReader = parser; +- mFlags = flags; +- } +- +- public Spanned convert() { +- +- mReader.setContentHandler(this); +- try { +- mReader.parse(new InputSource(new StringReader(mSource))); +- } catch (IOException e) { +- // We are reading from a string. There should not be IO problems. +- throw new RuntimeException(e); +- } catch (SAXException e) { +- // TagSoup doesn't throw parse exceptions. +- throw new RuntimeException(e); +- } +- +- // Fix flags and range for paragraph-type markup. +- Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class); +- for (int i = 0; i < obj.length; i++) { +- int start = mSpannableStringBuilder.getSpanStart(obj[i]); +- int end = mSpannableStringBuilder.getSpanEnd(obj[i]); +- +- // If the last line of the range is blank, back off by one. +- if (end - 2 >= 0) { +- if (mSpannableStringBuilder.charAt(end - 1) == '\n' && +- mSpannableStringBuilder.charAt(end - 2) == '\n') { +- end--; +- } +- } +- +- if (end == start) { +- mSpannableStringBuilder.removeSpan(obj[i]); +- } else { +- mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH); +- } +- } +- +- return mSpannableStringBuilder; +- } +- +- private void handleStartTag(String tag, Attributes attributes) { +- if (tag.equalsIgnoreCase("br")) { +- // We don't need to handle this. TagSoup will ensure that there's a
for each
+- // so we can safely emit the linebreaks when we handle the close tag. +- } else if (tag.equalsIgnoreCase("p")) { +- startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph()); +- startCssStyle(mSpannableStringBuilder, attributes); +- } else if (tag.equalsIgnoreCase("ul")) { +- startBlockElement(mSpannableStringBuilder, attributes, getMarginList()); +- } else if (tag.equalsIgnoreCase("li")) { +- startLi(mSpannableStringBuilder, attributes); +- } else if (tag.equalsIgnoreCase("div")) { +- startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv()); +- } else if (tag.equalsIgnoreCase("span")) { +- startCssStyle(mSpannableStringBuilder, attributes); +- } else if (tag.equalsIgnoreCase("strong")) { +- start(mSpannableStringBuilder, new Bold()); +- } else if (tag.equalsIgnoreCase("b")) { +- start(mSpannableStringBuilder, new Bold()); +- } else if (tag.equalsIgnoreCase("em")) { +- start(mSpannableStringBuilder, new Italic()); +- } else if (tag.equalsIgnoreCase("cite")) { +- start(mSpannableStringBuilder, new Italic()); +- } else if (tag.equalsIgnoreCase("dfn")) { +- start(mSpannableStringBuilder, new Italic()); +- } else if (tag.equalsIgnoreCase("i")) { +- start(mSpannableStringBuilder, new Italic()); +- } else if (tag.equalsIgnoreCase("big")) { +- start(mSpannableStringBuilder, new Big()); +- } else if (tag.equalsIgnoreCase("small")) { +- start(mSpannableStringBuilder, new Small()); +- } else if (tag.equalsIgnoreCase("font")) { +- startFont(mSpannableStringBuilder, attributes); +- } else if (tag.equalsIgnoreCase("blockquote")) { +- startBlockquote(mSpannableStringBuilder, attributes); +- } else if (tag.equalsIgnoreCase("tt")) { +- start(mSpannableStringBuilder, new Monospace()); +- } else if (tag.equalsIgnoreCase("a")) { +- startA(mSpannableStringBuilder, attributes); +- } else if (tag.equalsIgnoreCase("u")) { +- start(mSpannableStringBuilder, new Underline()); +- } else if (tag.equalsIgnoreCase("del")) { +- start(mSpannableStringBuilder, new Strikethrough()); +- } else if (tag.equalsIgnoreCase("s")) { +- start(mSpannableStringBuilder, new Strikethrough()); +- } else if (tag.equalsIgnoreCase("strike")) { +- start(mSpannableStringBuilder, new Strikethrough()); +- } else if (tag.equalsIgnoreCase("sup")) { +- start(mSpannableStringBuilder, new Super()); +- } else if (tag.equalsIgnoreCase("sub")) { +- start(mSpannableStringBuilder, new Sub()); +- } else if (tag.length() == 2 && +- Character.toLowerCase(tag.charAt(0)) == 'h' && +- tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { +- startHeading(mSpannableStringBuilder, attributes, tag.charAt(1) - '1'); +- } else if (tag.equalsIgnoreCase("img")) { +- startImg(mSpannableStringBuilder, attributes, mImageGetter); +- } else if (mTagHandler != null) { +- mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader); +- } +- } +- +- private void handleEndTag(String tag) { +- if (tag.equalsIgnoreCase("br")) { +- handleBr(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("p")) { +- endCssStyle(mSpannableStringBuilder); +- endBlockElement(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("ul")) { +- endBlockElement(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("li")) { +- endLi(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("div")) { +- endBlockElement(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("span")) { +- endCssStyle(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("strong")) { +- end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); +- } else if (tag.equalsIgnoreCase("b")) { +- end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); +- } else if (tag.equalsIgnoreCase("em")) { +- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); +- } else if (tag.equalsIgnoreCase("cite")) { +- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); +- } else if (tag.equalsIgnoreCase("dfn")) { +- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); +- } else if (tag.equalsIgnoreCase("i")) { +- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); +- } else if (tag.equalsIgnoreCase("big")) { +- end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f)); +- } else if (tag.equalsIgnoreCase("small")) { +- end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f)); +- } else if (tag.equalsIgnoreCase("font")) { +- endFont(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("blockquote")) { +- endBlockquote(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("tt")) { +- end(mSpannableStringBuilder, Monospace.class, new TypefaceSpan("monospace")); +- } else if (tag.equalsIgnoreCase("a")) { +- endA(mSpannableStringBuilder); +- } else if (tag.equalsIgnoreCase("u")) { +- end(mSpannableStringBuilder, Underline.class, new UnderlineSpan()); +- } else if (tag.equalsIgnoreCase("del")) { +- end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan()); +- } else if (tag.equalsIgnoreCase("s")) { +- end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan()); +- } else if (tag.equalsIgnoreCase("strike")) { +- end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan()); +- } else if (tag.equalsIgnoreCase("sup")) { +- end(mSpannableStringBuilder, Super.class, new SuperscriptSpan()); +- } else if (tag.equalsIgnoreCase("sub")) { +- end(mSpannableStringBuilder, Sub.class, new SubscriptSpan()); +- } else if (tag.length() == 2 && +- Character.toLowerCase(tag.charAt(0)) == 'h' && +- tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { +- endHeading(mSpannableStringBuilder); +- } else if (mTagHandler != null) { +- mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader); +- } +- } +- +- private int getMarginParagraph() { +- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH); +- } +- +- private int getMarginHeading() { +- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); +- } +- +- private int getMarginListItem() { +- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM); +- } +- +- private int getMarginList() { +- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST); +- } +- +- private int getMarginDiv() { +- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV); +- } +- +- private int getMarginBlockquote() { +- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE); +- } +- +- /** +- * Returns the minimum number of newline characters needed before and after a given block-level +- * element. +- * +- * @param flag the corresponding option flag defined in {@link Html} of a block-level element +- */ +- private int getMargin(int flag) { +- if ((flag & mFlags) != 0) { +- return 1; +- } +- return 2; +- } +- +- private static void appendNewlines(Editable text, int minNewline) { +- final int len = text.length(); +- +- if (len == 0) { +- return; +- } +- +- int existingNewlines = 0; +- for (int i = len - 1; i >= 0 && text.charAt(i) == '\n'; i--) { +- existingNewlines++; +- } +- +- for (int j = existingNewlines; j < minNewline; j++) { +- text.append("\n"); +- } +- } +- +- private static void startBlockElement(Editable text, Attributes attributes, int margin) { +- final int len = text.length(); +- if (margin > 0) { +- appendNewlines(text, margin); +- start(text, new Newline(margin)); +- } +- +- String style = attributes.getValue("", "style"); +- if (style != null) { +- Matcher m = getTextAlignPattern().matcher(style); +- if (m.find()) { +- String alignment = m.group(1); +- if (alignment.equalsIgnoreCase("start")) { +- start(text, new Alignment(Layout.Alignment.ALIGN_NORMAL)); +- } else if (alignment.equalsIgnoreCase("center")) { +- start(text, new Alignment(Layout.Alignment.ALIGN_CENTER)); +- } else if (alignment.equalsIgnoreCase("end")) { +- start(text, new Alignment(Layout.Alignment.ALIGN_OPPOSITE)); +- } +- } +- } +- } +- +- private static void endBlockElement(Editable text) { +- Newline n = getLast(text, Newline.class); +- if (n != null) { +- appendNewlines(text, n.mNumNewlines); +- text.removeSpan(n); +- } +- +- Alignment a = getLast(text, Alignment.class); +- if (a != null) { +- setSpanFromMark(text, a, new AlignmentSpan.Standard(a.mAlignment)); +- } +- } +- +- private static void handleBr(Editable text) { +- text.append('\n'); +- } +- +- private void startLi(Editable text, Attributes attributes) { +- startBlockElement(text, attributes, getMarginListItem()); +- start(text, new Bullet()); +- startCssStyle(text, attributes); +- } +- +- private static void endLi(Editable text) { +- endCssStyle(text); +- endBlockElement(text); +- end(text, Bullet.class, new BulletSpan()); +- } +- +- private void startBlockquote(Editable text, Attributes attributes) { +- startBlockElement(text, attributes, getMarginBlockquote()); +- start(text, new Blockquote()); +- } +- +- private static void endBlockquote(Editable text) { +- endBlockElement(text); +- end(text, Blockquote.class, new QuoteSpan()); +- } +- +- private void startHeading(Editable text, Attributes attributes, int level) { +- startBlockElement(text, attributes, getMarginHeading()); +- start(text, new Heading(level)); +- } +- +- private static void endHeading(Editable text) { +- // RelativeSizeSpan and StyleSpan are CharacterStyles +- // Their ranges should not include the newlines at the end +- Heading h = getLast(text, Heading.class); +- if (h != null) { +- setSpanFromMark(text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]), +- new StyleSpan(Typeface.BOLD)); +- } +- +- endBlockElement(text); +- } +- +- private static T getLast(Spanned text, Class kind) { +- /* +- * This knows that the last returned object from getSpans() +- * will be the most recently added. +- */ +- T[] objs = text.getSpans(0, text.length(), kind); +- +- if (objs.length == 0) { +- return null; +- } else { +- return objs[objs.length - 1]; +- } +- } +- +- private static void setSpanFromMark(Spannable text, Object mark, Object... spans) { +- int where = text.getSpanStart(mark); +- text.removeSpan(mark); +- int len = text.length(); +- if (where != len) { +- for (Object span : spans) { +- text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); +- } +- } +- } +- +- private static void start(Editable text, Object mark) { +- int len = text.length(); +- text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); +- } +- +- private static void end(Editable text, Class kind, Object repl) { +- int len = text.length(); +- Object obj = getLast(text, kind); +- if (obj != null) { +- setSpanFromMark(text, obj, repl); +- } +- } +- +- private void startCssStyle(Editable text, Attributes attributes) { +- String style = attributes.getValue("", "style"); +- if (style != null) { +- Matcher m = getForegroundColorPattern().matcher(style); +- if (m.find()) { +- int c = getHtmlColor(m.group(1)); +- if (c != -1) { +- start(text, new Foreground(c | 0xFF000000)); +- } +- } +- +- m = getBackgroundColorPattern().matcher(style); +- if (m.find()) { +- int c = getHtmlColor(m.group(1)); +- if (c != -1) { +- start(text, new Background(c | 0xFF000000)); +- } +- } +- +- m = getTextDecorationPattern().matcher(style); +- if (m.find()) { +- String textDecoration = m.group(1); +- if (textDecoration.equalsIgnoreCase("line-through")) { +- start(text, new Strikethrough()); +- } +- } +- } +- } +- +- private static void endCssStyle(Editable text) { +- Strikethrough s = getLast(text, Strikethrough.class); +- if (s != null) { +- setSpanFromMark(text, s, new StrikethroughSpan()); +- } +- +- Background b = getLast(text, Background.class); +- if (b != null) { +- setSpanFromMark(text, b, new BackgroundColorSpan(b.mBackgroundColor)); +- } +- +- Foreground f = getLast(text, Foreground.class); +- if (f != null) { +- setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor)); +- } +- } +- +- private static void startImg(Editable text, Attributes attributes, Html.ImageGetter img) { +- String src = attributes.getValue("", "src"); +- Drawable d = null; +- +- if (img != null) { +- d = img.getDrawable(src); +- } +- +- if (d == null) { +- d = Resources.getSystem(). +- getDrawable(com.android.internal.R.drawable.unknown_image); +- d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); +- } +- +- int len = text.length(); +- text.append("\uFFFC"); +- +- text.setSpan(new ImageSpan(d, src), len, text.length(), +- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); +- } +- +- private void startFont(Editable text, Attributes attributes) { +- String color = attributes.getValue("", "color"); +- String face = attributes.getValue("", "face"); +- +- if (!TextUtils.isEmpty(color)) { +- int c = getHtmlColor(color); +- if (c != -1) { +- start(text, new Foreground(c | 0xFF000000)); +- } +- } +- +- if (!TextUtils.isEmpty(face)) { +- start(text, new Font(face)); +- } +- } +- +- private static void endFont(Editable text) { +- Font font = getLast(text, Font.class); +- if (font != null) { +- setSpanFromMark(text, font, new TypefaceSpan(font.mFace)); +- } +- +- Foreground foreground = getLast(text, Foreground.class); +- if (foreground != null) { +- setSpanFromMark(text, foreground, +- new ForegroundColorSpan(foreground.mForegroundColor)); +- } +- } +- +- private static void startA(Editable text, Attributes attributes) { +- String href = attributes.getValue("", "href"); +- start(text, new Href(href)); +- } +- +- private static void endA(Editable text) { +- Href h = getLast(text, Href.class); +- if (h != null) { +- if (h.mHref != null) { +- setSpanFromMark(text, h, new URLSpan((h.mHref))); +- } +- } +- } +- +- private int getHtmlColor(String color) { +- if ((mFlags & Html.FROM_HTML_OPTION_USE_CSS_COLORS) +- == Html.FROM_HTML_OPTION_USE_CSS_COLORS) { +- Integer i = sColorMap.get(color.toLowerCase(Locale.US)); +- if (i != null) { +- return i; +- } +- } +- return Color.getHtmlColor(color); +- } +- +- public void setDocumentLocator(Locator locator) { +- } +- +- public void startDocument() throws SAXException { +- } +- +- public void endDocument() throws SAXException { +- } +- +- public void startPrefixMapping(String prefix, String uri) throws SAXException { +- } +- +- public void endPrefixMapping(String prefix) throws SAXException { +- } +- +- public void startElement(String uri, String localName, String qName, Attributes attributes) +- throws SAXException { +- handleStartTag(localName, attributes); +- } +- +- public void endElement(String uri, String localName, String qName) throws SAXException { +- handleEndTag(localName); +- } +- +- public void characters(char ch[], int start, int length) throws SAXException { +- StringBuilder sb = new StringBuilder(); +- +- /* +- * Ignore whitespace that immediately follows other whitespace; +- * newlines count as spaces. +- */ +- +- for (int i = 0; i < length; i++) { +- char c = ch[i + start]; +- +- if (c == ' ' || c == '\n') { +- char pred; +- int len = sb.length(); +- +- if (len == 0) { +- len = mSpannableStringBuilder.length(); +- +- if (len == 0) { +- pred = '\n'; +- } else { +- pred = mSpannableStringBuilder.charAt(len - 1); +- } +- } else { +- pred = sb.charAt(len - 1); +- } +- +- if (pred != ' ' && pred != '\n') { +- sb.append(' '); +- } +- } else { +- sb.append(c); +- } +- } +- +- mSpannableStringBuilder.append(sb); +- } +- +- public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { +- } +- +- public void processingInstruction(String target, String data) throws SAXException { +- } +- +- public void skippedEntity(String name) throws SAXException { +- } +- +- private static class Bold { } +- private static class Italic { } +- private static class Underline { } +- private static class Strikethrough { } +- private static class Big { } +- private static class Small { } +- private static class Monospace { } +- private static class Blockquote { } +- private static class Super { } +- private static class Sub { } +- private static class Bullet { } +- +- private static class Font { +- public String mFace; +- +- public Font(String face) { +- mFace = face; +- } +- } +- +- private static class Href { +- public String mHref; +- +- public Href(String href) { +- mHref = href; +- } +- } +- +- private static class Foreground { +- private int mForegroundColor; +- +- public Foreground(int foregroundColor) { +- mForegroundColor = foregroundColor; +- } +- } +- +- private static class Background { +- private int mBackgroundColor; +- +- public Background(int backgroundColor) { +- mBackgroundColor = backgroundColor; +- } +- } +- +- private static class Heading { +- private int mLevel; +- +- public Heading(int level) { +- mLevel = level; +- } +- } +- +- private static class Newline { +- private int mNumNewlines; +- +- public Newline(int numNewlines) { +- mNumNewlines = numNewlines; +- } +- } +- +- private static class Alignment { +- private Layout.Alignment mAlignment; +- +- public Alignment(Layout.Alignment alignment) { +- mAlignment = alignment; +- } +- } +-}