diff --git a/application/.classpath b/application/.classpath index ad0e3de..609aa00 100644 --- a/application/.classpath +++ b/application/.classpath @@ -3,6 +3,5 @@ - diff --git a/application/src/org/yaaic/model/Message.java b/application/src/org/yaaic/model/Message.java index a39773a..b02284b 100644 --- a/application/src/org/yaaic/model/Message.java +++ b/application/src/org/yaaic/model/Message.java @@ -23,15 +23,12 @@ package org.yaaic.model; import java.util.Date; import org.yaaic.utils.Colors; -import org.yaaic.utils.Html2; import android.content.Context; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.text.Spannable; import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; import android.widget.TextView; @@ -231,19 +228,12 @@ public class Message String nick = hasSender() ? "<" + sender + "> " : ""; String timestamp = settings.showTimestamp() ? renderTimeStamp(settings.use24hFormat()) : ""; if (settings.showMircColors()) { - // Tagsoup doesn't like when a html string begins with a tag so we'll surround the html with
 tags.
-                String entext = "
"+TextUtils.htmlEncode(text).replaceAll(" ", " ")+"
"; - String htmltext = Colors.mircColorParser(entext); - Spanned colortext = Html2.fromHtml(htmltext); - - canvas = new SpannableString(prefix + timestamp + nick); - canvas = new SpannableString(TextUtils.concat(canvas, colortext)); + canvas = new SpannableString(prefix + timestamp + nick + Colors.mircColorParserSpannable(text)); } else { canvas = new SpannableString(prefix + timestamp + nick + Colors.removeStyleAndColors(text)); } - if (hasSender()) { int start = (prefix + timestamp).length() + 1; int end = start + sender.length(); @@ -259,7 +249,7 @@ public class Message canvas.setSpan(new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } if (hasColor() && settings.showColors()) { - // Only apply the foreground color on areas that don't already have a foreground color. + // Only apply the foreground color to areas that don't already have a foreground color. ForegroundColorSpan[] spans = canvas.getSpans(0, canvas.length(), ForegroundColorSpan.class); int start = 0; for (int i = 0; i < spans.length; i++) { diff --git a/application/src/org/yaaic/utils/Colors.java b/application/src/org/yaaic/utils/Colors.java index 5d553a4..00fb05e 100644 --- a/application/src/org/yaaic/utils/Colors.java +++ b/application/src/org/yaaic/utils/Colors.java @@ -1,106 +1,141 @@ package org.yaaic.utils; +import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; -import android.util.Log; +import android.graphics.Typeface; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.BackgroundColorSpan; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; public class Colors { /* * Colors from the "Classic" theme in mIRC. */ - public static final String[] colors = { - "#FFFFFF", // White - "#000000", // Black - "#00007F", // Blue (navy) - "#009300", // Green - "#FC0000", // Red - "#7F0000", // Brown (maroon) - "#9C009C", // Purple - "#FC7F00", // Orange (olive) - "#FFFF00", // Yellow - "#00FC00", // Light Green (lime) - "#008080", // Teal (a green/blue cyan) - "#00FFFF", // Light Cyan (cyan) (aqua) - "#0000FF", // Light Blue (royal) - "#FF00FF", // Pink (light purple) (fuchsia) - "#7F7F7F", // Grey - "#D2D2D2" // Light Grey (silver) + public static final int[] colors = { + 0xFFFFFF, // White + 0x000000, // Black + 0x00007F, // Blue (navy) + 0x009300, // Green + 0xFC0000, // Red + 0x7F0000, // Brown (maroon) + 0x9C009C, // Purple + 0xFC7F00, // Orange (olive) + 0xFFFF00, // Yellow + 0x00FC00, // Light Green (lime) + 0x008080, // Teal (a green/blue cyan) + 0x00FFFF, // Light Cyan (cyan) (aqua) + 0x0000FF, // Light Blue (royal) + 0xFF00FF, // Pink (light purple) (fuchsia) + 0x7F7F7F, // Grey + 0xD2D2D2 // Light Grey (silver) }; - private static final Pattern boldPattern = Pattern.compile("\\x02([^\\x02\\x0F]*)(?:\\x02|(\\x0F))?"); - private static final Pattern underlinePattern = Pattern.compile("\\x1F([^\\x1F\\x0F]*)(?:\\x1F|(\\x0F))?"); - private static final Pattern italicPattern = Pattern.compile("\\x1D([^\\x1D\\x0F]*)(?:\\x1D|(\\x0F))?"); - private static final Pattern inversePattern = Pattern.compile("\\x16([^\\x16\\x0F]*)(?:\\x16|(\\x0F))?"); + private static final Pattern boldPattern = Pattern.compile("\\x02([^\\x02\\x0F]*)(\\x02|(\\x0F))?"); + private static final Pattern underlinePattern = Pattern.compile("\\x1F([^\\x1F\\x0F]*)(\\x1F|(\\x0F))?"); + private static final Pattern italicPattern = Pattern.compile("\\x1D([^\\x1D\\x0F]*)(\\x1D|(\\x0F))?"); + private static final Pattern inversePattern = Pattern.compile("\\x16([^\\x16\\x0F]*)(\\x16|(\\x0F))?"); private static final Pattern colorPattern = Pattern.compile("\\x03(\\d{1,2})(?:,(\\d{1,2}))?([^\\x03\\x0F]*)(\\x03|\\x0F)?"); private static final Pattern cleanupPattern = Pattern.compile("(?:\\x02|\\x1F|\\x1D|\\x0F|\\x16|\\x03(?:(?:\\d{1,2})(?:,\\d{1,2})?)?)"); private Colors() {} /** - * Converts a string with mIRC color codes to a HTML string. + * Converts a string with mIRC style and color codes to a SpannableString with + * all the style and color codes applied. * * @param text A string with mIRC color codes. - * @return HTML string. + * @return A SpannableString with all the styles applied. */ - public static String mircColorParser(String text) { - text = replaceControlCodes(boldPattern.matcher(text), "", ""); - text = replaceControlCodes(underlinePattern.matcher(text), "", ""); - text = replaceControlCodes(italicPattern.matcher(text), "", ""); - // Inverse assumes that the background is black and the foreground is white. - text = replaceControlCodes(inversePattern.matcher(text), "", ""); + public static SpannableString mircColorParserSpannable(String text) { + SpannableStringBuilder ssb = new SpannableStringBuilder(text); + replaceControlCodes(boldPattern.matcher(ssb), ssb, new StyleSpan(Typeface.BOLD)); + replaceControlCodes(underlinePattern.matcher(ssb), ssb, new UnderlineSpan()); + replaceControlCodes(italicPattern.matcher(ssb), ssb, new StyleSpan(Typeface.ITALIC)); - StringBuffer sb = new StringBuffer(text); - StringBuilder ft = new StringBuilder(); - Matcher m = colorPattern.matcher(text); + /* + * Inverse assumes that the background is black and the foreground is white. + * We apply the background color first and then apply the foreground color + * to all the parts where BackgroundColorSpans are found. + */ + replaceControlCodes(inversePattern.matcher(ssb), ssb, new BackgroundColorSpan(colors[0] | 0xFF000000)); + BackgroundColorSpan[] inverseSpans = ssb.getSpans(0, ssb.length(), BackgroundColorSpan.class); + for (int i = 0; i < inverseSpans.length; i++) { + ssb.setSpan(new ForegroundColorSpan(colors[1] | 0xFF000000), ssb.getSpanStart(inverseSpans[i]),ssb.getSpanEnd(inverseSpans[i]), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + Matcher m = colorPattern.matcher(ssb); while (m.find()) { - sb.delete(0, sb.length()); - ft.delete(0, ft.length()); + int start = m.start(); + int end = m.end(); - // Build the font tag - ft.append("= 0) { - ft.append(" color=\""+colors[color]+"\""); + ssb.setSpan(new ForegroundColorSpan(colors[color] | 0xFF000000), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } if (m.group(2) != null) { color = Integer.parseInt(m.group(2)); if (color <= 15 && color >= 0) { - ft.append(" bgcolor=\""+colors[color]+"\""); + ssb.setSpan(new BackgroundColorSpan(colors[color] | 0xFF000000), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } + codelength = codelength + m.group(2).length() + 1; } - ft.append(">"); - ft.append(m.group(3)); - ft.append("
"); - if (m.group(4) != null) { - ft.append(m.group(4)); - } - m.appendReplacement(sb, ft.toString()); - m.appendTail(sb); - m.reset(sb.toString()); + ssb.delete(start, start+codelength); + // Reset the matcher with the modified text so that the ending color code character can be matched again. + m.reset(ssb); } - // Remove left over codes - Log.d("html", removeStyleAndColors(sb.toString())); - return removeStyleAndColors(sb.toString()); + return new SpannableString(removeStyleAndColors(ssb)); } - private static String replaceControlCodes(Matcher m, String startTag, String endTag) { - /* - * matcher(...).replaceAll("\1\2") inserts "null" if the second - * capture group isn't found so we'll do it this way instead. - */ - StringBuffer sb = new StringBuffer(); + private static void replaceControlCodes(Matcher m, SpannableStringBuilder ssb, CharacterStyle style) { + ArrayList toremove = new ArrayList(); while (m.find()) { - m.appendReplacement(sb, startTag+m.group(1)+endTag+(m.group(2) == null ? "" : m.group(2))); + toremove.add(0, m.start()); + // Remove the ending control character unless it's \x0F + if (m.group(2) != null && m.group(2) != m.group(3)) { + toremove.add(0, m.end()-1); + } + ssb.setSpan(style, m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + for (Integer i : toremove) { + ssb.delete(i, i+1); } - m.appendTail(sb); - - return sb.toString(); } + /** + * Removes mIRC color and style codes and returns the message without them. + * + * @param text A message with mirc colors and styles. + * @return The same message with all the colors and styles removed. + */ public static String removeStyleAndColors(String text) { return cleanupPattern.matcher(text).replaceAll(""); } + + /** + * Removes mIRC color and style codes and returns the message without them. + * + * @param text A message with mirc colors and styles. + * @return The same message with all the colors and styles removed. + */ + public static SpannableStringBuilder removeStyleAndColors(SpannableStringBuilder text) { + ArrayList toremove = new ArrayList(); + Matcher m = cleanupPattern.matcher(text); + while (m.find()) { + toremove.add(0, new int[] {m.start(), m.end()}); + } + for (int[] i : toremove) { + text.delete(i[0], i[1]); + } + return text; + } } \ No newline at end of file diff --git a/application/src/org/yaaic/utils/Html2.java b/application/src/org/yaaic/utils/Html2.java deleted file mode 100644 index 0443418..0000000 --- a/application/src/org/yaaic/utils/Html2.java +++ /dev/null @@ -1,978 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.yaaic.utils; - -import java.io.IOException; -import java.io.StringReader; -import java.util.HashMap; - -import org.ccil.cowan.tagsoup.HTMLModels; -import org.ccil.cowan.tagsoup.HTMLSchema; -import org.ccil.cowan.tagsoup.Parser; -import org.ccil.cowan.tagsoup.Schema; -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.InputSource; -import org.xml.sax.Locator; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.yaaic.R; - -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.text.Editable; -import android.text.Layout; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.AbsoluteSizeSpan; -import android.text.style.AlignmentSpan; -import android.text.style.BackgroundColorSpan; -import android.text.style.CharacterStyle; -import android.text.style.ForegroundColorSpan; -import android.text.style.ImageSpan; -import android.text.style.ParagraphStyle; -import android.text.style.QuoteSpan; -import android.text.style.RelativeSizeSpan; -import android.text.style.StrikethroughSpan; -import android.text.style.StyleSpan; -import android.text.style.SubscriptSpan; -import android.text.style.SuperscriptSpan; -import android.text.style.TextAppearanceSpan; -import android.text.style.TypefaceSpan; -import android.text.style.URLSpan; -import android.text.style.UnderlineSpan; -import android.util.Log; - -/** - * This class processes HTML strings into displayable styled text. - * Not all HTML tags are supported. - * - * The only difference between Html2 and android.text.Html is that Html2 adds support for - * a bgcolor attribute in the font tag. - */ -public class Html2 { - /** - * Retrieves images for HTML <img> tags. - */ - public static interface ImageGetter { - /** - * This methos is called when the HTML parser encounters an - * <img> tag. The source argument is the - * string from the "src" attribute; the return value should be - * a Drawable representation of the image or null - * for a generic replacement image. Make sure you call - * setBounds() on your Drawable if it doesn't already have - * its bounds set. - */ - public Drawable getDrawable(String source); - } - - /** - * Is notified when HTML tags are encountered that the parser does - * not know how to interpret. - */ - public static interface TagHandler { - /** - * This method will be called when the HTML parser encounters - * a tag that it does not know how to interpret. - */ - public void handleTag(boolean opening, String tag, - Editable output, XMLReader xmlReader); - } - - private Html2() { } - private static HTMLSchema schema = new HTMLSchema(); - - /** - * Returns displayable styled text from the provided HTML string. - * Any <img> tags in the HTML will display as a generic - * replacement image which your program can then go through and - * replace with real images. - * - *

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

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. - */ - public static Spanned fromHtml(String source, ImageGetter imageGetter, - TagHandler tagHandler) { - Parser parser = new Parser(); - // Make the font tag restartable - schema.elementType("font", Schema.M_PCDATA|HTMLModels.M_INLINE, HTMLModels.M_INLINE|HTMLModels.M_NOLINK, Schema.F_RESTART); - try { - parser.setProperty(Parser.schemaProperty, schema); - } catch (org.xml.sax.SAXNotRecognizedException e) { - // Should not happen. - throw new RuntimeException(e); - } catch (org.xml.sax.SAXNotSupportedException e) { - // Should not happen. - throw new RuntimeException(e); - } - - HtmlToSpannedConverter converter = - new HtmlToSpannedConverter(source, imageGetter, tagHandler, - parser); - return converter.convert(); - } - - /** - * Returns an HTML representation of the provided Spanned text. - */ - public static String toHtml(Spanned text) { - StringBuilder out = new StringBuilder(); - withinHtml(out, text); - return out.toString(); - } - - private static void withinHtml(StringBuilder out, Spanned text) { - int len = text.length(); - - int next; - for (int i = 0; i < text.length(); i = next) { - next = text.nextSpanTransition(i, len, ParagraphStyle.class); - ParagraphStyle[] style = text.getSpans(i, next, ParagraphStyle.class); - String elements = " "; - boolean needDiv = false; - - for(int j = 0; j < style.length; j++) { - if (style[j] instanceof AlignmentSpan) { - Layout.Alignment align = - ((AlignmentSpan) style[j]).getAlignment(); - needDiv = true; - if (align == Layout.Alignment.ALIGN_CENTER) { - elements = "align=\"center\" " + elements; - } else if (align == Layout.Alignment.ALIGN_OPPOSITE) { - elements = "align=\"right\" " + elements; - } else { - elements = "align=\"left\" " + elements; - } - } - } - if (needDiv) { - out.append("

"); - } - - withinDiv(out, text, i, next); - - if (needDiv) { - out.append("
"); - } - } - } - - private static void withinDiv(StringBuilder out, Spanned text, - int start, int end) { - int next; - for (int i = start; i < end; i = next) { - next = text.nextSpanTransition(i, end, QuoteSpan.class); - QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class); - - for (QuoteSpan quote: quotes) { - out.append("
"); - } - - withinBlockquote(out, text, i, next); - - for (QuoteSpan quote: quotes) { - out.append("
\n"); - } - } - } - - private static void withinBlockquote(StringBuilder out, Spanned text, - int start, int end) { - out.append("

"); - - int next; - for (int i = start; i < end; i = next) { - next = TextUtils.indexOf(text, '\n', i, end); - if (next < 0) { - next = end; - } - - int nl = 0; - - while (next < end && text.charAt(next) == '\n') { - nl++; - next++; - } - - withinParagraph(out, text, i, next - nl, nl, next == end); - } - - out.append("

\n"); - } - - private static void withinParagraph(StringBuilder out, Spanned text, - int start, int end, int nl, - boolean last) { - int next; - for (int i = start; i < end; i = next) { - next = text.nextSpanTransition(i, end, CharacterStyle.class); - CharacterStyle[] style = text.getSpans(i, next, - CharacterStyle.class); - - for (int j = 0; j < style.length; j++) { - if (style[j] instanceof StyleSpan) { - int s = ((StyleSpan) style[j]).getStyle(); - - if ((s & Typeface.BOLD) != 0) { - out.append(""); - } - if ((s & Typeface.ITALIC) != 0) { - out.append(""); - } - } - if (style[j] instanceof TypefaceSpan) { - String s = ((TypefaceSpan) style[j]).getFamily(); - - if (s.equals("monospace")) { - out.append(""); - } - } - if (style[j] instanceof SuperscriptSpan) { - out.append(""); - } - if (style[j] instanceof SubscriptSpan) { - out.append(""); - } - if (style[j] instanceof UnderlineSpan) { - out.append(""); - } - if (style[j] instanceof StrikethroughSpan) { - out.append(""); - } - if (style[j] instanceof URLSpan) { - out.append(""); - } - if (style[j] instanceof ImageSpan) { - out.append(""); - - // Don't output the dummy character underlying the image. - i = next; - } - if (style[j] instanceof AbsoluteSizeSpan) { - out.append(""); - } - if (style[j] instanceof ForegroundColorSpan) { - out.append(""); - } - if (style[j] instanceof BackgroundColorSpan) { - out.append(""); - } - } - - withinStyle(out, text, i, next); - - for (int j = style.length - 1; j >= 0; j--) { - if (style[j] instanceof ForegroundColorSpan) { - out.append(""); - } - if (style[j] instanceof BackgroundColorSpan) { - out.append(""); - } - if (style[j] instanceof AbsoluteSizeSpan) { - out.append(""); - } - if (style[j] instanceof URLSpan) { - out.append(""); - } - if (style[j] instanceof StrikethroughSpan) { - out.append(""); - } - if (style[j] instanceof UnderlineSpan) { - out.append(""); - } - if (style[j] instanceof SubscriptSpan) { - out.append(""); - } - if (style[j] instanceof SuperscriptSpan) { - out.append(""); - } - if (style[j] instanceof TypefaceSpan) { - String s = ((TypefaceSpan) style[j]).getFamily(); - - if (s.equals("monospace")) { - out.append(""); - } - } - if (style[j] instanceof StyleSpan) { - int s = ((StyleSpan) style[j]).getStyle(); - - if ((s & Typeface.BOLD) != 0) { - out.append(""); - } - if ((s & Typeface.ITALIC) != 0) { - out.append(""); - } - } - } - } - - String p = last ? "" : "

\n

"; - - if (nl == 1) { - out.append("
\n"); - } else if (nl == 2) { - out.append(p); - } else { - for (int i = 2; i < nl; i++) { - out.append("
"); - } - - out.append(p); - } - } - - private static void withinStyle(StringBuilder out, Spanned text, - int start, int end) { - for (int i = start; i < end; i++) { - char c = text.charAt(i); - - if (c == '<') { - out.append("<"); - } else if (c == '>') { - out.append(">"); - } else if (c == '&') { - out.append("&"); - } else if (c > 0x7E || c < ' ') { - out.append("&#" + ((int) c) + ";"); - } else if (c == ' ') { - while (i + 1 < end && text.charAt(i + 1) == ' ') { - out.append(" "); - i++; - } - - out.append(' '); - } else { - out.append(c); - } - } - } -} - -class HtmlToSpannedConverter implements ContentHandler { - - private static final float[] HEADER_SIZES = { - 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f, - }; - - private final String mSource; - private final XMLReader mReader; - private final SpannableStringBuilder mSpannableStringBuilder; - private final Html2.ImageGetter mImageGetter; - private final Html2.TagHandler mTagHandler; - - public HtmlToSpannedConverter( - String source, Html2.ImageGetter imageGetter, Html2.TagHandler tagHandler, - Parser parser) { - mSource = source; - mSpannableStringBuilder = new SpannableStringBuilder(); - mImageGetter = imageGetter; - mTagHandler = tagHandler; - mReader = parser; - } - - public Spanned convert() { - - mReader.setContentHandler(this); - try { - mReader.parse(new InputSource(new StringReader(mSource))); - } catch (IOException e) { - // We are reading from a string. There should not be IO problems. - throw new RuntimeException(e); - } catch (SAXException e) { - // TagSoup doesn't throw parse exceptions. - throw new RuntimeException(e); - } - - // Fix flags and range for paragraph-type markup. - Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class); - for (int i = 0; i < obj.length; i++) { - int start = mSpannableStringBuilder.getSpanStart(obj[i]); - int end = mSpannableStringBuilder.getSpanEnd(obj[i]); - - // If the last line of the range is blank, back off by one. - if (end - 2 >= 0) { - if (mSpannableStringBuilder.charAt(end - 1) == '\n' && - mSpannableStringBuilder.charAt(end - 2) == '\n') { - end--; - } - } - - if (end == start) { - mSpannableStringBuilder.removeSpan(obj[i]); - } else { - mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH); - } - } - - return mSpannableStringBuilder; - } - - private void handleStartTag(String tag, Attributes attributes) { - Log.d("colors", "handleStartTag: "+tag); - if (tag.equalsIgnoreCase("br")) { - // We don't need to handle this. TagSoup will ensure that there's a
for each
- // so we can safely emite the linebreaks when we handle the close tag. - } else if (tag.equalsIgnoreCase("p")) { - handleP(mSpannableStringBuilder); - } else if (tag.equalsIgnoreCase("div")) { - handleP(mSpannableStringBuilder); - } else if (tag.equalsIgnoreCase("em")) { - start(mSpannableStringBuilder, new Bold()); - } else if (tag.equalsIgnoreCase("b")) { - start(mSpannableStringBuilder, new Bold()); - } else if (tag.equalsIgnoreCase("strong")) { - start(mSpannableStringBuilder, new Italic()); - } else if (tag.equalsIgnoreCase("cite")) { - start(mSpannableStringBuilder, new Italic()); - } else if (tag.equalsIgnoreCase("dfn")) { - start(mSpannableStringBuilder, new Italic()); - } else if (tag.equalsIgnoreCase("i")) { - start(mSpannableStringBuilder, new Italic()); - } else if (tag.equalsIgnoreCase("big")) { - start(mSpannableStringBuilder, new Big()); - } else if (tag.equalsIgnoreCase("small")) { - start(mSpannableStringBuilder, new Small()); - } else if (tag.equalsIgnoreCase("font")) { - startFont(mSpannableStringBuilder, attributes); - } else if (tag.equalsIgnoreCase("blockquote")) { - handleP(mSpannableStringBuilder); - start(mSpannableStringBuilder, new Blockquote()); - } else if (tag.equalsIgnoreCase("tt")) { - start(mSpannableStringBuilder, new Monospace()); - } else if (tag.equalsIgnoreCase("a")) { - startA(mSpannableStringBuilder, attributes); - } else if (tag.equalsIgnoreCase("u")) { - start(mSpannableStringBuilder, new Underline()); - } else if (tag.equalsIgnoreCase("sup")) { - start(mSpannableStringBuilder, new Super()); - } else if (tag.equalsIgnoreCase("sub")) { - start(mSpannableStringBuilder, new Sub()); - } else if (tag.length() == 2 && - Character.toLowerCase(tag.charAt(0)) == 'h' && - tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { - handleP(mSpannableStringBuilder); - start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1')); - } else if (tag.equalsIgnoreCase("img")) { - startImg(mSpannableStringBuilder, attributes, mImageGetter); - } else if (mTagHandler != null) { - mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader); - } - } - - private void handleEndTag(String tag) { - Log.d("colors", "handleEndTag: "+tag); - if (tag.equalsIgnoreCase("br")) { - handleBr(mSpannableStringBuilder); - } else if (tag.equalsIgnoreCase("p")) { - handleP(mSpannableStringBuilder); - } else if (tag.equalsIgnoreCase("div")) { - handleP(mSpannableStringBuilder); - } else if (tag.equalsIgnoreCase("em")) { - end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); - } else if (tag.equalsIgnoreCase("b")) { - end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); - } else if (tag.equalsIgnoreCase("strong")) { - end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); - } else if (tag.equalsIgnoreCase("cite")) { - end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); - } else if (tag.equalsIgnoreCase("dfn")) { - end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); - } else if (tag.equalsIgnoreCase("i")) { - end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); - } else if (tag.equalsIgnoreCase("big")) { - end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f)); - } else if (tag.equalsIgnoreCase("small")) { - end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f)); - } else if (tag.equalsIgnoreCase("font")) { - endFont(mSpannableStringBuilder); - } else if (tag.equalsIgnoreCase("blockquote")) { - handleP(mSpannableStringBuilder); - end(mSpannableStringBuilder, Blockquote.class, new QuoteSpan()); - } else if (tag.equalsIgnoreCase("tt")) { - end(mSpannableStringBuilder, Monospace.class, - new TypefaceSpan("monospace")); - } else if (tag.equalsIgnoreCase("a")) { - endA(mSpannableStringBuilder); - } else if (tag.equalsIgnoreCase("u")) { - end(mSpannableStringBuilder, Underline.class, new UnderlineSpan()); - } else if (tag.equalsIgnoreCase("sup")) { - end(mSpannableStringBuilder, Super.class, new SuperscriptSpan()); - } else if (tag.equalsIgnoreCase("sub")) { - end(mSpannableStringBuilder, Sub.class, new SubscriptSpan()); - } else if (tag.length() == 2 && - Character.toLowerCase(tag.charAt(0)) == 'h' && - tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { - handleP(mSpannableStringBuilder); - endHeader(mSpannableStringBuilder); - } else if (mTagHandler != null) { - mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader); - } - } - - private static void handleP(SpannableStringBuilder text) { - int len = text.length(); - - if (len >= 1 && text.charAt(len - 1) == '\n') { - if (len >= 2 && text.charAt(len - 2) == '\n') { - return; - } - - text.append("\n"); - return; - } - - if (len != 0) { - text.append("\n\n"); - } - } - - private static void handleBr(SpannableStringBuilder text) { - text.append("\n"); - } - - private static Object getLast(Spanned text, Class kind) { - /* - * This knows that the last returned object from getSpans() - * will be the most recently added. - */ - Object[] objs = text.getSpans(0, text.length(), kind); - - if (objs.length == 0) { - return null; - } else { - return objs[objs.length - 1]; - } - } - - private static void start(SpannableStringBuilder text, Object mark) { - int len = text.length(); - text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK); - } - - private static void end(SpannableStringBuilder text, Class kind, - Object repl) { - int len = text.length(); - Object obj = getLast(text, kind); - int where = text.getSpanStart(obj); - - text.removeSpan(obj); - - if (where != len) { - text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - - return; - } - - private static void startImg(SpannableStringBuilder text, - Attributes attributes, Html2.ImageGetter img) { - String src = attributes.getValue("", "src"); - Drawable d = null; - - if (img != null) { - d = img.getDrawable(src); - } - - if (d == null) { - d = Resources.getSystem(). - getDrawable(R.drawable.unknown_image); - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - } - - int len = text.length(); - text.append("\uFFFC"); - - text.setSpan(new ImageSpan(d, src), len, text.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - - private static void startFont(SpannableStringBuilder text, - Attributes attributes) { - String color = attributes.getValue("", "color"); - String face = attributes.getValue("", "face"); - String bgColor = attributes.getValue("", "bgcolor"); - - int len = text.length(); - text.setSpan(new Font(color, face, bgColor), len, len, Spannable.SPAN_MARK_MARK); - } - - private static void endFont(SpannableStringBuilder text) { - int len = text.length(); - Object obj = getLast(text, Font.class); - int where = text.getSpanStart(obj); - - text.removeSpan(obj); - - if (where != len) { - Font f = (Font) obj; - - if (!TextUtils.isEmpty(f.mColor)) { - if (f.mColor.startsWith("@")) { - Resources res = Resources.getSystem(); - String name = f.mColor.substring(1); - int colorRes = res.getIdentifier(name, "color", "android"); - if (colorRes != 0) { - ColorStateList colors = res.getColorStateList(colorRes); - text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - int c = getHtmlColor(f.mColor); - if (c != -1) { - text.setSpan(new ForegroundColorSpan(c | 0xFF000000), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - - if (!TextUtils.isEmpty(f.mBgColor)) { - if (f.mBgColor.startsWith("@")) { - Resources res = Resources.getSystem(); - String name = f.mBgColor.substring(1); - int colorRes = res.getIdentifier(name, "color", "android"); - if (colorRes != 0) { - ColorStateList colors = res.getColorStateList(colorRes); - text.setSpan(new BackgroundColorSpan(colors.getDefaultColor()), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - int c = getHtmlColor(f.mBgColor); - if (c != -1) { - text.setSpan(new BackgroundColorSpan(c | 0xFF000000), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - - if (f.mFace != null) { - text.setSpan(new TypefaceSpan(f.mFace), where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - - private static void startA(SpannableStringBuilder text, Attributes attributes) { - String href = attributes.getValue("", "href"); - - int len = text.length(); - text.setSpan(new Href(href), len, len, Spannable.SPAN_MARK_MARK); - } - - private static void endA(SpannableStringBuilder text) { - int len = text.length(); - Object obj = getLast(text, Href.class); - int where = text.getSpanStart(obj); - - text.removeSpan(obj); - - if (where != len) { - Href h = (Href) obj; - - if (h.mHref != null) { - text.setSpan(new URLSpan(h.mHref), where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - - private static void endHeader(SpannableStringBuilder text) { - int len = text.length(); - Object obj = getLast(text, Header.class); - - int where = text.getSpanStart(obj); - - text.removeSpan(obj); - - // Back off not to change only the text, not the blank line. - while (len > where && text.charAt(len - 1) == '\n') { - len--; - } - - if (where != len) { - Header h = (Header) obj; - - text.setSpan(new RelativeSizeSpan(HEADER_SIZES[h.mLevel]), - where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - text.setSpan(new StyleSpan(Typeface.BOLD), - where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - - @Override - public void setDocumentLocator(Locator locator) { - } - - @Override - public void startDocument() throws SAXException { - } - - @Override - public void endDocument() throws SAXException { - } - - @Override - public void startPrefixMapping(String prefix, String uri) throws SAXException { - } - - @Override - public void endPrefixMapping(String prefix) throws SAXException { - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - handleStartTag(localName, attributes); - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - handleEndTag(localName); - } - - @Override - public void characters(char ch[], int start, int length) throws SAXException { - StringBuilder sb = new StringBuilder(); - - /* - * Ignore whitespace that immediately follows other whitespace; - * newlines count as spaces. - */ - - for (int i = 0; i < length; i++) { - char c = ch[i + start]; - - if (c == ' ' || c == '\n') { - char pred; - int len = sb.length(); - - if (len == 0) { - len = mSpannableStringBuilder.length(); - - if (len == 0) { - pred = '\n'; - } else { - pred = mSpannableStringBuilder.charAt(len - 1); - } - } else { - pred = sb.charAt(len - 1); - } - - if (pred != ' ' && pred != '\n') { - sb.append(' '); - } - } else { - sb.append(c); - } - } - - mSpannableStringBuilder.append(sb); - } - - @Override - public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { - } - - @Override - public void processingInstruction(String target, String data) throws SAXException { - } - - @Override - public void skippedEntity(String name) throws SAXException { - } - - private static class Bold { } - private static class Italic { } - private static class Underline { } - private static class Big { } - private static class Small { } - private static class Monospace { } - private static class Blockquote { } - private static class Super { } - private static class Sub { } - - private static class Font { - public String mColor; - public String mBgColor; - public String mFace; - - public Font(String color, String face, String bgColor) { - mColor = color; - mFace = face; - mBgColor = bgColor; - } - } - - private static class Href { - public String mHref; - - public Href(String href) { - mHref = href; - } - } - - private static class Header { - private final int mLevel; - - public Header(int level) { - mLevel = level; - } - } - - private static HashMap COLORS = buildColorMap(); - - private static HashMap buildColorMap() { - HashMap map = new HashMap(); - map.put("aqua", 0x00FFFF); - map.put("black", 0x000000); - map.put("blue", 0x0000FF); - map.put("fuchsia", 0xFF00FF); - map.put("green", 0x008000); - map.put("grey", 0x808080); - map.put("lime", 0x00FF00); - map.put("maroon", 0x800000); - map.put("navy", 0x000080); - map.put("olive", 0x808000); - map.put("purple", 0x800080); - map.put("red", 0xFF0000); - map.put("silver", 0xC0C0C0); - map.put("teal", 0x008080); - map.put("white", 0xFFFFFF); - map.put("yellow", 0xFFFF00); - return map; - } - - /** - * Converts an HTML color (named or numeric) to an integer RGB value. - * - * @param color Non-null color string. - * @return A color value, or {@code -1} if the color string could not be interpreted. - */ - private static int getHtmlColor(String color) { - Integer i = COLORS.get(color.toLowerCase()); - if (i != null) { - return i; - } else { - try { - return convertValueToInt(color, -1); - } catch (NumberFormatException nfe) { - return -1; - } - } - } - - /* - * Method ripped from com.android.internal.util.XmlUtils - */ - public static final int - convertValueToInt(CharSequence charSeq, int defaultValue) - { - if (null == charSeq) { - return defaultValue; - } - - String nm = charSeq.toString(); - - // XXX This code is copied from Integer.decode() so we don't - // have to instantiate an Integer! - - int value; - int sign = 1; - int index = 0; - int len = nm.length(); - int base = 10; - - if ('-' == nm.charAt(0)) { - sign = -1; - index++; - } - - if ('0' == nm.charAt(index)) { - // Quick check for a zero by itself - if (index == (len - 1)) { - return 0; - } - - char c = nm.charAt(index + 1); - - if ('x' == c || 'X' == c) { - index += 2; - base = 16; - } else { - index++; - base = 8; - } - } - else if ('#' == nm.charAt(index)) - { - index++; - base = 16; - } - - return Integer.parseInt(nm.substring(index), base) * sign; - } - -} \ No newline at end of file