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.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 
     28 import com.android.internal.util.XmlUtils;
     29 
     30 /**
     31  * Conveniences for retrieving data out of a compiled string resource.
     32  *
     33  * {@hide}
     34  */
     35 final 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