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