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