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
17package com.android.contacts.util;
18
19import com.android.contacts.R;
20import com.google.common.annotations.VisibleForTesting;
21
22import android.content.Context;
23import android.content.res.Resources;
24import android.text.Html;
25import android.text.Html.ImageGetter;
26import android.text.Html.TagHandler;
27import android.text.SpannableStringBuilder;
28import android.text.Spanned;
29import android.text.TextUtils;
30import android.text.style.ImageSpan;
31import android.text.style.QuoteSpan;
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 */
38public 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