SpannableStringUtils.java revision 330d2720bb3b0b6333ec2f61f056a4f46cbfa106
1/*
2 * Copyright (C) 2013 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.inputmethod.latin.utils;
18
19import android.text.Spannable;
20import android.text.SpannableString;
21import android.text.Spanned;
22import android.text.SpannedString;
23import android.text.TextUtils;
24import android.text.style.SuggestionSpan;
25
26public final class SpannableStringUtils {
27    /**
28     * Copies the spans from the region <code>start...end</code> in
29     * <code>source</code> to the region
30     * <code>destoff...destoff+end-start</code> in <code>dest</code>.
31     * Spans in <code>source</code> that begin before <code>start</code>
32     * or end after <code>end</code> but overlap this range are trimmed
33     * as if they began at <code>start</code> or ended at <code>end</code>.
34     * Only SuggestionSpans that don't have the SPAN_PARAGRAPH span are copied.
35     *
36     * This code is almost entirely taken from {@link TextUtils#copySpansFrom}, except for the
37     * kind of span that is copied.
38     *
39     * @throws IndexOutOfBoundsException if any of the copied spans
40     * are out of range in <code>dest</code>.
41     */
42    public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
43            Spannable dest, int destoff) {
44        Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
45
46        for (int i = 0; i < spans.length; i++) {
47            int fl = source.getSpanFlags(spans[i]);
48            // We don't care about the PARAGRAPH flag in LatinIME code. However, if this flag
49            // is set, Spannable#setSpan will throw an exception unless the span is on the edge
50            // of a word. But the spans have been split into two by the getText{Before,After}Cursor
51            // methods, so after concatenation they may end in the middle of a word.
52            // Since we don't use them, we can just remove them and avoid crashing.
53            fl &= ~Spannable.SPAN_PARAGRAPH;
54
55            int st = source.getSpanStart(spans[i]);
56            int en = source.getSpanEnd(spans[i]);
57
58            if (st < start)
59                st = start;
60            if (en > end)
61                en = end;
62
63            dest.setSpan(spans[i], st - start + destoff, en - start + destoff,
64                         fl);
65        }
66    }
67
68    /**
69     * Returns a CharSequence concatenating the specified CharSequences, retaining their
70     * SuggestionSpans that don't have the PARAGRAPH flag, but not other spans.
71     *
72     * This code is almost entirely taken from {@link TextUtils#concat(CharSequence...)}, except
73     * it calls copyNonParagraphSuggestionSpansFrom instead of {@link TextUtils#copySpansFrom}.
74     */
75    public static CharSequence concatWithNonParagraphSuggestionSpansOnly(CharSequence... text) {
76        if (text.length == 0) {
77            return "";
78        }
79
80        if (text.length == 1) {
81            return text[0];
82        }
83
84        boolean spanned = false;
85        for (int i = 0; i < text.length; i++) {
86            if (text[i] instanceof Spanned) {
87                spanned = true;
88                break;
89            }
90        }
91
92        StringBuilder sb = new StringBuilder();
93        for (int i = 0; i < text.length; i++) {
94            sb.append(text[i]);
95        }
96
97        if (!spanned) {
98            return sb.toString();
99        }
100
101        SpannableString ss = new SpannableString(sb);
102        int off = 0;
103        for (int i = 0; i < text.length; i++) {
104            int len = text[i].length();
105
106            if (text[i] instanceof Spanned) {
107                copyNonParagraphSuggestionSpansFrom((Spanned) text[i], 0, len, ss, off);
108            }
109
110            off += len;
111        }
112
113        return new SpannedString(ss);
114    }
115}
116