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