BoringLayout.java revision 44e8d60f53ebeb9734395b041f20cd67dbcf287f
1/* 2 * Copyright (C) 2006 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.Canvas; 20import android.graphics.Paint; 21import android.graphics.Path; 22import android.text.style.ParagraphStyle; 23 24/** 25 * A BoringLayout is a very simple Layout implementation for text that 26 * fits on a single line and is all left-to-right characters. 27 * You will probably never want to make one of these yourself; 28 * if you do, be sure to call {@link #isBoring} first to make sure 29 * the text meets the criteria. 30 * <p>This class is used by widgets to control text layout. You should not need 31 * to use this class directly unless you are implementing your own widget 32 * or custom display object, in which case 33 * you are encouraged to use a Layout instead of calling 34 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) 35 * Canvas.drawText()} directly.</p> 36 */ 37public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback { 38 public static BoringLayout make(CharSequence source, 39 TextPaint paint, int outerwidth, 40 Alignment align, 41 float spacingmult, float spacingadd, 42 BoringLayout.Metrics metrics, boolean includepad) { 43 return new BoringLayout(source, paint, outerwidth, align, 44 spacingmult, spacingadd, metrics, 45 includepad); 46 } 47 48 public static BoringLayout make(CharSequence source, 49 TextPaint paint, int outerwidth, 50 Alignment align, 51 float spacingmult, float spacingadd, 52 BoringLayout.Metrics metrics, boolean includepad, 53 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 54 return new BoringLayout(source, paint, outerwidth, align, 55 spacingmult, spacingadd, metrics, 56 includepad, ellipsize, ellipsizedWidth); 57 } 58 59 /** 60 * Returns a BoringLayout for the specified text, potentially reusing 61 * this one if it is already suitable. The caller must make sure that 62 * no one is still using this Layout. 63 */ 64 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, 65 int outerwidth, Alignment align, 66 float spacingmult, float spacingadd, 67 BoringLayout.Metrics metrics, 68 boolean includepad) { 69 replaceWith(source, paint, outerwidth, align, spacingmult, 70 spacingadd); 71 72 mEllipsizedWidth = outerwidth; 73 mEllipsizedStart = 0; 74 mEllipsizedCount = 0; 75 76 init(source, paint, outerwidth, align, spacingmult, spacingadd, 77 metrics, includepad, true); 78 return this; 79 } 80 81 /** 82 * Returns a BoringLayout for the specified text, potentially reusing 83 * this one if it is already suitable. The caller must make sure that 84 * no one is still using this Layout. 85 */ 86 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, 87 int outerwidth, Alignment align, 88 float spacingmult, float spacingadd, 89 BoringLayout.Metrics metrics, 90 boolean includepad, 91 TextUtils.TruncateAt ellipsize, 92 int ellipsizedWidth) { 93 boolean trust; 94 95 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { 96 replaceWith(source, paint, outerwidth, align, spacingmult, 97 spacingadd); 98 99 mEllipsizedWidth = outerwidth; 100 mEllipsizedStart = 0; 101 mEllipsizedCount = 0; 102 trust = true; 103 } else { 104 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, 105 ellipsize, true, this), 106 paint, outerwidth, align, spacingmult, 107 spacingadd); 108 109 mEllipsizedWidth = ellipsizedWidth; 110 trust = false; 111 } 112 113 init(getText(), paint, outerwidth, align, spacingmult, spacingadd, 114 metrics, includepad, trust); 115 return this; 116 } 117 118 public BoringLayout(CharSequence source, 119 TextPaint paint, int outerwidth, 120 Alignment align, 121 float spacingmult, float spacingadd, 122 BoringLayout.Metrics metrics, boolean includepad) { 123 super(source, paint, outerwidth, align, spacingmult, spacingadd); 124 125 mEllipsizedWidth = outerwidth; 126 mEllipsizedStart = 0; 127 mEllipsizedCount = 0; 128 129 init(source, paint, outerwidth, align, spacingmult, spacingadd, 130 metrics, includepad, true); 131 } 132 133 public BoringLayout(CharSequence source, 134 TextPaint paint, int outerwidth, 135 Alignment align, 136 float spacingmult, float spacingadd, 137 BoringLayout.Metrics metrics, boolean includepad, 138 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 139 /* 140 * It is silly to have to call super() and then replaceWith(), 141 * but we can't use "this" for the callback until the call to 142 * super() finishes. 143 */ 144 super(source, paint, outerwidth, align, spacingmult, spacingadd); 145 146 boolean trust; 147 148 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { 149 mEllipsizedWidth = outerwidth; 150 mEllipsizedStart = 0; 151 mEllipsizedCount = 0; 152 trust = true; 153 } else { 154 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, 155 ellipsize, true, this), 156 paint, outerwidth, align, spacingmult, 157 spacingadd); 158 159 160 mEllipsizedWidth = ellipsizedWidth; 161 trust = false; 162 } 163 164 init(getText(), paint, outerwidth, align, spacingmult, spacingadd, 165 metrics, includepad, trust); 166 } 167 168 /* package */ void init(CharSequence source, 169 TextPaint paint, int outerwidth, 170 Alignment align, 171 float spacingmult, float spacingadd, 172 BoringLayout.Metrics metrics, boolean includepad, 173 boolean trustWidth) { 174 int spacing; 175 176 if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) { 177 mDirect = source.toString(); 178 } else { 179 mDirect = null; 180 } 181 182 mPaint = paint; 183 184 if (includepad) { 185 spacing = metrics.bottom - metrics.top; 186 } else { 187 spacing = metrics.descent - metrics.ascent; 188 } 189 190 mBottom = spacing; 191 192 if (includepad) { 193 mDesc = spacing + metrics.top; 194 } else { 195 mDesc = spacing + metrics.ascent; 196 } 197 198 if (trustWidth) { 199 mMax = metrics.width; 200 } else { 201 /* 202 * If we have ellipsized, we have to actually calculate the 203 * width because the width that was passed in was for the 204 * full text, not the ellipsized form. 205 */ 206 TextLine line = TextLine.obtain(); 207 line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT, 208 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); 209 mMax = (int) Math.ceil(line.metrics(null)); 210 TextLine.recycle(line); 211 } 212 213 if (includepad) { 214 mTopPadding = metrics.top - metrics.ascent; 215 mBottomPadding = metrics.bottom - metrics.descent; 216 } 217 } 218 219 /** 220 * Returns null if not boring; the width, ascent, and descent if boring. 221 */ 222 public static Metrics isBoring(CharSequence text, 223 TextPaint paint) { 224 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null); 225 } 226 227 /** 228 * Returns null if not boring; the width, ascent, and descent if boring. 229 * @hide 230 */ 231 public static Metrics isBoring(CharSequence text, 232 TextPaint paint, 233 TextDirectionHeuristic textDir) { 234 return isBoring(text, paint, textDir, null); 235 } 236 237 /** 238 * Returns null if not boring; the width, ascent, and descent in the 239 * provided Metrics object (or a new one if the provided one was null) 240 * if boring. 241 */ 242 public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) { 243 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics); 244 } 245 246 /** 247 * Returns null if not boring; the width, ascent, and descent in the 248 * provided Metrics object (or a new one if the provided one was null) 249 * if boring. 250 * @hide 251 */ 252 public static Metrics isBoring(CharSequence text, TextPaint paint, 253 TextDirectionHeuristic textDir, Metrics metrics) { 254 char[] temp = TextUtils.obtain(500); 255 int length = text.length(); 256 boolean boring = true; 257 258 outer: 259 for (int i = 0; i < length; i += 500) { 260 int j = i + 500; 261 262 if (j > length) 263 j = length; 264 265 TextUtils.getChars(text, i, j, temp, 0); 266 267 int n = j - i; 268 269 for (int a = 0; a < n; a++) { 270 char c = temp[a]; 271 272 if (c == '\n' || c == '\t' || 273 (c >= 0x0590 && c <= 0x08FF) || // RTL scripts 274 c == 0x200F || // Bidi format character 275 (c >= 0x202A && c <= 0x202E) || // Bidi format characters 276 (c >= 0x2066 && c <= 0x2069) || // Bidi format characters 277 (c >= 0xD800 && c <= 0xDFFF) || // surrogate pairs 278 (c >= 0xFB1D && c <= 0xFDFF) || // Hebrew and Arabic presentation forms 279 (c >= 0xFE70 && c <= 0xFEFE) // Arabic presentation forms 280 ) { 281 boring = false; 282 break outer; 283 } 284 } 285 286 if (textDir != null && textDir.isRtl(temp, 0, n)) { 287 boring = false; 288 break outer; 289 } 290 } 291 292 TextUtils.recycle(temp); 293 294 if (boring && text instanceof Spanned) { 295 Spanned sp = (Spanned) text; 296 Object[] styles = sp.getSpans(0, length, ParagraphStyle.class); 297 if (styles.length > 0) { 298 boring = false; 299 } 300 } 301 302 if (boring) { 303 Metrics fm = metrics; 304 if (fm == null) { 305 fm = new Metrics(); 306 } 307 308 TextLine line = TextLine.obtain(); 309 line.set(paint, text, 0, length, Layout.DIR_LEFT_TO_RIGHT, 310 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); 311 fm.width = (int) Math.ceil(line.metrics(fm)); 312 TextLine.recycle(line); 313 314 return fm; 315 } else { 316 return null; 317 } 318 } 319 320 @Override 321 public int getHeight() { 322 return mBottom; 323 } 324 325 @Override 326 public int getLineCount() { 327 return 1; 328 } 329 330 @Override 331 public int getLineTop(int line) { 332 if (line == 0) 333 return 0; 334 else 335 return mBottom; 336 } 337 338 @Override 339 public int getLineDescent(int line) { 340 return mDesc; 341 } 342 343 @Override 344 public int getLineStart(int line) { 345 if (line == 0) 346 return 0; 347 else 348 return getText().length(); 349 } 350 351 @Override 352 public int getParagraphDirection(int line) { 353 return DIR_LEFT_TO_RIGHT; 354 } 355 356 @Override 357 public boolean getLineContainsTab(int line) { 358 return false; 359 } 360 361 @Override 362 public float getLineMax(int line) { 363 return mMax; 364 } 365 366 @Override 367 public float getLineWidth(int line) { 368 return (line == 0 ? mMax : 0); 369 } 370 371 @Override 372 public final Directions getLineDirections(int line) { 373 return Layout.DIRS_ALL_LEFT_TO_RIGHT; 374 } 375 376 @Override 377 public int getTopPadding() { 378 return mTopPadding; 379 } 380 381 @Override 382 public int getBottomPadding() { 383 return mBottomPadding; 384 } 385 386 @Override 387 public int getEllipsisCount(int line) { 388 return mEllipsizedCount; 389 } 390 391 @Override 392 public int getEllipsisStart(int line) { 393 return mEllipsizedStart; 394 } 395 396 @Override 397 public int getEllipsizedWidth() { 398 return mEllipsizedWidth; 399 } 400 401 // Override draw so it will be faster. 402 @Override 403 public void draw(Canvas c, Path highlight, Paint highlightpaint, 404 int cursorOffset) { 405 if (mDirect != null && highlight == null) { 406 c.drawText(mDirect, 0, mBottom - mDesc, mPaint); 407 } else { 408 super.draw(c, highlight, highlightpaint, cursorOffset); 409 } 410 } 411 412 /** 413 * Callback for the ellipsizer to report what region it ellipsized. 414 */ 415 public void ellipsized(int start, int end) { 416 mEllipsizedStart = start; 417 mEllipsizedCount = end - start; 418 } 419 420 private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; 421 422 private String mDirect; 423 private Paint mPaint; 424 425 /* package */ int mBottom, mDesc; // for Direct 426 private int mTopPadding, mBottomPadding; 427 private float mMax; 428 private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount; 429 430 private static final TextPaint sTemp = 431 new TextPaint(); 432 433 public static class Metrics extends Paint.FontMetricsInt { 434 public int width; 435 436 @Override public String toString() { 437 return super.toString() + " width=" + width; 438 } 439 } 440} 441