blob: 4174df0f56c07ec8dbd790a83161d82f03964a7f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text;
18
19import android.graphics.Paint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080021import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.text.style.LineHeightSpan;
23import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070024import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070025import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
Doug Feltcb3791202011-07-07 11:57:48 -070027import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050028import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070029
Anish Athalyec8f9e622014-07-21 15:26:34 -070030import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080031import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070032
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033/**
34 * StaticLayout is a Layout for text that will not be edited after it
35 * is laid out. Use {@link DynamicLayout} for text that may change.
36 * <p>This is used by widgets to control text layout. You should not need
37 * to use this class directly unless you are implementing your own widget
38 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070039 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
40 * float, float, android.graphics.Paint)
41 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080043public class StaticLayout extends Layout {
44
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070045 static final String TAG = "StaticLayout";
46
Raph Leviend3ab6922015-03-02 14:30:53 -080047 /**
48 * Builder for static layouts. It would be better if this were a public
49 * API (as it would offer much greater flexibility for adding new options)
50 * but for the time being it's just internal.
51 *
52 * @hide
53 */
54 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080055 private Builder() {
56 mNativePtr = nNewBuilder();
57 }
58
Raph Leviend3ab6922015-03-02 14:30:53 -080059 static Builder obtain() {
60 Builder b = null;
61 synchronized (sLock) {
62 for (int i = 0; i < sCached.length; i++) {
63 if (sCached[i] != null) {
64 b = sCached[i];
65 sCached[i] = null;
66 break;
67 }
68 }
69 }
70 if (b == null) {
71 b = new Builder();
72 }
73
74 // set default initial values
75 b.mWidth = 0;
76 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
77 b.mSpacingMult = 1.0f;
78 b.mSpacingAdd = 0.0f;
79 b.mIncludePad = true;
80 b.mEllipsizedWidth = 0;
81 b.mEllipsize = null;
82 b.mMaxLines = Integer.MAX_VALUE;
83
84 b.mMeasuredText = MeasuredText.obtain();
85 return b;
86 }
87
88 static void recycle(Builder b) {
89 b.mPaint = null;
90 b.mText = null;
91 MeasuredText.recycle(b.mMeasuredText);
92 synchronized (sLock) {
93 for (int i = 0; i < sCached.length; i++) {
94 if (sCached[i] == null) {
95 sCached[i] = b;
96 break;
97 }
98 }
99 }
100 }
101
102 // release any expensive state
103 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800104 nFinishBuilder(mNativePtr);
Raph Leviend3ab6922015-03-02 14:30:53 -0800105 mMeasuredText.finish();
106 }
107
108 public Builder setText(CharSequence source) {
109 return setText(source, 0, source.length());
110 }
111
112 public Builder setText(CharSequence source, int start, int end) {
113 mText = source;
114 mStart = start;
115 mEnd = end;
116 return this;
117 }
118
119 public Builder setPaint(TextPaint paint) {
120 mPaint = paint;
121 return this;
122 }
123
124 public Builder setWidth(int width) {
125 mWidth = width;
126 if (mEllipsize == null) {
127 mEllipsizedWidth = width;
128 }
129 return this;
130 }
131
132 public Builder setTextDir(TextDirectionHeuristic textDir) {
133 mTextDir = textDir;
134 return this;
135 }
136
137 // TODO: combine the following, as they're almost always set together?
138 public Builder setSpacingMult(float spacingMult) {
139 mSpacingMult = spacingMult;
140 return this;
141 }
142
143 public Builder setSpacingAdd(float spacingAdd) {
144 mSpacingAdd = spacingAdd;
145 return this;
146 }
147
148 public Builder setIncludePad(boolean includePad) {
149 mIncludePad = includePad;
150 return this;
151 }
152
153 // TODO: combine the following?
154 public Builder setEllipsizedWidth(int ellipsizedWidth) {
155 mEllipsizedWidth = ellipsizedWidth;
156 return this;
157 }
158
159 public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
160 mEllipsize = ellipsize;
161 return this;
162 }
163
164 public Builder setMaxLines(int maxLines) {
165 mMaxLines = maxLines;
166 return this;
167 }
168
Raph Levien70616ec2015-03-04 10:41:30 -0800169 /**
170 * Measurement and break iteration is done in native code. The protocol for using
171 * the native code is as follows.
172 *
Raph Levien26d443a2015-03-30 14:18:32 -0700173 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
174 * stops, break strategy (and possibly other parameters in the future).
Raph Levienc94f7422015-03-06 19:19:48 -0800175 *
176 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800177 * - setLocale (this must be done at least for the first run, optional afterwards)
178 * - one of the following, depending on the type of run:
179 * + addStyleRun (a text run, to be measured in native code)
180 * + addMeasuredRun (a run already measured in Java, passed into native code)
181 * + addReplacementRun (a replacement run, width is given)
182 *
183 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
184 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
185 *
186 * After all paragraphs, call finish() to release expensive buffers.
187 */
188
189 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800190 if (!locale.equals(mLocale)) {
Raph Levien26d443a2015-03-30 14:18:32 -0700191 nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
Raph Levien4c1f12e2015-03-02 16:29:23 -0800192 mLocale = locale;
193 }
194 }
195
Raph Levien70616ec2015-03-04 10:41:30 -0800196 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
197 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
198 start, end, isRtl);
199 }
200
201 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
202 nAddMeasuredRun(mNativePtr, start, end, widths);
203 }
204
205 /* package */ void addReplacementRun(int start, int end, float width) {
206 nAddReplacementRun(mNativePtr, start, end, width);
207 }
208
Raph Leviend3ab6922015-03-02 14:30:53 -0800209 public StaticLayout build() {
210 // TODO: can optimize based on whether ellipsis is needed
211 StaticLayout result = new StaticLayout(mText);
Raph Levien70616ec2015-03-04 10:41:30 -0800212 result.generate(this, this.mIncludePad, this.mIncludePad);
Raph Leviend3ab6922015-03-02 14:30:53 -0800213 recycle(this);
214 return result;
215 }
216
Raph Levien4c1f12e2015-03-02 16:29:23 -0800217 @Override
218 protected void finalize() throws Throwable {
219 try {
220 nFreeBuilder(mNativePtr);
221 } finally {
222 super.finalize();
223 }
224 }
225
226 /* package */ long mNativePtr;
227
Raph Leviend3ab6922015-03-02 14:30:53 -0800228 CharSequence mText;
229 int mStart;
230 int mEnd;
231 TextPaint mPaint;
232 int mWidth;
233 TextDirectionHeuristic mTextDir;
234 float mSpacingMult;
235 float mSpacingAdd;
236 boolean mIncludePad;
237 int mEllipsizedWidth;
238 TextUtils.TruncateAt mEllipsize;
239 int mMaxLines;
240
241 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
242
243 // This will go away and be subsumed by native builder code
244 MeasuredText mMeasuredText;
245
Raph Levien4c1f12e2015-03-02 16:29:23 -0800246 Locale mLocale;
247
Raph Leviend3ab6922015-03-02 14:30:53 -0800248 private static final Object sLock = new Object();
249 private static final Builder[] sCached = new Builder[3];
250 }
251
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 public StaticLayout(CharSequence source, TextPaint paint,
253 int width,
254 Alignment align, float spacingmult, float spacingadd,
255 boolean includepad) {
256 this(source, 0, source.length(), paint, width, align,
257 spacingmult, spacingadd, includepad);
258 }
259
Doug Feltcb3791202011-07-07 11:57:48 -0700260 /**
261 * @hide
262 */
263 public StaticLayout(CharSequence source, TextPaint paint,
264 int width, Alignment align, TextDirectionHeuristic textDir,
265 float spacingmult, float spacingadd,
266 boolean includepad) {
267 this(source, 0, source.length(), paint, width, align, textDir,
268 spacingmult, spacingadd, includepad);
269 }
270
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 public StaticLayout(CharSequence source, int bufstart, int bufend,
272 TextPaint paint, int outerwidth,
273 Alignment align,
274 float spacingmult, float spacingadd,
275 boolean includepad) {
276 this(source, bufstart, bufend, paint, outerwidth, align,
277 spacingmult, spacingadd, includepad, null, 0);
278 }
279
Doug Feltcb3791202011-07-07 11:57:48 -0700280 /**
281 * @hide
282 */
283 public StaticLayout(CharSequence source, int bufstart, int bufend,
284 TextPaint paint, int outerwidth,
285 Alignment align, TextDirectionHeuristic textDir,
286 float spacingmult, float spacingadd,
287 boolean includepad) {
288 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700289 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700290}
291
292 public StaticLayout(CharSequence source, int bufstart, int bufend,
293 TextPaint paint, int outerwidth,
294 Alignment align,
295 float spacingmult, float spacingadd,
296 boolean includepad,
297 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
298 this(source, bufstart, bufend, paint, outerwidth, align,
299 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700300 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700301 }
302
303 /**
304 * @hide
305 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 public StaticLayout(CharSequence source, int bufstart, int bufend,
307 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700308 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 float spacingmult, float spacingadd,
310 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700311 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700313 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 : (source instanceof Spanned)
315 ? new SpannedEllipsizer(source)
316 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700317 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318
Raph Leviend3ab6922015-03-02 14:30:53 -0800319 Builder b = Builder.obtain();
320 b.setText(source, bufstart, bufend)
321 .setPaint(paint)
322 .setWidth(outerwidth)
323 .setTextDir(textDir)
324 .setSpacingMult(spacingmult)
325 .setSpacingAdd(spacingadd)
326 .setIncludePad(includepad)
327 .setEllipsizedWidth(ellipsizedWidth)
328 .setEllipsize(ellipsize)
329 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 /*
331 * This is annoying, but we can't refer to the layout until
332 * superclass construction is finished, and the superclass
333 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700334 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 * This will break if the superclass constructor ever actually
336 * cares about the content instead of just holding the reference.
337 */
338 if (ellipsize != null) {
339 Ellipsizer e = (Ellipsizer) getText();
340
341 e.mLayout = this;
342 e.mWidth = ellipsizedWidth;
343 e.mMethod = ellipsize;
344 mEllipsizedWidth = ellipsizedWidth;
345
346 mColumns = COLUMNS_ELLIPSIZE;
347 } else {
348 mColumns = COLUMNS_NORMAL;
349 mEllipsizedWidth = outerwidth;
350 }
351
Adam Lesinski776abc22014-03-07 11:30:59 -0500352 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
353 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700354 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355
Raph Levien70616ec2015-03-04 10:41:30 -0800356 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700357
Raph Leviend3ab6922015-03-02 14:30:53 -0800358 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 }
360
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700361 /* package */ StaticLayout(CharSequence text) {
362 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363
364 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500365 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
366 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 }
368
Raph Leviend3ab6922015-03-02 14:30:53 -0800369 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
370 CharSequence source = b.mText;
371 int bufStart = b.mStart;
372 int bufEnd = b.mEnd;
373 TextPaint paint = b.mPaint;
374 int outerWidth = b.mWidth;
375 TextDirectionHeuristic textDir = b.mTextDir;
376 float spacingmult = b.mSpacingMult;
377 float spacingadd = b.mSpacingAdd;
378 float ellipsizedWidth = b.mEllipsizedWidth;
379 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800380 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700381 // store span end locations
382 int[] spanEndCache = new int[4];
383 // store fontMetrics per span range
384 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
385 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800386 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700387
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800388 mLineCount = 0;
389
390 int v = 0;
391 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
392
Raph Leviend3ab6922015-03-02 14:30:53 -0800393 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800394 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395
Raph Leviend3ab6922015-03-02 14:30:53 -0800396 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 if (source instanceof Spanned)
400 spanned = (Spanned) source;
401
Doug Felte8e45f22010-03-29 14:58:40 -0700402 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800403 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
404 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700405 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800406 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407 else
Doug Felte8e45f22010-03-29 14:58:40 -0700408 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409
Anish Athalyec8f9e622014-07-21 15:26:34 -0700410 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800411 int firstWidth = outerWidth;
412 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800414 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415
416 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700417 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700418 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700420 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800421 firstWidth -= sp[i].getLeadingMargin(true);
422 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700423
Doug Feltc982f602010-05-25 11:51:40 -0700424 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700425 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700426 if (lms instanceof LeadingMarginSpan2) {
427 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700428 firstWidthLineCount = Math.max(firstWidthLineCount,
429 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700430 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 }
432
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800433 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800435 if (chooseHt.length != 0) {
436 if (chooseHtv == null ||
437 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500438 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 }
440
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800441 for (int i = 0; i < chooseHt.length; i++) {
442 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443
Doug Felte8e45f22010-03-29 14:58:40 -0700444 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 // starts in this layout, before the
446 // current paragraph
447
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800448 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 } else {
450 // starts in this paragraph
451
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800452 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 }
454 }
455 }
456 }
457
Raph Levien70616ec2015-03-04 10:41:30 -0800458 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700459 char[] chs = measured.mChars;
460 float[] widths = measured.mWidths;
461 byte[] chdirs = measured.mLevels;
462 int dir = measured.mDir;
463 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800464
465 // tab stop locations
466 int[] variableTabStops = null;
467 if (spanned != null) {
468 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
469 paraEnd, TabStopSpan.class);
470 if (spans.length > 0) {
471 int[] stops = new int[spans.length];
472 for (int i = 0; i < spans.length; i++) {
473 stops[i] = spans[i].getTabStop();
474 }
475 Arrays.sort(stops, 0, stops.length);
476 variableTabStops = stops;
477 }
478 }
479
480 int breakStrategy = 0; // 0 = kBreakStrategy_Greedy
481 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
482 firstWidth, firstWidthLineCount, restWidth,
483 variableTabStops, TAB_INCREMENT, breakStrategy);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484
Anish Athalyec8f9e622014-07-21 15:26:34 -0700485 // measurement has to be done before performing line breaking
486 // but we don't want to recompute fontmetrics or span ranges the
487 // second time, so we cache those and then use those stored values
488 int fmCacheCount = 0;
489 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700490 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700491 if (fmCacheCount * 4 >= fmCache.length) {
492 int[] grow = new int[fmCacheCount * 4 * 2];
493 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
494 fmCache = grow;
495 }
496
497 if (spanEndCacheCount >= spanEndCache.length) {
498 int[] grow = new int[spanEndCacheCount * 2];
499 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
500 spanEndCache = grow;
501 }
Doug Felte8e45f22010-03-29 14:58:40 -0700502
Gilles Debunnecd943a72012-06-07 17:54:47 -0700503 if (spanned == null) {
504 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700505 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700506 measured.addStyleRun(paint, spanLen, fm);
507 } else {
508 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
509 MetricAffectingSpan.class);
510 int spanLen = spanEnd - spanStart;
511 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700512 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700513 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
514 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700515 }
516
Anish Athalyec8f9e622014-07-21 15:26:34 -0700517 // the order of storage here (top, bottom, ascent, descent) has to match the code below
518 // where these values are retrieved
519 fmCache[fmCacheCount * 4 + 0] = fm.top;
520 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
521 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
522 fmCache[fmCacheCount * 4 + 3] = fm.descent;
523 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524
Anish Athalyec8f9e622014-07-21 15:26:34 -0700525 spanEndCache[spanEndCacheCount] = spanEnd;
526 spanEndCacheCount++;
527 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528
Raph Levien70616ec2015-03-04 10:41:30 -0800529 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800530 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
531 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532
Anish Athalyec8f9e622014-07-21 15:26:34 -0700533 int[] breaks = lineBreaks.breaks;
534 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700535 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700536
Anish Athalyec8f9e622014-07-21 15:26:34 -0700537 // here is the offset of the starting character of the line we are currently measuring
538 int here = paraStart;
539
540 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
541 int fmCacheIndex = 0;
542 int spanEndCacheIndex = 0;
543 int breakIndex = 0;
544 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
545 // retrieve end of span
546 spanEnd = spanEndCache[spanEndCacheIndex++];
547
548 // retrieve cached metrics, order matches above
549 fm.top = fmCache[fmCacheIndex * 4 + 0];
550 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
551 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
552 fm.descent = fmCache[fmCacheIndex * 4 + 3];
553 fmCacheIndex++;
554
555 if (fm.top < fmTop) {
556 fmTop = fm.top;
557 }
558 if (fm.ascent < fmAscent) {
559 fmAscent = fm.ascent;
560 }
561 if (fm.descent > fmDescent) {
562 fmDescent = fm.descent;
563 }
564 if (fm.bottom > fmBottom) {
565 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 }
567
Anish Athalyec8f9e622014-07-21 15:26:34 -0700568 // skip breaks ending before current span range
569 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
570 breakIndex++;
571 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572
Anish Athalyec8f9e622014-07-21 15:26:34 -0700573 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
574 int endPos = paraStart + breaks[breakIndex];
575
Raph Levience4155a2015-03-11 11:02:33 -0700576 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800577
Anish Athalyec8f9e622014-07-21 15:26:34 -0700578 v = out(source, here, endPos,
579 fmAscent, fmDescent, fmTop, fmBottom,
580 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
581 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
582 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800583 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700584
585 if (endPos < spanEnd) {
586 // preserve metrics for current span
587 fmTop = fm.top;
588 fmBottom = fm.bottom;
589 fmAscent = fm.ascent;
590 fmDescent = fm.descent;
591 } else {
592 fmTop = fmBottom = fmAscent = fmDescent = 0;
593 }
594
595 here = endPos;
596 breakIndex++;
597
598 if (mLineCount >= mMaximumVisibleLineCount) {
599 return;
600 }
601 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 }
603
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800604 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 break;
606 }
607
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700608 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700609 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800610 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611
Raph Levien70616ec2015-03-04 10:41:30 -0800612 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700613
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 paint.getFontMetricsInt(fm);
615
616 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800617 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 fm.top, fm.bottom,
619 v,
620 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700621 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700622 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700623 includepad, trackpad, null,
624 null, bufStart, ellipsize,
625 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 }
627 }
628
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800629 private int out(CharSequence text, int start, int end,
630 int above, int below, int top, int bottom, int v,
631 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800632 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700633 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700634 boolean needMultiply, byte[] chdirs, int dir,
635 boolean easy, int bufEnd, boolean includePad,
636 boolean trackPad, char[] chs,
637 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
638 float ellipsisWidth, float textWidth,
639 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 int j = mLineCount;
641 int off = j * mColumns;
642 int want = off + mColumns + TOP;
643 int[] lines = mLines;
644
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800645 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500646 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
647 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800648 System.arraycopy(mLineDirections, 0, grow2, 0,
649 mLineDirections.length);
650 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500651
652 int[] grow = new int[grow2.length];
653 System.arraycopy(lines, 0, grow, 0, lines.length);
654 mLines = grow;
655 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 }
657
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800658 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800659 fm.ascent = above;
660 fm.descent = below;
661 fm.top = top;
662 fm.bottom = bottom;
663
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800664 for (int i = 0; i < chooseHt.length; i++) {
665 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
666 ((LineHeightSpan.WithDensity) chooseHt[i]).
667 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700668
669 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800670 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700671 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672 }
673
674 above = fm.ascent;
675 below = fm.descent;
676 top = fm.top;
677 bottom = fm.bottom;
678 }
679
Raph Leviend97b0972014-04-24 12:51:35 -0700680 boolean firstLine = (j == 0);
681 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
682 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
683
684 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800685 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 mTopPadding = top - above;
687 }
688
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800689 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 above = top;
691 }
692 }
Raph Leviend97b0972014-04-24 12:51:35 -0700693
694 int extra;
695
696 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800697 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 mBottomPadding = bottom - below;
699 }
700
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800701 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702 below = bottom;
703 }
704 }
705
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800706
Raph Leviend97b0972014-04-24 12:51:35 -0700707 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800708 double ex = (below - above) * (spacingmult - 1) + spacingadd;
709 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800710 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800711 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800712 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800713 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800714 } else {
715 extra = 0;
716 }
717
718 lines[off + START] = start;
719 lines[off + TOP] = v;
720 lines[off + DESCENT] = below + extra;
721
722 v += (below - above) + extra;
723 lines[off + mColumns + START] = end;
724 lines[off + mColumns + TOP] = v;
725
Raph Levien26d443a2015-03-30 14:18:32 -0700726 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
727 // one bit for start field
728 lines[off + TAB] |= flags & TAB_MASK;
729 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730
Doug Felt9f7a4442010-03-01 12:45:56 -0800731 lines[off + DIR] |= dir << DIR_SHIFT;
732 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
733 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700734 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800735 // RTL paragraph. Make sure easy is false if this is the case.
736 if (easy) {
737 mLineDirections[j] = linedirs;
738 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800739 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
740 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800741 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700743 if (ellipsize != null) {
744 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
745 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700746 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700747
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800748 boolean doEllipsis =
749 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700750 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
751 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
752 ellipsize == TextUtils.TruncateAt.END);
753 if (doEllipsis) {
754 calculateEllipsis(start, end, widths, widthStart,
755 ellipsisWidth, ellipsize, j,
756 textWidth, paint, forceEllipsis);
757 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800758 }
759
760 mLineCount++;
761 return v;
762 }
763
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800764 private void calculateEllipsis(int lineStart, int lineEnd,
765 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700767 int line, float textWidth, TextPaint paint,
768 boolean forceEllipsis) {
769 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770 // Everything fits!
771 mLines[mColumns * line + ELLIPSIS_START] = 0;
772 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
773 return;
774 }
775
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700776 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700777 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000778 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700779 int ellipsisStart = 0;
780 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800781 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700783 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700785 if (mMaximumVisibleLineCount == 1) {
786 float sum = 0;
787 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700789 for (i = len; i >= 0; i--) {
790 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700792 if (w + sum + ellipsisWidth > avail) {
793 break;
794 }
795
796 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 }
798
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700799 ellipsisStart = 0;
800 ellipsisCount = i;
801 } else {
802 if (Log.isLoggable(TAG, Log.WARN)) {
803 Log.w(TAG, "Start Ellipsis only supported with one line");
804 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700806 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
807 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 float sum = 0;
809 int i;
810
811 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800812 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800814 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 break;
816 }
817
818 sum += w;
819 }
820
821 ellipsisStart = i;
822 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700823 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
824 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700825 ellipsisCount = 1;
826 }
827 } else {
828 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
829 if (mMaximumVisibleLineCount == 1) {
830 float lsum = 0, rsum = 0;
831 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700833 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800834 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700835 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800836
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700837 if (w + rsum > ravail) {
838 break;
839 }
840
841 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 }
843
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700844 float lavail = avail - ellipsisWidth - rsum;
845 for (left = 0; left < right; left++) {
846 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800847
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700848 if (w + lsum > lavail) {
849 break;
850 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700852 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 }
854
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700855 ellipsisStart = left;
856 ellipsisCount = right - left;
857 } else {
858 if (Log.isLoggable(TAG, Log.WARN)) {
859 Log.w(TAG, "Middle Ellipsis only supported with one line");
860 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 }
863
864 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
865 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
866 }
867
Doug Felte8e45f22010-03-29 14:58:40 -0700868 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 // rather than relying on member functions.
870 // The logic mirrors that of Layout.getLineForVertical
871 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800872 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 public int getLineForVertical(int vertical) {
874 int high = mLineCount;
875 int low = -1;
876 int guess;
877 int[] lines = mLines;
878 while (high - low > 1) {
879 guess = (high + low) >> 1;
880 if (lines[mColumns * guess + TOP] > vertical){
881 high = guess;
882 } else {
883 low = guess;
884 }
885 }
886 if (low < 0) {
887 return 0;
888 } else {
889 return low;
890 }
891 }
892
Gilles Debunne66111472010-11-19 11:04:37 -0800893 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 public int getLineCount() {
895 return mLineCount;
896 }
897
Gilles Debunne66111472010-11-19 11:04:37 -0800898 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800900 int top = mLines[mColumns * line + TOP];
901 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
902 line != mLineCount) {
903 top += getBottomPadding();
904 }
905 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906 }
907
Gilles Debunne66111472010-11-19 11:04:37 -0800908 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800910 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800911 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800912 line != mLineCount) {
913 descent += getBottomPadding();
914 }
915 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 }
917
Gilles Debunne66111472010-11-19 11:04:37 -0800918 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 public int getLineStart(int line) {
920 return mLines[mColumns * line + START] & START_MASK;
921 }
922
Gilles Debunne66111472010-11-19 11:04:37 -0800923 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 public int getParagraphDirection(int line) {
925 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
926 }
927
Gilles Debunne66111472010-11-19 11:04:37 -0800928 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800929 public boolean getLineContainsTab(int line) {
930 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
931 }
932
Gilles Debunne66111472010-11-19 11:04:37 -0800933 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 public final Directions getLineDirections(int line) {
935 return mLineDirections[line];
936 }
937
Gilles Debunne66111472010-11-19 11:04:37 -0800938 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 public int getTopPadding() {
940 return mTopPadding;
941 }
942
Gilles Debunne66111472010-11-19 11:04:37 -0800943 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 public int getBottomPadding() {
945 return mBottomPadding;
946 }
947
Raph Levien26d443a2015-03-30 14:18:32 -0700948 /**
949 * @hide
950 */
951 @Override
952 public int getHyphen(int line) {
953 return mLines[mColumns * line + HYPHEN] & 0xff;
954 }
955
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800956 @Override
957 public int getEllipsisCount(int line) {
958 if (mColumns < COLUMNS_ELLIPSIZE) {
959 return 0;
960 }
961
962 return mLines[mColumns * line + ELLIPSIS_COUNT];
963 }
964
965 @Override
966 public int getEllipsisStart(int line) {
967 if (mColumns < COLUMNS_ELLIPSIZE) {
968 return 0;
969 }
970
971 return mLines[mColumns * line + ELLIPSIS_START];
972 }
973
974 @Override
975 public int getEllipsizedWidth() {
976 return mEllipsizedWidth;
977 }
978
Raph Levien70616ec2015-03-04 10:41:30 -0800979 private static native long nNewBuilder();
980 private static native void nFreeBuilder(long nativePtr);
981 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -0700982
983 /* package */ static native long nLoadHyphenator(String patternData);
984
985 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -0800986
Raph Levienc94f7422015-03-06 19:19:48 -0800987 // Set up paragraph text and settings; done as one big method to minimize jni crossings
988 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
989 float firstWidth, int firstWidthLineCount, float restWidth,
990 int[] variableTabStops, int defaultTabStop, int breakStrategy);
Raph Levien70616ec2015-03-04 10:41:30 -0800991
992 private static native float nAddStyleRun(long nativePtr, long nativePaint,
993 long nativeTypeface, int start, int end, boolean isRtl);
994
995 private static native void nAddMeasuredRun(long nativePtr,
996 int start, int end, float[] widths);
997
998 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
999
1000 private static native void nGetWidths(long nativePtr, float[] widths);
1001
Anish Athalyec8f9e622014-07-21 15:26:34 -07001002 // populates LineBreaks and returns the number of breaks found
1003 //
1004 // the arrays inside the LineBreaks objects are passed in as well
1005 // to reduce the number of JNI calls in the common case where the
1006 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001007 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001008 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001009
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001010 private int mLineCount;
1011 private int mTopPadding, mBottomPadding;
1012 private int mColumns;
1013 private int mEllipsizedWidth;
1014
Raph Levien26d443a2015-03-30 14:18:32 -07001015 private static final int COLUMNS_NORMAL = 4;
1016 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 private static final int START = 0;
1018 private static final int DIR = START;
1019 private static final int TAB = START;
1020 private static final int TOP = 1;
1021 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001022 private static final int HYPHEN = 3;
1023 private static final int ELLIPSIS_START = 4;
1024 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001025
1026 private int[] mLines;
1027 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001028 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001029
1030 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001031 private static final int DIR_SHIFT = 30;
1032 private static final int TAB_MASK = 0x20000000;
1033
Doug Feltc982f602010-05-25 11:51:40 -07001034 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001036 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001037
1038 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001039
Anish Athalyec8f9e622014-07-21 15:26:34 -07001040 // This is used to return three arrays from a single JNI call when
1041 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001042 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001043 private static final int INITIAL_SIZE = 16;
1044 public int[] breaks = new int[INITIAL_SIZE];
1045 public float[] widths = new float[INITIAL_SIZE];
Raph Levien26d443a2015-03-30 14:18:32 -07001046 public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
Anish Athalyec8f9e622014-07-21 15:26:34 -07001047 // breaks, widths, and flags should all have the same length
1048 }
1049
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050}