blob: 23a6f97c37dba46ee8c46f91f97186482929014b [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
Dianne Hackborn2269d1572010-02-24 19:54:22 -080027
28import com.android.internal.util.XmlUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029
30/**
31 * Conveniences for retrieving data out of a compiled string resource.
32 *
33 * {@hide}
34 */
35final class StringBlock {
36 private static final String TAG = "AssetManager";
37 private static final boolean localLOGV = Config.LOGV || false;
38
39 private final int 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
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
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
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: " + style);
87 if (style != null) {
88 if (mStyleIDs == null) {
89 mStyleIDs = new StyleIDs();
Peter Eliassonac4a1812010-11-18 02:43:30 +010090 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091
Peter Eliassonac4a1812010-11-18 02:43:30 +010092 // 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 }
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
142 protected void finalize() throws Throwable {
143 if (mOwnsNative) {
144 nativeDestroy(mNative);
145 }
146 }
147
148 static final class StyleIDs {
Peter Eliassonac4a1812010-11-18 02:43:30 +0100149 private int boldId = -1;
150 private int italicId = -1;
151 private int underlineId = -1;
152 private int ttId = -1;
153 private int bigId = -1;
154 private int smallId = -1;
155 private int subId = -1;
156 private int supId = -1;
157 private int strikeId = -1;
158 private int listItemId = -1;
159 private int marqueeId = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 }
161
162 private CharSequence applyStyles(String str, int[] style, StyleIDs ids) {
163 if (style.length == 0)
164 return str;
165
166 SpannableString buffer = new SpannableString(str);
167 int i=0;
168 while (i < style.length) {
169 int type = style[i];
170 if (localLOGV) Log.v(TAG, "Applying style span id=" + type
171 + ", start=" + style[i+1] + ", end=" + style[i+2]);
172
173
174 if (type == ids.boldId) {
175 buffer.setSpan(new StyleSpan(Typeface.BOLD),
176 style[i+1], style[i+2]+1,
177 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
178 } else if (type == ids.italicId) {
179 buffer.setSpan(new StyleSpan(Typeface.ITALIC),
180 style[i+1], style[i+2]+1,
181 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
182 } else if (type == ids.underlineId) {
183 buffer.setSpan(new UnderlineSpan(),
184 style[i+1], style[i+2]+1,
185 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
186 } else if (type == ids.ttId) {
187 buffer.setSpan(new TypefaceSpan("monospace"),
188 style[i+1], style[i+2]+1,
189 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
190 } else if (type == ids.bigId) {
191 buffer.setSpan(new RelativeSizeSpan(1.25f),
192 style[i+1], style[i+2]+1,
193 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
194 } else if (type == ids.smallId) {
195 buffer.setSpan(new RelativeSizeSpan(0.8f),
196 style[i+1], style[i+2]+1,
197 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
198 } else if (type == ids.subId) {
199 buffer.setSpan(new SubscriptSpan(),
200 style[i+1], style[i+2]+1,
201 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
202 } else if (type == ids.supId) {
203 buffer.setSpan(new SuperscriptSpan(),
204 style[i+1], style[i+2]+1,
205 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
206 } else if (type == ids.strikeId) {
207 buffer.setSpan(new StrikethroughSpan(),
208 style[i+1], style[i+2]+1,
209 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
210 } else if (type == ids.listItemId) {
211 addParagraphSpan(buffer, new BulletSpan(10),
212 style[i+1], style[i+2]+1);
213 } else if (type == ids.marqueeId) {
214 buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
215 style[i+1], style[i+2]+1,
216 Spannable.SPAN_INCLUSIVE_INCLUSIVE);
217 } else {
218 String tag = nativeGetString(mNative, type);
219
220 if (tag.startsWith("font;")) {
221 String sub;
222
223 sub = subtag(tag, ";height=");
224 if (sub != null) {
225 int size = Integer.parseInt(sub);
226 addParagraphSpan(buffer, new Height(size),
227 style[i+1], style[i+2]+1);
228 }
229
230 sub = subtag(tag, ";size=");
231 if (sub != null) {
232 int size = Integer.parseInt(sub);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700233 buffer.setSpan(new AbsoluteSizeSpan(size, true),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 style[i+1], style[i+2]+1,
235 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
236 }
237
238 sub = subtag(tag, ";fgcolor=");
239 if (sub != null) {
240 int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
241 buffer.setSpan(new ForegroundColorSpan(color),
242 style[i+1], style[i+2]+1,
243 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
244 }
245
246 sub = subtag(tag, ";bgcolor=");
247 if (sub != null) {
248 int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
249 buffer.setSpan(new BackgroundColorSpan(color),
250 style[i+1], style[i+2]+1,
251 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
252 }
253 } else if (tag.startsWith("a;")) {
254 String sub;
255
256 sub = subtag(tag, ";href=");
257 if (sub != null) {
258 buffer.setSpan(new URLSpan(sub),
259 style[i+1], style[i+2]+1,
260 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
261 }
262 } else if (tag.startsWith("annotation;")) {
263 int len = tag.length();
264 int next;
265
266 for (int t = tag.indexOf(';'); t < len; t = next) {
267 int eq = tag.indexOf('=', t);
268 if (eq < 0) {
269 break;
270 }
271
272 next = tag.indexOf(';', eq);
273 if (next < 0) {
274 next = len;
275 }
276
277 String key = tag.substring(t + 1, eq);
278 String value = tag.substring(eq + 1, next);
279
280 buffer.setSpan(new Annotation(key, value),
281 style[i+1], style[i+2]+1,
282 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
283 }
284 }
285 }
286
287 i += 3;
288 }
289 return new SpannedString(buffer);
290 }
291
292 /**
293 * If a translator has messed up the edges of paragraph-level markup,
294 * fix it to actually cover the entire paragraph that it is attached to
295 * instead of just whatever range they put it on.
296 */
297 private static void addParagraphSpan(Spannable buffer, Object what,
298 int start, int end) {
299 int len = buffer.length();
300
301 if (start != 0 && start != len && buffer.charAt(start - 1) != '\n') {
302 for (start--; start > 0; start--) {
303 if (buffer.charAt(start - 1) == '\n') {
304 break;
305 }
306 }
307 }
308
309 if (end != 0 && end != len && buffer.charAt(end - 1) != '\n') {
310 for (end++; end < len; end++) {
311 if (buffer.charAt(end - 1) == '\n') {
312 break;
313 }
314 }
315 }
316
317 buffer.setSpan(what, start, end, Spannable.SPAN_PARAGRAPH);
318 }
319
320 private static String subtag(String full, String attribute) {
321 int start = full.indexOf(attribute);
322 if (start < 0) {
323 return null;
324 }
325
326 start += attribute.length();
327 int end = full.indexOf(';', start);
328
329 if (end < 0) {
330 return full.substring(start);
331 } else {
332 return full.substring(start, end);
333 }
334 }
335
336 /**
337 * Forces the text line to be the specified height, shrinking/stretching
338 * the ascent if possible, or the descent if shrinking the ascent further
339 * will make the text unreadable.
340 */
Eric Fischera9f1dd02009-08-12 15:00:10 -0700341 private static class Height implements LineHeightSpan.WithDensity {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 private int mSize;
343 private static float sProportion = 0;
344
345 public Height(int size) {
346 mSize = size;
347 }
348
349 public void chooseHeight(CharSequence text, int start, int end,
350 int spanstartv, int v,
351 Paint.FontMetricsInt fm) {
Eric Fischera9f1dd02009-08-12 15:00:10 -0700352 // Should not get called, at least not by StaticLayout.
353 chooseHeight(text, start, end, spanstartv, v, fm, null);
354 }
355
356 public void chooseHeight(CharSequence text, int start, int end,
357 int spanstartv, int v,
358 Paint.FontMetricsInt fm, TextPaint paint) {
359 int size = mSize;
360 if (paint != null) {
361 size *= paint.density;
362 }
363
364 if (fm.bottom - fm.top < size) {
365 fm.top = fm.bottom - size;
366 fm.ascent = fm.ascent - size;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 } else {
368 if (sProportion == 0) {
369 /*
370 * Calculate what fraction of the nominal ascent
371 * the height of a capital letter actually is,
372 * so that we won't reduce the ascent to less than
373 * that unless we absolutely have to.
374 */
375
376 Paint p = new Paint();
377 p.setTextSize(100);
378 Rect r = new Rect();
379 p.getTextBounds("ABCDEFG", 0, 7, r);
380
381 sProportion = (r.top) / p.ascent();
382 }
383
384 int need = (int) Math.ceil(-fm.top * sProportion);
385
Eric Fischera9f1dd02009-08-12 15:00:10 -0700386 if (size - fm.descent >= need) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 /*
388 * It is safe to shrink the ascent this much.
389 */
390
Eric Fischera9f1dd02009-08-12 15:00:10 -0700391 fm.top = fm.bottom - size;
392 fm.ascent = fm.descent - size;
393 } else if (size >= need) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 /*
395 * We can't show all the descent, but we can at least
396 * show all the ascent.
397 */
398
399 fm.top = fm.ascent = -need;
Eric Fischera9f1dd02009-08-12 15:00:10 -0700400 fm.bottom = fm.descent = fm.top + size;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800401 } else {
402 /*
403 * Show as much of the ascent as we can, and no descent.
404 */
405
Eric Fischera9f1dd02009-08-12 15:00:10 -0700406 fm.top = fm.ascent = -size;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407 fm.bottom = fm.descent = 0;
408 }
409 }
410 }
411 }
412
413 /**
414 * Create from an existing string block native object. This is
415 * -extremely- dangerous -- only use it if you absolutely know what you
416 * are doing! The given native object must exist for the entire lifetime
417 * of this newly creating StringBlock.
418 */
419 StringBlock(int obj, boolean useSparse) {
420 mNative = obj;
421 mUseSparse = useSparse;
422 mOwnsNative = false;
423 if (localLOGV) Log.v(TAG, "Created string block " + this
424 + ": " + nativeGetSize(mNative));
425 }
426
427 private static final native int nativeCreate(byte[] data,
428 int offset,
429 int size);
430 private static final native int nativeGetSize(int obj);
431 private static final native String nativeGetString(int obj, int idx);
432 private static final native int[] nativeGetStyle(int obj, int idx);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 private static final native void nativeDestroy(int obj);
434}