blob: 55df2069ea3534d2ed4f71ce8596460d70d7cd2e [file] [log] [blame]
Doug Felte8e45f22010-03-29 14:58:40 -07001/*
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
Doug Felte8e45f22010-03-29 14:58:40 -070019import android.graphics.Paint;
Doug Felte8e45f22010-03-29 14:58:40 -070020import android.text.style.MetricAffectingSpan;
21import android.text.style.ReplacementSpan;
22import android.util.Log;
23
Gilles Debunne9a3a8842011-05-27 09:33:46 -070024import com.android.internal.util.ArrayUtils;
25
Doug Felte8e45f22010-03-29 14:58:40 -070026/**
27 * @hide
28 */
29class MeasuredText {
Kenny Rootfbc86302011-01-31 13:54:58 -080030 private static final boolean localLOGV = false;
Romain Guye5ea4402011-08-01 14:01:37 -070031 CharSequence mText;
32 int mTextStart;
33 float[] mWidths;
34 char[] mChars;
35 byte[] mLevels;
36 int mDir;
37 boolean mEasy;
38 int mLen;
39
Doug Felte8e45f22010-03-29 14:58:40 -070040 private int mPos;
Doug Felte8e45f22010-03-29 14:58:40 -070041 private TextPaint mWorkPaint;
Raph Levien70616ec2015-03-04 10:41:30 -080042 private StaticLayout.Builder mBuilder;
Doug Felte8e45f22010-03-29 14:58:40 -070043
44 private MeasuredText() {
45 mWorkPaint = new TextPaint();
46 }
47
Romain Guye5ea4402011-08-01 14:01:37 -070048 private static final Object[] sLock = new Object[0];
Brian Carlstromd4f45262013-08-28 11:01:57 -070049 private static final MeasuredText[] sCached = new MeasuredText[3];
Doug Felte8e45f22010-03-29 14:58:40 -070050
Doug Felte8e45f22010-03-29 14:58:40 -070051 static MeasuredText obtain() {
52 MeasuredText mt;
Romain Guye5ea4402011-08-01 14:01:37 -070053 synchronized (sLock) {
54 for (int i = sCached.length; --i >= 0;) {
55 if (sCached[i] != null) {
56 mt = sCached[i];
57 sCached[i] = null;
Doug Felte8e45f22010-03-29 14:58:40 -070058 return mt;
59 }
60 }
61 }
62 mt = new MeasuredText();
Kenny Rootfbc86302011-01-31 13:54:58 -080063 if (localLOGV) {
64 Log.v("MEAS", "new: " + mt);
65 }
Doug Felte8e45f22010-03-29 14:58:40 -070066 return mt;
67 }
68
Doug Felte8e45f22010-03-29 14:58:40 -070069 static MeasuredText recycle(MeasuredText mt) {
Raph Leviend3ab6922015-03-02 14:30:53 -080070 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;
Doug Felte8e45f22010-03-29 14:58:40 -070077 }
78 }
79 }
80 return null;
81 }
82
Raph Leviend3ab6922015-03-02 14:30:53 -080083 void finish() {
84 mText = null;
Raph Levien70616ec2015-03-04 10:41:30 -080085 mBuilder = null;
Raph Leviend3ab6922015-03-02 14:30:53 -080086 if (mLen > 1000) {
87 mWidths = null;
88 mChars = null;
89 mLevels = null;
90 }
91 }
92
Gilles Debunnecd943a72012-06-07 17:54:47 -070093 void setPos(int pos) {
Raph Levien1341ab62012-07-09 16:57:35 -070094 mPos = pos - mTextStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -070095 }
96
Doug Felte8e45f22010-03-29 14:58:40 -070097 /**
Doug Felt0c702b82010-05-14 10:55:42 -070098 * Analyzes text for bidirectional runs. Allocates working buffers.
Doug Felte8e45f22010-03-29 14:58:40 -070099 */
Raph Levien70616ec2015-03-04 10:41:30 -0800100 void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
101 StaticLayout.Builder builder) {
102 mBuilder = builder;
Doug Felte8e45f22010-03-29 14:58:40 -0700103 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) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500111 mWidths = ArrayUtils.newUnpaddedFloatArray(len);
Doug Felte8e45f22010-03-29 14:58:40 -0700112 }
113 if (mChars == null || mChars.length < len) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500114 mChars = ArrayUtils.newUnpaddedCharArray(len);
Doug Felte8e45f22010-03-29 14:58:40 -0700115 }
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;
Gilles Debunneba3634f2012-01-23 16:36:33 -0800126 // 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;
Doug Felte8e45f22010-03-29 14:58:40 -0700129 for (int j = startInPara; j < endInPara; j++) {
Gilles Debunnecd943a72012-06-07 17:54:47 -0700130 mChars[j] = '\uFFFC'; // object replacement character
Doug Felte8e45f22010-03-29 14:58:40 -0700131 }
132 }
133 }
134
Doug Feltcb3791202011-07-07 11:57:48 -0700135 if ((textDir == TextDirectionHeuristics.LTR ||
136 textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
137 textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
138 TextUtils.doesNotNeedBidi(mChars, 0, len)) {
Doug Felt0c702b82010-05-14 10:55:42 -0700139 mDir = Layout.DIR_LEFT_TO_RIGHT;
Doug Felte8e45f22010-03-29 14:58:40 -0700140 mEasy = true;
141 } else {
142 if (mLevels == null || mLevels.length < len) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500143 mLevels = ArrayUtils.newUnpaddedByteArray(len);
Doug Felte8e45f22010-03-29 14:58:40 -0700144 }
Doug Feltcb3791202011-07-07 11:57:48 -0700145 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 }
Doug Felte8e45f22010-03-29 14:58:40 -0700158 mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
159 mEasy = false;
Doug Felte8e45f22010-03-29 14:58:40 -0700160 }
161 }
162
163 float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
Doug Felte8e45f22010-03-29 14:58:40 -0700164 if (fm != null) {
165 paint.getFontMetricsInt(fm);
166 }
Doug Felt0c702b82010-05-14 10:55:42 -0700167
168 int p = mPos;
169 mPos = p + len;
170
Raph Levien70616ec2015-03-04 10:41:30 -0800171 // 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 }
Doug Felt0c702b82010-05-14 10:55:42 -0700177 if (mEasy) {
Raph Levien051910b2014-06-15 18:25:29 -0700178 boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
Raph Levien70616ec2015-03-04 10:41:30 -0800179 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;
Doug Felt0c702b82010-05-14 10:55:42 -0700189 }
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) {
Raph Levien051910b2014-06-15 18:25:29 -0700195 boolean isRtl = (level & 0x1) != 0;
Raph Levien70616ec2015-03-04 10:41:30 -0800196 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 }
Doug Felt0c702b82010-05-14 10:55:42 -0700205 if (i == e) {
206 break;
207 }
208 q = i;
209 level = mLevels[i];
210 }
211 }
212 return totalAdvance;
Doug Felte8e45f22010-03-29 14:58:40 -0700213 }
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);
Raph Levien70616ec2015-03-04 10:41:30 -0800240 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 }
Gilles Debunne9a3a8842011-05-27 09:33:46 -0700248 mPos += len;
Doug Felte8e45f22010-03-29 14:58:40 -0700249 }
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
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800264 int breakText(int limit, boolean forwards, float width) {
Doug Felte8e45f22010-03-29 14:58:40 -0700265 float[] w = mWidths;
266 if (forwards) {
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800267 int i = 0;
268 while (i < limit) {
269 width -= w[i];
270 if (width < 0.0f) break;
271 i++;
Doug Felte8e45f22010-03-29 14:58:40 -0700272 }
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800273 while (i > 0 && mChars[i - 1] == ' ') i--;
274 return i;
Doug Felte8e45f22010-03-29 14:58:40 -0700275 } else {
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800276 int i = limit - 1;
277 while (i >= 0) {
278 width -= w[i];
279 if (width < 0.0f) break;
280 i--;
Doug Felte8e45f22010-03-29 14:58:40 -0700281 }
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800282 while (i < limit - 1 && mChars[i + 1] == ' ') i++;
283 return limit - i - 1;
Doug Felte8e45f22010-03-29 14:58:40 -0700284 }
Doug Felte8e45f22010-03-29 14:58:40 -0700285 }
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 }
Kenny Rootfbc86302011-01-31 13:54:58 -0800295}