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.Config; 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 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 = 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 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 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 finalize()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 applyStyles(String str, int[] style, StyleIDs ids)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); 205 buffer.setSpan(new AbsoluteSizeSpan(size, true), 206 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 */ addParagraphSpan(Spannable buffer, Object what, int start, int end)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 subtag(String full, String attribute)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 */ 313 private static class Height implements LineHeightSpan.WithDensity { 314 private int mSize; 315 private static float sProportion = 0; 316 Height(int size)317 public Height(int size) { 318 mSize = size; 319 } 320 chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm)321 public void chooseHeight(CharSequence text, int start, int end, 322 int spanstartv, int v, 323 Paint.FontMetricsInt fm) { 324 // Should not get called, at least not by StaticLayout. 325 chooseHeight(text, start, end, spanstartv, v, fm, null); 326 } 327 chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm, TextPaint paint)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; 339 } 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 358 if (size - fm.descent >= need) { 359 /* 360 * It is safe to shrink the ascent this much. 361 */ 362 363 fm.top = fm.bottom - size; 364 fm.ascent = fm.descent - size; 365 } else if (size >= need) { 366 /* 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; 372 fm.bottom = fm.descent = fm.top + size; 373 } else { 374 /* 375 * Show as much of the ascent as we can, and no descent. 376 */ 377 378 fm.top = fm.ascent = -size; 379 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 */ StringBlock(int obj, boolean useSparse)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 nativeCreate(byte[] data, int offset, int size)399 private static final native int nativeCreate(byte[] data, 400 int offset, 401 int size); nativeGetSize(int obj)402 private static final native int nativeGetSize(int obj); nativeGetString(int obj, int idx)403 private static final native String nativeGetString(int obj, int idx); nativeGetStyle(int obj, int idx)404 private static final native int[] nativeGetStyle(int obj, int idx); nativeIndexOfString(int obj, String str)405 private static final native int nativeIndexOfString(int obj, String str); nativeDestroy(int obj)406 private static final native void nativeDestroy(int obj); 407 } 408