19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/*
29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2007 The Android Open Source Project
39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License.
69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at
79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and
149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License.
159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.text;
189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
19b138e2877823a0e9f9d834059a64d4035556562bDaniel Uimport android.app.ActivityThread;
20b138e2877823a0e9f9d834059a64d4035556562bDaniel Uimport android.app.Application;
219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.res.Resources;
222102de9e1f7444001a5408856c7cf47df050c4e2Daniel Uimport android.graphics.Color;
239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.graphics.Typeface;
249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.graphics.drawable.Drawable;
25105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Projectimport android.text.style.AbsoluteSizeSpan;
26105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Projectimport android.text.style.AlignmentSpan;
275c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel Uimport android.text.style.BackgroundColorSpan;
282102de9e1f7444001a5408856c7cf47df050c4e2Daniel Uimport android.text.style.BulletSpan;
299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.CharacterStyle;
309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.ForegroundColorSpan;
319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.ImageSpan;
329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.ParagraphStyle;
339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.QuoteSpan;
349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.RelativeSizeSpan;
359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.StrikethroughSpan;
369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.StyleSpan;
379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.SubscriptSpan;
389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.SuperscriptSpan;
399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.TypefaceSpan;
409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.URLSpan;
419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.style.UnderlineSpan;
422269d1572e5fcfb725ea55f5764d8c3280d69f6dDianne Hackborn
434037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.ccil.cowan.tagsoup.HTMLSchema;
444037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.ccil.cowan.tagsoup.Parser;
454037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.xml.sax.Attributes;
464037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.xml.sax.ContentHandler;
474037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.xml.sax.InputSource;
484037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.xml.sax.Locator;
494037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.xml.sax.SAXException;
504037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport org.xml.sax.XMLReader;
514037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikas
529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.IOException;
539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.StringReader;
541eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel Uimport java.util.HashMap;
551eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel Uimport java.util.Locale;
561eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel Uimport java.util.Map;
57cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel Uimport java.util.regex.Matcher;
58cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel Uimport java.util.regex.Pattern;
599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/**
619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * This class processes HTML strings into displayable styled text.
629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Not all HTML tags are supported.
639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class Html {
659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Retrieves images for HTML <img> tags.
679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static interface ImageGetter {
699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        /**
70d0646dca40ff740bd49755ad60751678b0ccca52Mark Doliner         * This method is called when the HTML parser encounters an
719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * &lt;img&gt; tag.  The <code>source</code> argument is the
729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * string from the "src" attribute; the return value should be
739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * a Drawable representation of the image or <code>null</code>
749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * for a generic replacement image.  Make sure you call
759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * setBounds() on your Drawable if it doesn't already have
769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * its bounds set.
779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         */
789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public Drawable getDrawable(String source);
799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Is notified when HTML tags are encountered that the parser does
839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * not know how to interpret.
849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static interface TagHandler {
869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        /**
879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * This method will be called whenn the HTML parser encounters
889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * a tag that it does not know how to interpret.
899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         */
909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public void handleTag(boolean opening, String tag,
919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                                 Editable output, XMLReader xmlReader);
929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
942102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
952102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Option for {@link #toHtml(Spanned, int)}: Wrap consecutive lines of text delimited by '\n'
962102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * inside &lt;p&gt; elements. {@link BulletSpan}s are ignored.
972102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
982102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int TO_HTML_PARAGRAPH_LINES_CONSECUTIVE = 0x00000000;
992102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1002102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1012102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Option for {@link #toHtml(Spanned, int)}: Wrap each line of text delimited by '\n' inside a
1022102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * &lt;p&gt; or a &lt;li&gt; element. This allows {@link ParagraphStyle}s attached to be
1032102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * encoded as CSS styles within the corresponding &lt;p&gt; or &lt;li&gt; element.
1042102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1052102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int TO_HTML_PARAGRAPH_LINES_INDIVIDUAL = 0x00000001;
1062102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1072102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1082102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flag indicating that texts inside &lt;p&gt; elements will be separated from other texts with
1092102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * one newline character by default.
1102102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1112102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH = 0x00000001;
1122102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1132102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1142102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flag indicating that texts inside &lt;h1&gt;~&lt;h6&gt; elements will be separated from
1152102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * other texts with one newline character by default.
1162102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1172102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_SEPARATOR_LINE_BREAK_HEADING = 0x00000002;
1182102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1192102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1202102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flag indicating that texts inside &lt;li&gt; elements will be separated from other texts
1212102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * with one newline character by default.
1222102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1232102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM = 0x00000004;
1242102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1252102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1262102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flag indicating that texts inside &lt;ul&gt; elements will be separated from other texts
1272102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * with one newline character by default.
1282102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1292102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST = 0x00000008;
1302102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1312102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1322102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flag indicating that texts inside &lt;div&gt; elements will be separated from other texts
1332102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * with one newline character by default.
1342102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1352102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_SEPARATOR_LINE_BREAK_DIV = 0x00000010;
1362102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1372102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1382102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flag indicating that texts inside &lt;blockquote&gt; elements will be separated from other
1392102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * texts with one newline character by default.
1402102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1412102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE = 0x00000020;
1422102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1432102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1442102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flag indicating that CSS color values should be used instead of those defined in
1452102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * {@link Color}.
1462102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1472102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_OPTION_USE_CSS_COLORS = 0x00000100;
1482102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1492102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1502102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level
1512102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * elements with blank lines (two newline characters) in between. This is the legacy behavior
1522102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * prior to N.
1532102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1542102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_MODE_LEGACY = 0x00000000;
1552102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1562102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1572102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level
1582102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * elements with line breaks (single newline character) in between. This inverts the
1592102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * {@link Spanned} to HTML string conversion done with the option
1602102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}.
1612102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1622102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static final int FROM_HTML_MODE_COMPACT =
1632102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH
1642102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            | FROM_HTML_SEPARATOR_LINE_BREAK_HEADING
1652102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            | FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM
1662102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            | FROM_HTML_SEPARATOR_LINE_BREAK_LIST
1672102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            | FROM_HTML_SEPARATOR_LINE_BREAK_DIV
1682102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            | FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE;
1692102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
170ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    /**
171ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U     * The bit which indicates if lines delimited by '\n' will be grouped into &lt;p&gt; elements.
172ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U     */
173ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static final int TO_HTML_PARAGRAPH_FLAG = 0x00000001;
174ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private Html() { }
1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
1782102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Returns displayable styled text from the provided HTML string with the legacy flags
1792102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * {@link #FROM_HTML_MODE_LEGACY}.
1802102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     *
1812102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * @deprecated use {@link #fromHtml(String, int)} instead.
1822102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
1832102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    @Deprecated
1842102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static Spanned fromHtml(String source) {
1852102de9e1f7444001a5408856c7cf47df050c4e2Daniel U        return fromHtml(source, FROM_HTML_MODE_LEGACY, null, null);
1862102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    }
1872102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
1882102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
1892102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Returns displayable styled text from the provided HTML string. Any &lt;img&gt; tags in the
1902102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * HTML will display as a generic replacement image which your program can then go through and
1919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * replace with real images.
1929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     *
1939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
1949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
1952102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static Spanned fromHtml(String source, int flags) {
1962102de9e1f7444001a5408856c7cf47df050c4e2Daniel U        return fromHtml(source, flags, null, null);
1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Lazy initialization holder for HTML parser. This class will
2019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * a) be preloaded by the zygote, or b) not loaded until absolutely
2029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * necessary.
2039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class HtmlParser {
2059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        private static final HTMLSchema schema = new HTMLSchema();
2069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2092102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Returns displayable styled text from the provided HTML string with the legacy flags
2102102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * {@link #FROM_HTML_MODE_LEGACY}.
2112102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     *
2122102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * @deprecated use {@link #fromHtml(String, int, ImageGetter, TagHandler)} instead.
2132102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
2142102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    @Deprecated
2152102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler) {
2162102de9e1f7444001a5408856c7cf47df050c4e2Daniel U        return fromHtml(source, FROM_HTML_MODE_LEGACY, imageGetter, tagHandler);
2172102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    }
2182102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
2192102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
2202102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * Returns displayable styled text from the provided HTML string. Any &lt;img&gt; tags in the
2212102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * HTML will use the specified ImageGetter to request a representation of the image (use null
2222102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * if you don't want this) and the specified TagHandler to handle unknown tags (specify null if
2232102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * you don't want this).
2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     *
2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2272102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter,
2282102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            TagHandler tagHandler) {
2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        Parser parser = new Parser();
2309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
2319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (org.xml.sax.SAXNotRecognizedException e) {
2339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // Should not happen.
2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            throw new RuntimeException(e);
2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (org.xml.sax.SAXNotSupportedException e) {
2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // Should not happen.
2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            throw new RuntimeException(e);
2389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        HtmlToSpannedConverter converter =
2412102de9e1f7444001a5408856c7cf47df050c4e2Daniel U                new HtmlToSpannedConverter(source, imageGetter, tagHandler, parser, flags);
2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return converter.convert();
2439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2462102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * @deprecated use {@link #toHtml(Spanned, int)} instead.
2472102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     */
2482102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    @Deprecated
2492102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static String toHtml(Spanned text) {
2502102de9e1f7444001a5408856c7cf47df050c4e2Daniel U        return toHtml(text, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
2512102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    }
2522102de9e1f7444001a5408856c7cf47df050c4e2Daniel U
2532102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    /**
254c55277d689dee83f58f63f56a0e26debff62aa56Raph Levien     * Returns an HTML representation of the provided Spanned text. A best effort is
255c55277d689dee83f58f63f56a0e26debff62aa56Raph Levien     * made to add HTML tags corresponding to spans. Also note that HTML metacharacters
256c55277d689dee83f58f63f56a0e26debff62aa56Raph Levien     * (such as "&lt;" and "&amp;") within the input text are escaped.
257c55277d689dee83f58f63f56a0e26debff62aa56Raph Levien     *
258c55277d689dee83f58f63f56a0e26debff62aa56Raph Levien     * @param text input text to convert
2592102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     * @param option one of {@link #TO_HTML_PARAGRAPH_LINES_CONSECUTIVE} or
2602102de9e1f7444001a5408856c7cf47df050c4e2Daniel U     *     {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}
261c55277d689dee83f58f63f56a0e26debff62aa56Raph Levien     * @return string containing input converted to HTML
2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2632102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public static String toHtml(Spanned text, int option) {
2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        StringBuilder out = new StringBuilder();
265ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        withinHtml(out, text, option);
266105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project        return out.toString();
267105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project    }
268105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project
269acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn    /**
270acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn     * Returns an HTML escaped representation of the given plain text.
271acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn     */
272acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn    public static String escapeHtml(CharSequence text) {
273acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn        StringBuilder out = new StringBuilder();
274acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn        withinStyle(out, text, 0, text.length());
275acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn        return out.toString();
276acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn    }
277acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn
278ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static void withinHtml(StringBuilder out, Spanned text, int option) {
279ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) {
280ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            encodeTextAlignmentByDiv(out, text, option);
281ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            return;
282ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        }
283ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
284ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        withinDiv(out, text, 0, text.length(), option);
285ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    }
286ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
287ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) {
2889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int len = text.length();
2899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int next;
2915c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U        for (int i = 0; i < len; i = next) {
292105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project            next = text.nextSpanTransition(i, len, ParagraphStyle.class);
293105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project            ParagraphStyle[] style = text.getSpans(i, next, ParagraphStyle.class);
29432048300e917c9181927ac017d02855bbde940efSatoshi Kataoka            String elements = " ";
29500ba76670fd06d9c51cce36a74c384a212f705b5Eric Fischer            boolean needDiv = false;
29600ba76670fd06d9c51cce36a74c384a212f705b5Eric Fischer
297105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project            for(int j = 0; j < style.length; j++) {
298105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                if (style[j] instanceof AlignmentSpan) {
299a8f6d5f0720f400b6f59b0809aaefea83c5f51d4Romain Guy                    Layout.Alignment align =
300105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                        ((AlignmentSpan) style[j]).getAlignment();
30100ba76670fd06d9c51cce36a74c384a212f705b5Eric Fischer                    needDiv = true;
302105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                    if (align == Layout.Alignment.ALIGN_CENTER) {
30332048300e917c9181927ac017d02855bbde940efSatoshi Kataoka                        elements = "align=\"center\" " + elements;
304105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                    } else if (align == Layout.Alignment.ALIGN_OPPOSITE) {
30532048300e917c9181927ac017d02855bbde940efSatoshi Kataoka                        elements = "align=\"right\" " + elements;
306105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                    } else {
30732048300e917c9181927ac017d02855bbde940efSatoshi Kataoka                        elements = "align=\"left\" " + elements;
308105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                    }
309105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                }
310105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project            }
31100ba76670fd06d9c51cce36a74c384a212f705b5Eric Fischer            if (needDiv) {
312a8f6d5f0720f400b6f59b0809aaefea83c5f51d4Romain Guy                out.append("<div ").append(elements).append(">");
313105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project            }
314105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project
315ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            withinDiv(out, text, i, next, option);
316105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project
31700ba76670fd06d9c51cce36a74c384a212f705b5Eric Fischer            if (needDiv) {
318105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                out.append("</div>");
319105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project            }
320105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project        }
321105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project    }
322105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project
323ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static void withinDiv(StringBuilder out, Spanned text, int start, int end,
324ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            int option) {
325105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project        int next;
326105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project        for (int i = start; i < end; i = next) {
327105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project            next = text.nextSpanTransition(i, end, QuoteSpan.class);
3289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class);
3299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
330a8f6d5f0720f400b6f59b0809aaefea83c5f51d4Romain Guy            for (QuoteSpan quote : quotes) {
3319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.append("<blockquote>");
3329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
3339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
334ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            withinBlockquote(out, text, i, next, option);
3359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
336a8f6d5f0720f400b6f59b0809aaefea83c5f51d4Romain Guy            for (QuoteSpan quote : quotes) {
3379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.append("</blockquote>\n");
3389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
3399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
3409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
3419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
342ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static String getTextDirection(Spanned text, int start, int end) {
343aa0af8e86278d8b0935c95a5ff06c80532bf7733Roozbeh Pournader        if (TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(text, start, end - start)) {
344aa0af8e86278d8b0935c95a5ff06c80532bf7733Roozbeh Pournader            return " dir=\"rtl\"";
345aa0af8e86278d8b0935c95a5ff06c80532bf7733Roozbeh Pournader        } else {
346aa0af8e86278d8b0935c95a5ff06c80532bf7733Roozbeh Pournader            return " dir=\"ltr\"";
347ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        }
348ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    }
349ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
3504a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U    private static String getTextStyles(Spanned text, int start, int end,
3514a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            boolean forceNoVerticalMargin, boolean includeTextAlign) {
3524a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        String margin = null;
3534a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        String textAlign = null;
3544a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U
3554a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        if (forceNoVerticalMargin) {
3564a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            margin = "margin-top:0; margin-bottom:0;";
3574a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        }
3584a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        if (includeTextAlign) {
3594a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            final AlignmentSpan[] alignmentSpans = text.getSpans(start, end, AlignmentSpan.class);
3604a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U
3614a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            // Only use the last AlignmentSpan with flag SPAN_PARAGRAPH
3624a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            for (int i = alignmentSpans.length - 1; i >= 0; i--) {
3634a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                AlignmentSpan s = alignmentSpans[i];
3644a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH) {
3654a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    final Layout.Alignment alignment = s.getAlignment();
3664a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    if (alignment == Layout.Alignment.ALIGN_NORMAL) {
3674a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        textAlign = "text-align:start;";
3684a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    } else if (alignment == Layout.Alignment.ALIGN_CENTER) {
3694a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        textAlign = "text-align:center;";
3704a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    } else if (alignment == Layout.Alignment.ALIGN_OPPOSITE) {
3714a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        textAlign = "text-align:end;";
3724a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    }
3734a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    break;
3744a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                }
375ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            }
376cd4161b134387457bac9943db569afbfa427b87bFabrice Di Meglio        }
377ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
3784a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        if (margin == null && textAlign == null) {
3794a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            return "";
3804a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        }
3814a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U
3824a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        final StringBuilder style = new StringBuilder(" style=\"");
3834a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        if (margin != null && textAlign != null) {
3844a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            style.append(margin).append(" ").append(textAlign);
3854a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        } else if (margin != null) {
3864a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            style.append(margin);
3874a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        } else if (textAlign != null) {
3884a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            style.append(textAlign);
3894a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        }
3904a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U
3914a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U        return style.append("\"").toString();
392cd4161b134387457bac9943db569afbfa427b87bFabrice Di Meglio    }
393cd4161b134387457bac9943db569afbfa427b87bFabrice Di Meglio
394ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static void withinBlockquote(StringBuilder out, Spanned text, int start, int end,
395ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            int option) {
396ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) {
397ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            withinBlockquoteConsecutive(out, text, start, end);
398ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        } else {
399ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            withinBlockquoteIndividual(out, text, start, end);
400ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        }
401ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    }
402ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
403ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start,
404ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            int end) {
405ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        boolean isInList = false;
406ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        int next;
407ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        for (int i = start; i <= end; i = next) {
408ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            next = TextUtils.indexOf(text, '\n', i, end);
409ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            if (next < 0) {
410ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                next = end;
411ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            }
412ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
4134a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            if (next == i) {
4144a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                if (isInList) {
4154a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    // Current paragraph is no longer a list item; close the previously opened list
4164a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    isInList = false;
4174a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    out.append("</ul>\n");
4184a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                }
4194a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                out.append("<br>\n");
4204a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U            } else {
4214a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                boolean isListItem = false;
4224a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class);
4234a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                for (ParagraphStyle paragraphStyle : paragraphStyles) {
4244a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    final int spanFlags = text.getSpanFlags(paragraphStyle);
4254a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH
4264a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                            && paragraphStyle instanceof BulletSpan) {
4274a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        isListItem = true;
4284a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        break;
4294a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    }
430ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                }
431ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
4324a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                if (isListItem && !isInList) {
4334a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    // Current paragraph is the first item in a list
4344a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    isInList = true;
4354a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    out.append("<ul")
4364a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                            .append(getTextStyles(text, i, next, true, false))
4374a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                            .append(">\n");
4384a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                }
439ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
4404a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                if (isInList && !isListItem) {
4414a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    // Current paragraph is no longer a list item; close the previously opened list
4424a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    isInList = false;
4434a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    out.append("</ul>\n");
4444a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                }
445ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
4464a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                String tagType = isListItem ? "li" : "p";
4474a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                out.append("<").append(tagType)
4484a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        .append(getTextDirection(text, i, next))
4494a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        .append(getTextStyles(text, i, next, !isListItem, true))
4504a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                        .append(">");
451ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
452ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                withinParagraph(out, text, i, next);
453ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
4544a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                out.append("</");
4554a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                out.append(tagType);
4564a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                out.append(">\n");
457ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
4584a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                if (next == end && isInList) {
4594a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    isInList = false;
4604a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                    out.append("</ul>\n");
4614a70f09e39ac7c5f57b0f6dd44475641c68afafdDaniel U                }
462ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            }
463ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
464ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            next++;
465ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        }
466ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    }
467ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
468ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start,
469ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            int end) {
470ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U        out.append("<p").append(getTextDirection(text, start, end)).append(">");
4719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
4729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int next;
4739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        for (int i = start; i < end; i = next) {
4749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            next = TextUtils.indexOf(text, '\n', i, end);
4759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            if (next < 0) {
4769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                next = end;
4779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
4789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
4799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            int nl = 0;
4809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
4819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            while (next < end && text.charAt(next) == '\n') {
4829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                nl++;
4839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                next++;
4849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
4859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
486ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            withinParagraph(out, text, i, next - nl);
487ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U
488ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            if (nl == 1) {
489ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                out.append("<br>\n");
490ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U            } else {
491ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                for (int j = 2; j < nl; j++) {
492ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                    out.append("<br>");
493ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                }
494ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                if (next != end) {
495ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                    /* Paragraph should be closed and reopened */
496ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                    out.append("</p>\n");
497ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                    out.append("<p").append(getTextDirection(text, start, end)).append(">");
498ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U                }
4992243ae17a9df91d589c8580f3ae499b63de91166Roozbeh Pournader            }
5009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
5019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        out.append("</p>\n");
5039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
5049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
505ca124ab1e25b4709c46de50ae8536d4a9338c8cbDaniel U    private static void withinParagraph(StringBuilder out, Spanned text, int start, int end) {
5069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int next;
5079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        for (int i = start; i < end; i = next) {
5089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            next = text.nextSpanTransition(i, end, CharacterStyle.class);
5095c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U            CharacterStyle[] style = text.getSpans(i, next, CharacterStyle.class);
5109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            for (int j = 0; j < style.length; j++) {
5129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof StyleSpan) {
5139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    int s = ((StyleSpan) style[j]).getStyle();
5149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if ((s & Typeface.BOLD) != 0) {
5169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        out.append("<b>");
5179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
5189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if ((s & Typeface.ITALIC) != 0) {
5199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        out.append("<i>");
5209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
5219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof TypefaceSpan) {
5239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    String s = ((TypefaceSpan) style[j]).getFamily();
5249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5258e71a397c6542d6a37cd59ea8b53236ac2dba86aRaph Levien                    if ("monospace".equals(s)) {
5269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        out.append("<tt>");
5279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
5289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof SuperscriptSpan) {
5309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("<sup>");
5319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof SubscriptSpan) {
5339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("<sub>");
5349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof UnderlineSpan) {
5369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("<u>");
5379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof StrikethroughSpan) {
5395c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    out.append("<span style=\"text-decoration:line-through;\">");
5409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof URLSpan) {
5429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("<a href=\"");
5439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append(((URLSpan) style[j]).getURL());
5449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("\">");
5459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof ImageSpan) {
5479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("<img src=\"");
5489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append(((ImageSpan) style[j]).getSource());
5499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("\">");
5509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    // Don't output the dummy character underlying the image.
5529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    i = next;
5539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
554105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                if (style[j] instanceof AbsoluteSizeSpan) {
555b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]);
556b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    float sizeDip = s.getSize();
557b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    if (!s.getDip()) {
558b138e2877823a0e9f9d834059a64d4035556562bDaniel U                        Application application = ActivityThread.currentApplication();
559b138e2877823a0e9f9d834059a64d4035556562bDaniel U                        sizeDip /= application.getResources().getDisplayMetrics().density;
560b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    }
561b138e2877823a0e9f9d834059a64d4035556562bDaniel U
562b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    // px in CSS is the equivalance of dip in Android
563b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    out.append(String.format("<span style=\"font-size:%.0fpx\";>", sizeDip));
564b138e2877823a0e9f9d834059a64d4035556562bDaniel U                }
565b138e2877823a0e9f9d834059a64d4035556562bDaniel U                if (style[j] instanceof RelativeSizeSpan) {
566b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    float sizeEm = ((RelativeSizeSpan) style[j]).getSizeChange();
567b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    out.append(String.format("<span style=\"font-size:%.2fem;\">", sizeEm));
568105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                }
569105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                if (style[j] instanceof ForegroundColorSpan) {
5705c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    int color = ((ForegroundColorSpan) style[j]).getForegroundColor();
5715c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    out.append(String.format("<span style=\"color:#%06X;\">", 0xFFFFFF & color));
5725c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                }
5735c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                if (style[j] instanceof BackgroundColorSpan) {
5745c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    int color = ((BackgroundColorSpan) style[j]).getBackgroundColor();
5755c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    out.append(String.format("<span style=\"background-color:#%06X;\">",
5765c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                            0xFFFFFF & color));
577105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                }
5789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
5799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            withinStyle(out, text, i, next);
5819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            for (int j = style.length - 1; j >= 0; j--) {
5835c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                if (style[j] instanceof BackgroundColorSpan) {
5845c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    out.append("</span>");
5855c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                }
586105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                if (style[j] instanceof ForegroundColorSpan) {
5875c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    out.append("</span>");
588105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                }
589b138e2877823a0e9f9d834059a64d4035556562bDaniel U                if (style[j] instanceof RelativeSizeSpan) {
590b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    out.append("</span>");
591b138e2877823a0e9f9d834059a64d4035556562bDaniel U                }
592105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                if (style[j] instanceof AbsoluteSizeSpan) {
593b138e2877823a0e9f9d834059a64d4035556562bDaniel U                    out.append("</span>");
594105925376f8d0f6b318c9938c7b83ef7fef094daThe Android Open Source Project                }
5959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof URLSpan) {
5969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("</a>");
5979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
5989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof StrikethroughSpan) {
5995c02d7374f36672e8c3bab8c7cf519ed4a4d8eadDaniel U                    out.append("</span>");
6009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
6019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof UnderlineSpan) {
6029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("</u>");
6039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
6049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof SubscriptSpan) {
6059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("</sub>");
6069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
6079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof SuperscriptSpan) {
6089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("</sup>");
6099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
6109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof TypefaceSpan) {
6119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    String s = ((TypefaceSpan) style[j]).getFamily();
6129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
6139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (s.equals("monospace")) {
6149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        out.append("</tt>");
6159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
6169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
6179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (style[j] instanceof StyleSpan) {
6189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    int s = ((StyleSpan) style[j]).getStyle();
6199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
6209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if ((s & Typeface.BOLD) != 0) {
6219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        out.append("</b>");
6229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
6239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if ((s & Typeface.ITALIC) != 0) {
6249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        out.append("</i>");
6259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
6269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
6279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
6289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
6299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
6309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
631acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn    private static void withinStyle(StringBuilder out, CharSequence text,
6329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                                    int start, int end) {
6339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        for (int i = start; i < end; i++) {
6349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            char c = text.charAt(i);
6359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
6369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            if (c == '<') {
6379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.append("&lt;");
6389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else if (c == '>') {
6399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.append("&gt;");
6409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else if (c == '&') {
6419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.append("&amp;");
6423d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease            } else if (c >= 0xD800 && c <= 0xDFFF) {
6433d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                if (c < 0xDC00 && i + 1 < end) {
6443d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                    char d = text.charAt(i + 1);
6453d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                    if (d >= 0xDC00 && d <= 0xDFFF) {
6463d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                        i++;
6473d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                        int codepoint = 0x010000 | (int) c - 0xD800 << 10 | (int) d - 0xDC00;
6483d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                        out.append("&#").append(codepoint).append(";");
6493d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                    }
6503d4764159c78857bbd3305f4fa9c11c70e742d88Victoria Lease                }
6519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else if (c > 0x7E || c < ' ') {
652a8f6d5f0720f400b6f59b0809aaefea83c5f51d4Romain Guy                out.append("&#").append((int) c).append(";");
6539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else if (c == ' ') {
6549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                while (i + 1 < end && text.charAt(i + 1) == ' ') {
6559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.append("&nbsp;");
6569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    i++;
6579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
6589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
6599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.append(' ');
6609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else {
6619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.append(c);
6629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
6639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
6649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
6659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
6669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
6679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectclass HtmlToSpannedConverter implements ContentHandler {
6689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
669cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static final float[] HEADING_SIZES = {
6709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f,
6719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    };
6729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
6739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private String mSource;
6749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private XMLReader mReader;
6759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private SpannableStringBuilder mSpannableStringBuilder;
6769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private Html.ImageGetter mImageGetter;
6779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private Html.TagHandler mTagHandler;
6782102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    private int mFlags;
6799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
680cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static Pattern sTextAlignPattern;
6818b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static Pattern sForegroundColorPattern;
6828b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static Pattern sBackgroundColorPattern;
6838b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static Pattern sTextDecorationPattern;
684cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
6851eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    /**
6861eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U     * Name-value mapping of HTML/CSS colors which have different values in {@link Color}.
6871eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U     */
6881eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    private static final Map<String, Integer> sColorMap;
6891eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U
6901eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    static {
6911eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap = new HashMap<>();
6921eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap.put("darkgray", 0xFFA9A9A9);
6931eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap.put("gray", 0xFF808080);
6941eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap.put("lightgray", 0xFFD3D3D3);
6951eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap.put("darkgrey", 0xFFA9A9A9);
6961eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap.put("grey", 0xFF808080);
6971eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap.put("lightgrey", 0xFFD3D3D3);
6981eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        sColorMap.put("green", 0xFF008000);
6991eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    }
7001eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U
701cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static Pattern getTextAlignPattern() {
702cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (sTextAlignPattern == null) {
703cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            sTextAlignPattern = Pattern.compile("(?:\\s+|\\A)text-align\\s*:\\s*(\\S*)\\b");
704cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
705cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        return sTextAlignPattern;
706cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
707cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
7088b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static Pattern getForegroundColorPattern() {
7098b36c0bbd1503c61c111feac939193c47f812190Daniel U        if (sForegroundColorPattern == null) {
7108b36c0bbd1503c61c111feac939193c47f812190Daniel U            sForegroundColorPattern = Pattern.compile(
7118b36c0bbd1503c61c111feac939193c47f812190Daniel U                    "(?:\\s+|\\A)color\\s*:\\s*(\\S*)\\b");
7128b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
7138b36c0bbd1503c61c111feac939193c47f812190Daniel U        return sForegroundColorPattern;
7148b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
7158b36c0bbd1503c61c111feac939193c47f812190Daniel U
7168b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static Pattern getBackgroundColorPattern() {
7178b36c0bbd1503c61c111feac939193c47f812190Daniel U        if (sBackgroundColorPattern == null) {
7188b36c0bbd1503c61c111feac939193c47f812190Daniel U            sBackgroundColorPattern = Pattern.compile(
7198b36c0bbd1503c61c111feac939193c47f812190Daniel U                    "(?:\\s+|\\A)background(?:-color)?\\s*:\\s*(\\S*)\\b");
7208b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
7218b36c0bbd1503c61c111feac939193c47f812190Daniel U        return sBackgroundColorPattern;
7228b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
7238b36c0bbd1503c61c111feac939193c47f812190Daniel U
7248b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static Pattern getTextDecorationPattern() {
7258b36c0bbd1503c61c111feac939193c47f812190Daniel U        if (sTextDecorationPattern == null) {
7268b36c0bbd1503c61c111feac939193c47f812190Daniel U            sTextDecorationPattern = Pattern.compile(
7278b36c0bbd1503c61c111feac939193c47f812190Daniel U                    "(?:\\s+|\\A)text-decoration\\s*:\\s*(\\S*)\\b");
7288b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
7298b36c0bbd1503c61c111feac939193c47f812190Daniel U        return sTextDecorationPattern;
7308b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
7318b36c0bbd1503c61c111feac939193c47f812190Daniel U
7322102de9e1f7444001a5408856c7cf47df050c4e2Daniel U    public HtmlToSpannedConverter( String source, Html.ImageGetter imageGetter,
7332102de9e1f7444001a5408856c7cf47df050c4e2Daniel U            Html.TagHandler tagHandler, Parser parser, int flags) {
7349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mSource = source;
7359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mSpannableStringBuilder = new SpannableStringBuilder();
7369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mImageGetter = imageGetter;
7379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mTagHandler = tagHandler;
7389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mReader = parser;
7392102de9e1f7444001a5408856c7cf47df050c4e2Daniel U        mFlags = flags;
7409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
7419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public Spanned convert() {
7439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mReader.setContentHandler(this);
7459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
7469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mReader.parse(new InputSource(new StringReader(mSource)));
7479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (IOException e) {
7489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // We are reading from a string. There should not be IO problems.
7499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            throw new RuntimeException(e);
7509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (SAXException e) {
7519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // TagSoup doesn't throw parse exceptions.
7529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            throw new RuntimeException(e);
7539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
7549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Fix flags and range for paragraph-type markup.
7569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class);
7579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        for (int i = 0; i < obj.length; i++) {
7589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            int start = mSpannableStringBuilder.getSpanStart(obj[i]);
7599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            int end = mSpannableStringBuilder.getSpanEnd(obj[i]);
7609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // If the last line of the range is blank, back off by one.
7629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            if (end - 2 >= 0) {
7639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (mSpannableStringBuilder.charAt(end - 1) == '\n' &&
7649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    mSpannableStringBuilder.charAt(end - 2) == '\n') {
7659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    end--;
7669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
7679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
7689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            if (end == start) {
7709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                mSpannableStringBuilder.removeSpan(obj[i]);
7719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else {
7729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH);
7739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
7749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
7759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return mSpannableStringBuilder;
7779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
7789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private void handleStartTag(String tag, Attributes attributes) {
7809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (tag.equalsIgnoreCase("br")) {
7819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>
782cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            // so we can safely emit the linebreaks when we handle the close tag.
7839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("p")) {
784cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph());
7858b36c0bbd1503c61c111feac939193c47f812190Daniel U            startCssStyle(mSpannableStringBuilder, attributes);
7868b36c0bbd1503c61c111feac939193c47f812190Daniel U        } else if (tag.equalsIgnoreCase("ul")) {
7878b36c0bbd1503c61c111feac939193c47f812190Daniel U            startBlockElement(mSpannableStringBuilder, attributes, getMarginList());
7888b36c0bbd1503c61c111feac939193c47f812190Daniel U        } else if (tag.equalsIgnoreCase("li")) {
7898b36c0bbd1503c61c111feac939193c47f812190Daniel U            startLi(mSpannableStringBuilder, attributes);
7909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("div")) {
791cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv());
7928b36c0bbd1503c61c111feac939193c47f812190Daniel U        } else if (tag.equalsIgnoreCase("span")) {
7938b36c0bbd1503c61c111feac939193c47f812190Daniel U            startCssStyle(mSpannableStringBuilder, attributes);
79494d5e9ad6533864f098b99c231d69c77230a828fRomain Guy        } else if (tag.equalsIgnoreCase("strong")) {
7959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Bold());
7969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("b")) {
7979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Bold());
79894d5e9ad6533864f098b99c231d69c77230a828fRomain Guy        } else if (tag.equalsIgnoreCase("em")) {
7999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Italic());
8009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("cite")) {
8019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Italic());
8029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("dfn")) {
8039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Italic());
8049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("i")) {
8059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Italic());
8069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("big")) {
8079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Big());
8089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("small")) {
8099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Small());
8109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("font")) {
8119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            startFont(mSpannableStringBuilder, attributes);
8129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("blockquote")) {
813cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            startBlockquote(mSpannableStringBuilder, attributes);
8149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("tt")) {
8159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Monospace());
8169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("a")) {
8179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            startA(mSpannableStringBuilder, attributes);
8189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("u")) {
8199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Underline());
8205e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader        } else if (tag.equalsIgnoreCase("del")) {
8215e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader            start(mSpannableStringBuilder, new Strikethrough());
8225e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader        } else if (tag.equalsIgnoreCase("s")) {
8235e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader            start(mSpannableStringBuilder, new Strikethrough());
8245e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader        } else if (tag.equalsIgnoreCase("strike")) {
8255e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader            start(mSpannableStringBuilder, new Strikethrough());
8269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("sup")) {
8279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Super());
8289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("sub")) {
8299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            start(mSpannableStringBuilder, new Sub());
8309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.length() == 2 &&
831cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                Character.toLowerCase(tag.charAt(0)) == 'h' &&
832cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
833cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            startHeading(mSpannableStringBuilder, attributes, tag.charAt(1) - '1');
8349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("img")) {
8359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            startImg(mSpannableStringBuilder, attributes, mImageGetter);
8369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (mTagHandler != null) {
8379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
8389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
8399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
8409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
8419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private void handleEndTag(String tag) {
8429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (tag.equalsIgnoreCase("br")) {
8439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            handleBr(mSpannableStringBuilder);
8449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("p")) {
8458b36c0bbd1503c61c111feac939193c47f812190Daniel U            endCssStyle(mSpannableStringBuilder);
8468b36c0bbd1503c61c111feac939193c47f812190Daniel U            endBlockElement(mSpannableStringBuilder);
8478b36c0bbd1503c61c111feac939193c47f812190Daniel U        } else if (tag.equalsIgnoreCase("ul")) {
848cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            endBlockElement(mSpannableStringBuilder);
8498b36c0bbd1503c61c111feac939193c47f812190Daniel U        } else if (tag.equalsIgnoreCase("li")) {
8508b36c0bbd1503c61c111feac939193c47f812190Daniel U            endLi(mSpannableStringBuilder);
8519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("div")) {
852cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            endBlockElement(mSpannableStringBuilder);
8538b36c0bbd1503c61c111feac939193c47f812190Daniel U        } else if (tag.equalsIgnoreCase("span")) {
8548b36c0bbd1503c61c111feac939193c47f812190Daniel U            endCssStyle(mSpannableStringBuilder);
855dd808c0bd498854e878db257bbf82b73ea5000b4Romain Guy        } else if (tag.equalsIgnoreCase("strong")) {
8569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
8579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("b")) {
8589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
859dd808c0bd498854e878db257bbf82b73ea5000b4Romain Guy        } else if (tag.equalsIgnoreCase("em")) {
8609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
8619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("cite")) {
8629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
8639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("dfn")) {
8649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
8659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("i")) {
8669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
8679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("big")) {
8689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));
8699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("small")) {
8709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));
8719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("font")) {
8729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            endFont(mSpannableStringBuilder);
8739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("blockquote")) {
874cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            endBlockquote(mSpannableStringBuilder);
8759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("tt")) {
876cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            end(mSpannableStringBuilder, Monospace.class, new TypefaceSpan("monospace"));
8779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("a")) {
8789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            endA(mSpannableStringBuilder);
8799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("u")) {
8809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
8815e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader        } else if (tag.equalsIgnoreCase("del")) {
8825e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader            end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
8835e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader        } else if (tag.equalsIgnoreCase("s")) {
8845e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader            end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
8855e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader        } else if (tag.equalsIgnoreCase("strike")) {
8865e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader            end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
8879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("sup")) {
8889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
8899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.equalsIgnoreCase("sub")) {
8909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());
8919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (tag.length() == 2 &&
8929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                Character.toLowerCase(tag.charAt(0)) == 'h' &&
8939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
894cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            endHeading(mSpannableStringBuilder);
8959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else if (mTagHandler != null) {
8969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
8979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
8989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
8999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
900cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private int getMarginParagraph() {
901cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH);
902cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
9039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
904cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private int getMarginHeading() {
905cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
906cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
9079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
9088b36c0bbd1503c61c111feac939193c47f812190Daniel U    private int getMarginListItem() {
9098b36c0bbd1503c61c111feac939193c47f812190Daniel U        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM);
9108b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
9118b36c0bbd1503c61c111feac939193c47f812190Daniel U
9128b36c0bbd1503c61c111feac939193c47f812190Daniel U    private int getMarginList() {
9138b36c0bbd1503c61c111feac939193c47f812190Daniel U        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST);
9148b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
9158b36c0bbd1503c61c111feac939193c47f812190Daniel U
916cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private int getMarginDiv() {
917cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV);
918cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
919cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
920cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private int getMarginBlockquote() {
921cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE);
922cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
923cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
924cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    /**
925cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U     * Returns the minimum number of newline characters needed before and after a given block-level
926cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U     * element.
927cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U     *
928cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U     * @param flag the corresponding option flag defined in {@link Html} of a block-level element
929cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U     */
930cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private int getMargin(int flag) {
931cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if ((flag & mFlags) != 0) {
932cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            return 1;
933cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
934cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        return 2;
935cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
936cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
937cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void appendNewlines(Editable text, int minNewline) {
938cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        final int len = text.length();
939cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
940cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (len == 0) {
9419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return;
9429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
9439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
944cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        int existingNewlines = 0;
945cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        for (int i = len - 1; i >= 0 && text.charAt(i) == '\n'; i--) {
946cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            existingNewlines++;
947cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
948cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
949cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        for (int j = existingNewlines; j < minNewline; j++) {
950cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            text.append("\n");
951cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
952cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
953cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
954cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void startBlockElement(Editable text, Attributes attributes, int margin) {
955cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        final int len = text.length();
956cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (margin > 0) {
957cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            appendNewlines(text, margin);
9581eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            start(text, new Newline(margin));
959cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
960cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
961cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        String style = attributes.getValue("", "style");
962cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (style != null) {
963cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            Matcher m = getTextAlignPattern().matcher(style);
964cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            if (m.find()) {
965cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                String alignment = m.group(1);
966cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                if (alignment.equalsIgnoreCase("start")) {
9671eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                    start(text, new Alignment(Layout.Alignment.ALIGN_NORMAL));
968cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                } else if (alignment.equalsIgnoreCase("center")) {
9691eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                    start(text, new Alignment(Layout.Alignment.ALIGN_CENTER));
970cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                } else if (alignment.equalsIgnoreCase("end")) {
9711eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                    start(text, new Alignment(Layout.Alignment.ALIGN_OPPOSITE));
972cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                }
973cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            }
974cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
975cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
976cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
977cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void endBlockElement(Editable text) {
978cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        Newline n = getLast(text, Newline.class);
979cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (n != null) {
980cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            appendNewlines(text, n.mNumNewlines);
981cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            text.removeSpan(n);
982cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
983cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
984cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        Alignment a = getLast(text, Alignment.class);
985cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (a != null) {
986cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            setSpanFromMark(text, a, new AlignmentSpan.Standard(a.mAlignment));
9879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
9889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
9899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
990cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void handleBr(Editable text) {
991cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        text.append('\n');
992cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
993cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
9948b36c0bbd1503c61c111feac939193c47f812190Daniel U    private void startLi(Editable text, Attributes attributes) {
9958b36c0bbd1503c61c111feac939193c47f812190Daniel U        startBlockElement(text, attributes, getMarginListItem());
9968b36c0bbd1503c61c111feac939193c47f812190Daniel U        start(text, new Bullet());
9978b36c0bbd1503c61c111feac939193c47f812190Daniel U        startCssStyle(text, attributes);
9988b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
9998b36c0bbd1503c61c111feac939193c47f812190Daniel U
10008b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static void endLi(Editable text) {
10018b36c0bbd1503c61c111feac939193c47f812190Daniel U        endCssStyle(text);
10028b36c0bbd1503c61c111feac939193c47f812190Daniel U        endBlockElement(text);
10038b36c0bbd1503c61c111feac939193c47f812190Daniel U        end(text, Bullet.class, new BulletSpan());
10048b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
10058b36c0bbd1503c61c111feac939193c47f812190Daniel U
1006cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private void startBlockquote(Editable text, Attributes attributes) {
1007cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        startBlockElement(text, attributes, getMarginBlockquote());
1008cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        start(text, new Blockquote());
1009cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
1010cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1011cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void endBlockquote(Editable text) {
1012cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        endBlockElement(text);
1013cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        end(text, Blockquote.class, new QuoteSpan());
10149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
10159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1016cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private void startHeading(Editable text, Attributes attributes, int level) {
1017cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        startBlockElement(text, attributes, getMarginHeading());
1018cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        start(text, new Heading(level));
1019cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
1020cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1021cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void endHeading(Editable text) {
1022cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        // RelativeSizeSpan and StyleSpan are CharacterStyles
1023cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        // Their ranges should not include the newlines at the end
1024cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        Heading h = getLast(text, Heading.class);
1025cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (h != null) {
1026cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            setSpanFromMark(text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]),
1027cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                    new StyleSpan(Typeface.BOLD));
1028cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
1029cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1030cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        endBlockElement(text);
1031cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
1032cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1033cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static <T> T getLast(Spanned text, Class<T> kind) {
10349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        /*
10359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * This knows that the last returned object from getSpans()
10369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * will be the most recently added.
10379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         */
1038cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        T[] objs = text.getSpans(0, text.length(), kind);
10399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
10409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (objs.length == 0) {
10419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return null;
10429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else {
10439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return objs[objs.length - 1];
10449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
10459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
10469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1047cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void setSpanFromMark(Spannable text, Object mark, Object... spans) {
1048cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        int where = text.getSpanStart(mark);
1049cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        text.removeSpan(mark);
1050cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        int len = text.length();
1051cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (where != len) {
1052cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            for (Object span : spans) {
1053cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1054cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            }
1055cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
1056cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
1057cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1058cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void start(Editable text, Object mark) {
10599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int len = text.length();
10601eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
10619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
10629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1063cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void end(Editable text, Class kind, Object repl) {
10649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int len = text.length();
10659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        Object obj = getLast(text, kind);
1066cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (obj != null) {
1067cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            setSpanFromMark(text, obj, repl);
10689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
10699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
10709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
10711eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    private void startCssStyle(Editable text, Attributes attributes) {
10728b36c0bbd1503c61c111feac939193c47f812190Daniel U        String style = attributes.getValue("", "style");
10738b36c0bbd1503c61c111feac939193c47f812190Daniel U        if (style != null) {
10748b36c0bbd1503c61c111feac939193c47f812190Daniel U            Matcher m = getForegroundColorPattern().matcher(style);
10758b36c0bbd1503c61c111feac939193c47f812190Daniel U            if (m.find()) {
10761eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                int c = getHtmlColor(m.group(1));
10778b36c0bbd1503c61c111feac939193c47f812190Daniel U                if (c != -1) {
10781eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                    start(text, new Foreground(c | 0xFF000000));
10798b36c0bbd1503c61c111feac939193c47f812190Daniel U                }
10808b36c0bbd1503c61c111feac939193c47f812190Daniel U            }
10818b36c0bbd1503c61c111feac939193c47f812190Daniel U
10828b36c0bbd1503c61c111feac939193c47f812190Daniel U            m = getBackgroundColorPattern().matcher(style);
10838b36c0bbd1503c61c111feac939193c47f812190Daniel U            if (m.find()) {
10841eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                int c = getHtmlColor(m.group(1));
10858b36c0bbd1503c61c111feac939193c47f812190Daniel U                if (c != -1) {
10861eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                    start(text, new Background(c | 0xFF000000));
10878b36c0bbd1503c61c111feac939193c47f812190Daniel U                }
10888b36c0bbd1503c61c111feac939193c47f812190Daniel U            }
10898b36c0bbd1503c61c111feac939193c47f812190Daniel U
10908b36c0bbd1503c61c111feac939193c47f812190Daniel U            m = getTextDecorationPattern().matcher(style);
10918b36c0bbd1503c61c111feac939193c47f812190Daniel U            if (m.find()) {
10928b36c0bbd1503c61c111feac939193c47f812190Daniel U                String textDecoration = m.group(1);
10938b36c0bbd1503c61c111feac939193c47f812190Daniel U                if (textDecoration.equalsIgnoreCase("line-through")) {
10941eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                    start(text, new Strikethrough());
10958b36c0bbd1503c61c111feac939193c47f812190Daniel U                }
10968b36c0bbd1503c61c111feac939193c47f812190Daniel U            }
10978b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
10988b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
10998b36c0bbd1503c61c111feac939193c47f812190Daniel U
11008b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static void endCssStyle(Editable text) {
11018b36c0bbd1503c61c111feac939193c47f812190Daniel U        Strikethrough s = getLast(text, Strikethrough.class);
11028b36c0bbd1503c61c111feac939193c47f812190Daniel U        if (s != null) {
11038b36c0bbd1503c61c111feac939193c47f812190Daniel U            setSpanFromMark(text, s, new StrikethroughSpan());
11048b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
11058b36c0bbd1503c61c111feac939193c47f812190Daniel U
11068b36c0bbd1503c61c111feac939193c47f812190Daniel U        Background b = getLast(text, Background.class);
11078b36c0bbd1503c61c111feac939193c47f812190Daniel U        if (b != null) {
11088b36c0bbd1503c61c111feac939193c47f812190Daniel U            setSpanFromMark(text, b, new BackgroundColorSpan(b.mBackgroundColor));
11098b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
11108b36c0bbd1503c61c111feac939193c47f812190Daniel U
11118b36c0bbd1503c61c111feac939193c47f812190Daniel U        Foreground f = getLast(text, Foreground.class);
11128b36c0bbd1503c61c111feac939193c47f812190Daniel U        if (f != null) {
11138b36c0bbd1503c61c111feac939193c47f812190Daniel U            setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor));
11148b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
11158b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
11168b36c0bbd1503c61c111feac939193c47f812190Daniel U
1117cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void startImg(Editable text, Attributes attributes, Html.ImageGetter img) {
11189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        String src = attributes.getValue("", "src");
11199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        Drawable d = null;
11209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (img != null) {
11229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            d = img.getDrawable(src);
11239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
11249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (d == null) {
11269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            d = Resources.getSystem().
11279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    getDrawable(com.android.internal.R.drawable.unknown_image);
11289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
11299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
11309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int len = text.length();
11329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        text.append("\uFFFC");
11339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        text.setSpan(new ImageSpan(d, src), len, text.length(),
11359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
11369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
11379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11381eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    private void startFont(Editable text, Attributes attributes) {
11399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        String color = attributes.getValue("", "color");
11409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        String face = attributes.getValue("", "face");
11419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11421eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        if (!TextUtils.isEmpty(color)) {
11431eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            int c = getHtmlColor(color);
11441eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            if (c != -1) {
11451eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                start(text, new Foreground(c | 0xFF000000));
11461eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            }
11471eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        }
11481eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U
11491eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        if (!TextUtils.isEmpty(face)) {
11501eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            start(text, new Font(face));
11511eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        }
11529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
11539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1154cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void endFont(Editable text) {
11551eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        Font font = getLast(text, Font.class);
11561eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        if (font != null) {
11571eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            setSpanFromMark(text, font, new TypefaceSpan(font.mFace));
11581eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        }
11599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11601eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        Foreground foreground = getLast(text, Foreground.class);
11611eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        if (foreground != null) {
11621eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            setSpanFromMark(text, foreground,
11631eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                    new ForegroundColorSpan(foreground.mForegroundColor));
11649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
11659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
11669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1167cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void startA(Editable text, Attributes attributes) {
11689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        String href = attributes.getValue("", "href");
11691eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        start(text, new Href(href));
11709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
11719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1172cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static void endA(Editable text) {
1173cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        Href h = getLast(text, Href.class);
1174cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        if (h != null) {
11759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            if (h.mHref != null) {
1176cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U                setSpanFromMark(text, h, new URLSpan((h.mHref)));
11779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
11789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
11799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
11809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11811eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    private int getHtmlColor(String color) {
11821eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        if ((mFlags & Html.FROM_HTML_OPTION_USE_CSS_COLORS)
11831eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                == Html.FROM_HTML_OPTION_USE_CSS_COLORS) {
11841eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            Integer i = sColorMap.get(color.toLowerCase(Locale.US));
11851eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            if (i != null) {
11861eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U                return i;
11871eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U            }
11881eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        }
11891eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        return Color.getHtmlColor(color);
11901eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U    }
11911eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U
11929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void setDocumentLocator(Locator locator) {
11939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
11949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void startDocument() throws SAXException {
11969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
11979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
11989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void endDocument() throws SAXException {
11999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void startPrefixMapping(String prefix, String uri) throws SAXException {
12029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void endPrefixMapping(String prefix) throws SAXException {
12059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void startElement(String uri, String localName, String qName, Attributes attributes)
12089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            throws SAXException {
12099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        handleStartTag(localName, attributes);
12109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void endElement(String uri, String localName, String qName) throws SAXException {
12139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        handleEndTag(localName);
12149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void characters(char ch[], int start, int length) throws SAXException {
12179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        StringBuilder sb = new StringBuilder();
12189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        /*
12209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * Ignore whitespace that immediately follows other whitespace;
12219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         * newlines count as spaces.
12229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project         */
12239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        for (int i = 0; i < length; i++) {
12259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            char c = ch[i + start];
12269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            if (c == ' ' || c == '\n') {
12289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                char pred;
12299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                int len = sb.length();
12309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (len == 0) {
12329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    len = mSpannableStringBuilder.length();
12339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (len == 0) {
12359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        pred = '\n';
12369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    } else {
12379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        pred = mSpannableStringBuilder.charAt(len - 1);
12389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
12399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                } else {
12409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    pred = sb.charAt(len - 1);
12419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
12429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (pred != ' ' && pred != '\n') {
12449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    sb.append(' ');
12459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
12469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else {
12479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                sb.append(c);
12489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
12499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
12509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mSpannableStringBuilder.append(sb);
12529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
12559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void processingInstruction(String target, String data) throws SAXException {
12589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void skippedEntity(String name) throws SAXException {
12619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Bold { }
12649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Italic { }
12659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Underline { }
12665e9ed365ade5b0e24c2f6bd61f6dc36fb0f66c0fRoozbeh Pournader    private static class Strikethrough { }
12679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Big { }
12689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Small { }
12699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Monospace { }
12709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Blockquote { }
12719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Super { }
12729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Sub { }
12738b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static class Bullet { }
12749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Font {
12769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public String mFace;
12779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12781eab97ac8edec59e3c0453af7c56e1a751e3fa85Daniel U        public Font(String face) {
12799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mFace = face;
12809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
12819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static class Href {
12849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public String mHref;
12859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public Href(String href) {
12879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mHref = href;
12889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
12899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
12909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
12918b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static class Foreground {
12928b36c0bbd1503c61c111feac939193c47f812190Daniel U        private int mForegroundColor;
12938b36c0bbd1503c61c111feac939193c47f812190Daniel U
12948b36c0bbd1503c61c111feac939193c47f812190Daniel U        public Foreground(int foregroundColor) {
12958b36c0bbd1503c61c111feac939193c47f812190Daniel U            mForegroundColor = foregroundColor;
12968b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
12978b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
12988b36c0bbd1503c61c111feac939193c47f812190Daniel U
12998b36c0bbd1503c61c111feac939193c47f812190Daniel U    private static class Background {
13008b36c0bbd1503c61c111feac939193c47f812190Daniel U        private int mBackgroundColor;
13018b36c0bbd1503c61c111feac939193c47f812190Daniel U
13028b36c0bbd1503c61c111feac939193c47f812190Daniel U        public Background(int backgroundColor) {
13038b36c0bbd1503c61c111feac939193c47f812190Daniel U            mBackgroundColor = backgroundColor;
13048b36c0bbd1503c61c111feac939193c47f812190Daniel U        }
13058b36c0bbd1503c61c111feac939193c47f812190Daniel U    }
13068b36c0bbd1503c61c111feac939193c47f812190Daniel U
1307cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static class Heading {
13089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        private int mLevel;
13099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1310cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        public Heading(int level) {
13119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mLevel = level;
13129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
13139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1314cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1315cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static class Newline {
1316cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        private int mNumNewlines;
1317cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1318cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        public Newline(int numNewlines) {
1319cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            mNumNewlines = numNewlines;
1320cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
1321cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
1322cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1323cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    private static class Alignment {
1324cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        private Layout.Alignment mAlignment;
1325cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U
1326cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        public Alignment(Layout.Alignment alignment) {
1327cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U            mAlignment = alignment;
1328cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U        }
1329cf1fa60331b6ce935788c06fcdfd09817e104c8cDaniel U    }
13309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
1331