1 /*
2  * Copyright (C) 2018 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.graphics.text;
18 
19 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
20 import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
21 import static com.android.text.flags.Flags.FLAG_MISSING_GETTER_APIS;
22 
23 
24 import android.annotation.FlaggedApi;
25 import android.annotation.FloatRange;
26 import android.annotation.IntDef;
27 import android.annotation.IntRange;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.Px;
31 import android.text.Layout;
32 
33 import dalvik.annotation.optimization.CriticalNative;
34 import dalvik.annotation.optimization.FastNative;
35 
36 import libcore.util.NativeAllocationRegistry;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 
41 /**
42  * Provides automatic line breaking for a <em>single</em> paragraph.
43  *
44  * <p>
45  * <pre>
46  * <code>
47  * Paint paint = new Paint();
48  * Paint bigPaint = new Paint();
49  * bigPaint.setTextSize(paint.getTextSize() * 2.0);
50  * String text = "Hello, Android.";
51  *
52  * // Prepare the measured text
53  * MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
54  *     .appendStyleRun(paint, 7, false)  // Use paint for "Hello, "
55  *     .appednStyleRun(bigPaint, 8, false)  // Use bigPaint for "Hello, "
56  *     .build();
57  *
58  * LineBreaker lb = new LineBreaker.Builder()
59  *     // Use simple line breaker
60  *     .setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE)
61  *     // Do not add hyphenation.
62  *     .setHyphenationFrequency(LineBreaker.HYPHENATION_FREQUENCY_NONE)
63  *     // Build the LineBreaker
64  *     .build();
65  *
66  * ParagraphConstraints c = new ParagraphConstraints();
67  * c.setWidth(240);  // Set the line wieth as 1024px
68  *
69  * // Do the line breaking
70  * Result r = lb.computeLineBreaks(mt, c, 0);
71  *
72  * // Compute the total height of the text.
73  * float totalHeight = 0;
74  * for (int i = 0; i < r.getLineCount(); ++i) {  // iterate over the lines
75  *    totalHeight += r.getLineDescent(i) - r.getLineAscent(i);
76  * }
77  *
78  * // Draw text to the canvas
79  * Bitmap bmp = Bitmap.createBitmap(240, totalHeight, Bitmap.Config.ARGB_8888);
80  * Canvas c = new Canvas(bmp);
81  * float yOffset = 0f;
82  * int prevOffset = 0;
83  * for (int i = 0; i < r.getLineCount(); ++i) {  // iterate over the lines
84  *     int nextOffset = r.getLineBreakOffset(i);
85  *     c.drawText(text, prevOffset, nextOffset, 0f, yOffset, paint);
86  *
87  *     prevOffset = nextOffset;
88  *     yOffset += r.getLineDescent(i) - r.getLineAscent(i);
89  * }
90  * </code>
91  * </pre>
92  * </p>
93  */
94 public class LineBreaker {
95     /** @hide */
96     @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
97             BREAK_STRATEGY_SIMPLE,
98             BREAK_STRATEGY_HIGH_QUALITY,
99             BREAK_STRATEGY_BALANCED
100     })
101     @Retention(RetentionPolicy.SOURCE)
102     public @interface BreakStrategy {}
103 
104     /**
105      * Value for break strategy indicating simple line breaking.
106      *
107      * The line breaker puts words to the line as much as possible and breaks line if no more words
108      * can fit into the same line. Automatic hyphens are only added when a line has a single word
109      * and that word is longer than line width. This is the fastest break strategy and ideal for
110      * editor.
111      */
112     public static final int BREAK_STRATEGY_SIMPLE = 0;
113 
114     /**
115      * Value for break strategy indicating high quality line breaking.
116      *
117      * With this option line breaker does whole-paragraph optimization for more readable text, and
118      * also applies automatic hyphenation when required.
119      */
120     public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
121 
122     /**
123      * Value for break strategy indicating balanced line breaking.
124      *
125      * The line breaker does whole-paragraph optimization for making all lines similar length, and
126      * also applies automatic hyphenation when required. This break strategy is good for small
127      * screen devices such as watch screens.
128      */
129     public static final int BREAK_STRATEGY_BALANCED = 2;
130 
131     /** @hide */
132     @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
133             HYPHENATION_FREQUENCY_NORMAL,
134             HYPHENATION_FREQUENCY_FULL,
135             HYPHENATION_FREQUENCY_NONE
136     })
137     @Retention(RetentionPolicy.SOURCE)
138     public @interface HyphenationFrequency {}
139 
140     /**
141      * Value for hyphenation frequency indicating no automatic hyphenation.
142      *
143      * Using this option disables auto hyphenation which results in better text layout performance.
144      * A word may be broken without hyphens when a line has a single word and that word is longer
145      * than line width. Soft hyphens are ignored and will not be used as suggestions for potential
146      * line breaks.
147      */
148     public static final int HYPHENATION_FREQUENCY_NONE = 0;
149 
150     /**
151      * Value for hyphenation frequency indicating a light amount of automatic hyphenation.
152      *
153      * This hyphenation frequency is useful for informal cases, such as short sentences or chat
154      * messages.
155      */
156     public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
157 
158     /**
159      * Value for hyphenation frequency indicating the full amount of automatic hyphenation.
160      *
161      * This hyphenation frequency is useful for running text and where it's important to put the
162      * maximum amount of text in a screen with limited space.
163      */
164     public static final int HYPHENATION_FREQUENCY_FULL = 2;
165 
166     /** @hide */
167     @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
168             JUSTIFICATION_MODE_NONE,
169             JUSTIFICATION_MODE_INTER_WORD,
170             JUSTIFICATION_MODE_INTER_CHARACTER,
171     })
172     @Retention(RetentionPolicy.SOURCE)
173     public @interface JustificationMode {}
174 
175     /**
176      * Value for justification mode indicating no justification.
177      */
178     public static final int JUSTIFICATION_MODE_NONE = 0;
179 
180     /**
181      * Value for justification mode indicating the text is justified by stretching word spacing.
182      */
183     public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
184 
185     /**
186      * Value for justification mode indicating the text is justified by stretching letter spacing.
187      */
188     @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
189     public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
190 
191     /**
192      * Helper class for creating a {@link LineBreaker}.
193      */
194     public static final class Builder {
195         private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE;
196         private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
197         private @JustificationMode int mJustificationMode = JUSTIFICATION_MODE_NONE;
198         private @Nullable int[] mIndents = null;
199         private boolean mUseBoundsForWidth = false;
200 
201         /**
202          * Set break strategy.
203          *
204          * You can change the line breaking behavior by setting break strategy. The default value is
205          * {@link #BREAK_STRATEGY_SIMPLE}.
206          */
setBreakStrategy(@reakStrategy int breakStrategy)207         public @NonNull Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
208             mBreakStrategy = breakStrategy;
209             return this;
210         }
211 
212         /**
213          * Set hyphenation frequency.
214          *
215          * You can change the amount of automatic hyphenation used. The default value is
216          * {@link #HYPHENATION_FREQUENCY_NONE}.
217          */
setHyphenationFrequency( @yphenationFrequency int hyphenationFrequency)218         public @NonNull Builder setHyphenationFrequency(
219                 @HyphenationFrequency int hyphenationFrequency) {
220             mHyphenationFrequency = hyphenationFrequency;
221             return this;
222         }
223 
224         /**
225          * Set whether the text is justified.
226          *
227          * By setting {@link #JUSTIFICATION_MODE_INTER_WORD}, the line breaker will change the
228          * internal parameters for justification.
229          * The default value is {@link #JUSTIFICATION_MODE_NONE}
230          */
setJustificationMode(@ustificationMode int justificationMode)231         public @NonNull Builder setJustificationMode(@JustificationMode int justificationMode) {
232             mJustificationMode = justificationMode;
233             return this;
234         }
235 
236         /**
237          * Set indents.
238          *
239          * The supplied array provides the total amount of indentation per line, in pixel. This
240          * amount is the sum of both left and right indentations. For lines past the last element in
241          * the array, the indentation amount of the last element is used.
242          */
setIndents(@ullable int[] indents)243         public @NonNull Builder setIndents(@Nullable int[] indents) {
244             mIndents = indents;
245             return this;
246         }
247 
248         /**
249          * Set true for using width of bounding box as a source of automatic line breaking.
250          *
251          * If this value is false, the automatic line breaking uses total amount of advances as text
252          * widths. By setting true, it uses joined all glyph bound's width as a width of the text.
253          *
254          * If the font has glyphs that have negative bearing X or its xMax is greater than advance,
255          * the glyph clipping can happen because the drawing area may be bigger. By setting this to
256          * true, the line breaker will break line based on bounding box, so clipping can be
257          * prevented.
258          *
259          * @param useBoundsForWidth True for using bounding box, false for advances.
260          * @return this builder instance
261          * @see Layout#getUseBoundsForWidth()
262          * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)
263          */
264         @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
setUseBoundsForWidth(boolean useBoundsForWidth)265         public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
266             mUseBoundsForWidth = useBoundsForWidth;
267             return this;
268         }
269 
270         /**
271          * Build a new LineBreaker with given parameters.
272          *
273          * You can reuse the Builder instance even after calling this method.
274          */
build()275         public @NonNull LineBreaker build() {
276             return new LineBreaker(mBreakStrategy, mHyphenationFrequency, mJustificationMode,
277                     mIndents, mUseBoundsForWidth);
278         }
279     }
280 
281     /**
282      * Line breaking constraints for single paragraph.
283      */
284     public static class ParagraphConstraints {
285         private @FloatRange(from = 0.0f) float mWidth = 0;
286         private @FloatRange(from = 0.0f) float mFirstWidth = 0;
287         private @IntRange(from = 0) int mFirstWidthLineCount = 0;
288         private @Nullable float[] mVariableTabStops = null;
289         private @FloatRange(from = 0) float mDefaultTabStop = 0;
290 
ParagraphConstraints()291         public ParagraphConstraints() {}
292 
293         /**
294          * Set width for this paragraph.
295          *
296          * @see #getWidth()
297          */
setWidth(@x @loatRangefrom = 0.0f) float width)298         public void setWidth(@Px @FloatRange(from = 0.0f) float width) {
299             mWidth = width;
300         }
301 
302         /**
303          * Set indent for this paragraph.
304          *
305          * @param firstWidth the line width of the starting of the paragraph
306          * @param firstWidthLineCount the number of lines that applies the firstWidth
307          * @see #getFirstWidth()
308          * @see #getFirstWidthLineCount()
309          */
setIndent(@x @loatRangefrom = 0.0f) float firstWidth, @Px @IntRange(from = 0) int firstWidthLineCount)310         public void setIndent(@Px @FloatRange(from = 0.0f) float firstWidth,
311                 @Px @IntRange(from = 0) int firstWidthLineCount) {
312             mFirstWidth = firstWidth;
313             mFirstWidthLineCount = firstWidthLineCount;
314         }
315 
316         /**
317          * Set tab stops for this paragraph.
318          *
319          * @param tabStops the array of pixels of tap stopping position
320          * @param defaultTabStop pixels of the default tab stopping position
321          * @see #getTabStops()
322          * @see #getDefaultTabStop()
323          */
setTabStops(@ullable float[] tabStops, @Px @FloatRange(from = 0) float defaultTabStop)324         public void setTabStops(@Nullable float[] tabStops,
325                 @Px @FloatRange(from = 0) float defaultTabStop) {
326             mVariableTabStops = tabStops;
327             mDefaultTabStop = defaultTabStop;
328         }
329 
330         /**
331          * Return the width for this paragraph in pixels.
332          *
333          * @see #setWidth(float)
334          */
getWidth()335         public @Px @FloatRange(from = 0.0f) float getWidth() {
336             return mWidth;
337         }
338 
339         /**
340          * Return the first line's width for this paragraph in pixel.
341          *
342          * @see #setIndent(float, int)
343          */
getFirstWidth()344         public @Px @FloatRange(from = 0.0f) float getFirstWidth() {
345             return mFirstWidth;
346         }
347 
348         /**
349          * Return the number of lines to apply the first line's width.
350          *
351          * @see #setIndent(float, int)
352          */
getFirstWidthLineCount()353         public @Px @IntRange(from = 0) int getFirstWidthLineCount() {
354             return mFirstWidthLineCount;
355         }
356 
357         /**
358          * Returns the array of tab stops in pixels.
359          *
360          * @see #setTabStops
361          */
getTabStops()362         public @Nullable float[] getTabStops() {
363             return mVariableTabStops;
364         }
365 
366         /**
367          * Returns the default tab stops in pixels.
368          *
369          * @see #setTabStops
370          */
getDefaultTabStop()371         public @Px @FloatRange(from = 0) float getDefaultTabStop() {
372             return mDefaultTabStop;
373         }
374     }
375 
376     /**
377      * Holds the result of the {@link LineBreaker#computeLineBreaks line breaking algorithm}.
378      * @see LineBreaker#computeLineBreaks
379      */
380     public static class Result {
381         // Following two constants must be synced with minikin's line breaker.
382         // TODO(nona): Remove these constants by introducing native methods.
383         private static final int TAB_MASK = 0x20000000;
384         private static final int HYPHEN_MASK = 0xFF;
385         private static final int START_HYPHEN_MASK = 0x18;  // 0b11000
386         private static final int END_HYPHEN_MASK = 0x7;  // 0b00111
387         private static final int START_HYPHEN_BITS_SHIFT = 3;
388 
389         private static final NativeAllocationRegistry sRegistry =
390                 NativeAllocationRegistry.createMalloced(
391                 Result.class.getClassLoader(), nGetReleaseResultFunc());
392         private final long mPtr;
393 
Result(long ptr)394         private Result(long ptr) {
395             mPtr = ptr;
396             sRegistry.registerNativeAllocation(this, mPtr);
397         }
398 
399         /**
400          * Returns the number of lines in the paragraph.
401          *
402          * @return number of lines
403          */
getLineCount()404         public @IntRange(from = 0) int getLineCount() {
405             return nGetLineCount(mPtr);
406         }
407 
408         /**
409          * Returns character offset of the break for a given line.
410          *
411          * @param lineIndex an index of the line.
412          * @return the break offset.
413          */
getLineBreakOffset(@ntRangefrom = 0) int lineIndex)414         public @IntRange(from = 0) int getLineBreakOffset(@IntRange(from = 0) int lineIndex) {
415             return nGetLineBreakOffset(mPtr, lineIndex);
416         }
417 
418         /**
419          * Returns width of a given line in pixels.
420          *
421          * @param lineIndex an index of the line.
422          * @return width of the line in pixels
423          */
getLineWidth(@ntRangefrom = 0) int lineIndex)424         public @Px float getLineWidth(@IntRange(from = 0) int lineIndex) {
425             return nGetLineWidth(mPtr, lineIndex);
426         }
427 
428         /**
429          * Returns font ascent of the line in pixels.
430          *
431          * @param lineIndex an index of the line.
432          * @return an entier font ascent of the line in pixels.
433          */
getLineAscent(@ntRangefrom = 0) int lineIndex)434         public @Px float getLineAscent(@IntRange(from = 0) int lineIndex) {
435             return nGetLineAscent(mPtr, lineIndex);
436         }
437 
438         /**
439          * Returns font descent of the line in pixels.
440          *
441          * @param lineIndex an index of the line.
442          * @return an entier font descent of the line in pixels.
443          */
getLineDescent(@ntRangefrom = 0) int lineIndex)444         public @Px float getLineDescent(@IntRange(from = 0) int lineIndex) {
445             return nGetLineDescent(mPtr, lineIndex);
446         }
447 
448         /**
449          * Returns true if the line has a TAB character.
450          *
451          * @param lineIndex an index of the line.
452          * @return true if the line has a TAB character
453          */
hasLineTab(int lineIndex)454         public boolean hasLineTab(int lineIndex) {
455             return (nGetLineFlag(mPtr, lineIndex) & TAB_MASK) != 0;
456         }
457 
458         /**
459          * Returns a start hyphen edit for the line.
460          *
461          * @param lineIndex an index of the line.
462          * @return a start hyphen edit for the line.
463          *
464          * @see android.graphics.Paint#setStartHyphenEdit
465          * @see android.graphics.Paint#getStartHyphenEdit
466          */
getStartLineHyphenEdit(int lineIndex)467         public int getStartLineHyphenEdit(int lineIndex) {
468             return (nGetLineFlag(mPtr, lineIndex) & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
469         }
470 
471         /**
472          * Returns an end hyphen edit for the line.
473          *
474          * @param lineIndex an index of the line.
475          * @return an end hyphen edit for the line.
476          *
477          * @see android.graphics.Paint#setEndHyphenEdit
478          * @see android.graphics.Paint#getEndHyphenEdit
479          */
getEndLineHyphenEdit(int lineIndex)480         public int getEndLineHyphenEdit(int lineIndex) {
481             return nGetLineFlag(mPtr, lineIndex) & END_HYPHEN_MASK;
482         }
483     }
484 
485     private static class NoImagePreloadHolder {
486         private static final NativeAllocationRegistry sRegistry =
487                 NativeAllocationRegistry.createMalloced(
488                         LineBreaker.class.getClassLoader(), nGetReleaseFunc());
489     }
490 
491     private final long mNativePtr;
492 
493     private final @BreakStrategy int mBreakStrategy;
494     private final @HyphenationFrequency int mHyphenationFrequency;
495     private final @JustificationMode int mJustificationMode;
496     private final int[] mIndents;
497     private final boolean mUseBoundsForWidth;
498 
499     /**
500      * Use Builder instead.
501      */
LineBreaker(@reakStrategy int breakStrategy, @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify, @Nullable int[] indents, boolean useBoundsForWidth)502     private LineBreaker(@BreakStrategy int breakStrategy,
503             @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify,
504             @Nullable int[] indents, boolean useBoundsForWidth) {
505         mNativePtr = nInit(breakStrategy, hyphenationFrequency,
506                 justify == JUSTIFICATION_MODE_INTER_WORD, indents, useBoundsForWidth);
507         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr);
508 
509         mBreakStrategy = breakStrategy;
510         mHyphenationFrequency = hyphenationFrequency;
511         mJustificationMode = justify;
512         mIndents = indents;
513         mUseBoundsForWidth = useBoundsForWidth;
514     }
515 
516     /**
517      * Returns the break strategy used for this line breaker.
518      *
519      * @return the break strategy used for this line breaker.
520      * @see Builder#setBreakStrategy(int)
521      */
522     @FlaggedApi(FLAG_MISSING_GETTER_APIS)
getBreakStrategy()523     public @BreakStrategy int getBreakStrategy() {
524         return mBreakStrategy;
525     }
526 
527     /**
528      * Returns the hyphenation frequency used for this line breaker.
529      *
530      * @return the hyphenation frequency used for this line breaker.
531      * @see Builder#setHyphenationFrequency(int)
532      */
533     @FlaggedApi(FLAG_MISSING_GETTER_APIS)
getHyphenationFrequency()534     public @HyphenationFrequency int getHyphenationFrequency() {
535         return mHyphenationFrequency;
536     }
537 
538     /**
539      * Returns the justification mode used for this line breaker.
540      *
541      * @return the justification mode used for this line breaker.
542      * @see Builder#setJustificationMode(int)
543      */
544     @FlaggedApi(FLAG_MISSING_GETTER_APIS)
getJustificationMode()545     public @JustificationMode int getJustificationMode() {
546         return mJustificationMode;
547     }
548 
549     /**
550      * Returns the indents used for this line breaker.
551      *
552      * @return the indents used for this line breaker.
553      * @see Builder#setIndents(int[])
554      */
555     @FlaggedApi(FLAG_MISSING_GETTER_APIS)
getIndents()556     public @Nullable int[] getIndents() {
557         return mIndents;
558     }
559 
560     /**
561      * Returns true if this line breaker uses bounds as width for line breaking.
562      *
563      * @return true if this line breaker uses bounds as width for line breaking.
564      * @see Builder#setUseBoundsForWidth(boolean)
565      */
566     @FlaggedApi(FLAG_MISSING_GETTER_APIS)
getUseBoundsForWidth()567     public boolean getUseBoundsForWidth() {
568         return mUseBoundsForWidth;
569     }
570 
571     /**
572      * Break paragraph into lines.
573      *
574      * The result is filled to out param.
575      *
576      * @param measuredPara a result of the text measurement
577      * @param constraints for a single paragraph
578      * @param lineNumber a line number of this paragraph
579      */
computeLineBreaks( @onNull MeasuredText measuredPara, @NonNull ParagraphConstraints constraints, @IntRange(from = 0) int lineNumber)580     public @NonNull Result computeLineBreaks(
581             @NonNull MeasuredText measuredPara,
582             @NonNull ParagraphConstraints constraints,
583             @IntRange(from = 0) int lineNumber) {
584         return new Result(nComputeLineBreaks(
585                 mNativePtr,
586 
587                 // Inputs
588                 measuredPara.getChars(),
589                 measuredPara.getNativePtr(),
590                 measuredPara.getChars().length,
591                 constraints.mFirstWidth,
592                 constraints.mFirstWidthLineCount,
593                 constraints.mWidth,
594                 constraints.mVariableTabStops,
595                 constraints.mDefaultTabStop,
596                 lineNumber));
597     }
598 
599     @FastNative
nInit(@reakStrategy int breakStrategy, @HyphenationFrequency int hyphenationFrequency, boolean isJustified, @Nullable int[] indents, boolean useBoundsForWidth)600     private static native long nInit(@BreakStrategy int breakStrategy,
601             @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
602             @Nullable int[] indents, boolean useBoundsForWidth);
603 
604     @CriticalNative
nGetReleaseFunc()605     private static native long nGetReleaseFunc();
606 
607     // populates LineBreaks and returns the number of breaks found
608     //
609     // the arrays inside the LineBreaks objects are passed in as well
610     // to reduce the number of JNI calls in the common case where the
611     // arrays do not have to be resized
612     // The individual character widths will be returned in charWidths. The length of
613     // charWidths must be at least the length of the text.
nComputeLineBreaks( long nativePtr, @NonNull char[] text, long measuredTextPtr, @IntRange(from = 0) int length, @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount, @FloatRange(from = 0.0f) float restWidth, @Nullable float[] variableTabStops, float defaultTabStop, @IntRange(from = 0) int indentsOffset)614     private static native long nComputeLineBreaks(
615             /* non zero */ long nativePtr,
616 
617             // Inputs
618             @NonNull char[] text,
619             /* Non Zero */ long measuredTextPtr,
620             @IntRange(from = 0) int length,
621             @FloatRange(from = 0.0f) float firstWidth,
622             @IntRange(from = 0) int firstWidthLineCount,
623             @FloatRange(from = 0.0f) float restWidth,
624             @Nullable float[] variableTabStops,
625             float defaultTabStop,
626             @IntRange(from = 0) int indentsOffset);
627 
628     // Result accessors
629     @CriticalNative
nGetLineCount(long ptr)630     private static native int nGetLineCount(long ptr);
631     @CriticalNative
nGetLineBreakOffset(long ptr, int idx)632     private static native int nGetLineBreakOffset(long ptr, int idx);
633     @CriticalNative
nGetLineWidth(long ptr, int idx)634     private static native float nGetLineWidth(long ptr, int idx);
635     @CriticalNative
nGetLineAscent(long ptr, int idx)636     private static native float nGetLineAscent(long ptr, int idx);
637     @CriticalNative
nGetLineDescent(long ptr, int idx)638     private static native float nGetLineDescent(long ptr, int idx);
639     @CriticalNative
nGetLineFlag(long ptr, int idx)640     private static native int nGetLineFlag(long ptr, int idx);
641     @CriticalNative
nGetReleaseResultFunc()642     private static native long nGetReleaseResultFunc();
643 }
644