• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Canvas;
20 import android.graphics.Paint;
21 import android.text.style.MetricAffectingSpan;
22 import android.text.style.ReplacementSpan;
23 import android.util.Log;
24 
25 import com.android.internal.util.ArrayUtils;
26 
27 /**
28  * @hide
29  */
30 class MeasuredText {
31     private static final boolean localLOGV = false;
32     CharSequence mText;
33     int mTextStart;
34     float[] mWidths;
35     char[] mChars;
36     byte[] mLevels;
37     int mDir;
38     boolean mEasy;
39     int mLen;
40 
41     private int mPos;
42     private TextPaint mWorkPaint;
43 
MeasuredText()44     private MeasuredText() {
45         mWorkPaint = new TextPaint();
46     }
47 
48     private static final Object[] sLock = new Object[0];
49     private static MeasuredText[] sCached = new MeasuredText[3];
50 
obtain()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 
recycle(MeasuredText mt)69     static MeasuredText recycle(MeasuredText mt) {
70         mt.mText = null;
71         if (mt.mLen < 1000) {
72             synchronized(sLock) {
73                 for (int i = 0; i < sCached.length; ++i) {
74                     if (sCached[i] == null) {
75                         sCached[i] = mt;
76                         mt.mText = null;
77                         break;
78                     }
79                 }
80             }
81         }
82         return null;
83     }
84 
85     /**
86      * Analyzes text for bidirectional runs.  Allocates working buffers.
87      */
setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir)88     void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
89         mText = text;
90         mTextStart = start;
91 
92         int len = end - start;
93         mLen = len;
94         mPos = 0;
95 
96         if (mWidths == null || mWidths.length < len) {
97             mWidths = new float[ArrayUtils.idealFloatArraySize(len)];
98         }
99         if (mChars == null || mChars.length < len) {
100             mChars = new char[ArrayUtils.idealCharArraySize(len)];
101         }
102         TextUtils.getChars(text, start, end, mChars, 0);
103 
104         if (text instanceof Spanned) {
105             Spanned spanned = (Spanned) text;
106             ReplacementSpan[] spans = spanned.getSpans(start, end,
107                     ReplacementSpan.class);
108 
109             for (int i = 0; i < spans.length; i++) {
110                 int startInPara = spanned.getSpanStart(spans[i]) - start;
111                 int endInPara = spanned.getSpanEnd(spans[i]) - start;
112                 for (int j = startInPara; j < endInPara; j++) {
113                     mChars[j] = '\uFFFC';
114                 }
115             }
116         }
117 
118         if ((textDir == TextDirectionHeuristics.LTR ||
119                 textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
120                 textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
121                 TextUtils.doesNotNeedBidi(mChars, 0, len)) {
122             mDir = Layout.DIR_LEFT_TO_RIGHT;
123             mEasy = true;
124         } else {
125             if (mLevels == null || mLevels.length < len) {
126                 mLevels = new byte[ArrayUtils.idealByteArraySize(len)];
127             }
128             int bidiRequest;
129             if (textDir == TextDirectionHeuristics.LTR) {
130                 bidiRequest = Layout.DIR_REQUEST_LTR;
131             } else if (textDir == TextDirectionHeuristics.RTL) {
132                 bidiRequest = Layout.DIR_REQUEST_RTL;
133             } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
134                 bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
135             } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
136                 bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
137             } else {
138                 boolean isRtl = textDir.isRtl(mChars, 0, len);
139                 bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
140             }
141             mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
142             mEasy = false;
143         }
144     }
145 
addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm)146     float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
147         if (fm != null) {
148             paint.getFontMetricsInt(fm);
149         }
150 
151         int p = mPos;
152         mPos = p + len;
153 
154         if (mEasy) {
155             int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
156                 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
157             return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
158         }
159 
160         float totalAdvance = 0;
161         int level = mLevels[p];
162         for (int q = p, i = p + 1, e = p + len;; ++i) {
163             if (i == e || mLevels[i] != level) {
164                 int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
165                 totalAdvance +=
166                         paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
167                 if (i == e) {
168                     break;
169                 }
170                 q = i;
171                 level = mLevels[i];
172             }
173         }
174         return totalAdvance;
175     }
176 
addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, Paint.FontMetricsInt fm)177     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
178             Paint.FontMetricsInt fm) {
179 
180         TextPaint workPaint = mWorkPaint;
181         workPaint.set(paint);
182         // XXX paint should not have a baseline shift, but...
183         workPaint.baselineShift = 0;
184 
185         ReplacementSpan replacement = null;
186         for (int i = 0; i < spans.length; i++) {
187             MetricAffectingSpan span = spans[i];
188             if (span instanceof ReplacementSpan) {
189                 replacement = (ReplacementSpan)span;
190             } else {
191                 span.updateMeasureState(workPaint);
192             }
193         }
194 
195         float wid;
196         if (replacement == null) {
197             wid = addStyleRun(workPaint, len, fm);
198         } else {
199             // Use original text.  Shouldn't matter.
200             wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
201                     mTextStart + mPos + len, fm);
202             float[] w = mWidths;
203             w[mPos] = wid;
204             for (int i = mPos + 1, e = mPos + len; i < e; i++)
205                 w[i] = 0;
206             mPos += len;
207         }
208 
209         if (fm != null) {
210             if (workPaint.baselineShift < 0) {
211                 fm.ascent += workPaint.baselineShift;
212                 fm.top += workPaint.baselineShift;
213             } else {
214                 fm.descent += workPaint.baselineShift;
215                 fm.bottom += workPaint.baselineShift;
216             }
217         }
218 
219         return wid;
220     }
221 
breakText(int start, int limit, boolean forwards, float width)222     int breakText(int start, int limit, boolean forwards, float width) {
223         float[] w = mWidths;
224         if (forwards) {
225             for (int i = start; i < limit; ++i) {
226                 if ((width -= w[i]) < 0) {
227                     return i - start;
228                 }
229             }
230         } else {
231             for (int i = limit; --i >= start;) {
232                 if ((width -= w[i]) < 0) {
233                     return limit - i -1;
234                 }
235             }
236         }
237 
238         return limit - start;
239     }
240 
measure(int start, int limit)241     float measure(int start, int limit) {
242         float width = 0;
243         float[] w = mWidths;
244         for (int i = start; i < limit; ++i) {
245             width += w[i];
246         }
247         return width;
248     }
249 }
250