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