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.annotation.FloatRange; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.graphics.Paint; 24 import android.text.AutoGrowArray.FloatArray; 25 import android.text.style.LeadingMarginSpan; 26 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; 27 import android.text.style.LineHeightSpan; 28 import android.text.style.TabStopSpan; 29 import android.util.Log; 30 import android.util.Pools.SynchronizedPool; 31 32 import com.android.internal.util.ArrayUtils; 33 import com.android.internal.util.GrowingArrayUtils; 34 35 import dalvik.annotation.optimization.CriticalNative; 36 import dalvik.annotation.optimization.FastNative; 37 38 import java.util.Arrays; 39 40 /** 41 * StaticLayout is a Layout for text that will not be edited after it 42 * is laid out. Use {@link DynamicLayout} for text that may change. 43 * <p>This is used by widgets to control text layout. You should not need 44 * to use this class directly unless you are implementing your own widget 45 * or custom display object, or would be tempted to call 46 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, 47 * float, float, android.graphics.Paint) 48 * Canvas.drawText()} directly.</p> 49 */ 50 public class StaticLayout extends Layout { 51 /* 52 * The break iteration is done in native code. The protocol for using the native code is as 53 * follows. 54 * 55 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the 56 * following: 57 * 58 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in 59 * native. 60 * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. 61 * 62 * After all paragraphs, call finish() to release expensive buffers. 63 */ 64 65 static final String TAG = "StaticLayout"; 66 67 /** 68 * Builder for static layouts. The builder is the preferred pattern for constructing 69 * StaticLayout objects and should be preferred over the constructors, particularly to access 70 * newer features. To build a static layout, first call {@link #obtain} with the required 71 * arguments (text, paint, and width), then call setters for optional parameters, and finally 72 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get 73 * default values. 74 */ 75 public final static class Builder { Builder()76 private Builder() {} 77 78 /** 79 * Obtain a builder for constructing StaticLayout objects. 80 * 81 * @param source The text to be laid out, optionally with spans 82 * @param start The index of the start of the text 83 * @param end The index + 1 of the end of the text 84 * @param paint The base paint used for layout 85 * @param width The width in pixels 86 * @return a builder object used for constructing the StaticLayout 87 */ 88 @NonNull obtain(@onNull CharSequence source, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextPaint paint, @IntRange(from = 0) int width)89 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start, 90 @IntRange(from = 0) int end, @NonNull TextPaint paint, 91 @IntRange(from = 0) int width) { 92 Builder b = sPool.acquire(); 93 if (b == null) { 94 b = new Builder(); 95 } 96 97 // set default initial values 98 b.mText = source; 99 b.mStart = start; 100 b.mEnd = end; 101 b.mPaint = paint; 102 b.mWidth = width; 103 b.mAlignment = Alignment.ALIGN_NORMAL; 104 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR; 105 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER; 106 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION; 107 b.mIncludePad = true; 108 b.mFallbackLineSpacing = false; 109 b.mEllipsizedWidth = width; 110 b.mEllipsize = null; 111 b.mMaxLines = Integer.MAX_VALUE; 112 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 113 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 114 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 115 return b; 116 } 117 118 /** 119 * This method should be called after the layout is finished getting constructed and the 120 * builder needs to be cleaned up and returned to the pool. 121 */ recycle(@onNull Builder b)122 private static void recycle(@NonNull Builder b) { 123 b.mPaint = null; 124 b.mText = null; 125 b.mLeftIndents = null; 126 b.mRightIndents = null; 127 b.mLeftPaddings = null; 128 b.mRightPaddings = null; 129 sPool.release(b); 130 } 131 132 // release any expensive state finish()133 /* package */ void finish() { 134 mText = null; 135 mPaint = null; 136 mLeftIndents = null; 137 mRightIndents = null; 138 mLeftPaddings = null; 139 mRightPaddings = null; 140 } 141 setText(CharSequence source)142 public Builder setText(CharSequence source) { 143 return setText(source, 0, source.length()); 144 } 145 146 /** 147 * Set the text. Only useful when re-using the builder, which is done for 148 * the internal implementation of {@link DynamicLayout} but not as part 149 * of normal {@link StaticLayout} usage. 150 * 151 * @param source The text to be laid out, optionally with spans 152 * @param start The index of the start of the text 153 * @param end The index + 1 of the end of the text 154 * @return this builder, useful for chaining 155 * 156 * @hide 157 */ 158 @NonNull setText(@onNull CharSequence source, int start, int end)159 public Builder setText(@NonNull CharSequence source, int start, int end) { 160 mText = source; 161 mStart = start; 162 mEnd = end; 163 return this; 164 } 165 166 /** 167 * Set the paint. Internal for reuse cases only. 168 * 169 * @param paint The base paint used for layout 170 * @return this builder, useful for chaining 171 * 172 * @hide 173 */ 174 @NonNull setPaint(@onNull TextPaint paint)175 public Builder setPaint(@NonNull TextPaint paint) { 176 mPaint = paint; 177 return this; 178 } 179 180 /** 181 * Set the width. Internal for reuse cases only. 182 * 183 * @param width The width in pixels 184 * @return this builder, useful for chaining 185 * 186 * @hide 187 */ 188 @NonNull setWidth(@ntRangefrom = 0) int width)189 public Builder setWidth(@IntRange(from = 0) int width) { 190 mWidth = width; 191 if (mEllipsize == null) { 192 mEllipsizedWidth = width; 193 } 194 return this; 195 } 196 197 /** 198 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}. 199 * 200 * @param alignment Alignment for the resulting {@link StaticLayout} 201 * @return this builder, useful for chaining 202 */ 203 @NonNull setAlignment(@onNull Alignment alignment)204 public Builder setAlignment(@NonNull Alignment alignment) { 205 mAlignment = alignment; 206 return this; 207 } 208 209 /** 210 * Set the text direction heuristic. The text direction heuristic is used to 211 * resolve text direction per-paragraph based on the input text. The default is 212 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}. 213 * 214 * @param textDir text direction heuristic for resolving bidi behavior. 215 * @return this builder, useful for chaining 216 */ 217 @NonNull setTextDirection(@onNull TextDirectionHeuristic textDir)218 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) { 219 mTextDir = textDir; 220 return this; 221 } 222 223 /** 224 * Set line spacing parameters. Each line will have its line spacing multiplied by 225 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for 226 * {@code spacingAdd} and 1.0 for {@code spacingMult}. 227 * 228 * @param spacingAdd the amount of line spacing addition 229 * @param spacingMult the line spacing multiplier 230 * @return this builder, useful for chaining 231 * @see android.widget.TextView#setLineSpacing 232 */ 233 @NonNull setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult)234 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) { 235 mSpacingAdd = spacingAdd; 236 mSpacingMult = spacingMult; 237 return this; 238 } 239 240 /** 241 * Set whether to include extra space beyond font ascent and descent (which is 242 * needed to avoid clipping in some languages, such as Arabic and Kannada). The 243 * default is {@code true}. 244 * 245 * @param includePad whether to include padding 246 * @return this builder, useful for chaining 247 * @see android.widget.TextView#setIncludeFontPadding 248 */ 249 @NonNull setIncludePad(boolean includePad)250 public Builder setIncludePad(boolean includePad) { 251 mIncludePad = includePad; 252 return this; 253 } 254 255 /** 256 * Set whether to respect the ascent and descent of the fallback fonts that are used in 257 * displaying the text (which is needed to avoid text from consecutive lines running into 258 * each other). If set, fallback fonts that end up getting used can increase the ascent 259 * and descent of the lines that they are used on. 260 * 261 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to 262 * true is strongly recommended. It is required to be true if text could be in languages 263 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text. 264 * 265 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts 266 * @return this builder, useful for chaining 267 */ 268 @NonNull setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks)269 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) { 270 mFallbackLineSpacing = useLineSpacingFromFallbacks; 271 return this; 272 } 273 274 /** 275 * Set the width as used for ellipsizing purposes, if it differs from the 276 * normal layout width. The default is the {@code width} 277 * passed to {@link #obtain}. 278 * 279 * @param ellipsizedWidth width used for ellipsizing, in pixels 280 * @return this builder, useful for chaining 281 * @see android.widget.TextView#setEllipsize 282 */ 283 @NonNull setEllipsizedWidth(@ntRangefrom = 0) int ellipsizedWidth)284 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) { 285 mEllipsizedWidth = ellipsizedWidth; 286 return this; 287 } 288 289 /** 290 * Set ellipsizing on the layout. Causes words that are longer than the view 291 * is wide, or exceeding the number of lines (see #setMaxLines) in the case 292 * of {@link android.text.TextUtils.TruncateAt#END} or 293 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead 294 * of broken. The default is {@code null}, indicating no ellipsis is to be applied. 295 * 296 * @param ellipsize type of ellipsis behavior 297 * @return this builder, useful for chaining 298 * @see android.widget.TextView#setEllipsize 299 */ 300 @NonNull setEllipsize(@ullable TextUtils.TruncateAt ellipsize)301 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) { 302 mEllipsize = ellipsize; 303 return this; 304 } 305 306 /** 307 * Set maximum number of lines. This is particularly useful in the case of 308 * ellipsizing, where it changes the layout of the last line. The default is 309 * unlimited. 310 * 311 * @param maxLines maximum number of lines in the layout 312 * @return this builder, useful for chaining 313 * @see android.widget.TextView#setMaxLines 314 */ 315 @NonNull setMaxLines(@ntRangefrom = 0) int maxLines)316 public Builder setMaxLines(@IntRange(from = 0) int maxLines) { 317 mMaxLines = maxLines; 318 return this; 319 } 320 321 /** 322 * Set break strategy, useful for selecting high quality or balanced paragraph 323 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}. 324 * 325 * @param breakStrategy break strategy for paragraph layout 326 * @return this builder, useful for chaining 327 * @see android.widget.TextView#setBreakStrategy 328 */ 329 @NonNull setBreakStrategy(@reakStrategy int breakStrategy)330 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) { 331 mBreakStrategy = breakStrategy; 332 return this; 333 } 334 335 /** 336 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The 337 * possible values are defined in {@link Layout}, by constants named with the pattern 338 * {@code HYPHENATION_FREQUENCY_*}. The default is 339 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. 340 * 341 * @param hyphenationFrequency hyphenation frequency for the paragraph 342 * @return this builder, useful for chaining 343 * @see android.widget.TextView#setHyphenationFrequency 344 */ 345 @NonNull setHyphenationFrequency(@yphenationFrequency int hyphenationFrequency)346 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) { 347 mHyphenationFrequency = hyphenationFrequency; 348 return this; 349 } 350 351 /** 352 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in 353 * pixels. For lines past the last element in the array, the last element repeats. 354 * 355 * @param leftIndents array of indent values for left margin, in pixels 356 * @param rightIndents array of indent values for right margin, in pixels 357 * @return this builder, useful for chaining 358 */ 359 @NonNull setIndents(@ullable int[] leftIndents, @Nullable int[] rightIndents)360 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) { 361 mLeftIndents = leftIndents; 362 mRightIndents = rightIndents; 363 return this; 364 } 365 366 /** 367 * Set available paddings to draw overhanging text on. Arguments are arrays holding the 368 * amount of padding available, one per line, measured in pixels. For lines past the last 369 * element in the array, the last element repeats. 370 * 371 * The individual padding amounts should be non-negative. The result of passing negative 372 * paddings is undefined. 373 * 374 * @param leftPaddings array of amounts of available padding for left margin, in pixels 375 * @param rightPaddings array of amounts of available padding for right margin, in pixels 376 * @return this builder, useful for chaining 377 * 378 * @hide 379 */ 380 @NonNull setAvailablePaddings(@ullable int[] leftPaddings, @Nullable int[] rightPaddings)381 public Builder setAvailablePaddings(@Nullable int[] leftPaddings, 382 @Nullable int[] rightPaddings) { 383 mLeftPaddings = leftPaddings; 384 mRightPaddings = rightPaddings; 385 return this; 386 } 387 388 /** 389 * Set paragraph justification mode. The default value is 390 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification, 391 * the last line will be displayed with the alignment set by {@link #setAlignment}. 392 * 393 * @param justificationMode justification mode for the paragraph. 394 * @return this builder, useful for chaining. 395 */ 396 @NonNull setJustificationMode(@ustificationMode int justificationMode)397 public Builder setJustificationMode(@JustificationMode int justificationMode) { 398 mJustificationMode = justificationMode; 399 return this; 400 } 401 402 /** 403 * Sets whether the line spacing should be applied for the last line. Default value is 404 * {@code false}. 405 * 406 * @hide 407 */ 408 @NonNull setAddLastLineLineSpacing(boolean value)409 /* package */ Builder setAddLastLineLineSpacing(boolean value) { 410 mAddLastLineLineSpacing = value; 411 return this; 412 } 413 414 /** 415 * Build the {@link StaticLayout} after options have been set. 416 * 417 * <p>Note: the builder object must not be reused in any way after calling this 418 * method. Setting parameters after calling this method, or calling it a second 419 * time on the same builder object, will likely lead to unexpected results. 420 * 421 * @return the newly constructed {@link StaticLayout} object 422 */ 423 @NonNull build()424 public StaticLayout build() { 425 StaticLayout result = new StaticLayout(this); 426 Builder.recycle(this); 427 return result; 428 } 429 430 private CharSequence mText; 431 private int mStart; 432 private int mEnd; 433 private TextPaint mPaint; 434 private int mWidth; 435 private Alignment mAlignment; 436 private TextDirectionHeuristic mTextDir; 437 private float mSpacingMult; 438 private float mSpacingAdd; 439 private boolean mIncludePad; 440 private boolean mFallbackLineSpacing; 441 private int mEllipsizedWidth; 442 private TextUtils.TruncateAt mEllipsize; 443 private int mMaxLines; 444 private int mBreakStrategy; 445 private int mHyphenationFrequency; 446 @Nullable private int[] mLeftIndents; 447 @Nullable private int[] mRightIndents; 448 @Nullable private int[] mLeftPaddings; 449 @Nullable private int[] mRightPaddings; 450 private int mJustificationMode; 451 private boolean mAddLastLineLineSpacing; 452 453 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); 454 455 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3); 456 } 457 458 /** 459 * @deprecated Use {@link Builder} instead. 460 */ 461 @Deprecated StaticLayout(CharSequence source, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)462 public StaticLayout(CharSequence source, TextPaint paint, 463 int width, 464 Alignment align, float spacingmult, float spacingadd, 465 boolean includepad) { 466 this(source, 0, source.length(), paint, width, align, 467 spacingmult, spacingadd, includepad); 468 } 469 470 /** 471 * @deprecated Use {@link Builder} instead. 472 */ 473 @Deprecated StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, boolean includepad)474 public StaticLayout(CharSequence source, int bufstart, int bufend, 475 TextPaint paint, int outerwidth, 476 Alignment align, 477 float spacingmult, float spacingadd, 478 boolean includepad) { 479 this(source, bufstart, bufend, paint, outerwidth, align, 480 spacingmult, spacingadd, includepad, null, 0); 481 } 482 483 /** 484 * @deprecated Use {@link Builder} instead. 485 */ 486 @Deprecated StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)487 public StaticLayout(CharSequence source, int bufstart, int bufend, 488 TextPaint paint, int outerwidth, 489 Alignment align, 490 float spacingmult, float spacingadd, 491 boolean includepad, 492 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 493 this(source, bufstart, bufend, paint, outerwidth, align, 494 TextDirectionHeuristics.FIRSTSTRONG_LTR, 495 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE); 496 } 497 498 /** 499 * @hide 500 * @deprecated Use {@link Builder} instead. 501 */ 502 @Deprecated StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines)503 public StaticLayout(CharSequence source, int bufstart, int bufend, 504 TextPaint paint, int outerwidth, 505 Alignment align, TextDirectionHeuristic textDir, 506 float spacingmult, float spacingadd, 507 boolean includepad, 508 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) { 509 super((ellipsize == null) 510 ? source 511 : (source instanceof Spanned) 512 ? new SpannedEllipsizer(source) 513 : new Ellipsizer(source), 514 paint, outerwidth, align, textDir, spacingmult, spacingadd); 515 516 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth) 517 .setAlignment(align) 518 .setTextDirection(textDir) 519 .setLineSpacing(spacingadd, spacingmult) 520 .setIncludePad(includepad) 521 .setEllipsizedWidth(ellipsizedWidth) 522 .setEllipsize(ellipsize) 523 .setMaxLines(maxLines); 524 /* 525 * This is annoying, but we can't refer to the layout until superclass construction is 526 * finished, and the superclass constructor wants the reference to the display text. 527 * 528 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout 529 * as a parameter to do their calculations, but the Ellipsizers also need to be the input 530 * to the superclass's constructor (Layout). In order to go around the circular 531 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And 532 * we fill in the rest of the needed information (layout, width, and method) later, here. 533 * 534 * This will break if the superclass constructor ever actually cares about the content 535 * instead of just holding the reference. 536 */ 537 if (ellipsize != null) { 538 Ellipsizer e = (Ellipsizer) getText(); 539 540 e.mLayout = this; 541 e.mWidth = ellipsizedWidth; 542 e.mMethod = ellipsize; 543 mEllipsizedWidth = ellipsizedWidth; 544 545 mColumns = COLUMNS_ELLIPSIZE; 546 } else { 547 mColumns = COLUMNS_NORMAL; 548 mEllipsizedWidth = outerwidth; 549 } 550 551 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2); 552 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns); 553 mMaximumVisibleLineCount = maxLines; 554 555 generate(b, b.mIncludePad, b.mIncludePad); 556 557 Builder.recycle(b); 558 } 559 560 /** 561 * Used by DynamicLayout. 562 */ StaticLayout(@ullable CharSequence text)563 /* package */ StaticLayout(@Nullable CharSequence text) { 564 super(text, null, 0, null, 0, 0); 565 566 mColumns = COLUMNS_ELLIPSIZE; 567 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2); 568 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns); 569 } 570 StaticLayout(Builder b)571 private StaticLayout(Builder b) { 572 super((b.mEllipsize == null) 573 ? b.mText 574 : (b.mText instanceof Spanned) 575 ? new SpannedEllipsizer(b.mText) 576 : new Ellipsizer(b.mText), 577 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd); 578 579 if (b.mEllipsize != null) { 580 Ellipsizer e = (Ellipsizer) getText(); 581 582 e.mLayout = this; 583 e.mWidth = b.mEllipsizedWidth; 584 e.mMethod = b.mEllipsize; 585 mEllipsizedWidth = b.mEllipsizedWidth; 586 587 mColumns = COLUMNS_ELLIPSIZE; 588 } else { 589 mColumns = COLUMNS_NORMAL; 590 mEllipsizedWidth = b.mWidth; 591 } 592 593 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2); 594 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns); 595 mMaximumVisibleLineCount = b.mMaxLines; 596 597 mLeftIndents = b.mLeftIndents; 598 mRightIndents = b.mRightIndents; 599 mLeftPaddings = b.mLeftPaddings; 600 mRightPaddings = b.mRightPaddings; 601 setJustificationMode(b.mJustificationMode); 602 603 generate(b, b.mIncludePad, b.mIncludePad); 604 } 605 generate(Builder b, boolean includepad, boolean trackpad)606 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) { 607 final CharSequence source = b.mText; 608 final int bufStart = b.mStart; 609 final int bufEnd = b.mEnd; 610 TextPaint paint = b.mPaint; 611 int outerWidth = b.mWidth; 612 TextDirectionHeuristic textDir = b.mTextDir; 613 final boolean fallbackLineSpacing = b.mFallbackLineSpacing; 614 float spacingmult = b.mSpacingMult; 615 float spacingadd = b.mSpacingAdd; 616 float ellipsizedWidth = b.mEllipsizedWidth; 617 TextUtils.TruncateAt ellipsize = b.mEllipsize; 618 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing; 619 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs 620 FloatArray widths = new FloatArray(); 621 622 mLineCount = 0; 623 mEllipsized = false; 624 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT; 625 626 int v = 0; 627 boolean needMultiply = (spacingmult != 1 || spacingadd != 0); 628 629 Paint.FontMetricsInt fm = b.mFontMetricsInt; 630 int[] chooseHtv = null; 631 632 final int[] indents; 633 if (mLeftIndents != null || mRightIndents != null) { 634 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length; 635 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length; 636 final int indentsLen = Math.max(leftLen, rightLen); 637 indents = new int[indentsLen]; 638 for (int i = 0; i < leftLen; i++) { 639 indents[i] = mLeftIndents[i]; 640 } 641 for (int i = 0; i < rightLen; i++) { 642 indents[i] += mRightIndents[i]; 643 } 644 } else { 645 indents = null; 646 } 647 648 final long nativePtr = nInit( 649 b.mBreakStrategy, b.mHyphenationFrequency, 650 // TODO: Support more justification mode, e.g. letter spacing, stretching. 651 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, 652 indents, mLeftPaddings, mRightPaddings); 653 654 PrecomputedText.ParagraphInfo[] paragraphInfo = null; 655 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null; 656 if (source instanceof PrecomputedText) { 657 PrecomputedText precomputed = (PrecomputedText) source; 658 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint, 659 b.mBreakStrategy, b.mHyphenationFrequency)) { 660 // Some parameters are different from the ones when measured text is created. 661 paragraphInfo = precomputed.getParagraphInfo(); 662 } 663 } 664 665 if (paragraphInfo == null) { 666 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir, 667 b.mBreakStrategy, b.mHyphenationFrequency); 668 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart, 669 bufEnd, false /* computeLayout */); 670 } 671 672 try { 673 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) { 674 final int paraStart = paraIndex == 0 675 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd; 676 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd; 677 678 int firstWidthLineCount = 1; 679 int firstWidth = outerWidth; 680 int restWidth = outerWidth; 681 682 LineHeightSpan[] chooseHt = null; 683 684 if (spanned != null) { 685 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, 686 LeadingMarginSpan.class); 687 for (int i = 0; i < sp.length; i++) { 688 LeadingMarginSpan lms = sp[i]; 689 firstWidth -= sp[i].getLeadingMargin(true); 690 restWidth -= sp[i].getLeadingMargin(false); 691 692 // LeadingMarginSpan2 is odd. The count affects all 693 // leading margin spans, not just this particular one 694 if (lms instanceof LeadingMarginSpan2) { 695 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; 696 firstWidthLineCount = Math.max(firstWidthLineCount, 697 lms2.getLeadingMarginLineCount()); 698 } 699 } 700 701 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); 702 703 if (chooseHt.length == 0) { 704 chooseHt = null; // So that out() would not assume it has any contents 705 } else { 706 if (chooseHtv == null || chooseHtv.length < chooseHt.length) { 707 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length); 708 } 709 710 for (int i = 0; i < chooseHt.length; i++) { 711 int o = spanned.getSpanStart(chooseHt[i]); 712 713 if (o < paraStart) { 714 // starts in this layout, before the 715 // current paragraph 716 717 chooseHtv[i] = getLineTop(getLineForOffset(o)); 718 } else { 719 // starts in this paragraph 720 721 chooseHtv[i] = v; 722 } 723 } 724 } 725 } 726 727 // tab stop locations 728 int[] variableTabStops = null; 729 if (spanned != null) { 730 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, 731 paraEnd, TabStopSpan.class); 732 if (spans.length > 0) { 733 int[] stops = new int[spans.length]; 734 for (int i = 0; i < spans.length; i++) { 735 stops[i] = spans[i].getTabStop(); 736 } 737 Arrays.sort(stops, 0, stops.length); 738 variableTabStops = stops; 739 } 740 } 741 742 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured; 743 final char[] chs = measuredPara.getChars(); 744 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray(); 745 final int[] fmCache = measuredPara.getFontMetrics().getRawArray(); 746 // TODO: Stop keeping duplicated width copy in native and Java. 747 widths.resize(chs.length); 748 749 // measurement has to be done before performing line breaking 750 // but we don't want to recompute fontmetrics or span ranges the 751 // second time, so we cache those and then use those stored values 752 753 int breakCount = nComputeLineBreaks( 754 nativePtr, 755 756 // Inputs 757 chs, 758 measuredPara.getNativePtr(), 759 paraEnd - paraStart, 760 firstWidth, 761 firstWidthLineCount, 762 restWidth, 763 variableTabStops, 764 TAB_INCREMENT, 765 mLineCount, 766 767 // Outputs 768 lineBreaks, 769 lineBreaks.breaks.length, 770 lineBreaks.breaks, 771 lineBreaks.widths, 772 lineBreaks.ascents, 773 lineBreaks.descents, 774 lineBreaks.flags, 775 widths.getRawArray()); 776 777 final int[] breaks = lineBreaks.breaks; 778 final float[] lineWidths = lineBreaks.widths; 779 final float[] ascents = lineBreaks.ascents; 780 final float[] descents = lineBreaks.descents; 781 final int[] flags = lineBreaks.flags; 782 783 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; 784 final boolean ellipsisMayBeApplied = ellipsize != null 785 && (ellipsize == TextUtils.TruncateAt.END 786 || (mMaximumVisibleLineCount == 1 787 && ellipsize != TextUtils.TruncateAt.MARQUEE)); 788 if (0 < remainingLineCount && remainingLineCount < breakCount 789 && ellipsisMayBeApplied) { 790 // Calculate width and flag. 791 float width = 0; 792 int flag = 0; // XXX May need to also have starting hyphen edit 793 for (int i = remainingLineCount - 1; i < breakCount; i++) { 794 if (i == breakCount - 1) { 795 width += lineWidths[i]; 796 } else { 797 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { 798 width += widths.get(j); 799 } 800 } 801 flag |= flags[i] & TAB_MASK; 802 } 803 // Treat the last line and overflowed lines as a single line. 804 breaks[remainingLineCount - 1] = breaks[breakCount - 1]; 805 lineWidths[remainingLineCount - 1] = width; 806 flags[remainingLineCount - 1] = flag; 807 808 breakCount = remainingLineCount; 809 } 810 811 // here is the offset of the starting character of the line we are currently 812 // measuring 813 int here = paraStart; 814 815 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; 816 int fmCacheIndex = 0; 817 int spanEndCacheIndex = 0; 818 int breakIndex = 0; 819 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { 820 // retrieve end of span 821 spanEnd = spanEndCache[spanEndCacheIndex++]; 822 823 // retrieve cached metrics, order matches above 824 fm.top = fmCache[fmCacheIndex * 4 + 0]; 825 fm.bottom = fmCache[fmCacheIndex * 4 + 1]; 826 fm.ascent = fmCache[fmCacheIndex * 4 + 2]; 827 fm.descent = fmCache[fmCacheIndex * 4 + 3]; 828 fmCacheIndex++; 829 830 if (fm.top < fmTop) { 831 fmTop = fm.top; 832 } 833 if (fm.ascent < fmAscent) { 834 fmAscent = fm.ascent; 835 } 836 if (fm.descent > fmDescent) { 837 fmDescent = fm.descent; 838 } 839 if (fm.bottom > fmBottom) { 840 fmBottom = fm.bottom; 841 } 842 843 // skip breaks ending before current span range 844 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { 845 breakIndex++; 846 } 847 848 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { 849 int endPos = paraStart + breaks[breakIndex]; 850 851 boolean moreChars = (endPos < bufEnd); 852 853 final int ascent = fallbackLineSpacing 854 ? Math.min(fmAscent, Math.round(ascents[breakIndex])) 855 : fmAscent; 856 final int descent = fallbackLineSpacing 857 ? Math.max(fmDescent, Math.round(descents[breakIndex])) 858 : fmDescent; 859 v = out(source, here, endPos, 860 ascent, descent, fmTop, fmBottom, 861 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, 862 flags[breakIndex], needMultiply, measuredPara, bufEnd, 863 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(), 864 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], 865 paint, moreChars); 866 867 if (endPos < spanEnd) { 868 // preserve metrics for current span 869 fmTop = fm.top; 870 fmBottom = fm.bottom; 871 fmAscent = fm.ascent; 872 fmDescent = fm.descent; 873 } else { 874 fmTop = fmBottom = fmAscent = fmDescent = 0; 875 } 876 877 here = endPos; 878 breakIndex++; 879 880 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { 881 return; 882 } 883 } 884 } 885 886 if (paraEnd == bufEnd) { 887 break; 888 } 889 } 890 891 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) 892 && mLineCount < mMaximumVisibleLineCount) { 893 final MeasuredParagraph measuredPara = 894 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null); 895 paint.getFontMetricsInt(fm); 896 v = out(source, 897 bufEnd, bufEnd, fm.ascent, fm.descent, 898 fm.top, fm.bottom, 899 v, 900 spacingmult, spacingadd, null, 901 null, fm, 0, 902 needMultiply, measuredPara, bufEnd, 903 includepad, trackpad, addLastLineSpacing, null, 904 null, bufStart, ellipsize, 905 ellipsizedWidth, 0, paint, false); 906 } 907 } finally { 908 nFinish(nativePtr); 909 } 910 } 911 912 private int out(final CharSequence text, final int start, final int end, int above, int below, 913 int top, int bottom, int v, final float spacingmult, final float spacingadd, 914 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, 915 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured, 916 final int bufEnd, final boolean includePad, final boolean trackPad, 917 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, 918 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, 919 final float textWidth, final TextPaint paint, final boolean moreChars) { 920 final int j = mLineCount; 921 final int off = j * mColumns; 922 final int want = off + mColumns + TOP; 923 int[] lines = mLines; 924 final int dir = measured.getParagraphDir(); 925 926 if (want >= lines.length) { 927 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want)); 928 System.arraycopy(lines, 0, grow, 0, lines.length); 929 mLines = grow; 930 lines = grow; 931 } 932 933 if (j >= mLineDirections.length) { 934 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class, 935 GrowingArrayUtils.growSize(j)); 936 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length); 937 mLineDirections = grow; 938 } 939 940 if (chooseHt != null) { 941 fm.ascent = above; 942 fm.descent = below; 943 fm.top = top; 944 fm.bottom = bottom; 945 946 for (int i = 0; i < chooseHt.length; i++) { 947 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) { 948 ((LineHeightSpan.WithDensity) chooseHt[i]) 949 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint); 950 } else { 951 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm); 952 } 953 } 954 955 above = fm.ascent; 956 below = fm.descent; 957 top = fm.top; 958 bottom = fm.bottom; 959 } 960 961 boolean firstLine = (j == 0); 962 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); 963 964 if (ellipsize != null) { 965 // If there is only one line, then do any type of ellipsis except when it is MARQUEE 966 // if there are multiple lines, just allow END ellipsis on the last line 967 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount); 968 969 boolean doEllipsis = 970 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) && 971 ellipsize != TextUtils.TruncateAt.MARQUEE) || 972 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) && 973 ellipsize == TextUtils.TruncateAt.END); 974 if (doEllipsis) { 975 calculateEllipsis(start, end, widths, widthStart, 976 ellipsisWidth, ellipsize, j, 977 textWidth, paint, forceEllipsis); 978 } 979 } 980 981 final boolean lastLine; 982 if (mEllipsized) { 983 lastLine = true; 984 } else { 985 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0 986 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE; 987 if (end == bufEnd && !lastCharIsNewLine) { 988 lastLine = true; 989 } else if (start == bufEnd && lastCharIsNewLine) { 990 lastLine = true; 991 } else { 992 lastLine = false; 993 } 994 } 995 996 if (firstLine) { 997 if (trackPad) { 998 mTopPadding = top - above; 999 } 1000 1001 if (includePad) { 1002 above = top; 1003 } 1004 } 1005 1006 int extra; 1007 1008 if (lastLine) { 1009 if (trackPad) { 1010 mBottomPadding = bottom - below; 1011 } 1012 1013 if (includePad) { 1014 below = bottom; 1015 } 1016 } 1017 1018 if (needMultiply && (addLastLineLineSpacing || !lastLine)) { 1019 double ex = (below - above) * (spacingmult - 1) + spacingadd; 1020 if (ex >= 0) { 1021 extra = (int)(ex + EXTRA_ROUNDING); 1022 } else { 1023 extra = -(int)(-ex + EXTRA_ROUNDING); 1024 } 1025 } else { 1026 extra = 0; 1027 } 1028 1029 lines[off + START] = start; 1030 lines[off + TOP] = v; 1031 lines[off + DESCENT] = below + extra; 1032 lines[off + EXTRA] = extra; 1033 1034 // special case for non-ellipsized last visible line when maxLines is set 1035 // store the height as if it was ellipsized 1036 if (!mEllipsized && currentLineIsTheLastVisibleOne) { 1037 // below calculation as if it was the last line 1038 int maxLineBelow = includePad ? bottom : below; 1039 // similar to the calculation of v below, without the extra. 1040 mMaxLineHeight = v + (maxLineBelow - above); 1041 } 1042 1043 v += (below - above) + extra; 1044 lines[off + mColumns + START] = end; 1045 lines[off + mColumns + TOP] = v; 1046 1047 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining 1048 // one bit for start field 1049 lines[off + TAB] |= flags & TAB_MASK; 1050 lines[off + HYPHEN] = flags; 1051 lines[off + DIR] |= dir << DIR_SHIFT; 1052 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart); 1053 1054 mLineCount++; 1055 return v; 1056 } 1057 1058 private void calculateEllipsis(int lineStart, int lineEnd, 1059 float[] widths, int widthStart, 1060 float avail, TextUtils.TruncateAt where, 1061 int line, float textWidth, TextPaint paint, 1062 boolean forceEllipsis) { 1063 avail -= getTotalInsets(line); 1064 if (textWidth <= avail && !forceEllipsis) { 1065 // Everything fits! 1066 mLines[mColumns * line + ELLIPSIS_START] = 0; 1067 mLines[mColumns * line + ELLIPSIS_COUNT] = 0; 1068 return; 1069 } 1070 1071 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where)); 1072 int ellipsisStart = 0; 1073 int ellipsisCount = 0; 1074 int len = lineEnd - lineStart; 1075 1076 // We only support start ellipsis on a single line 1077 if (where == TextUtils.TruncateAt.START) { 1078 if (mMaximumVisibleLineCount == 1) { 1079 float sum = 0; 1080 int i; 1081 1082 for (i = len; i > 0; i--) { 1083 float w = widths[i - 1 + lineStart - widthStart]; 1084 if (w + sum + ellipsisWidth > avail) { 1085 while (i < len && widths[i + lineStart - widthStart] == 0.0f) { 1086 i++; 1087 } 1088 break; 1089 } 1090 1091 sum += w; 1092 } 1093 1094 ellipsisStart = 0; 1095 ellipsisCount = i; 1096 } else { 1097 if (Log.isLoggable(TAG, Log.WARN)) { 1098 Log.w(TAG, "Start Ellipsis only supported with one line"); 1099 } 1100 } 1101 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE || 1102 where == TextUtils.TruncateAt.END_SMALL) { 1103 float sum = 0; 1104 int i; 1105 1106 for (i = 0; i < len; i++) { 1107 float w = widths[i + lineStart - widthStart]; 1108 1109 if (w + sum + ellipsisWidth > avail) { 1110 break; 1111 } 1112 1113 sum += w; 1114 } 1115 1116 ellipsisStart = i; 1117 ellipsisCount = len - i; 1118 if (forceEllipsis && ellipsisCount == 0 && len > 0) { 1119 ellipsisStart = len - 1; 1120 ellipsisCount = 1; 1121 } 1122 } else { 1123 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line 1124 if (mMaximumVisibleLineCount == 1) { 1125 float lsum = 0, rsum = 0; 1126 int left = 0, right = len; 1127 1128 float ravail = (avail - ellipsisWidth) / 2; 1129 for (right = len; right > 0; right--) { 1130 float w = widths[right - 1 + lineStart - widthStart]; 1131 1132 if (w + rsum > ravail) { 1133 while (right < len && widths[right + lineStart - widthStart] == 0.0f) { 1134 right++; 1135 } 1136 break; 1137 } 1138 rsum += w; 1139 } 1140 1141 float lavail = avail - ellipsisWidth - rsum; 1142 for (left = 0; left < right; left++) { 1143 float w = widths[left + lineStart - widthStart]; 1144 1145 if (w + lsum > lavail) { 1146 break; 1147 } 1148 1149 lsum += w; 1150 } 1151 1152 ellipsisStart = left; 1153 ellipsisCount = right - left; 1154 } else { 1155 if (Log.isLoggable(TAG, Log.WARN)) { 1156 Log.w(TAG, "Middle Ellipsis only supported with one line"); 1157 } 1158 } 1159 } 1160 mEllipsized = true; 1161 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; 1162 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; 1163 } 1164 1165 private float getTotalInsets(int line) { 1166 int totalIndent = 0; 1167 if (mLeftIndents != null) { 1168 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)]; 1169 } 1170 if (mRightIndents != null) { 1171 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)]; 1172 } 1173 return totalIndent; 1174 } 1175 1176 // Override the base class so we can directly access our members, 1177 // rather than relying on member functions. 1178 // The logic mirrors that of Layout.getLineForVertical 1179 // FIXME: It may be faster to do a linear search for layouts without many lines. 1180 @Override 1181 public int getLineForVertical(int vertical) { 1182 int high = mLineCount; 1183 int low = -1; 1184 int guess; 1185 int[] lines = mLines; 1186 while (high - low > 1) { 1187 guess = (high + low) >> 1; 1188 if (lines[mColumns * guess + TOP] > vertical){ 1189 high = guess; 1190 } else { 1191 low = guess; 1192 } 1193 } 1194 if (low < 0) { 1195 return 0; 1196 } else { 1197 return low; 1198 } 1199 } 1200 1201 @Override 1202 public int getLineCount() { 1203 return mLineCount; 1204 } 1205 1206 @Override 1207 public int getLineTop(int line) { 1208 return mLines[mColumns * line + TOP]; 1209 } 1210 1211 /** 1212 * @hide 1213 */ 1214 @Override 1215 public int getLineExtra(int line) { 1216 return mLines[mColumns * line + EXTRA]; 1217 } 1218 1219 @Override 1220 public int getLineDescent(int line) { 1221 return mLines[mColumns * line + DESCENT]; 1222 } 1223 1224 @Override 1225 public int getLineStart(int line) { 1226 return mLines[mColumns * line + START] & START_MASK; 1227 } 1228 1229 @Override 1230 public int getParagraphDirection(int line) { 1231 return mLines[mColumns * line + DIR] >> DIR_SHIFT; 1232 } 1233 1234 @Override 1235 public boolean getLineContainsTab(int line) { 1236 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0; 1237 } 1238 1239 @Override 1240 public final Directions getLineDirections(int line) { 1241 if (line > getLineCount()) { 1242 throw new ArrayIndexOutOfBoundsException(); 1243 } 1244 return mLineDirections[line]; 1245 } 1246 1247 @Override 1248 public int getTopPadding() { 1249 return mTopPadding; 1250 } 1251 1252 @Override 1253 public int getBottomPadding() { 1254 return mBottomPadding; 1255 } 1256 1257 /** 1258 * @hide 1259 */ 1260 @Override 1261 public int getHyphen(int line) { 1262 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK; 1263 } 1264 1265 /** 1266 * @hide 1267 */ 1268 @Override 1269 public int getIndentAdjust(int line, Alignment align) { 1270 if (align == Alignment.ALIGN_LEFT) { 1271 if (mLeftIndents == null) { 1272 return 0; 1273 } else { 1274 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)]; 1275 } 1276 } else if (align == Alignment.ALIGN_RIGHT) { 1277 if (mRightIndents == null) { 1278 return 0; 1279 } else { 1280 return -mRightIndents[Math.min(line, mRightIndents.length - 1)]; 1281 } 1282 } else if (align == Alignment.ALIGN_CENTER) { 1283 int left = 0; 1284 if (mLeftIndents != null) { 1285 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)]; 1286 } 1287 int right = 0; 1288 if (mRightIndents != null) { 1289 right = mRightIndents[Math.min(line, mRightIndents.length - 1)]; 1290 } 1291 return (left - right) >> 1; 1292 } else { 1293 throw new AssertionError("unhandled alignment " + align); 1294 } 1295 } 1296 1297 @Override 1298 public int getEllipsisCount(int line) { 1299 if (mColumns < COLUMNS_ELLIPSIZE) { 1300 return 0; 1301 } 1302 1303 return mLines[mColumns * line + ELLIPSIS_COUNT]; 1304 } 1305 1306 @Override 1307 public int getEllipsisStart(int line) { 1308 if (mColumns < COLUMNS_ELLIPSIZE) { 1309 return 0; 1310 } 1311 1312 return mLines[mColumns * line + ELLIPSIS_START]; 1313 } 1314 1315 @Override 1316 public int getEllipsizedWidth() { 1317 return mEllipsizedWidth; 1318 } 1319 1320 /** 1321 * Return the total height of this layout. 1322 * 1323 * @param cap if true and max lines is set, returns the height of the layout at the max lines. 1324 * 1325 * @hide 1326 */ 1327 public int getHeight(boolean cap) { 1328 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 && 1329 Log.isLoggable(TAG, Log.WARN)) { 1330 Log.w(TAG, "maxLineHeight should not be -1. " 1331 + " maxLines:" + mMaximumVisibleLineCount 1332 + " lineCount:" + mLineCount); 1333 } 1334 1335 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ? 1336 mMaxLineHeight : super.getHeight(); 1337 } 1338 1339 @FastNative 1340 private static native long nInit( 1341 @BreakStrategy int breakStrategy, 1342 @HyphenationFrequency int hyphenationFrequency, 1343 boolean isJustified, 1344 @Nullable int[] indents, 1345 @Nullable int[] leftPaddings, 1346 @Nullable int[] rightPaddings); 1347 1348 @CriticalNative 1349 private static native void nFinish(long nativePtr); 1350 1351 // populates LineBreaks and returns the number of breaks found 1352 // 1353 // the arrays inside the LineBreaks objects are passed in as well 1354 // to reduce the number of JNI calls in the common case where the 1355 // arrays do not have to be resized 1356 // The individual character widths will be returned in charWidths. The length of charWidths must 1357 // be at least the length of the text. 1358 private static native int nComputeLineBreaks( 1359 /* non zero */ long nativePtr, 1360 1361 // Inputs 1362 @NonNull char[] text, 1363 /* Non Zero */ long measuredTextPtr, 1364 @IntRange(from = 0) int length, 1365 @FloatRange(from = 0.0f) float firstWidth, 1366 @IntRange(from = 0) int firstWidthLineCount, 1367 @FloatRange(from = 0.0f) float restWidth, 1368 @Nullable int[] variableTabStops, 1369 int defaultTabStop, 1370 @IntRange(from = 0) int indentsOffset, 1371 1372 // Outputs 1373 @NonNull LineBreaks recycle, 1374 @IntRange(from = 0) int recycleLength, 1375 @NonNull int[] recycleBreaks, 1376 @NonNull float[] recycleWidths, 1377 @NonNull float[] recycleAscents, 1378 @NonNull float[] recycleDescents, 1379 @NonNull int[] recycleFlags, 1380 @NonNull float[] charWidths); 1381 1382 private int mLineCount; 1383 private int mTopPadding, mBottomPadding; 1384 private int mColumns; 1385 private int mEllipsizedWidth; 1386 1387 /** 1388 * Keeps track if ellipsize is applied to the text. 1389 */ 1390 private boolean mEllipsized; 1391 1392 /** 1393 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than 1394 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line 1395 * starting from the top of the layout. If maxLines is not set its value will be -1. 1396 * 1397 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no 1398 * more than maxLines is contained. 1399 */ 1400 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT; 1401 1402 private static final int COLUMNS_NORMAL = 5; 1403 private static final int COLUMNS_ELLIPSIZE = 7; 1404 private static final int START = 0; 1405 private static final int DIR = START; 1406 private static final int TAB = START; 1407 private static final int TOP = 1; 1408 private static final int DESCENT = 2; 1409 private static final int EXTRA = 3; 1410 private static final int HYPHEN = 4; 1411 private static final int ELLIPSIS_START = 5; 1412 private static final int ELLIPSIS_COUNT = 6; 1413 1414 private int[] mLines; 1415 private Directions[] mLineDirections; 1416 private int mMaximumVisibleLineCount = Integer.MAX_VALUE; 1417 1418 private static final int START_MASK = 0x1FFFFFFF; 1419 private static final int DIR_SHIFT = 30; 1420 private static final int TAB_MASK = 0x20000000; 1421 private static final int HYPHEN_MASK = 0xFF; 1422 1423 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private 1424 1425 private static final char CHAR_NEW_LINE = '\n'; 1426 1427 private static final double EXTRA_ROUNDING = 0.5; 1428 1429 private static final int DEFAULT_MAX_LINE_HEIGHT = -1; 1430 1431 // This is used to return three arrays from a single JNI call when 1432 // performing line breaking 1433 /*package*/ static class LineBreaks { 1434 private static final int INITIAL_SIZE = 16; 1435 public int[] breaks = new int[INITIAL_SIZE]; 1436 public float[] widths = new float[INITIAL_SIZE]; 1437 public float[] ascents = new float[INITIAL_SIZE]; 1438 public float[] descents = new float[INITIAL_SIZE]; 1439 public int[] flags = new int[INITIAL_SIZE]; // hasTab 1440 // breaks, widths, and flags should all have the same length 1441 } 1442 1443 @Nullable private int[] mLeftIndents; 1444 @Nullable private int[] mRightIndents; 1445 @Nullable private int[] mLeftPaddings; 1446 @Nullable private int[] mRightPaddings; 1447 } 1448