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