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 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.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 = ArrayUtils.newUnpaddedFloatArray(len); 102 } 103 if (mChars == null || mChars.length < len) { 104 mChars = ArrayUtils.newUnpaddedCharArray(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 = ArrayUtils.newUnpaddedByteArray(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 boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT; 163 return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p); 164 } 165 166 float totalAdvance = 0; 167 int level = mLevels[p]; 168 for (int q = p, i = p + 1, e = p + len;; ++i) { 169 if (i == e || mLevels[i] != level) { 170 boolean isRtl = (level & 0x1) != 0; 171 totalAdvance += 172 paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q); 173 if (i == e) { 174 break; 175 } 176 q = i; 177 level = mLevels[i]; 178 } 179 } 180 return totalAdvance; 181 } 182 183 float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, 184 Paint.FontMetricsInt fm) { 185 186 TextPaint workPaint = mWorkPaint; 187 workPaint.set(paint); 188 // XXX paint should not have a baseline shift, but... 189 workPaint.baselineShift = 0; 190 191 ReplacementSpan replacement = null; 192 for (int i = 0; i < spans.length; i++) { 193 MetricAffectingSpan span = spans[i]; 194 if (span instanceof ReplacementSpan) { 195 replacement = (ReplacementSpan)span; 196 } else { 197 span.updateMeasureState(workPaint); 198 } 199 } 200 201 float wid; 202 if (replacement == null) { 203 wid = addStyleRun(workPaint, len, fm); 204 } else { 205 // Use original text. Shouldn't matter. 206 wid = replacement.getSize(workPaint, mText, mTextStart + mPos, 207 mTextStart + mPos + len, fm); 208 float[] w = mWidths; 209 w[mPos] = wid; 210 for (int i = mPos + 1, e = mPos + len; i < e; i++) 211 w[i] = 0; 212 mPos += len; 213 } 214 215 if (fm != null) { 216 if (workPaint.baselineShift < 0) { 217 fm.ascent += workPaint.baselineShift; 218 fm.top += workPaint.baselineShift; 219 } else { 220 fm.descent += workPaint.baselineShift; 221 fm.bottom += workPaint.baselineShift; 222 } 223 } 224 225 return wid; 226 } 227 228 int breakText(int limit, boolean forwards, float width) { 229 float[] w = mWidths; 230 if (forwards) { 231 int i = 0; 232 while (i < limit) { 233 width -= w[i]; 234 if (width < 0.0f) break; 235 i++; 236 } 237 while (i > 0 && mChars[i - 1] == ' ') i--; 238 return i; 239 } else { 240 int i = limit - 1; 241 while (i >= 0) { 242 width -= w[i]; 243 if (width < 0.0f) break; 244 i--; 245 } 246 while (i < limit - 1 && mChars[i + 1] == ' ') i++; 247 return limit - i - 1; 248 } 249 } 250 251 float measure(int start, int limit) { 252 float width = 0; 253 float[] w = mWidths; 254 for (int i = start; i < limit; ++i) { 255 width += w[i]; 256 } 257 return width; 258 } 259 } 260