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