• 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 
setPos(int pos)85     void setPos(int pos) {
86         mPos = pos - mTextStart;
87     }
88 
89     /**
90      * Analyzes text for bidirectional runs.  Allocates working buffers.
91      */
setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir)92     void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
93         mText = text;
94         mTextStart = start;
95 
96         int len = end - start;
97         mLen = len;
98         mPos = 0;
99 
100         if (mWidths == null || mWidths.length < len) {
101             mWidths = new float[ArrayUtils.idealFloatArraySize(len)];
102         }
103         if (mChars == null || mChars.length < len) {
104             mChars = new char[ArrayUtils.idealCharArraySize(len)];
105         }
106         TextUtils.getChars(text, start, end, mChars, 0);
107 
108         if (text instanceof Spanned) {
109             Spanned spanned = (Spanned) text;
110             ReplacementSpan[] spans = spanned.getSpans(start, end,
111                     ReplacementSpan.class);
112 
113             for (int i = 0; i < spans.length; i++) {
114                 int startInPara = spanned.getSpanStart(spans[i]) - start;
115                 int endInPara = spanned.getSpanEnd(spans[i]) - start;
116                 // The span interval may be larger and must be restricted to [start, end[
117                 if (startInPara < 0) startInPara = 0;
118                 if (endInPara > len) endInPara = len;
119                 for (int j = startInPara; j < endInPara; j++) {
120                     mChars[j] = '\uFFFC'; // object replacement character
121                 }
122             }
123         }
124 
125         if ((textDir == TextDirectionHeuristics.LTR ||
126                 textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
127                 textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
128                 TextUtils.doesNotNeedBidi(mChars, 0, len)) {
129             mDir = Layout.DIR_LEFT_TO_RIGHT;
130             mEasy = true;
131         } else {
132             if (mLevels == null || mLevels.length < len) {
133                 mLevels = new byte[ArrayUtils.idealByteArraySize(len)];
134             }
135             int bidiRequest;
136             if (textDir == TextDirectionHeuristics.LTR) {
137                 bidiRequest = Layout.DIR_REQUEST_LTR;
138             } else if (textDir == TextDirectionHeuristics.RTL) {
139                 bidiRequest = Layout.DIR_REQUEST_RTL;
140             } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
141                 bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
142             } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
143                 bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
144             } else {
145                 boolean isRtl = textDir.isRtl(mChars, 0, len);
146                 bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
147             }
148             mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
149             mEasy = false;
150         }
151     }
152 
addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm)153     float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
154         if (fm != null) {
155             paint.getFontMetricsInt(fm);
156         }
157 
158         int p = mPos;
159         mPos = p + len;
160 
161         if (mEasy) {
162             int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
163                 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
164             return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
165         }
166 
167         float totalAdvance = 0;
168         int level = mLevels[p];
169         for (int q = p, i = p + 1, e = p + len;; ++i) {
170             if (i == e || mLevels[i] != level) {
171                 int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
172                 totalAdvance +=
173                         paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
174                 if (i == e) {
175                     break;
176                 }
177                 q = i;
178                 level = mLevels[i];
179             }
180         }
181         return totalAdvance;
182     }
183 
addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, Paint.FontMetricsInt fm)184     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
185             Paint.FontMetricsInt fm) {
186 
187         TextPaint workPaint = mWorkPaint;
188         workPaint.set(paint);
189         // XXX paint should not have a baseline shift, but...
190         workPaint.baselineShift = 0;
191 
192         ReplacementSpan replacement = null;
193         for (int i = 0; i < spans.length; i++) {
194             MetricAffectingSpan span = spans[i];
195             if (span instanceof ReplacementSpan) {
196                 replacement = (ReplacementSpan)span;
197             } else {
198                 span.updateMeasureState(workPaint);
199             }
200         }
201 
202         float wid;
203         if (replacement == null) {
204             wid = addStyleRun(workPaint, len, fm);
205         } else {
206             // Use original text.  Shouldn't matter.
207             wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
208                     mTextStart + mPos + len, fm);
209             float[] w = mWidths;
210             w[mPos] = wid;
211             for (int i = mPos + 1, e = mPos + len; i < e; i++)
212                 w[i] = 0;
213             mPos += len;
214         }
215 
216         if (fm != null) {
217             if (workPaint.baselineShift < 0) {
218                 fm.ascent += workPaint.baselineShift;
219                 fm.top += workPaint.baselineShift;
220             } else {
221                 fm.descent += workPaint.baselineShift;
222                 fm.bottom += workPaint.baselineShift;
223             }
224         }
225 
226         return wid;
227     }
228 
breakText(int limit, boolean forwards, float width)229     int breakText(int limit, boolean forwards, float width) {
230         float[] w = mWidths;
231         if (forwards) {
232             int i = 0;
233             while (i < limit) {
234                 width -= w[i];
235                 if (width < 0.0f) break;
236                 i++;
237             }
238             while (i > 0 && mChars[i - 1] == ' ') i--;
239             return i;
240         } else {
241             int i = limit - 1;
242             while (i >= 0) {
243                 width -= w[i];
244                 if (width < 0.0f) break;
245                 i--;
246             }
247             while (i < limit - 1 && mChars[i + 1] == ' ') i++;
248             return limit - i - 1;
249         }
250     }
251 
measure(int start, int limit)252     float measure(int start, int limit) {
253         float width = 0;
254         float[] w = mWidths;
255         for (int i = start; i < limit; ++i) {
256             width += w[i];
257         }
258         return width;
259     }
260 }
261