StringBlock.java revision a9f1dd021f8f6ee777bc4d27913bd40c42e753af
1/*
2 * Copyright (C) 2006 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 android.content.res;
18
19import android.text.*;
20import android.text.style.*;
21import android.util.Config;
22import android.util.Log;
23import android.util.SparseArray;
24import android.graphics.Paint;
25import android.graphics.Rect;
26import android.graphics.Typeface;
27import com.android.internal.util.XmlUtils;
28
29/**
30 * Conveniences for retrieving data out of a compiled string resource.
31 *
32 * {@hide}
33 */
34final class StringBlock {
35    private static final String TAG = "AssetManager";
36    private static final boolean localLOGV = Config.LOGV || false;
37
38    private final int mNative;
39    private final boolean mUseSparse;
40    private final boolean mOwnsNative;
41    private CharSequence[] mStrings;
42    private SparseArray<CharSequence> mSparseStrings;
43    StyleIDs mStyleIDs = null;
44
45    public StringBlock(byte[] data, boolean useSparse) {
46        mNative = nativeCreate(data, 0, data.length);
47        mUseSparse = useSparse;
48        mOwnsNative = true;
49        if (localLOGV) Log.v(TAG, "Created string block " + this
50                + ": " + nativeGetSize(mNative));
51    }
52
53    public StringBlock(byte[] data, int offset, int size, boolean useSparse) {
54        mNative = nativeCreate(data, offset, size);
55        mUseSparse = useSparse;
56        mOwnsNative = true;
57        if (localLOGV) Log.v(TAG, "Created string block " + this
58                + ": " + nativeGetSize(mNative));
59    }
60
61    public CharSequence get(int idx) {
62        synchronized (this) {
63            if (mStrings != null) {
64                CharSequence res = mStrings[idx];
65                if (res != null) {
66                    return res;
67                }
68            } else if (mSparseStrings != null) {
69                CharSequence res = mSparseStrings.get(idx);
70                if (res != null) {
71                    return res;
72                }
73            } else {
74                final int num = nativeGetSize(mNative);
75                if (mUseSparse && num > 250) {
76                    mSparseStrings = new SparseArray<CharSequence>();
77                } else {
78                    mStrings = new CharSequence[num];
79                }
80            }
81            String str = nativeGetString(mNative, idx);
82            CharSequence res = str;
83            int[] style = nativeGetStyle(mNative, idx);
84            if (localLOGV) Log.v(TAG, "Got string: " + str);
85            if (localLOGV) Log.v(TAG, "Got styles: " + style);
86            if (style != null) {
87                if (mStyleIDs == null) {
88                    mStyleIDs = new StyleIDs();
89                    mStyleIDs.boldId = nativeIndexOfString(mNative, "b");
90                    mStyleIDs.italicId = nativeIndexOfString(mNative, "i");
91                    mStyleIDs.underlineId = nativeIndexOfString(mNative, "u");
92                    mStyleIDs.ttId = nativeIndexOfString(mNative, "tt");
93                    mStyleIDs.bigId = nativeIndexOfString(mNative, "big");
94                    mStyleIDs.smallId = nativeIndexOfString(mNative, "small");
95                    mStyleIDs.supId = nativeIndexOfString(mNative, "sup");
96                    mStyleIDs.subId = nativeIndexOfString(mNative, "sub");
97                    mStyleIDs.strikeId = nativeIndexOfString(mNative, "strike");
98                    mStyleIDs.listItemId = nativeIndexOfString(mNative, "li");
99                    mStyleIDs.marqueeId = nativeIndexOfString(mNative, "marquee");
100
101                    if (localLOGV) Log.v(TAG, "BoldId=" + mStyleIDs.boldId
102                            + ", ItalicId=" + mStyleIDs.italicId
103                            + ", UnderlineId=" + mStyleIDs.underlineId);
104                }
105
106                res = applyStyles(str, style, mStyleIDs);
107            }
108            if (mStrings != null) mStrings[idx] = res;
109            else mSparseStrings.put(idx, res);
110            return res;
111        }
112    }
113
114    protected void finalize() throws Throwable {
115        if (mOwnsNative) {
116            nativeDestroy(mNative);
117        }
118    }
119
120    static final class StyleIDs {
121        private int boldId;
122        private int italicId;
123        private int underlineId;
124        private int ttId;
125        private int bigId;
126        private int smallId;
127        private int subId;
128        private int supId;
129        private int strikeId;
130        private int listItemId;
131        private int marqueeId;
132    }
133
134    private CharSequence applyStyles(String str, int[] style, StyleIDs ids) {
135        if (style.length == 0)
136            return str;
137
138        SpannableString buffer = new SpannableString(str);
139        int i=0;
140        while (i < style.length) {
141            int type = style[i];
142            if (localLOGV) Log.v(TAG, "Applying style span id=" + type
143                    + ", start=" + style[i+1] + ", end=" + style[i+2]);
144
145
146            if (type == ids.boldId) {
147                buffer.setSpan(new StyleSpan(Typeface.BOLD),
148                               style[i+1], style[i+2]+1,
149                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
150            } else if (type == ids.italicId) {
151                buffer.setSpan(new StyleSpan(Typeface.ITALIC),
152                               style[i+1], style[i+2]+1,
153                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
154            } else if (type == ids.underlineId) {
155                buffer.setSpan(new UnderlineSpan(),
156                               style[i+1], style[i+2]+1,
157                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
158            } else if (type == ids.ttId) {
159                buffer.setSpan(new TypefaceSpan("monospace"),
160                               style[i+1], style[i+2]+1,
161                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
162            } else if (type == ids.bigId) {
163                buffer.setSpan(new RelativeSizeSpan(1.25f),
164                               style[i+1], style[i+2]+1,
165                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
166            } else if (type == ids.smallId) {
167                buffer.setSpan(new RelativeSizeSpan(0.8f),
168                               style[i+1], style[i+2]+1,
169                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
170            } else if (type == ids.subId) {
171                buffer.setSpan(new SubscriptSpan(),
172                               style[i+1], style[i+2]+1,
173                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
174            } else if (type == ids.supId) {
175                buffer.setSpan(new SuperscriptSpan(),
176                               style[i+1], style[i+2]+1,
177                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
178            } else if (type == ids.strikeId) {
179                buffer.setSpan(new StrikethroughSpan(),
180                               style[i+1], style[i+2]+1,
181                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
182            } else if (type == ids.listItemId) {
183                addParagraphSpan(buffer, new BulletSpan(10),
184                                style[i+1], style[i+2]+1);
185            } else if (type == ids.marqueeId) {
186                buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
187                               style[i+1], style[i+2]+1,
188                               Spannable.SPAN_INCLUSIVE_INCLUSIVE);
189            } else {
190                String tag = nativeGetString(mNative, type);
191
192                if (tag.startsWith("font;")) {
193                    String sub;
194
195                    sub = subtag(tag, ";height=");
196                    if (sub != null) {
197                        int size = Integer.parseInt(sub);
198                        addParagraphSpan(buffer, new Height(size),
199                                       style[i+1], style[i+2]+1);
200                    }
201
202                    sub = subtag(tag, ";size=");
203                    if (sub != null) {
204                        int size = Integer.parseInt(sub);
205                        buffer.setSpan(new AbsoluteSizeSpan(size, true),
206                                       style[i+1], style[i+2]+1,
207                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
208                    }
209
210                    sub = subtag(tag, ";fgcolor=");
211                    if (sub != null) {
212                        int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
213                        buffer.setSpan(new ForegroundColorSpan(color),
214                                       style[i+1], style[i+2]+1,
215                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
216                    }
217
218                    sub = subtag(tag, ";bgcolor=");
219                    if (sub != null) {
220                        int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
221                        buffer.setSpan(new BackgroundColorSpan(color),
222                                       style[i+1], style[i+2]+1,
223                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
224                    }
225                } else if (tag.startsWith("a;")) {
226                    String sub;
227
228                    sub = subtag(tag, ";href=");
229                    if (sub != null) {
230                        buffer.setSpan(new URLSpan(sub),
231                                       style[i+1], style[i+2]+1,
232                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
233                    }
234                } else if (tag.startsWith("annotation;")) {
235                    int len = tag.length();
236                    int next;
237
238                    for (int t = tag.indexOf(';'); t < len; t = next) {
239                        int eq = tag.indexOf('=', t);
240                        if (eq < 0) {
241                            break;
242                        }
243
244                        next = tag.indexOf(';', eq);
245                        if (next < 0) {
246                            next = len;
247                        }
248
249                        String key = tag.substring(t + 1, eq);
250                        String value = tag.substring(eq + 1, next);
251
252                        buffer.setSpan(new Annotation(key, value),
253                                       style[i+1], style[i+2]+1,
254                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
255                    }
256                }
257            }
258
259            i += 3;
260        }
261        return new SpannedString(buffer);
262    }
263
264    /**
265     * If a translator has messed up the edges of paragraph-level markup,
266     * fix it to actually cover the entire paragraph that it is attached to
267     * instead of just whatever range they put it on.
268     */
269    private static void addParagraphSpan(Spannable buffer, Object what,
270                                         int start, int end) {
271        int len = buffer.length();
272
273        if (start != 0 && start != len && buffer.charAt(start - 1) != '\n') {
274            for (start--; start > 0; start--) {
275                if (buffer.charAt(start - 1) == '\n') {
276                    break;
277                }
278            }
279        }
280
281        if (end != 0 && end != len && buffer.charAt(end - 1) != '\n') {
282            for (end++; end < len; end++) {
283                if (buffer.charAt(end - 1) == '\n') {
284                    break;
285                }
286            }
287        }
288
289        buffer.setSpan(what, start, end, Spannable.SPAN_PARAGRAPH);
290    }
291
292    private static String subtag(String full, String attribute) {
293        int start = full.indexOf(attribute);
294        if (start < 0) {
295            return null;
296        }
297
298        start += attribute.length();
299        int end = full.indexOf(';', start);
300
301        if (end < 0) {
302            return full.substring(start);
303        } else {
304            return full.substring(start, end);
305        }
306    }
307
308    /**
309     * Forces the text line to be the specified height, shrinking/stretching
310     * the ascent if possible, or the descent if shrinking the ascent further
311     * will make the text unreadable.
312     */
313    private static class Height implements LineHeightSpan.WithDensity {
314        private int mSize;
315        private static float sProportion = 0;
316
317        public Height(int size) {
318            mSize = size;
319        }
320
321        public void chooseHeight(CharSequence text, int start, int end,
322                                 int spanstartv, int v,
323                                 Paint.FontMetricsInt fm) {
324            // Should not get called, at least not by StaticLayout.
325            chooseHeight(text, start, end, spanstartv, v, fm, null);
326        }
327
328        public void chooseHeight(CharSequence text, int start, int end,
329                                 int spanstartv, int v,
330                                 Paint.FontMetricsInt fm, TextPaint paint) {
331            int size = mSize;
332            if (paint != null) {
333                size *= paint.density;
334            }
335
336            if (fm.bottom - fm.top < size) {
337                fm.top = fm.bottom - size;
338                fm.ascent = fm.ascent - size;
339            } else {
340                if (sProportion == 0) {
341                    /*
342                     * Calculate what fraction of the nominal ascent
343                     * the height of a capital letter actually is,
344                     * so that we won't reduce the ascent to less than
345                     * that unless we absolutely have to.
346                     */
347
348                    Paint p = new Paint();
349                    p.setTextSize(100);
350                    Rect r = new Rect();
351                    p.getTextBounds("ABCDEFG", 0, 7, r);
352
353                    sProportion = (r.top) / p.ascent();
354                }
355
356                int need = (int) Math.ceil(-fm.top * sProportion);
357
358                if (size - fm.descent >= need) {
359                    /*
360                     * It is safe to shrink the ascent this much.
361                     */
362
363                    fm.top = fm.bottom - size;
364                    fm.ascent = fm.descent - size;
365                } else if (size >= need) {
366                    /*
367                     * We can't show all the descent, but we can at least
368                     * show all the ascent.
369                     */
370
371                    fm.top = fm.ascent = -need;
372                    fm.bottom = fm.descent = fm.top + size;
373                } else {
374                    /*
375                     * Show as much of the ascent as we can, and no descent.
376                     */
377
378                    fm.top = fm.ascent = -size;
379                    fm.bottom = fm.descent = 0;
380                }
381            }
382        }
383    }
384
385    /**
386     * Create from an existing string block native object.  This is
387     * -extremely- dangerous -- only use it if you absolutely know what you
388     *  are doing!  The given native object must exist for the entire lifetime
389     *  of this newly creating StringBlock.
390     */
391    StringBlock(int obj, boolean useSparse) {
392        mNative = obj;
393        mUseSparse = useSparse;
394        mOwnsNative = false;
395        if (localLOGV) Log.v(TAG, "Created string block " + this
396                + ": " + nativeGetSize(mNative));
397    }
398
399    private static final native int nativeCreate(byte[] data,
400                                                 int offset,
401                                                 int size);
402    private static final native int nativeGetSize(int obj);
403    private static final native String nativeGetString(int obj, int idx);
404    private static final native int[] nativeGetStyle(int obj, int idx);
405    private static final native int nativeIndexOfString(int obj, String str);
406    private static final native void nativeDestroy(int obj);
407}
408