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
17package android.text;
18
19import android.graphics.Paint;
20import android.text.style.MetricAffectingSpan;
21import android.text.style.ReplacementSpan;
22import android.util.Log;
23
24import com.android.internal.util.ArrayUtils;
25
26/**
27 * @hide
28 */
29class 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