Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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 | |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 19 | import android.graphics.Canvas; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 20 | import android.graphics.Paint; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 21 | import android.text.style.MetricAffectingSpan; |
| 22 | import android.text.style.ReplacementSpan; |
| 23 | import android.util.Log; |
| 24 | |
Gilles Debunne | 9a3a884 | 2011-05-27 09:33:46 -0700 | [diff] [blame] | 25 | import com.android.internal.util.ArrayUtils; |
| 26 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 27 | /** |
| 28 | * @hide |
| 29 | */ |
| 30 | class MeasuredText { |
Kenny Root | fbc8630 | 2011-01-31 13:54:58 -0800 | [diff] [blame] | 31 | private static final boolean localLOGV = false; |
Romain Guy | e5ea440 | 2011-08-01 14:01:37 -0700 | [diff] [blame] | 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 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 41 | private int mPos; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 42 | private TextPaint mWorkPaint; |
| 43 | |
| 44 | private MeasuredText() { |
| 45 | mWorkPaint = new TextPaint(); |
| 46 | } |
| 47 | |
Romain Guy | e5ea440 | 2011-08-01 14:01:37 -0700 | [diff] [blame] | 48 | private static final Object[] sLock = new Object[0]; |
Brian Carlstrom | d4f4526 | 2013-08-28 11:01:57 -0700 | [diff] [blame] | 49 | private static final MeasuredText[] sCached = new MeasuredText[3]; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 50 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 51 | static MeasuredText obtain() { |
| 52 | MeasuredText mt; |
Romain Guy | e5ea440 | 2011-08-01 14:01:37 -0700 | [diff] [blame] | 53 | synchronized (sLock) { |
| 54 | for (int i = sCached.length; --i >= 0;) { |
| 55 | if (sCached[i] != null) { |
| 56 | mt = sCached[i]; |
| 57 | sCached[i] = null; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 58 | return mt; |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | mt = new MeasuredText(); |
Kenny Root | fbc8630 | 2011-01-31 13:54:58 -0800 | [diff] [blame] | 63 | if (localLOGV) { |
| 64 | Log.v("MEAS", "new: " + mt); |
| 65 | } |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 66 | return mt; |
| 67 | } |
| 68 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 69 | static MeasuredText recycle(MeasuredText mt) { |
| 70 | mt.mText = null; |
| 71 | if (mt.mLen < 1000) { |
Romain Guy | e5ea440 | 2011-08-01 14:01:37 -0700 | [diff] [blame] | 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; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 77 | break; |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | return null; |
| 83 | } |
| 84 | |
Gilles Debunne | cd943a7 | 2012-06-07 17:54:47 -0700 | [diff] [blame] | 85 | void setPos(int pos) { |
Raph Levien | 1341ab6 | 2012-07-09 16:57:35 -0700 | [diff] [blame] | 86 | mPos = pos - mTextStart; |
Gilles Debunne | cd943a7 | 2012-06-07 17:54:47 -0700 | [diff] [blame] | 87 | } |
| 88 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 89 | /** |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 90 | * Analyzes text for bidirectional runs. Allocates working buffers. |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 91 | */ |
Doug Felt | cb379120 | 2011-07-07 11:57:48 -0700 | [diff] [blame] | 92 | void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) { |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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) { |
Adam Lesinski | 776abc2 | 2014-03-07 11:30:59 -0500 | [diff] [blame] | 101 | mWidths = ArrayUtils.newUnpaddedFloatArray(len); |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 102 | } |
| 103 | if (mChars == null || mChars.length < len) { |
Adam Lesinski | 776abc2 | 2014-03-07 11:30:59 -0500 | [diff] [blame] | 104 | mChars = ArrayUtils.newUnpaddedCharArray(len); |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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; |
Gilles Debunne | ba3634f | 2012-01-23 16:36:33 -0800 | [diff] [blame] | 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; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 119 | for (int j = startInPara; j < endInPara; j++) { |
Gilles Debunne | cd943a7 | 2012-06-07 17:54:47 -0700 | [diff] [blame] | 120 | mChars[j] = '\uFFFC'; // object replacement character |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
Doug Felt | cb379120 | 2011-07-07 11:57:48 -0700 | [diff] [blame] | 125 | if ((textDir == TextDirectionHeuristics.LTR || |
| 126 | textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || |
| 127 | textDir == TextDirectionHeuristics.ANYRTL_LTR) && |
| 128 | TextUtils.doesNotNeedBidi(mChars, 0, len)) { |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 129 | mDir = Layout.DIR_LEFT_TO_RIGHT; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 130 | mEasy = true; |
| 131 | } else { |
| 132 | if (mLevels == null || mLevels.length < len) { |
Adam Lesinski | 776abc2 | 2014-03-07 11:30:59 -0500 | [diff] [blame] | 133 | mLevels = ArrayUtils.newUnpaddedByteArray(len); |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 134 | } |
Doug Felt | cb379120 | 2011-07-07 11:57:48 -0700 | [diff] [blame] | 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 | } |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 148 | mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false); |
| 149 | mEasy = false; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 150 | } |
| 151 | } |
| 152 | |
| 153 | float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) { |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 154 | if (fm != null) { |
| 155 | paint.getFontMetricsInt(fm); |
| 156 | } |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 157 | |
| 158 | int p = mPos; |
| 159 | mPos = p + len; |
| 160 | |
| 161 | if (mEasy) { |
Fabrice Di Meglio | da12f38 | 2013-03-15 11:26:56 -0700 | [diff] [blame] | 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); |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 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) { |
Fabrice Di Meglio | da12f38 | 2013-03-15 11:26:56 -0700 | [diff] [blame] | 171 | int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL; |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 172 | totalAdvance += |
Fabrice Di Meglio | da12f38 | 2013-03-15 11:26:56 -0700 | [diff] [blame] | 173 | paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q); |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 174 | if (i == e) { |
| 175 | break; |
| 176 | } |
| 177 | q = i; |
| 178 | level = mLevels[i]; |
| 179 | } |
| 180 | } |
| 181 | return totalAdvance; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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; |
Gilles Debunne | 9a3a884 | 2011-05-27 09:33:46 -0700 | [diff] [blame] | 213 | mPos += len; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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 | |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 229 | int breakText(int limit, boolean forwards, float width) { |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 230 | float[] w = mWidths; |
| 231 | if (forwards) { |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 232 | int i = 0; |
| 233 | while (i < limit) { |
| 234 | width -= w[i]; |
| 235 | if (width < 0.0f) break; |
| 236 | i++; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 237 | } |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 238 | while (i > 0 && mChars[i - 1] == ' ') i--; |
| 239 | return i; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 240 | } else { |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 241 | int i = limit - 1; |
| 242 | while (i >= 0) { |
| 243 | width -= w[i]; |
| 244 | if (width < 0.0f) break; |
| 245 | i--; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 246 | } |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 247 | while (i < limit - 1 && mChars[i + 1] == ' ') i++; |
| 248 | return limit - i - 1; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 249 | } |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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 | } |
Kenny Root | fbc8630 | 2011-01-31 13:54:58 -0800 | [diff] [blame] | 260 | } |