blob: 2411177327c24a7280e203a4996840618b40a6d8 [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;
Tom Taylord4a47292009-12-21 13:59:18 -080027import com.android.common.XmlUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028
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);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700205 buffer.setSpan(new AbsoluteSizeSpan(size, true),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 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 */
Eric Fischera9f1dd02009-08-12 15:00:10 -0700313 private static class Height implements LineHeightSpan.WithDensity {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 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) {
Eric Fischera9f1dd02009-08-12 15:00:10 -0700324 // 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 } 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
Eric Fischera9f1dd02009-08-12 15:00:10 -0700358 if (size - fm.descent >= need) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 /*
360 * It is safe to shrink the ascent this much.
361 */
362
Eric Fischera9f1dd02009-08-12 15:00:10 -0700363 fm.top = fm.bottom - size;
364 fm.ascent = fm.descent - size;
365 } else if (size >= need) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 /*
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;
Eric Fischera9f1dd02009-08-12 15:00:10 -0700372 fm.bottom = fm.descent = fm.top + size;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 } else {
374 /*
375 * Show as much of the ascent as we can, and no descent.
376 */
377
Eric Fischera9f1dd02009-08-12 15:00:10 -0700378 fm.top = fm.ascent = -size;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 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}