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