--- /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) { return toHtml(text, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE); } @@ -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) { StringBuilder out = new StringBuilder(); withinHtml(out, text, option); return out.toString(); @@ -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) { 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) { if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) { encodeTextAlignmentByDiv(out, text, option); return; @@ -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) { int len = text.length(); int next; @@ -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, - 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) { 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) { if (TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(text, start, end - start)) { return " dir=\"rtl\""; } else { @@ -348,8 +156,8 @@ public class Html { } } - 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; @@ -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, - 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); } else { @@ -401,9 +209,9 @@ public class Html { } } - 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); @@ -412,42 +220,48 @@ public class Html { } if (next == i) { - if (isInList) { + if (isInBulletList != null) { // Current paragraph is no longer a list item; close the previously opened list - isInList = false; - out.append("\n"); + out.append(isInBulletList ? "\n" : "\n"); + isInBulletList = null; } - out.append("
\n"); + if (i != text.length()) + out.append("
\n"); } else { - 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) { - isListItem = true; + isBulletListItem = !(paragraphStyle instanceof eu.faircode.email.NumberSpan); break; } } - 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 - isInList = true; - out.append("\n"); } - if (isInList && !isListItem) { + if (isInBulletList != null && isBulletListItem == null) { // Current paragraph is no longer a list item; close the previously opened list - isInList = false; - out.append("\n"); + out.append(isInBulletList ? "\n" : "\n"); + isInBulletList = null; } - 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, !isListItem, true)) + .append(getTextStyles(text, i, next, isBulletListItem == null, true)) .append(">"); withinParagraph(out, text, i, next); @@ -455,10 +269,12 @@ public class Html { out.append("\n"); + if (isBulletListItem == null) + out.append("
\n"); - if (next == end && isInList) { - isInList = false; - out.append("\n"); + if (next == end && isInBulletList != null) { + out.append(isInBulletList ? "\n" : "\n"); + isInBulletList = null; } } @@ -466,9 +282,9 @@ public class Html { } } - 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) { @@ -486,24 +302,24 @@ public class Html { withinParagraph(out, text, i, next - nl); - if (nl == 1) { + if (nl == 0) { out.append("
\n"); } else { - 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("\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) { int next; for (int i = start; i < end; i = next) { next = text.nextSpanTransition(i, end, CharacterStyle.class); @@ -523,9 +339,11 @@ public class Html { if (style[j] instanceof TypefaceSpan) { String s = ((TypefaceSpan) style[j]).getFamily(); - if ("monospace".equals(s)) { - out.append(""); - } + //if ("monospace".equals(s)) { + // out.append(""); + //} + + out.append(""); } if (style[j] instanceof SuperscriptSpan) { out.append(""); @@ -556,8 +374,8 @@ public class Html { AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]); float sizeDip = s.getSize(); if (!s.getDip()) { - 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 @@ -609,11 +427,13 @@ public class Html { out.append(""); } if (style[j] instanceof TypefaceSpan) { - String s = ((TypefaceSpan) style[j]).getFamily(); + //String s = ((TypefaceSpan) style[j]).getFamily(); - 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(); @@ -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, 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; - } - } -}