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 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 */ isBoring(CharSequence text, TextPaint paint)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 */ isBoring(CharSequence text, TextPaint paint, Metrics metrics)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 getHeight()293 @Override public int getHeight() { 294 return mBottom; 295 } 296 getLineCount()297 @Override public int getLineCount() { 298 return 1; 299 } 300 getLineTop(int line)301 @Override public int getLineTop(int line) { 302 if (line == 0) 303 return 0; 304 else 305 return mBottom; 306 } 307 getLineDescent(int line)308 @Override public int getLineDescent(int line) { 309 return mDesc; 310 } 311 getLineStart(int line)312 @Override public int getLineStart(int line) { 313 if (line == 0) 314 return 0; 315 else 316 return getText().length(); 317 } 318 getParagraphDirection(int line)319 @Override public int getParagraphDirection(int line) { 320 return DIR_LEFT_TO_RIGHT; 321 } 322 getLineContainsTab(int line)323 @Override public boolean getLineContainsTab(int line) { 324 return false; 325 } 326 getLineMax(int line)327 @Override public float getLineMax(int line) { 328 return mMax; 329 } 330 getLineDirections(int line)331 @Override public final Directions getLineDirections(int line) { 332 return Layout.DIRS_ALL_LEFT_TO_RIGHT; 333 } 334 getTopPadding()335 public int getTopPadding() { 336 return mTopPadding; 337 } 338 getBottomPadding()339 public int getBottomPadding() { 340 return mBottomPadding; 341 } 342 343 @Override getEllipsisCount(int line)344 public int getEllipsisCount(int line) { 345 return mEllipsizedCount; 346 } 347 348 @Override getEllipsisStart(int line)349 public int getEllipsisStart(int line) { 350 return mEllipsizedStart; 351 } 352 353 @Override getEllipsizedWidth()354 public int getEllipsizedWidth() { 355 return mEllipsizedWidth; 356 } 357 358 // Override draw so it will be faster. 359 @Override draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset)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 */ ellipsized(int start, int end)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 toString()393 @Override public String toString() { 394 return super.toString() + " width=" + width; 395 } 396 } 397 } 398