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 | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 19 | import android.graphics.Paint; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 20 | import android.text.style.MetricAffectingSpan; |
| 21 | import android.text.style.ReplacementSpan; |
| 22 | import android.util.Log; |
| 23 | |
Gilles Debunne | 9a3a884 | 2011-05-27 09:33:46 -0700 | [diff] [blame] | 24 | import com.android.internal.util.ArrayUtils; |
| 25 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 26 | /** |
| 27 | * @hide |
| 28 | */ |
| 29 | class MeasuredText { |
Kenny Root | fbc8630 | 2011-01-31 13:54:58 -0800 | [diff] [blame] | 30 | private static final boolean localLOGV = false; |
Romain Guy | e5ea440 | 2011-08-01 14:01:37 -0700 | [diff] [blame] | 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 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 40 | private int mPos; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 41 | private TextPaint mWorkPaint; |
Raph Levien | 70616ec | 2015-03-04 10:41:30 -0800 | [diff] [blame] | 42 | private StaticLayout.Builder mBuilder; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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) { |
Raph Levien | d3ab692 | 2015-03-02 14:30:53 -0800 | [diff] [blame] | 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; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 77 | } |
| 78 | } |
| 79 | } |
| 80 | return null; |
| 81 | } |
| 82 | |
Raph Levien | d3ab692 | 2015-03-02 14:30:53 -0800 | [diff] [blame] | 83 | void finish() { |
| 84 | mText = null; |
Raph Levien | 70616ec | 2015-03-04 10:41:30 -0800 | [diff] [blame] | 85 | mBuilder = null; |
Raph Levien | d3ab692 | 2015-03-02 14:30:53 -0800 | [diff] [blame] | 86 | if (mLen > 1000) { |
| 87 | mWidths = null; |
| 88 | mChars = null; |
| 89 | mLevels = null; |
| 90 | } |
| 91 | } |
| 92 | |
Gilles Debunne | cd943a7 | 2012-06-07 17:54:47 -0700 | [diff] [blame] | 93 | void setPos(int pos) { |
Raph Levien | 1341ab6 | 2012-07-09 16:57:35 -0700 | [diff] [blame] | 94 | mPos = pos - mTextStart; |
Gilles Debunne | cd943a7 | 2012-06-07 17:54:47 -0700 | [diff] [blame] | 95 | } |
| 96 | |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 97 | /** |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 98 | * Analyzes text for bidirectional runs. Allocates working buffers. |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 99 | */ |
Raph Levien | 70616ec | 2015-03-04 10:41:30 -0800 | [diff] [blame] | 100 | void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir, |
| 101 | StaticLayout.Builder builder) { |
| 102 | mBuilder = builder; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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) { |
Adam Lesinski | 776abc2 | 2014-03-07 11:30:59 -0500 | [diff] [blame] | 111 | mWidths = ArrayUtils.newUnpaddedFloatArray(len); |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 112 | } |
| 113 | if (mChars == null || mChars.length < len) { |
Adam Lesinski | 776abc2 | 2014-03-07 11:30:59 -0500 | [diff] [blame] | 114 | mChars = ArrayUtils.newUnpaddedCharArray(len); |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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; |
Gilles Debunne | ba3634f | 2012-01-23 16:36:33 -0800 | [diff] [blame] | 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; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 129 | for (int j = startInPara; j < endInPara; j++) { |
Gilles Debunne | cd943a7 | 2012-06-07 17:54:47 -0700 | [diff] [blame] | 130 | mChars[j] = '\uFFFC'; // object replacement character |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
Doug Felt | cb379120 | 2011-07-07 11:57:48 -0700 | [diff] [blame] | 135 | if ((textDir == TextDirectionHeuristics.LTR || |
| 136 | textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || |
| 137 | textDir == TextDirectionHeuristics.ANYRTL_LTR) && |
| 138 | TextUtils.doesNotNeedBidi(mChars, 0, len)) { |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 139 | mDir = Layout.DIR_LEFT_TO_RIGHT; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 140 | mEasy = true; |
| 141 | } else { |
| 142 | if (mLevels == null || mLevels.length < len) { |
Adam Lesinski | 776abc2 | 2014-03-07 11:30:59 -0500 | [diff] [blame] | 143 | mLevels = ArrayUtils.newUnpaddedByteArray(len); |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 144 | } |
Doug Felt | cb379120 | 2011-07-07 11:57:48 -0700 | [diff] [blame] | 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 | } |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 158 | mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false); |
| 159 | mEasy = false; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 160 | } |
| 161 | } |
| 162 | |
| 163 | float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) { |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 164 | if (fm != null) { |
| 165 | paint.getFontMetricsInt(fm); |
| 166 | } |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 167 | |
| 168 | int p = mPos; |
| 169 | mPos = p + len; |
| 170 | |
Raph Levien | 70616ec | 2015-03-04 10:41:30 -0800 | [diff] [blame] | 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 | } |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 177 | if (mEasy) { |
Raph Levien | 051910b | 2014-06-15 18:25:29 -0700 | [diff] [blame] | 178 | boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT; |
Raph Levien | 70616ec | 2015-03-04 10:41:30 -0800 | [diff] [blame] | 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; |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 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) { |
Raph Levien | 051910b | 2014-06-15 18:25:29 -0700 | [diff] [blame] | 195 | boolean isRtl = (level & 0x1) != 0; |
Raph Levien | 70616ec | 2015-03-04 10:41:30 -0800 | [diff] [blame] | 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 | } |
Doug Felt | 0c702b8 | 2010-05-14 10:55:42 -0700 | [diff] [blame] | 205 | if (i == e) { |
| 206 | break; |
| 207 | } |
| 208 | q = i; |
| 209 | level = mLevels[i]; |
| 210 | } |
| 211 | } |
| 212 | return totalAdvance; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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); |
Raph Levien | 70616ec | 2015-03-04 10:41:30 -0800 | [diff] [blame] | 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 | } |
Gilles Debunne | 9a3a884 | 2011-05-27 09:33:46 -0700 | [diff] [blame] | 248 | mPos += len; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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 | |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 264 | int breakText(int limit, boolean forwards, float width) { |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 265 | float[] w = mWidths; |
| 266 | if (forwards) { |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 267 | int i = 0; |
| 268 | while (i < limit) { |
| 269 | width -= w[i]; |
| 270 | if (width < 0.0f) break; |
| 271 | i++; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 272 | } |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 273 | while (i > 0 && mChars[i - 1] == ' ') i--; |
| 274 | return i; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 275 | } else { |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 276 | int i = limit - 1; |
| 277 | while (i >= 0) { |
| 278 | width -= w[i]; |
| 279 | if (width < 0.0f) break; |
| 280 | i--; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 281 | } |
Gilles Debunne | c70e7a0 | 2012-02-23 18:05:55 -0800 | [diff] [blame] | 282 | while (i < limit - 1 && mChars[i + 1] == ' ') i++; |
| 283 | return limit - i - 1; |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 284 | } |
Doug Felt | e8e45f2 | 2010-03-29 14:58:40 -0700 | [diff] [blame] | 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 | } |
Kenny Root | fbc8630 | 2011-01-31 13:54:58 -0800 | [diff] [blame] | 295 | } |