blob: f8e3c83b5879b7b99ab42100c90a453158240e6e [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 Felt0c702b82010-05-14 10:55:42 -070019import android.graphics.Canvas;
Doug Felte8e45f22010-03-29 14:58:40 -070020import android.graphics.Paint;
Doug Felte8e45f22010-03-29 14:58:40 -070021import android.text.style.MetricAffectingSpan;
22import android.text.style.ReplacementSpan;
23import android.util.Log;
24
Gilles Debunne9a3a8842011-05-27 09:33:46 -070025import com.android.internal.util.ArrayUtils;
26
Doug Felte8e45f22010-03-29 14:58:40 -070027/**
28 * @hide
29 */
30class MeasuredText {
Kenny Rootfbc86302011-01-31 13:54:58 -080031 private static final boolean localLOGV = false;
Romain Guye5ea4402011-08-01 14:01:37 -070032 CharSequence mText;
33 int mTextStart;
34 float[] mWidths;
35 char[] mChars;
36 byte[] mLevels;
37 int mDir;
38 boolean mEasy;
39 int mLen;
40
Doug Felte8e45f22010-03-29 14:58:40 -070041 private int mPos;
Doug Felte8e45f22010-03-29 14:58:40 -070042 private TextPaint mWorkPaint;
43
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) {
70 mt.mText = null;
71 if (mt.mLen < 1000) {
Romain Guye5ea4402011-08-01 14:01:37 -070072 synchronized(sLock) {
73 for (int i = 0; i < sCached.length; ++i) {
74 if (sCached[i] == null) {
75 sCached[i] = mt;
76 mt.mText = null;
Doug Felte8e45f22010-03-29 14:58:40 -070077 break;
78 }
79 }
80 }
81 }
82 return null;
83 }
84
Gilles Debunnecd943a72012-06-07 17:54:47 -070085 void setPos(int pos) {
Raph Levien1341ab62012-07-09 16:57:35 -070086 mPos = pos - mTextStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -070087 }
88
Doug Felte8e45f22010-03-29 14:58:40 -070089 /**
Doug Felt0c702b82010-05-14 10:55:42 -070090 * Analyzes text for bidirectional runs. Allocates working buffers.
Doug Felte8e45f22010-03-29 14:58:40 -070091 */
Doug Feltcb3791202011-07-07 11:57:48 -070092 void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
Doug Felte8e45f22010-03-29 14:58:40 -070093 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) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500101 mWidths = ArrayUtils.newUnpaddedFloatArray(len);
Doug Felte8e45f22010-03-29 14:58:40 -0700102 }
103 if (mChars == null || mChars.length < len) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500104 mChars = ArrayUtils.newUnpaddedCharArray(len);
Doug Felte8e45f22010-03-29 14:58:40 -0700105 }
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;
Gilles Debunneba3634f2012-01-23 16:36:33 -0800116 // 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;
Doug Felte8e45f22010-03-29 14:58:40 -0700119 for (int j = startInPara; j < endInPara; j++) {
Gilles Debunnecd943a72012-06-07 17:54:47 -0700120 mChars[j] = '\uFFFC'; // object replacement character
Doug Felte8e45f22010-03-29 14:58:40 -0700121 }
122 }
123 }
124
Doug Feltcb3791202011-07-07 11:57:48 -0700125 if ((textDir == TextDirectionHeuristics.LTR ||
126 textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
127 textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
128 TextUtils.doesNotNeedBidi(mChars, 0, len)) {
Doug Felt0c702b82010-05-14 10:55:42 -0700129 mDir = Layout.DIR_LEFT_TO_RIGHT;
Doug Felte8e45f22010-03-29 14:58:40 -0700130 mEasy = true;
131 } else {
132 if (mLevels == null || mLevels.length < len) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500133 mLevels = ArrayUtils.newUnpaddedByteArray(len);
Doug Felte8e45f22010-03-29 14:58:40 -0700134 }
Doug Feltcb3791202011-07-07 11:57:48 -0700135 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 }
Doug Felte8e45f22010-03-29 14:58:40 -0700148 mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
149 mEasy = false;
Doug Felte8e45f22010-03-29 14:58:40 -0700150 }
151 }
152
153 float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
Doug Felte8e45f22010-03-29 14:58:40 -0700154 if (fm != null) {
155 paint.getFontMetricsInt(fm);
156 }
Doug Felt0c702b82010-05-14 10:55:42 -0700157
158 int p = mPos;
159 mPos = p + len;
160
161 if (mEasy) {
Fabrice Di Meglioda12f382013-03-15 11:26:56 -0700162 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);
Doug Felt0c702b82010-05-14 10:55:42 -0700165 }
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) {
Fabrice Di Meglioda12f382013-03-15 11:26:56 -0700171 int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
Doug Felt0c702b82010-05-14 10:55:42 -0700172 totalAdvance +=
Fabrice Di Meglioda12f382013-03-15 11:26:56 -0700173 paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
Doug Felt0c702b82010-05-14 10:55:42 -0700174 if (i == e) {
175 break;
176 }
177 q = i;
178 level = mLevels[i];
179 }
180 }
181 return totalAdvance;
Doug Felte8e45f22010-03-29 14:58:40 -0700182 }
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;
Gilles Debunne9a3a8842011-05-27 09:33:46 -0700213 mPos += len;
Doug Felte8e45f22010-03-29 14:58:40 -0700214 }
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
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800229 int breakText(int limit, boolean forwards, float width) {
Doug Felte8e45f22010-03-29 14:58:40 -0700230 float[] w = mWidths;
231 if (forwards) {
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800232 int i = 0;
233 while (i < limit) {
234 width -= w[i];
235 if (width < 0.0f) break;
236 i++;
Doug Felte8e45f22010-03-29 14:58:40 -0700237 }
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800238 while (i > 0 && mChars[i - 1] == ' ') i--;
239 return i;
Doug Felte8e45f22010-03-29 14:58:40 -0700240 } else {
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800241 int i = limit - 1;
242 while (i >= 0) {
243 width -= w[i];
244 if (width < 0.0f) break;
245 i--;
Doug Felte8e45f22010-03-29 14:58:40 -0700246 }
Gilles Debunnec70e7a02012-02-23 18:05:55 -0800247 while (i < limit - 1 && mChars[i + 1] == ' ') i++;
248 return limit - i - 1;
Doug Felte8e45f22010-03-29 14:58:40 -0700249 }
Doug Felte8e45f22010-03-29 14:58:40 -0700250 }
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 }
Kenny Rootfbc86302011-01-31 13:54:58 -0800260}