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