• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.content.res;
18 
19 import android.text.*;
20 import android.text.style.*;
21 import android.util.Config;
22 import android.util.Log;
23 import android.util.SparseArray;
24 import android.graphics.Paint;
25 import android.graphics.Rect;
26 import android.graphics.Typeface;
27 import com.android.internal.util.XmlUtils;
28 
29 /**
30  * Conveniences for retrieving data out of a compiled string resource.
31  *
32  * {@hide}
33  */
34 final 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 
StringBlock(byte[] data, boolean useSparse)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 
StringBlock(byte[] data, int offset, int size, boolean useSparse)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 
get(int idx)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 
finalize()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 
applyStyles(String str, int[] style, StyleIDs ids)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      */
addParagraphSpan(Spannable buffer, Object what, int start, int end)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 
subtag(String full, String attribute)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 
Height(int size)317         public Height(int size) {
318             mSize = size;
319         }
320 
chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm)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 
chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm, TextPaint paint)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      */
StringBlock(int obj, boolean useSparse)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 
nativeCreate(byte[] data, int offset, int size)399     private static final native int nativeCreate(byte[] data,
400                                                  int offset,
401                                                  int size);
nativeGetSize(int obj)402     private static final native int nativeGetSize(int obj);
nativeGetString(int obj, int idx)403     private static final native String nativeGetString(int obj, int idx);
nativeGetStyle(int obj, int idx)404     private static final native int[] nativeGetStyle(int obj, int idx);
nativeIndexOfString(int obj, String str)405     private static final native int nativeIndexOfString(int obj, String str);
nativeDestroy(int obj)406     private static final native void nativeDestroy(int obj);
407 }
408