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