blob: e684cb8018df9bc1df77d741394496c388e8c539 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.res;
import android.text.*;
import android.text.style.*;
import android.util.Config;
import android.util.Log;
import android.util.SparseArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import com.android.internal.util.XmlUtils;
/**
* Conveniences for retrieving data out of a compiled string resource.
*
* {@hide}
*/
final class StringBlock {
private static final String TAG = "AssetManager";
private static final boolean localLOGV = Config.LOGV || false;
private final int mNative;
private final boolean mUseSparse;
private final boolean mOwnsNative;
private CharSequence[] mStrings;
private SparseArray<CharSequence> mSparseStrings;
StyleIDs mStyleIDs = null;
public StringBlock(byte[] data, boolean useSparse) {
mNative = nativeCreate(data, 0, data.length);
mUseSparse = useSparse;
mOwnsNative = true;
if (localLOGV) Log.v(TAG, "Created string block " + this
+ ": " + nativeGetSize(mNative));
}
public StringBlock(byte[] data, int offset, int size, boolean useSparse) {
mNative = nativeCreate(data, offset, size);
mUseSparse = useSparse;
mOwnsNative = true;
if (localLOGV) Log.v(TAG, "Created string block " + this
+ ": " + nativeGetSize(mNative));
}
public CharSequence get(int idx) {
synchronized (this) {
if (mStrings != null) {
CharSequence res = mStrings[idx];
if (res != null) {
return res;
}
} else if (mSparseStrings != null) {
CharSequence res = mSparseStrings.get(idx);
if (res != null) {
return res;
}
} else {
final int num = nativeGetSize(mNative);
if (mUseSparse && num > 250) {
mSparseStrings = new SparseArray<CharSequence>();
} else {
mStrings = new CharSequence[num];
}
}
String str = nativeGetString(mNative, idx);
CharSequence res = str;
int[] style = nativeGetStyle(mNative, idx);
if (localLOGV) Log.v(TAG, "Got string: " + str);
if (localLOGV) Log.v(TAG, "Got styles: " + style);
if (style != null) {
if (mStyleIDs == null) {
mStyleIDs = new StyleIDs();
mStyleIDs.boldId = nativeIndexOfString(mNative, "b");
mStyleIDs.italicId = nativeIndexOfString(mNative, "i");
mStyleIDs.underlineId = nativeIndexOfString(mNative, "u");
mStyleIDs.ttId = nativeIndexOfString(mNative, "tt");
mStyleIDs.bigId = nativeIndexOfString(mNative, "big");
mStyleIDs.smallId = nativeIndexOfString(mNative, "small");
mStyleIDs.supId = nativeIndexOfString(mNative, "sup");
mStyleIDs.subId = nativeIndexOfString(mNative, "sub");
mStyleIDs.strikeId = nativeIndexOfString(mNative, "strike");
mStyleIDs.listItemId = nativeIndexOfString(mNative, "li");
mStyleIDs.marqueeId = nativeIndexOfString(mNative, "marquee");
if (localLOGV) Log.v(TAG, "BoldId=" + mStyleIDs.boldId
+ ", ItalicId=" + mStyleIDs.italicId
+ ", UnderlineId=" + mStyleIDs.underlineId);
}
res = applyStyles(str, style, mStyleIDs);
}
if (mStrings != null) mStrings[idx] = res;
else mSparseStrings.put(idx, res);
return res;
}
}
protected void finalize() throws Throwable {
if (mOwnsNative) {
nativeDestroy(mNative);
}
}
static final class StyleIDs {
private int boldId;
private int italicId;
private int underlineId;
private int ttId;
private int bigId;
private int smallId;
private int subId;
private int supId;
private int strikeId;
private int listItemId;
private int marqueeId;
}
private CharSequence applyStyles(String str, int[] style, StyleIDs ids) {
if (style.length == 0)
return str;
SpannableString buffer = new SpannableString(str);
int i=0;
while (i < style.length) {
int type = style[i];
if (localLOGV) Log.v(TAG, "Applying style span id=" + type
+ ", start=" + style[i+1] + ", end=" + style[i+2]);
if (type == ids.boldId) {
buffer.setSpan(new StyleSpan(Typeface.BOLD),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.italicId) {
buffer.setSpan(new StyleSpan(Typeface.ITALIC),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.underlineId) {
buffer.setSpan(new UnderlineSpan(),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.ttId) {
buffer.setSpan(new TypefaceSpan("monospace"),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.bigId) {
buffer.setSpan(new RelativeSizeSpan(1.25f),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.smallId) {
buffer.setSpan(new RelativeSizeSpan(0.8f),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.subId) {
buffer.setSpan(new SubscriptSpan(),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.supId) {
buffer.setSpan(new SuperscriptSpan(),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.strikeId) {
buffer.setSpan(new StrikethroughSpan(),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.listItemId) {
addParagraphSpan(buffer, new BulletSpan(10),
style[i+1], style[i+2]+1);
} else if (type == ids.marqueeId) {
buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
style[i+1], style[i+2]+1,
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
} else {
String tag = nativeGetString(mNative, type);
if (tag.startsWith("font;")) {
String sub;
sub = subtag(tag, ";height=");
if (sub != null) {
int size = Integer.parseInt(sub);
addParagraphSpan(buffer, new Height(size),
style[i+1], style[i+2]+1);
}
sub = subtag(tag, ";size=");
if (sub != null) {
int size = Integer.parseInt(sub);
buffer.setSpan(new AbsoluteSizeSpan(size),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
sub = subtag(tag, ";fgcolor=");
if (sub != null) {
int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
buffer.setSpan(new ForegroundColorSpan(color),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
sub = subtag(tag, ";bgcolor=");
if (sub != null) {
int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
buffer.setSpan(new BackgroundColorSpan(color),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else if (tag.startsWith("a;")) {
String sub;
sub = subtag(tag, ";href=");
if (sub != null) {
buffer.setSpan(new URLSpan(sub),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else if (tag.startsWith("annotation;")) {
int len = tag.length();
int next;
for (int t = tag.indexOf(';'); t < len; t = next) {
int eq = tag.indexOf('=', t);
if (eq < 0) {
break;
}
next = tag.indexOf(';', eq);
if (next < 0) {
next = len;
}
String key = tag.substring(t + 1, eq);
String value = tag.substring(eq + 1, next);
buffer.setSpan(new Annotation(key, value),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
i += 3;
}
return new SpannedString(buffer);
}
/**
* If a translator has messed up the edges of paragraph-level markup,
* fix it to actually cover the entire paragraph that it is attached to
* instead of just whatever range they put it on.
*/
private static void addParagraphSpan(Spannable buffer, Object what,
int start, int end) {
int len = buffer.length();
if (start != 0 && start != len && buffer.charAt(start - 1) != '\n') {
for (start--; start > 0; start--) {
if (buffer.charAt(start - 1) == '\n') {
break;
}
}
}
if (end != 0 && end != len && buffer.charAt(end - 1) != '\n') {
for (end++; end < len; end++) {
if (buffer.charAt(end - 1) == '\n') {
break;
}
}
}
buffer.setSpan(what, start, end, Spannable.SPAN_PARAGRAPH);
}
private static String subtag(String full, String attribute) {
int start = full.indexOf(attribute);
if (start < 0) {
return null;
}
start += attribute.length();
int end = full.indexOf(';', start);
if (end < 0) {
return full.substring(start);
} else {
return full.substring(start, end);
}
}
/**
* Forces the text line to be the specified height, shrinking/stretching
* the ascent if possible, or the descent if shrinking the ascent further
* will make the text unreadable.
*/
private static class Height implements LineHeightSpan {
private int mSize;
private static float sProportion = 0;
public Height(int size) {
mSize = size;
}
public void chooseHeight(CharSequence text, int start, int end,
int spanstartv, int v,
Paint.FontMetricsInt fm) {
if (fm.bottom - fm.top < mSize) {
fm.top = fm.bottom - mSize;
fm.ascent = fm.ascent - mSize;
} else {
if (sProportion == 0) {
/*
* Calculate what fraction of the nominal ascent
* the height of a capital letter actually is,
* so that we won't reduce the ascent to less than
* that unless we absolutely have to.
*/
Paint p = new Paint();
p.setTextSize(100);
Rect r = new Rect();
p.getTextBounds("ABCDEFG", 0, 7, r);
sProportion = (r.top) / p.ascent();
}
int need = (int) Math.ceil(-fm.top * sProportion);
if (mSize - fm.descent >= need) {
/*
* It is safe to shrink the ascent this much.
*/
fm.top = fm.bottom - mSize;
fm.ascent = fm.descent - mSize;
} else if (mSize >= need) {
/*
* We can't show all the descent, but we can at least
* show all the ascent.
*/
fm.top = fm.ascent = -need;
fm.bottom = fm.descent = fm.top + mSize;
} else {
/*
* Show as much of the ascent as we can, and no descent.
*/
fm.top = fm.ascent = -mSize;
fm.bottom = fm.descent = 0;
}
}
}
}
/**
* Create from an existing string block native object. This is
* -extremely- dangerous -- only use it if you absolutely know what you
* are doing! The given native object must exist for the entire lifetime
* of this newly creating StringBlock.
*/
StringBlock(int obj, boolean useSparse) {
mNative = obj;
mUseSparse = useSparse;
mOwnsNative = false;
if (localLOGV) Log.v(TAG, "Created string block " + this
+ ": " + nativeGetSize(mNative));
}
private static final native int nativeCreate(byte[] data,
int offset,
int size);
private static final native int nativeGetSize(int obj);
private static final native String nativeGetString(int obj, int idx);
private static final native int[] nativeGetStyle(int obj, int idx);
private static final native int nativeIndexOfString(int obj, String str);
private static final native void nativeDestroy(int obj);
}