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 static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntRange; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.graphics.Canvas; 27 import android.graphics.Paint; 28 import android.graphics.Path; 29 import android.graphics.RectF; 30 import android.graphics.text.LineBreakConfig; 31 import android.text.style.ParagraphStyle; 32 33 import com.android.text.flags.Flags; 34 35 /** 36 * A BoringLayout is a very simple Layout implementation for text that 37 * fits on a single line and is all left-to-right characters. 38 * You will probably never want to make one of these yourself; 39 * if you do, be sure to call {@link #isBoring} first to make sure 40 * the text meets the criteria. 41 * <p>This class is used by widgets to control text layout. You should not need 42 * to use this class directly unless you are implementing your own widget 43 * or custom display object, in which case 44 * you are encouraged to use a Layout instead of calling 45 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) 46 * Canvas.drawText()} directly.</p> 47 */ 48 @android.ravenwood.annotation.RavenwoodKeepWholeClass 49 public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback { 50 51 /** 52 * Utility function to construct a BoringLayout instance. 53 * 54 * @param source the text to render 55 * @param paint the default paint for the layout 56 * @param outerWidth the wrapping width for the text 57 * @param align whether to left, right, or center the text 58 * @param spacingMult this value is no longer used by BoringLayout 59 * @param spacingAdd this value is no longer used by BoringLayout 60 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 61 * line width 62 * @param includePad set whether to include extra space beyond font ascent and descent which is 63 * needed to avoid clipping in some scripts 64 */ make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad)65 public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth, 66 Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, 67 boolean includePad) { 68 return new BoringLayout(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, 69 includePad); 70 } 71 72 /** 73 * Utility function to construct a BoringLayout instance. 74 * 75 * @param source the text to render 76 * @param paint the default paint for the layout 77 * @param outerWidth the wrapping width for the text 78 * @param align whether to left, right, or center the text 79 * @param spacingmult this value is no longer used by BoringLayout 80 * @param spacingadd this value is no longer used by BoringLayout 81 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 82 * line width 83 * @param includePad set whether to include extra space beyond font ascent and descent which is 84 * needed to avoid clipping in some scripts 85 * @param ellipsize whether to ellipsize the text if width of the text is longer than the 86 * requested width 87 * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is 88 * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is 89 * not used, {@code outerWidth} is used instead 90 */ make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)91 public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth, 92 Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, 93 boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 94 return new BoringLayout(source, paint, outerWidth, align, spacingmult, spacingadd, metrics, 95 includePad, ellipsize, ellipsizedWidth); 96 } 97 98 /** 99 * Utility function to construct a BoringLayout instance. 100 * 101 * The spacing multiplier and additional amount spacing are not used by BoringLayout. 102 * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will 103 * return 0.0. 104 * 105 * @param source the text to render 106 * @param paint the default paint for the layout 107 * @param outerWidth the wrapping width for the text 108 * @param align whether to left, right, or center the text 109 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 110 * line width 111 * @param includePad set whether to include extra space beyond font ascent and descent which is 112 * needed to avoid clipping in some scripts 113 * @param ellipsize whether to ellipsize the text if width of the text is longer than the 114 * requested width. null if ellipsis is not applied. 115 * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is 116 * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is 117 * not used, {@code outerWidth} is used instead 118 * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. 119 * False for keeping the first font's line height. If some glyphs 120 * requires larger vertical spaces, by passing true to this 121 * argument, the layout increase the line height to fit all glyphs. 122 */ make( @onNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing)123 public static @NonNull BoringLayout make( 124 @NonNull CharSequence source, @NonNull TextPaint paint, 125 @IntRange(from = 0) int outerWidth, 126 @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, 127 boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, 128 @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) { 129 return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad, 130 ellipsize, ellipsizedWidth, useFallbackLineSpacing); 131 } 132 133 /** 134 * Returns a BoringLayout for the specified text, potentially reusing 135 * this one if it is already suitable. The caller must make sure that 136 * no one is still using this Layout. 137 * 138 * @param source the text to render 139 * @param paint the default paint for the layout 140 * @param outerwidth the wrapping width for the text 141 * @param align whether to left, right, or center the text 142 * @param spacingMult this value is no longer used by BoringLayout 143 * @param spacingAdd this value is no longer used by BoringLayout 144 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 145 * line width 146 * @param includePad set whether to include extra space beyond font ascent and descent which is 147 * needed to avoid clipping in some scripts 148 */ replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad)149 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, 150 Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, 151 boolean includePad) { 152 replaceWith(source, paint, outerwidth, align, spacingMult, spacingAdd); 153 154 mEllipsizedWidth = outerwidth; 155 mEllipsizedStart = 0; 156 mEllipsizedCount = 0; 157 mUseFallbackLineSpacing = false; 158 159 init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); 160 return this; 161 } 162 163 /** 164 * Returns a BoringLayout for the specified text, potentially reusing 165 * this one if it is already suitable. The caller must make sure that 166 * no one is still using this Layout. 167 * 168 * The spacing multiplier and additional amount spacing are not used by BoringLayout. 169 * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will 170 * return 0.0. 171 * 172 * @param source the text to render 173 * @param paint the default paint for the layout 174 * @param outerWidth the wrapping width for the text 175 * @param align whether to left, right, or center the text 176 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 177 * line width 178 * @param includePad set whether to include extra space beyond font ascent and descent which is 179 * needed to avoid clipping in some scripts 180 * @param ellipsize whether to ellipsize the text if width of the text is longer than the 181 * requested width. null if ellipsis not applied. 182 * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is 183 * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is 184 * not used, {@code outerWidth} is used instead 185 * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. 186 * False for keeping the first font's line height. If some glyphs 187 * requires larger vertical spaces, by passing true to this 188 * argument, the layout increase the line height to fit all glyphs. 189 */ replaceOrMake(@onNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing)190 public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source, 191 @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, 192 @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, 193 @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, 194 boolean useFallbackLineSpacing) { 195 return replaceOrMake(source, paint, outerWidth, align, 1.0f, 0.0f, metrics, includePad, 196 ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */, 197 null /* minimumFontMetrics */); 198 } 199 200 /** @hide */ replaceOrMake(@onNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMultiplier, float spacingAmount, @NonNull BoringLayout.Metrics metrics, boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing, boolean useBoundsForWidth, @Nullable Paint.FontMetrics minimumFontMetrics)201 public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source, 202 @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, 203 @NonNull Alignment align, float spacingMultiplier, float spacingAmount, 204 @NonNull BoringLayout.Metrics metrics, boolean includePad, 205 @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, 206 boolean useFallbackLineSpacing, boolean useBoundsForWidth, 207 @Nullable Paint.FontMetrics minimumFontMetrics) { 208 boolean trust; 209 210 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { 211 replaceWith(source, paint, outerWidth, align, 1f, 0f); 212 213 mEllipsizedWidth = outerWidth; 214 mEllipsizedStart = 0; 215 mEllipsizedCount = 0; 216 trust = true; 217 } else { 218 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), 219 paint, outerWidth, align, spacingMultiplier, spacingAmount); 220 221 mEllipsizedWidth = ellipsizedWidth; 222 trust = false; 223 } 224 225 mUseFallbackLineSpacing = useFallbackLineSpacing; 226 227 init(getText(), paint, align, metrics, includePad, trust, 228 useFallbackLineSpacing); 229 return this; 230 } 231 232 /** 233 * Returns a BoringLayout for the specified text, potentially reusing 234 * this one if it is already suitable. The caller must make sure that 235 * no one is still using this Layout. 236 * 237 * @param source the text to render 238 * @param paint the default paint for the layout 239 * @param outerWidth the wrapping width for the text 240 * @param align whether to left, right, or center the text 241 * @param spacingMult this value is no longer used by BoringLayout 242 * @param spacingAdd this value is no longer used by BoringLayout 243 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 244 * line width 245 * @param includePad set whether to include extra space beyond font ascent and descent which is 246 * needed to avoid clipping in some scripts 247 * @param ellipsize whether to ellipsize the text if width of the text is longer than the 248 * requested width 249 * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is 250 * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is 251 * not used, {@code outerWidth} is used instead 252 */ replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)253 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, 254 Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, 255 boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 256 return replaceOrMake(source, paint, outerWidth, align, metrics, 257 includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */); 258 } 259 260 /** 261 * @param source the text to render 262 * @param paint the default paint for the layout 263 * @param outerwidth the wrapping width for the text 264 * @param align whether to left, right, or center the text 265 * @param spacingMult this value is no longer used by BoringLayout 266 * @param spacingAdd this value is no longer used by BoringLayout 267 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 268 * line width 269 * @param includePad set whether to include extra space beyond font ascent and descent which is 270 * needed to avoid clipping in some scripts 271 */ BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad)272 public BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, 273 float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) { 274 super(source, paint, outerwidth, align, TextDirectionHeuristics.LTR, spacingMult, 275 spacingAdd, includePad, false /* fallbackLineSpacing */, 276 outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */, 277 BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */, 278 null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, 279 false /* shiftDrawingOffsetForStartOverhang */, null); 280 281 mEllipsizedWidth = outerwidth; 282 mEllipsizedStart = 0; 283 mEllipsizedCount = 0; 284 mUseFallbackLineSpacing = false; 285 286 init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); 287 } 288 289 /** 290 * 291 * @param source the text to render 292 * @param paint the default paint for the layout 293 * @param outerWidth the wrapping width for the text 294 * @param align whether to left, right, or center the text 295 * @param spacingMult this value is no longer used by BoringLayout 296 * @param spacingAdd this value is no longer used by BoringLayout 297 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 298 * line width 299 * @param includePad set whether to include extra space beyond font ascent and descent which is 300 * needed to avoid clipping in some scripts 301 * @param ellipsize whether to ellipsize the text if width of the text is longer than the 302 * requested {@code outerWidth} 303 * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is 304 * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is 305 * not used, {@code outerWidth} is used instead 306 */ BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)307 public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, 308 float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, 309 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 310 this(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad, 311 ellipsize, ellipsizedWidth, false /* fallbackLineSpacing */); 312 } 313 314 /** 315 * 316 * @param source the text to render 317 * @param paint the default paint for the layout 318 * @param outerWidth the wrapping width for the text 319 * @param align whether to left, right, or center the text 320 * @param spacingMult this value is no longer used by BoringLayout 321 * @param spacingAdd this value is no longer used by BoringLayout 322 * @param metrics {@code #Metrics} instance that contains information about FontMetrics and 323 * line width 324 * @param includePad set whether to include extra space beyond font ascent and descent which is 325 * needed to avoid clipping in some scripts 326 * @param ellipsize whether to ellipsize the text if width of the text is longer than the 327 * requested {@code outerWidth}. null if ellipsis is not applied. 328 * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is 329 * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is 330 * not used, {@code outerWidth} is used instead 331 * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. 332 * False for keeping the first font's line height. If some glyphs 333 * requires larger vertical spaces, by passing true to this 334 * argument, the layout increase the line height to fit all glyphs. 335 */ BoringLayout( @onNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult, float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing)336 public BoringLayout( 337 @NonNull CharSequence source, @NonNull TextPaint paint, 338 @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult, 339 float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad, 340 @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, 341 boolean useFallbackLineSpacing) { 342 /* 343 * It is silly to have to call super() and then replaceWith(), 344 * but we can't use "this" for the callback until the call to 345 * super() finishes. 346 */ 347 this(source, paint, outerWidth, align, TextDirectionHeuristics.LTR, spacingMult, 348 spacingAdd, includePad, useFallbackLineSpacing, 349 ellipsizedWidth, ellipsize, 1 /* maxLines */, 350 BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */, 351 null /* rightIndents */, JUSTIFICATION_MODE_NONE, 352 LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, 353 false /* shiftDrawingOffsetForStartOverhang */, null); 354 } 355 356 /** @hide */ BoringLayout( CharSequence text, TextPaint paint, int width, Alignment align, float spacingMult, float spacingAdd, boolean includePad, boolean fallbackLineSpacing, int ellipsizedWidth, TextUtils.TruncateAt ellipsize, Metrics metrics, boolean useBoundsForWidth, boolean shiftDrawingOffsetForStartOverhang, @Nullable Paint.FontMetrics minimumFontMetrics)357 public BoringLayout( 358 CharSequence text, 359 TextPaint paint, 360 int width, 361 Alignment align, 362 float spacingMult, 363 float spacingAdd, 364 boolean includePad, 365 boolean fallbackLineSpacing, 366 int ellipsizedWidth, 367 TextUtils.TruncateAt ellipsize, 368 Metrics metrics, 369 boolean useBoundsForWidth, 370 boolean shiftDrawingOffsetForStartOverhang, 371 @Nullable Paint.FontMetrics minimumFontMetrics) { 372 this(text, paint, width, align, TextDirectionHeuristics.LTR, 373 spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth, 374 ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE, 375 Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE, 376 LineBreakConfig.NONE, metrics, useBoundsForWidth, 377 shiftDrawingOffsetForStartOverhang, minimumFontMetrics); 378 } 379 BoringLayout( CharSequence text, TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingMult, float spacingAdd, boolean includePad, boolean fallbackLineSpacing, int ellipsizedWidth, TextUtils.TruncateAt ellipsize, int maxLines, int breakStrategy, int hyphenationFrequency, int[] leftIndents, int[] rightIndents, int justificationMode, LineBreakConfig lineBreakConfig, Metrics metrics, boolean useBoundsForWidth, boolean shiftDrawingOffsetForStartOverhang, @Nullable Paint.FontMetrics minimumFontMetrics)380 /* package */ BoringLayout( 381 CharSequence text, 382 TextPaint paint, 383 int width, 384 Alignment align, 385 TextDirectionHeuristic textDir, 386 float spacingMult, 387 float spacingAdd, 388 boolean includePad, 389 boolean fallbackLineSpacing, 390 int ellipsizedWidth, 391 TextUtils.TruncateAt ellipsize, 392 int maxLines, 393 int breakStrategy, 394 int hyphenationFrequency, 395 int[] leftIndents, 396 int[] rightIndents, 397 int justificationMode, 398 LineBreakConfig lineBreakConfig, 399 Metrics metrics, 400 boolean useBoundsForWidth, 401 boolean shiftDrawingOffsetForStartOverhang, 402 @Nullable Paint.FontMetrics minimumFontMetrics) { 403 404 super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad, 405 fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy, 406 hyphenationFrequency, leftIndents, rightIndents, justificationMode, 407 lineBreakConfig, useBoundsForWidth, shiftDrawingOffsetForStartOverhang, 408 minimumFontMetrics); 409 410 411 boolean trust; 412 413 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { 414 mEllipsizedWidth = width; 415 mEllipsizedStart = 0; 416 mEllipsizedCount = 0; 417 trust = true; 418 } else { 419 replaceWith(TextUtils.ellipsize(text, paint, ellipsizedWidth, ellipsize, true, this), 420 paint, width, align, spacingMult, spacingAdd); 421 422 mEllipsizedWidth = ellipsizedWidth; 423 trust = false; 424 } 425 426 mUseFallbackLineSpacing = fallbackLineSpacing; 427 init(getText(), paint, align, metrics, includePad, trust, fallbackLineSpacing); 428 } 429 init(CharSequence source, TextPaint paint, Alignment align, BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth, boolean useFallbackLineSpacing)430 /* package */ void init(CharSequence source, TextPaint paint, Alignment align, 431 BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth, 432 boolean useFallbackLineSpacing) { 433 int spacing; 434 435 if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) { 436 mDirect = source.toString(); 437 } else { 438 mDirect = null; 439 } 440 441 mPaint = paint; 442 443 if (includePad) { 444 spacing = metrics.bottom - metrics.top; 445 mDesc = metrics.bottom; 446 } else { 447 spacing = metrics.descent - metrics.ascent; 448 mDesc = metrics.descent; 449 } 450 451 mBottom = spacing; 452 453 if (trustWidth) { 454 mMax = metrics.width; 455 } else { 456 /* 457 * If we have ellipsized, we have to actually calculate the 458 * width because the width that was passed in was for the 459 * full text, not the ellipsized form. 460 */ 461 TextLine line = TextLine.obtain(); 462 line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT, 463 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, 464 mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing); 465 mMax = (int) Math.ceil(line.metrics(null, null, false, null)); 466 TextLine.recycle(line); 467 } 468 469 if (includePad) { 470 mTopPadding = metrics.top - metrics.ascent; 471 mBottomPadding = metrics.bottom - metrics.descent; 472 } 473 474 mDrawingBounds.set(metrics.mDrawingBounds); 475 mDrawingBounds.offset(0, mBottom - mDesc); 476 } 477 478 /** 479 * Determine and compute metrics if given text can be handled by BoringLayout. 480 * 481 * @param text a text 482 * @param paint a paint 483 * @return layout metric for the given text. null if given text is unable to be handled by 484 * BoringLayout. 485 */ isBoring(CharSequence text, TextPaint paint)486 public static Metrics isBoring(CharSequence text, TextPaint paint) { 487 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null); 488 } 489 490 /** 491 * Determine and compute metrics if given text can be handled by BoringLayout. 492 * 493 * @param text a text 494 * @param paint a paint 495 * @param metrics a metrics object to be recycled. If null is passed, this function creat new 496 * object. 497 * @return layout metric for the given text. If metrics is not null, this method fills values 498 * to given metrics object instead of allocating new metrics object. null if given text 499 * is unable to be handled by BoringLayout. 500 */ isBoring(CharSequence text, TextPaint paint, Metrics metrics)501 public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) { 502 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics); 503 } 504 505 /** 506 * Returns true if the text contains any RTL characters, bidi format characters, or surrogate 507 * code units. 508 */ hasAnyInterestingChars(CharSequence text, int textLength)509 private static boolean hasAnyInterestingChars(CharSequence text, int textLength) { 510 final int MAX_BUF_LEN = 500; 511 final char[] buffer = TextUtils.obtain(MAX_BUF_LEN); 512 try { 513 for (int start = 0; start < textLength; start += MAX_BUF_LEN) { 514 final int end = Math.min(start + MAX_BUF_LEN, textLength); 515 516 // No need to worry about getting half codepoints, since we consider surrogate code 517 // units "interesting" as soon we see one. 518 TextUtils.getChars(text, start, end, buffer, 0); 519 520 final int len = end - start; 521 for (int i = 0; i < len; i++) { 522 final char c = buffer[i]; 523 if (c == '\n' || c == '\t' || TextUtils.couldAffectRtl(c)) { 524 return true; 525 } 526 } 527 } 528 return false; 529 } finally { 530 TextUtils.recycle(buffer); 531 } 532 } 533 534 /** 535 * Returns null if not boring; the width, ascent, and descent in the 536 * provided Metrics object (or a new one if the provided one was null) 537 * if boring. 538 * @hide 539 */ 540 @UnsupportedAppUsage isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics)541 public static Metrics isBoring(CharSequence text, TextPaint paint, 542 TextDirectionHeuristic textDir, Metrics metrics) { 543 return isBoring(text, paint, textDir, false /* useFallbackLineSpacing */, metrics); 544 } 545 546 /** 547 * Returns null if not boring; the width, ascent, and descent in the 548 * provided Metrics object (or a new one if the provided one was null) 549 * if boring. 550 * 551 * @param text a text to be calculated text layout. 552 * @param paint a paint object used for styling. 553 * @param textDir a text direction. 554 * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. 555 * False for keeping the first font's line height. If some glyphs 556 * requires larger vertical spaces, by passing true to this 557 * argument, the layout increase the line height to fit all glyphs. 558 * @param metrics the out metrics. 559 * @return metrics on success. null if text cannot be rendered by BoringLayout. 560 */ isBoring(@onNull CharSequence text, @NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, @Nullable Metrics metrics)561 public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint, 562 @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, 563 @Nullable Metrics metrics) { 564 return isBoring(text, paint, textDir, useFallbackLineSpacing, null, metrics); 565 } 566 567 /** 568 * @hide 569 */ isBoring(@onNull CharSequence text, @NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics)570 public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint, 571 @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, 572 @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) { 573 final int textLength = text.length(); 574 if (hasAnyInterestingChars(text, textLength)) { 575 return null; // There are some interesting characters. Not boring. 576 } 577 if (textDir != null && textDir.isRtl(text, 0, textLength)) { 578 return null; // The heuristic considers the whole text RTL. Not boring. 579 } 580 if (text instanceof Spanned) { 581 Spanned sp = (Spanned) text; 582 Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class); 583 if (styles.length > 0) { 584 return null; // There are some ParagraphStyle spans. Not boring. 585 } 586 } 587 588 Metrics fm = metrics; 589 if (fm == null) { 590 fm = new Metrics(); 591 } else { 592 fm.reset(); 593 } 594 595 if (Flags.fixLineHeightForLocale()) { 596 if (minimumFontMetrics != null) { 597 fm.set(minimumFontMetrics); 598 // Because the font metrics is provided by public APIs, adjust the top/bottom with 599 // ascent/descent: top must be smaller than ascent, bottom must be larger than 600 // descent. 601 fm.top = Math.min(fm.top, fm.ascent); 602 fm.bottom = Math.max(fm.bottom, fm.descent); 603 } 604 } 605 606 TextLine line = TextLine.obtain(); 607 line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, 608 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, 609 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */, 610 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */, 611 useFallbackLineSpacing); 612 fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null)); 613 TextLine.recycle(line); 614 615 return fm; 616 } 617 618 @Override getHeight()619 public int getHeight() { 620 return mBottom; 621 } 622 623 @Override getLineCount()624 public int getLineCount() { 625 return 1; 626 } 627 628 @Override getLineTop(int line)629 public int getLineTop(int line) { 630 if (line == 0) 631 return 0; 632 else 633 return mBottom; 634 } 635 636 @Override getLineDescent(int line)637 public int getLineDescent(int line) { 638 return mDesc; 639 } 640 641 @Override getLineStart(int line)642 public int getLineStart(int line) { 643 if (line == 0) 644 return 0; 645 else 646 return getText().length(); 647 } 648 649 @Override getParagraphDirection(int line)650 public int getParagraphDirection(int line) { 651 return DIR_LEFT_TO_RIGHT; 652 } 653 654 @Override getLineContainsTab(int line)655 public boolean getLineContainsTab(int line) { 656 return false; 657 } 658 659 @Override getLineMax(int line)660 public float getLineMax(int line) { 661 if (getUseBoundsForWidth()) { 662 return super.getLineMax(line); 663 } else { 664 return mMax; 665 } 666 } 667 668 @Override getLineWidth(int line)669 public float getLineWidth(int line) { 670 if (getUseBoundsForWidth()) { 671 return super.getLineWidth(line); 672 } else { 673 return (line == 0 ? mMax : 0); 674 } 675 } 676 677 @Override getLineDirections(int line)678 public final Directions getLineDirections(int line) { 679 return Layout.DIRS_ALL_LEFT_TO_RIGHT; 680 } 681 682 @Override getTopPadding()683 public int getTopPadding() { 684 return mTopPadding; 685 } 686 687 @Override getBottomPadding()688 public int getBottomPadding() { 689 return mBottomPadding; 690 } 691 692 @Override getEllipsisCount(int line)693 public int getEllipsisCount(int line) { 694 return mEllipsizedCount; 695 } 696 697 @Override getEllipsisStart(int line)698 public int getEllipsisStart(int line) { 699 return mEllipsizedStart; 700 } 701 702 @Override getEllipsizedWidth()703 public int getEllipsizedWidth() { 704 return mEllipsizedWidth; 705 } 706 707 @Override isFallbackLineSpacingEnabled()708 public boolean isFallbackLineSpacingEnabled() { 709 return mUseFallbackLineSpacing; 710 } 711 712 @Override computeDrawingBoundingBox()713 public @NonNull RectF computeDrawingBoundingBox() { 714 return mDrawingBounds; 715 } 716 717 // Override draw so it will be faster. 718 @Override draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset)719 public void draw(Canvas c, Path highlight, Paint highlightpaint, 720 int cursorOffset) { 721 if (mDirect != null && highlight == null) { 722 float leftShift = 0; 723 if (getUseBoundsForWidth() && getShiftDrawingOffsetForStartOverhang()) { 724 RectF drawingRect = computeDrawingBoundingBox(); 725 if (drawingRect.left < 0) { 726 leftShift = -drawingRect.left; 727 c.translate(leftShift, 0); 728 } 729 } 730 731 c.drawText(mDirect, 0, mBottom - mDesc, mPaint); 732 733 if (leftShift != 0) { 734 // Manually translate back to the original position because of b/324498002, using 735 // save/restore disappears the toggle switch drawables. 736 c.translate(-leftShift, 0); 737 } 738 } else { 739 super.draw(c, highlight, highlightpaint, cursorOffset); 740 } 741 } 742 743 /** 744 * Callback for the ellipsizer to report what region it ellipsized. 745 */ ellipsized(int start, int end)746 public void ellipsized(int start, int end) { 747 mEllipsizedStart = start; 748 mEllipsizedCount = end - start; 749 } 750 751 private String mDirect; 752 private Paint mPaint; 753 private boolean mUseFallbackLineSpacing; 754 755 /* package */ int mBottom, mDesc; // for Direct 756 private int mTopPadding, mBottomPadding; 757 private float mMax; 758 private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount; 759 private final RectF mDrawingBounds = new RectF(); 760 761 public static class Metrics extends Paint.FontMetricsInt { 762 public int width; 763 private final RectF mDrawingBounds = new RectF(); 764 765 /** 766 * Returns drawing bounding box. 767 * 768 * @return a drawing bounding box. 769 */ 770 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) getDrawingBoundingBox()771 @NonNull public RectF getDrawingBoundingBox() { 772 return mDrawingBounds; 773 } 774 toString()775 @Override public String toString() { 776 return super.toString() + " width=" + width + ", drawingBounds = " + mDrawingBounds; 777 } 778 reset()779 private void reset() { 780 top = 0; 781 bottom = 0; 782 ascent = 0; 783 descent = 0; 784 width = 0; 785 leading = 0; 786 mDrawingBounds.setEmpty(); 787 } 788 } 789 } 790