Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2010 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.text;
     18 
     19 import android.graphics.Paint;
     20 import android.text.style.MetricAffectingSpan;
     21 import android.text.style.ReplacementSpan;
     22 import android.util.Log;
     23 
     24 import com.android.internal.util.ArrayUtils;
     25 
     26 /**
     27  * @hide
     28  */
     29 class MeasuredText {
     30     private static final boolean localLOGV = false;
     31     CharSequence mText;
     32     int mTextStart;
     33     float[] mWidths;
     34     char[] mChars;
     35     byte[] mLevels;
     36     int mDir;
     37     boolean mEasy;
     38     int mLen;
     39 
     40     private int mPos;
     41     private TextPaint mWorkPaint;
     42     private StaticLayout.Builder mBuilder;
     43 
     44     private MeasuredText() {
     45         mWorkPaint = new TextPaint();
     46     }
     47 
     48     private static final Object[] sLock = new Object[0];
     49     private static final MeasuredText[] sCached = new MeasuredText[3];
     50 
     51     static MeasuredText obtain() {
     52         MeasuredText mt;
     53         synchronized (sLock) {
     54             for (int i = sCached.length; --i >= 0;) {
     55                 if (sCached[i] != null) {
     56                     mt = sCached[i];
     57                     sCached[i] = null;
     58                     return mt;
     59                 }
     60             }
     61         }
     62         mt = new MeasuredText();
     63         if (localLOGV) {
     64             Log.v("MEAS", "new: " + mt);
     65         }
     66         return mt;
     67     }
     68 
     69     static MeasuredText recycle(MeasuredText mt) {
     70         mt.finish();
     71         synchronized(sLock) {
     72             for (int i = 0; i < sCached.length; ++i) {
     73                 if (sCached[i] == null) {
     74                     sCached[i] = mt;
     75                     mt.mText = null;
     76                     break;
     77                 }
     78             }
     79         }
     80         return null;
     81     }
     82 
     83     void finish() {
     84         mText = null;
     85         mBuilder = null;
     86         if (mLen > 1000) {
     87             mWidths = null;
     88             mChars = null;
     89             mLevels = null;
     90         }
     91     }
     92 
     93     void setPos(int pos) {
     94         mPos = pos - mTextStart;
     95     }
     96 
     97     /**
     98      * Analyzes text for bidirectional runs.  Allocates working buffers.
     99      */
    100     void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
    101             StaticLayout.Builder builder) {
    102         mBuilder = builder;
    103         mText = text;
    104         mTextStart = start;
    105 
    106         int len = end - start;
    107         mLen = len;
    108         mPos = 0;
    109 
    110         if (mWidths == null || mWidths.length < len) {
    111             mWidths = ArrayUtils.newUnpaddedFloatArray(len);
    112         }
    113         if (mChars == null || mChars.length < len) {
    114             mChars = ArrayUtils.newUnpaddedCharArray(len);
    115         }
    116         TextUtils.getChars(text, start, end, mChars, 0);
    117 
    118         if (text instanceof Spanned) {
    119             Spanned spanned = (Spanned) text;
    120             ReplacementSpan[] spans = spanned.getSpans(start, end,
    121                     ReplacementSpan.class);
    122 
    123             for (int i = 0; i < spans.length; i++) {
    124                 int startInPara = spanned.getSpanStart(spans[i]) - start;
    125                 int endInPara = spanned.getSpanEnd(spans[i]) - start;
    126                 // The span interval may be larger and must be restricted to [start, end[
    127                 if (startInPara < 0) startInPara = 0;
    128                 if (endInPara > len) endInPara = len;
    129                 for (int j = startInPara; j < endInPara; j++) {
    130                     mChars[j] = '\uFFFC'; // object replacement character
    131                 }
    132             }
    133         }
    134 
    135         if ((textDir == TextDirectionHeuristics.LTR ||
    136                 textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
    137                 textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
    138                 TextUtils.doesNotNeedBidi(mChars, 0, len)) {
    139             mDir = Layout.DIR_LEFT_TO_RIGHT;
    140             mEasy = true;
    141         } else {
    142             if (mLevels == null || mLevels.length < len) {
    143                 mLevels = ArrayUtils.newUnpaddedByteArray(len);
    144             }
    145             int bidiRequest;
    146             if (textDir == TextDirectionHeuristics.LTR) {
    147                 bidiRequest = Layout.DIR_REQUEST_LTR;
    148             } else if (textDir == TextDirectionHeuristics.RTL) {
    149                 bidiRequest = Layout.DIR_REQUEST_RTL;
    150             } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
    151                 bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
    152             } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
    153                 bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
    154             } else {
    155                 boolean isRtl = textDir.isRtl(mChars, 0, len);
    156                 bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
    157             }
    158             mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
    159             mEasy = false;
    160         }
    161     }
    162 
    163     float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
    164         if (fm != null) {
    165             paint.getFontMetricsInt(fm);
    166         }
    167 
    168         int p = mPos;
    169         mPos = p + len;
    170 
    171         // try to do widths measurement in native code, but use Java if paint has been subclassed
    172         // FIXME: may want to eliminate special case for subclass
    173         float[] widths = null;
    174         if (mBuilder == null || paint.getClass() != TextPaint.class) {
    175             widths = mWidths;
    176         }
    177         if (mEasy) {
    178             boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
    179             float width = 0;
    180             if (widths != null) {
    181                 width = paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, widths, p);
    182                 if (mBuilder != null) {
    183                     mBuilder.addMeasuredRun(p, p + len, widths);
    184                 }
    185             } else {
    186                 width = mBuilder.addStyleRun(paint, p, p + len, isRtl);
    187             }
    188             return width;
    189         }
    190 
    191         float totalAdvance = 0;
    192         int level = mLevels[p];
    193         for (int q = p, i = p + 1, e = p + len;; ++i) {
    194             if (i == e || mLevels[i] != level) {
    195                 boolean isRtl = (level & 0x1) != 0;
    196                 if (widths != null) {
    197                     totalAdvance +=
    198                             paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, widths, q);
    199                     if (mBuilder != null) {
    200                         mBuilder.addMeasuredRun(q, i, widths);
    201                     }
    202                 } else {
    203                     totalAdvance += mBuilder.addStyleRun(paint, q, i, isRtl);
    204                 }
    205                 if (i == e) {
    206                     break;
    207                 }
    208                 q = i;
    209                 level = mLevels[i];
    210             }
    211         }
    212         return totalAdvance;
    213     }
    214 
    215     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
    216             Paint.FontMetricsInt fm) {
    217 
    218         TextPaint workPaint = mWorkPaint;
    219         workPaint.set(paint);
    220         // XXX paint should not have a baseline shift, but...
    221         workPaint.baselineShift = 0;
    222 
    223         ReplacementSpan replacement = null;
    224         for (int i = 0; i < spans.length; i++) {
    225             MetricAffectingSpan span = spans[i];
    226             if (span instanceof ReplacementSpan) {
    227                 replacement = (ReplacementSpan)span;
    228             } else {
    229                 span.updateMeasureState(workPaint);
    230             }
    231         }
    232 
    233         float wid;
    234         if (replacement == null) {
    235             wid = addStyleRun(workPaint, len, fm);
    236         } else {
    237             // Use original text.  Shouldn't matter.
    238             wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
    239                     mTextStart + mPos + len, fm);
    240             if (mBuilder == null) {
    241                 float[] w = mWidths;
    242                 w[mPos] = wid;
    243                 for (int i = mPos + 1, e = mPos + len; i < e; i++)
    244                     w[i] = 0;
    245             } else {
    246                 mBuilder.addReplacementRun(mPos, mPos + len, wid);
    247             }
    248             mPos += len;
    249         }
    250 
    251         if (fm != null) {
    252             if (workPaint.baselineShift < 0) {
    253                 fm.ascent += workPaint.baselineShift;
    254                 fm.top += workPaint.baselineShift;
    255             } else {
    256                 fm.descent += workPaint.baselineShift;
    257                 fm.bottom += workPaint.baselineShift;
    258             }
    259         }
    260 
    261         return wid;
    262     }
    263 
    264     int breakText(int limit, boolean forwards, float width) {
    265         float[] w = mWidths;
    266         if (forwards) {
    267             int i = 0;
    268             while (i < limit) {
    269                 width -= w[i];
    270                 if (width < 0.0f) break;
    271                 i++;
    272             }
    273             while (i > 0 && mChars[i - 1] == ' ') i--;
    274             return i;
    275         } else {
    276             int i = limit - 1;
    277             while (i >= 0) {
    278                 width -= w[i];
    279                 if (width < 0.0f) break;
    280                 i--;
    281             }
    282             while (i < limit - 1 && mChars[i + 1] == ' ') i++;
    283             return limit - i - 1;
    284         }
    285     }
    286 
    287     float measure(int start, int limit) {
    288         float width = 0;
    289         float[] w = mWidths;
    290         for (int i = start; i < limit; ++i) {
    291             width += w[i];
    292         }
    293         return width;
    294     }
    295 }
    296