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 */
16package com.android.contacts.format;
17
18import android.database.CharArrayBuffer;
19import android.graphics.Typeface;
20import android.text.SpannableString;
21import android.text.style.StyleSpan;
22
23import com.android.contacts.test.NeededForTesting;
24
25import java.util.Arrays;
26
27/**
28 * Assorted utility methods related to text formatting in Contacts.
29 */
30public class FormatUtils {
31    private static final char LEFT_TO_RIGHT_EMBEDDING = '\u202A';
32    private static final char POP_DIRECTIONAL_FORMATTING = '\u202C';
33
34    /**
35     * Finds the earliest point in buffer1 at which the first part of buffer2 matches.  For example,
36     * overlapPoint("abcd", "cdef") == 2.
37     */
38    public static int overlapPoint(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
39        if (buffer1 == null || buffer2 == null) {
40            return -1;
41        }
42        return overlapPoint(Arrays.copyOfRange(buffer1.data, 0, buffer1.sizeCopied),
43                Arrays.copyOfRange(buffer2.data, 0, buffer2.sizeCopied));
44    }
45
46    /**
47     * Finds the earliest point in string1 at which the first part of string2 matches.  For example,
48     * overlapPoint("abcd", "cdef") == 2.
49     */
50    @NeededForTesting  // App itself doesn't use this right now, but we don't want to remove it.
51    public static int overlapPoint(String string1, String string2) {
52        if (string1 == null || string2 == null) {
53            return -1;
54        }
55        return overlapPoint(string1.toCharArray(), string2.toCharArray());
56    }
57
58    /**
59     * Finds the earliest point in array1 at which the first part of array2 matches.  For example,
60     * overlapPoint("abcd", "cdef") == 2.
61     */
62    public static int overlapPoint(char[] array1, char[] array2) {
63        if (array1 == null || array2 == null) {
64            return -1;
65        }
66        int count1 = array1.length;
67        int count2 = array2.length;
68
69        // Ignore matching tails of the two arrays.
70        while (count1 > 0 && count2 > 0 && array1[count1 - 1] == array2[count2 - 1]) {
71            count1--;
72            count2--;
73        }
74
75        int size = count2;
76        for (int i = 0; i < count1; i++) {
77            if (i + size > count1) {
78                size = count1 - i;
79            }
80            int j;
81            for (j = 0; j < size; j++) {
82                if (array1[i+j] != array2[j]) {
83                    break;
84                }
85            }
86            if (j == size) {
87                return i;
88            }
89        }
90
91        return -1;
92    }
93
94    /**
95     * Applies the given style to a range of the input CharSequence.
96     * @param style The style to apply (see the style constants in {@link Typeface}).
97     * @param input The CharSequence to style.
98     * @param start Starting index of the range to style (will be clamped to be a minimum of 0).
99     * @param end Ending index of the range to style (will be clamped to a maximum of the input
100     *     length).
101     * @param flags Bitmask for configuring behavior of the span.  See {@link android.text.Spanned}.
102     * @return The styled CharSequence.
103     */
104    public static CharSequence applyStyleToSpan(int style, CharSequence input, int start, int end,
105            int flags) {
106        // Enforce bounds of the char sequence.
107        start = Math.max(0, start);
108        end = Math.min(input.length(), end);
109        SpannableString text = new SpannableString(input);
110        text.setSpan(new StyleSpan(style), start, end, flags);
111        return text;
112    }
113
114    @NeededForTesting
115    public static void copyToCharArrayBuffer(String text, CharArrayBuffer buffer) {
116        if (text != null) {
117            char[] data = buffer.data;
118            if (data == null || data.length < text.length()) {
119                buffer.data = text.toCharArray();
120            } else {
121                text.getChars(0, text.length(), data, 0);
122            }
123            buffer.sizeCopied = text.length();
124        } else {
125            buffer.sizeCopied = 0;
126        }
127    }
128
129    /** Returns a String that represents the content of the given {@link CharArrayBuffer}. */
130    @NeededForTesting
131    public static String charArrayBufferToString(CharArrayBuffer buffer) {
132        return new String(buffer.data, 0, buffer.sizeCopied);
133    }
134
135    /**
136     * Finds the index of the first word that starts with the given prefix.
137     * <p>
138     * If not found, returns -1.
139     *
140     * @param text the text in which to search for the prefix
141     * @param prefix the text to find, in upper case letters
142     */
143    public static int indexOfWordPrefix(CharSequence text, char[] prefix) {
144        if (prefix == null || text == null) {
145            return -1;
146        }
147
148        int textLength = text.length();
149        int prefixLength = prefix.length;
150
151        if (prefixLength == 0 || textLength < prefixLength) {
152            return -1;
153        }
154
155        int i = 0;
156        while (i < textLength) {
157            // Skip non-word characters
158            while (i < textLength && !Character.isLetterOrDigit(text.charAt(i))) {
159                i++;
160            }
161
162            if (i + prefixLength > textLength) {
163                return -1;
164            }
165
166            // Compare the prefixes
167            int j;
168            for (j = 0; j < prefixLength; j++) {
169                if (Character.toUpperCase(text.charAt(i + j)) != prefix[j]) {
170                    break;
171                }
172            }
173            if (j == prefixLength) {
174                return i;
175            }
176
177            // Skip this word
178            while (i < textLength && Character.isLetterOrDigit(text.charAt(i))) {
179                i++;
180            }
181        }
182
183        return -1;
184    }
185
186    /** Returns the given text, forced to be left-to-right. */
187    public static CharSequence forceLeftToRight(CharSequence text) {
188        StringBuilder sb = new StringBuilder();
189        sb.append(LEFT_TO_RIGHT_EMBEDDING);
190        sb.append(text);
191        sb.append(POP_DIRECTIONAL_FORMATTING);
192        return sb.toString();
193    }
194}
195