Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.util;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.text.Html;
     22 import android.text.Html.ImageGetter;
     23 import android.text.Html.TagHandler;
     24 import android.text.SpannableStringBuilder;
     25 import android.text.Spanned;
     26 import android.text.TextUtils;
     27 import android.text.style.ImageSpan;
     28 import android.text.style.QuoteSpan;
     29 
     30 import com.android.contacts.R;
     31 import com.google.common.annotations.VisibleForTesting;
     32 
     33 /**
     34  * Provides static functions to perform custom HTML to text conversions.
     35  * Specifically, it adjusts the color and padding of the vertical
     36  * stripe on block quotes and alignment of inlined images.
     37  */
     38 public class HtmlUtils {
     39 
     40     /**
     41      * Converts HTML string to a {@link Spanned} text, adjusting formatting. Any extra new line
     42      * characters at the end of the text will be trimmed.
     43      */
     44     public static Spanned fromHtml(Context context, String text) {
     45         if (TextUtils.isEmpty(text)) {
     46             return null;
     47         }
     48         Spanned spanned = Html.fromHtml(text);
     49         return postprocess(context, spanned);
     50     }
     51 
     52     /**
     53      * Converts HTML string to a {@link Spanned} text, adjusting formatting and using a custom
     54      * image getter. Any extra new line characters at the end of the text will be trimmed.
     55      */
     56     public static CharSequence fromHtml(Context context, String text, ImageGetter imageGetter,
     57             TagHandler tagHandler) {
     58         if (TextUtils.isEmpty(text)) {
     59             return null;
     60         }
     61         return postprocess(context, Html.fromHtml(text, imageGetter, tagHandler));
     62     }
     63 
     64     /**
     65      * Replaces some spans with custom versions of those. Any extra new line characters at the end
     66      * of the text will be trimmed.
     67      */
     68     @VisibleForTesting
     69     static Spanned postprocess(Context context, Spanned original) {
     70         if (original == null) {
     71             return null;
     72         }
     73         final int length = original.length();
     74         if (length == 0) {
     75             return original; // Bail early.
     76         }
     77 
     78         // If it's a SpannableStringBuilder, just use it.  Otherwise, create a new
     79         // SpannableStringBuilder based on the passed Spanned.
     80         final SpannableStringBuilder builder;
     81         if (original instanceof SpannableStringBuilder) {
     82             builder = (SpannableStringBuilder) original;
     83         } else {
     84             builder = new SpannableStringBuilder(original);
     85         }
     86 
     87         final QuoteSpan[] quoteSpans = builder.getSpans(0, length, QuoteSpan.class);
     88         if (quoteSpans != null && quoteSpans.length != 0) {
     89             Resources resources = context.getResources();
     90             int color = resources.getColor(R.color.stream_item_stripe_color);
     91             int width = resources.getDimensionPixelSize(R.dimen.stream_item_stripe_width);
     92             for (int i = 0; i < quoteSpans.length; i++) {
     93                 replaceSpan(builder, quoteSpans[i], new StreamItemQuoteSpan(color, width));
     94             }
     95         }
     96 
     97         final ImageSpan[] imageSpans = builder.getSpans(0, length, ImageSpan.class);
     98         if (imageSpans != null) {
     99             for (int i = 0; i < imageSpans.length; i++) {
    100                 ImageSpan span = imageSpans[i];
    101                 replaceSpan(builder, span, new ImageSpan(span.getDrawable(),
    102                         ImageSpan.ALIGN_BASELINE));
    103             }
    104         }
    105 
    106         // Trim the trailing new line characters at the end of the text (which can be added
    107         // when HTML block quote tags are turned into new line characters).
    108         int end = length;
    109         for (int i = builder.length() - 1; i >= 0; i--) {
    110             if (builder.charAt(i) != '\n') {
    111                 break;
    112             }
    113             end = i;
    114         }
    115 
    116         // If there's no trailing newlines, just return it.
    117         if (end == length) {
    118             return builder;
    119         }
    120 
    121         // Otherwise, Return a substring of the original {@link Spanned} text
    122         // from the start index (inclusive) to the end index (exclusive).
    123         return new SpannableStringBuilder(builder, 0, end);
    124     }
    125 
    126     /**
    127      * Replaces one span with the other.
    128      */
    129     private static void replaceSpan(SpannableStringBuilder builder, Object originalSpan,
    130             Object newSpan) {
    131         builder.setSpan(newSpan,
    132                 builder.getSpanStart(originalSpan),
    133                 builder.getSpanEnd(originalSpan),
    134                 builder.getSpanFlags(originalSpan));
    135         builder.removeSpan(originalSpan);
    136     }
    137 
    138     public static class StreamItemQuoteSpan extends QuoteSpan {
    139         private final int mWidth;
    140 
    141         public StreamItemQuoteSpan(int color, int width) {
    142             super(color);
    143             this.mWidth = width;
    144         }
    145 
    146         /**
    147          * {@inheritDoc}
    148          */
    149         @Override
    150         public int getLeadingMargin(boolean first) {
    151             return mWidth;
    152         }
    153     }
    154 }
    155