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.Canvas;
20import android.graphics.Paint;
21import android.text.style.MetricAffectingSpan;
22import android.text.style.ReplacementSpan;
23import android.util.Log;
24
25import com.android.internal.util.ArrayUtils;
26
27/**
28 * @hide
29 */
30class 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
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
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.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    void setPos(int pos) {
86        mPos = pos - mTextStart;
87    }
88
89    /**
90     * Analyzes text for bidirectional runs.  Allocates working buffers.
91     */
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
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
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
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
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