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; 23import 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 */ 38public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback { 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 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 */ 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 */ 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 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 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 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 if (spacingmult != 1 || spacingadd != 0) { 192 spacing = (int)(spacing * spacingmult + spacingadd + 0.5f); 193 } 194 195 mBottom = spacing; 196 197 if (includepad) { 198 mDesc = spacing + metrics.top; 199 } else { 200 mDesc = spacing + metrics.ascent; 201 } 202 203 if (trustWidth) { 204 mMax = metrics.width; 205 } else { 206 /* 207 * If we have ellipsized, we have to actually calculate the 208 * width because the width that was passed in was for the 209 * full text, not the ellipsized form. 210 */ 211 synchronized (sTemp) { 212 mMax = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp, 213 source, 0, source.length(), 214 null))); 215 } 216 } 217 218 if (includepad) { 219 mTopPadding = metrics.top - metrics.ascent; 220 mBottomPadding = metrics.bottom - metrics.descent; 221 } 222 } 223 224 /** 225 * Returns null if not boring; the width, ascent, and descent if boring. 226 */ 227 public static Metrics isBoring(CharSequence text, 228 TextPaint paint) { 229 return isBoring(text, paint, null); 230 } 231 232 /** 233 * Returns null if not boring; the width, ascent, and descent in the 234 * provided Metrics object (or a new one if the provided one was null) 235 * if boring. 236 */ 237 public static Metrics isBoring(CharSequence text, TextPaint paint, 238 Metrics metrics) { 239 char[] temp = TextUtils.obtain(500); 240 int len = text.length(); 241 boolean boring = true; 242 243 outer: 244 for (int i = 0; i < len; i += 500) { 245 int j = i + 500; 246 247 if (j > len) 248 j = len; 249 250 TextUtils.getChars(text, i, j, temp, 0); 251 252 int n = j - i; 253 254 for (int a = 0; a < n; a++) { 255 char c = temp[a]; 256 257 if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) { 258 boring = false; 259 break outer; 260 } 261 } 262 } 263 264 TextUtils.recycle(temp); 265 266 if (boring && text instanceof Spanned) { 267 Spanned sp = (Spanned) text; 268 Object[] styles = sp.getSpans(0, text.length(), ParagraphStyle.class); 269 if (styles.length > 0) { 270 boring = false; 271 } 272 } 273 274 if (boring) { 275 Metrics fm = metrics; 276 if (fm == null) { 277 fm = new Metrics(); 278 } 279 280 int wid; 281 282 synchronized (sTemp) { 283 wid = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp, 284 text, 0, text.length(), fm))); 285 } 286 fm.width = wid; 287 return fm; 288 } else { 289 return null; 290 } 291 } 292 293 @Override public int getHeight() { 294 return mBottom; 295 } 296 297 @Override public int getLineCount() { 298 return 1; 299 } 300 301 @Override public int getLineTop(int line) { 302 if (line == 0) 303 return 0; 304 else 305 return mBottom; 306 } 307 308 @Override public int getLineDescent(int line) { 309 return mDesc; 310 } 311 312 @Override public int getLineStart(int line) { 313 if (line == 0) 314 return 0; 315 else 316 return getText().length(); 317 } 318 319 @Override public int getParagraphDirection(int line) { 320 return DIR_LEFT_TO_RIGHT; 321 } 322 323 @Override public boolean getLineContainsTab(int line) { 324 return false; 325 } 326 327 @Override public float getLineMax(int line) { 328 return mMax; 329 } 330 331 @Override public final Directions getLineDirections(int line) { 332 return Layout.DIRS_ALL_LEFT_TO_RIGHT; 333 } 334 335 public int getTopPadding() { 336 return mTopPadding; 337 } 338 339 public int getBottomPadding() { 340 return mBottomPadding; 341 } 342 343 @Override 344 public int getEllipsisCount(int line) { 345 return mEllipsizedCount; 346 } 347 348 @Override 349 public int getEllipsisStart(int line) { 350 return mEllipsizedStart; 351 } 352 353 @Override 354 public int getEllipsizedWidth() { 355 return mEllipsizedWidth; 356 } 357 358 // Override draw so it will be faster. 359 @Override 360 public void draw(Canvas c, Path highlight, Paint highlightpaint, 361 int cursorOffset) { 362 if (mDirect != null && highlight == null) { 363 c.drawText(mDirect, 0, mBottom - mDesc, mPaint); 364 } else { 365 super.draw(c, highlight, highlightpaint, cursorOffset); 366 } 367 } 368 369 /** 370 * Callback for the ellipsizer to report what region it ellipsized. 371 */ 372 public void ellipsized(int start, int end) { 373 mEllipsizedStart = start; 374 mEllipsizedCount = end - start; 375 } 376 377 private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; 378 379 private String mDirect; 380 private Paint mPaint; 381 382 /* package */ int mBottom, mDesc; // for Direct 383 private int mTopPadding, mBottomPadding; 384 private float mMax; 385 private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount; 386 387 private static final TextPaint sTemp = 388 new TextPaint(); 389 390 public static class Metrics extends Paint.FontMetricsInt { 391 public int width; 392 393 @Override public String toString() { 394 return super.toString() + " width=" + width; 395 } 396 } 397} 398