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 17 package android.text; 18 19 import android.graphics.Canvas; 20 import android.graphics.Paint; 21 import android.graphics.Path; 22 import 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 */ 37 public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback { make(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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 make(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 */ replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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 */ replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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 BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 init(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, boolean trustWidth)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 */ isBoring(CharSequence text, TextPaint paint)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 */ isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir)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 */ isBoring(CharSequence text, TextPaint paint, Metrics metrics)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 */ isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics)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 getHeight()321 public int getHeight() { 322 return mBottom; 323 } 324 325 @Override getLineCount()326 public int getLineCount() { 327 return 1; 328 } 329 330 @Override getLineTop(int line)331 public int getLineTop(int line) { 332 if (line == 0) 333 return 0; 334 else 335 return mBottom; 336 } 337 338 @Override getLineDescent(int line)339 public int getLineDescent(int line) { 340 return mDesc; 341 } 342 343 @Override getLineStart(int line)344 public int getLineStart(int line) { 345 if (line == 0) 346 return 0; 347 else 348 return getText().length(); 349 } 350 351 @Override getParagraphDirection(int line)352 public int getParagraphDirection(int line) { 353 return DIR_LEFT_TO_RIGHT; 354 } 355 356 @Override getLineContainsTab(int line)357 public boolean getLineContainsTab(int line) { 358 return false; 359 } 360 361 @Override getLineMax(int line)362 public float getLineMax(int line) { 363 return mMax; 364 } 365 366 @Override getLineDirections(int line)367 public final Directions getLineDirections(int line) { 368 return Layout.DIRS_ALL_LEFT_TO_RIGHT; 369 } 370 371 @Override getTopPadding()372 public int getTopPadding() { 373 return mTopPadding; 374 } 375 376 @Override getBottomPadding()377 public int getBottomPadding() { 378 return mBottomPadding; 379 } 380 381 @Override getEllipsisCount(int line)382 public int getEllipsisCount(int line) { 383 return mEllipsizedCount; 384 } 385 386 @Override getEllipsisStart(int line)387 public int getEllipsisStart(int line) { 388 return mEllipsizedStart; 389 } 390 391 @Override getEllipsizedWidth()392 public int getEllipsizedWidth() { 393 return mEllipsizedWidth; 394 } 395 396 // Override draw so it will be faster. 397 @Override draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset)398 public void draw(Canvas c, Path highlight, Paint highlightpaint, 399 int cursorOffset) { 400 if (mDirect != null && highlight == null) { 401 c.drawText(mDirect, 0, mBottom - mDesc, mPaint); 402 } else { 403 super.draw(c, highlight, highlightpaint, cursorOffset); 404 } 405 } 406 407 /** 408 * Callback for the ellipsizer to report what region it ellipsized. 409 */ ellipsized(int start, int end)410 public void ellipsized(int start, int end) { 411 mEllipsizedStart = start; 412 mEllipsizedCount = end - start; 413 } 414 415 private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; 416 417 private String mDirect; 418 private Paint mPaint; 419 420 /* package */ int mBottom, mDesc; // for Direct 421 private int mTopPadding, mBottomPadding; 422 private float mMax; 423 private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount; 424 425 private static final TextPaint sTemp = 426 new TextPaint(); 427 428 public static class Metrics extends Paint.FontMetricsInt { 429 public int width; 430 toString()431 @Override public String toString() { 432 return super.toString() + " width=" + width; 433 } 434 } 435 } 436