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