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 
MeasuredText()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 
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.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 
finish()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 
setPos(int pos)93     void setPos(int pos) {
94         mPos = pos - mTextStart;
95     }
96 
97     /**
98      * Analyzes text for bidirectional runs.  Allocates working buffers.
99      */
setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir, StaticLayout.Builder builder)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 
addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm)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 
addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, Paint.FontMetricsInt fm)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 
breakText(int limit, boolean forwards, float width)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 
measure(int start, int limit)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