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