blob: 67794b1c7d5fed0445f47049da970d9b3efcfadf [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;
Raph Levien39b4db72015-03-25 13:18:20 -070026import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027
Doug Feltcb3791202011-07-07 11:57:48 -070028import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050029import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070030
Anish Athalyec8f9e622014-07-21 15:26:34 -070031import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080032import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070033
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034/**
35 * StaticLayout is a Layout for text that will not be edited after it
36 * is laid out. Use {@link DynamicLayout} for text that may change.
37 * <p>This is used by widgets to control text layout. You should not need
38 * to use this class directly unless you are implementing your own widget
39 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070040 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
41 * float, float, android.graphics.Paint)
42 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080044public class StaticLayout extends Layout {
45
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070046 static final String TAG = "StaticLayout";
47
Raph Leviend3ab6922015-03-02 14:30:53 -080048 /**
49 * Builder for static layouts. It would be better if this were a public
50 * API (as it would offer much greater flexibility for adding new options)
51 * but for the time being it's just internal.
52 *
53 * @hide
54 */
55 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080056 private Builder() {
57 mNativePtr = nNewBuilder();
58 }
59
Raph Levien39b4db72015-03-25 13:18:20 -070060 public static Builder obtain(CharSequence source, int start, int end, int width) {
61 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080062 if (b == null) {
63 b = new Builder();
64 }
65
66 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070067 b.mText = source;
68 b.mStart = start;
69 b.mEnd = end;
70 b.mWidth = width;
71 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -080072 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
73 b.mSpacingMult = 1.0f;
74 b.mSpacingAdd = 0.0f;
75 b.mIncludePad = true;
Raph Levien39b4db72015-03-25 13:18:20 -070076 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080077 b.mEllipsize = null;
78 b.mMaxLines = Integer.MAX_VALUE;
79
80 b.mMeasuredText = MeasuredText.obtain();
81 return b;
82 }
83
Raph Levien39b4db72015-03-25 13:18:20 -070084 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -080085 b.mPaint = null;
86 b.mText = null;
87 MeasuredText.recycle(b.mMeasuredText);
Raph Levien39b4db72015-03-25 13:18:20 -070088 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -080089 }
90
91 // release any expensive state
92 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -080093 nFinishBuilder(mNativePtr);
Raph Leviend3ab6922015-03-02 14:30:53 -080094 mMeasuredText.finish();
95 }
96
97 public Builder setText(CharSequence source) {
98 return setText(source, 0, source.length());
99 }
100
101 public Builder setText(CharSequence source, int start, int end) {
102 mText = source;
103 mStart = start;
104 mEnd = end;
105 return this;
106 }
107
108 public Builder setPaint(TextPaint paint) {
109 mPaint = paint;
110 return this;
111 }
112
113 public Builder setWidth(int width) {
114 mWidth = width;
115 if (mEllipsize == null) {
116 mEllipsizedWidth = width;
117 }
118 return this;
119 }
120
Raph Levien39b4db72015-03-25 13:18:20 -0700121 public Builder setAlignment(Alignment alignment) {
122 mAlignment = alignment;
123 return this;
124 }
125
Raph Leviend3ab6922015-03-02 14:30:53 -0800126 public Builder setTextDir(TextDirectionHeuristic textDir) {
127 mTextDir = textDir;
128 return this;
129 }
130
131 // TODO: combine the following, as they're almost always set together?
132 public Builder setSpacingMult(float spacingMult) {
133 mSpacingMult = spacingMult;
134 return this;
135 }
136
137 public Builder setSpacingAdd(float spacingAdd) {
138 mSpacingAdd = spacingAdd;
139 return this;
140 }
141
142 public Builder setIncludePad(boolean includePad) {
143 mIncludePad = includePad;
144 return this;
145 }
146
147 // TODO: combine the following?
148 public Builder setEllipsizedWidth(int ellipsizedWidth) {
149 mEllipsizedWidth = ellipsizedWidth;
150 return this;
151 }
152
153 public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
154 mEllipsize = ellipsize;
155 return this;
156 }
157
158 public Builder setMaxLines(int maxLines) {
159 mMaxLines = maxLines;
160 return this;
161 }
162
Raph Levien39b4db72015-03-25 13:18:20 -0700163 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
164 mBreakStrategy = breakStrategy;
165 return this;
166 }
167
Raph Leviene319d5a2015-04-14 23:51:07 -0700168 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
169 int leftLen = leftIndents == null ? 0 : leftIndents.length;
170 int rightLen = rightIndents == null ? 0 : rightIndents.length;
171 int[] indents = new int[Math.max(leftLen, rightLen)];
172 for (int i = 0; i < indents.length; i++) {
173 int leftMargin = i < leftLen ? leftIndents[i] : 0;
174 int rightMargin = i < rightLen ? rightIndents[i] : 0;
175 indents[i] = leftMargin + rightMargin;
176 }
177 nSetIndents(mNativePtr, indents);
178 return this;
179 }
180
Raph Levien70616ec2015-03-04 10:41:30 -0800181 /**
182 * Measurement and break iteration is done in native code. The protocol for using
183 * the native code is as follows.
184 *
Raph Levien26d443a2015-03-30 14:18:32 -0700185 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
186 * stops, break strategy (and possibly other parameters in the future).
Raph Levienc94f7422015-03-06 19:19:48 -0800187 *
188 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800189 * - setLocale (this must be done at least for the first run, optional afterwards)
190 * - one of the following, depending on the type of run:
191 * + addStyleRun (a text run, to be measured in native code)
192 * + addMeasuredRun (a run already measured in Java, passed into native code)
193 * + addReplacementRun (a replacement run, width is given)
194 *
195 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
196 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
197 *
198 * After all paragraphs, call finish() to release expensive buffers.
199 */
200
201 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800202 if (!locale.equals(mLocale)) {
Raph Levien26d443a2015-03-30 14:18:32 -0700203 nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
Raph Levien4c1f12e2015-03-02 16:29:23 -0800204 mLocale = locale;
205 }
206 }
207
Raph Levien70616ec2015-03-04 10:41:30 -0800208 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
209 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
210 start, end, isRtl);
211 }
212
213 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
214 nAddMeasuredRun(mNativePtr, start, end, widths);
215 }
216
217 /* package */ void addReplacementRun(int start, int end, float width) {
218 nAddReplacementRun(mNativePtr, start, end, width);
219 }
220
Raph Leviend3ab6922015-03-02 14:30:53 -0800221 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700222 StaticLayout result = new StaticLayout(this);
223 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800224 return result;
225 }
226
Raph Levien4c1f12e2015-03-02 16:29:23 -0800227 @Override
228 protected void finalize() throws Throwable {
229 try {
230 nFreeBuilder(mNativePtr);
231 } finally {
232 super.finalize();
233 }
234 }
235
236 /* package */ long mNativePtr;
237
Raph Leviend3ab6922015-03-02 14:30:53 -0800238 CharSequence mText;
239 int mStart;
240 int mEnd;
241 TextPaint mPaint;
242 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700243 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800244 TextDirectionHeuristic mTextDir;
245 float mSpacingMult;
246 float mSpacingAdd;
247 boolean mIncludePad;
248 int mEllipsizedWidth;
249 TextUtils.TruncateAt mEllipsize;
250 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700251 int mBreakStrategy;
Raph Leviend3ab6922015-03-02 14:30:53 -0800252
253 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
254
255 // This will go away and be subsumed by native builder code
256 MeasuredText mMeasuredText;
257
Raph Levien4c1f12e2015-03-02 16:29:23 -0800258 Locale mLocale;
259
Raph Levien39b4db72015-03-25 13:18:20 -0700260 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800261 }
262
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 public StaticLayout(CharSequence source, TextPaint paint,
264 int width,
265 Alignment align, float spacingmult, float spacingadd,
266 boolean includepad) {
267 this(source, 0, source.length(), paint, width, align,
268 spacingmult, spacingadd, includepad);
269 }
270
Doug Feltcb3791202011-07-07 11:57:48 -0700271 /**
272 * @hide
273 */
274 public StaticLayout(CharSequence source, TextPaint paint,
275 int width, Alignment align, TextDirectionHeuristic textDir,
276 float spacingmult, float spacingadd,
277 boolean includepad) {
278 this(source, 0, source.length(), paint, width, align, textDir,
279 spacingmult, spacingadd, includepad);
280 }
281
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 public StaticLayout(CharSequence source, int bufstart, int bufend,
283 TextPaint paint, int outerwidth,
284 Alignment align,
285 float spacingmult, float spacingadd,
286 boolean includepad) {
287 this(source, bufstart, bufend, paint, outerwidth, align,
288 spacingmult, spacingadd, includepad, null, 0);
289 }
290
Doug Feltcb3791202011-07-07 11:57:48 -0700291 /**
292 * @hide
293 */
294 public StaticLayout(CharSequence source, int bufstart, int bufend,
295 TextPaint paint, int outerwidth,
296 Alignment align, TextDirectionHeuristic textDir,
297 float spacingmult, float spacingadd,
298 boolean includepad) {
299 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700300 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700301}
302
303 public StaticLayout(CharSequence source, int bufstart, int bufend,
304 TextPaint paint, int outerwidth,
305 Alignment align,
306 float spacingmult, float spacingadd,
307 boolean includepad,
308 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
309 this(source, bufstart, bufend, paint, outerwidth, align,
310 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700311 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700312 }
313
314 /**
315 * @hide
316 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800317 public StaticLayout(CharSequence source, int bufstart, int bufend,
318 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700319 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 float spacingmult, float spacingadd,
321 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700322 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700324 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 : (source instanceof Spanned)
326 ? new SpannedEllipsizer(source)
327 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700328 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329
Raph Levien39b4db72015-03-25 13:18:20 -0700330 Builder b = Builder.obtain(source, bufstart, bufend, outerwidth)
Raph Leviend3ab6922015-03-02 14:30:53 -0800331 .setPaint(paint)
Raph Levien39b4db72015-03-25 13:18:20 -0700332 .setAlignment(align)
Raph Leviend3ab6922015-03-02 14:30:53 -0800333 .setTextDir(textDir)
334 .setSpacingMult(spacingmult)
335 .setSpacingAdd(spacingadd)
336 .setIncludePad(includepad)
337 .setEllipsizedWidth(ellipsizedWidth)
338 .setEllipsize(ellipsize)
339 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 /*
341 * This is annoying, but we can't refer to the layout until
342 * superclass construction is finished, and the superclass
343 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700344 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 * This will break if the superclass constructor ever actually
346 * cares about the content instead of just holding the reference.
347 */
348 if (ellipsize != null) {
349 Ellipsizer e = (Ellipsizer) getText();
350
351 e.mLayout = this;
352 e.mWidth = ellipsizedWidth;
353 e.mMethod = ellipsize;
354 mEllipsizedWidth = ellipsizedWidth;
355
356 mColumns = COLUMNS_ELLIPSIZE;
357 } else {
358 mColumns = COLUMNS_NORMAL;
359 mEllipsizedWidth = outerwidth;
360 }
361
Adam Lesinski776abc22014-03-07 11:30:59 -0500362 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
363 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700364 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365
Raph Levien70616ec2015-03-04 10:41:30 -0800366 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700367
Raph Leviend3ab6922015-03-02 14:30:53 -0800368 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 }
370
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700371 /* package */ StaticLayout(CharSequence text) {
372 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373
374 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500375 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
376 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 }
378
Raph Levien39b4db72015-03-25 13:18:20 -0700379 private StaticLayout(Builder b) {
380 super((b.mEllipsize == null)
381 ? b.mText
382 : (b.mText instanceof Spanned)
383 ? new SpannedEllipsizer(b.mText)
384 : new Ellipsizer(b.mText),
385 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
386
387 if (b.mEllipsize != null) {
388 Ellipsizer e = (Ellipsizer) getText();
389
390 e.mLayout = this;
391 e.mWidth = b.mEllipsizedWidth;
392 e.mMethod = b.mEllipsize;
393 mEllipsizedWidth = b.mEllipsizedWidth;
394
395 mColumns = COLUMNS_ELLIPSIZE;
396 } else {
397 mColumns = COLUMNS_NORMAL;
398 mEllipsizedWidth = b.mWidth;
399 }
400
401 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
402 mLines = new int[mLineDirections.length];
403 mMaximumVisibleLineCount = b.mMaxLines;
404
405 generate(b, b.mIncludePad, b.mIncludePad);
406 }
407
Raph Leviend3ab6922015-03-02 14:30:53 -0800408 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
409 CharSequence source = b.mText;
410 int bufStart = b.mStart;
411 int bufEnd = b.mEnd;
412 TextPaint paint = b.mPaint;
413 int outerWidth = b.mWidth;
414 TextDirectionHeuristic textDir = b.mTextDir;
415 float spacingmult = b.mSpacingMult;
416 float spacingadd = b.mSpacingAdd;
417 float ellipsizedWidth = b.mEllipsizedWidth;
418 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800419 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700420 // store span end locations
421 int[] spanEndCache = new int[4];
422 // store fontMetrics per span range
423 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
424 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800425 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700426
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 mLineCount = 0;
428
429 int v = 0;
430 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
431
Raph Leviend3ab6922015-03-02 14:30:53 -0800432 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800433 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434
Raph Leviend3ab6922015-03-02 14:30:53 -0800435 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 if (source instanceof Spanned)
439 spanned = (Spanned) source;
440
Doug Felte8e45f22010-03-29 14:58:40 -0700441 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800442 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
443 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700444 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800445 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446 else
Doug Felte8e45f22010-03-29 14:58:40 -0700447 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448
Anish Athalyec8f9e622014-07-21 15:26:34 -0700449 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800450 int firstWidth = outerWidth;
451 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800453 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454
455 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700456 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700457 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700459 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800460 firstWidth -= sp[i].getLeadingMargin(true);
461 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700462
Doug Feltc982f602010-05-25 11:51:40 -0700463 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700464 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700465 if (lms instanceof LeadingMarginSpan2) {
466 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700467 firstWidthLineCount = Math.max(firstWidthLineCount,
468 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700469 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 }
471
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800472 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800474 if (chooseHt.length != 0) {
475 if (chooseHtv == null ||
476 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500477 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 }
479
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800480 for (int i = 0; i < chooseHt.length; i++) {
481 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482
Doug Felte8e45f22010-03-29 14:58:40 -0700483 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 // starts in this layout, before the
485 // current paragraph
486
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800487 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488 } else {
489 // starts in this paragraph
490
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800491 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 }
493 }
494 }
495 }
496
Raph Levien70616ec2015-03-04 10:41:30 -0800497 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700498 char[] chs = measured.mChars;
499 float[] widths = measured.mWidths;
500 byte[] chdirs = measured.mLevels;
501 int dir = measured.mDir;
502 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800503
504 // tab stop locations
505 int[] variableTabStops = null;
506 if (spanned != null) {
507 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
508 paraEnd, TabStopSpan.class);
509 if (spans.length > 0) {
510 int[] stops = new int[spans.length];
511 for (int i = 0; i < spans.length; i++) {
512 stops[i] = spans[i].getTabStop();
513 }
514 Arrays.sort(stops, 0, stops.length);
515 variableTabStops = stops;
516 }
517 }
518
Raph Levienc94f7422015-03-06 19:19:48 -0800519 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
520 firstWidth, firstWidthLineCount, restWidth,
Raph Levien39b4db72015-03-25 13:18:20 -0700521 variableTabStops, TAB_INCREMENT, b.mBreakStrategy);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522
Anish Athalyec8f9e622014-07-21 15:26:34 -0700523 // measurement has to be done before performing line breaking
524 // but we don't want to recompute fontmetrics or span ranges the
525 // second time, so we cache those and then use those stored values
526 int fmCacheCount = 0;
527 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700528 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700529 if (fmCacheCount * 4 >= fmCache.length) {
530 int[] grow = new int[fmCacheCount * 4 * 2];
531 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
532 fmCache = grow;
533 }
534
535 if (spanEndCacheCount >= spanEndCache.length) {
536 int[] grow = new int[spanEndCacheCount * 2];
537 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
538 spanEndCache = grow;
539 }
Doug Felte8e45f22010-03-29 14:58:40 -0700540
Gilles Debunnecd943a72012-06-07 17:54:47 -0700541 if (spanned == null) {
542 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700543 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700544 measured.addStyleRun(paint, spanLen, fm);
545 } else {
546 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
547 MetricAffectingSpan.class);
548 int spanLen = spanEnd - spanStart;
549 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700550 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700551 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
552 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700553 }
554
Anish Athalyec8f9e622014-07-21 15:26:34 -0700555 // the order of storage here (top, bottom, ascent, descent) has to match the code below
556 // where these values are retrieved
557 fmCache[fmCacheCount * 4 + 0] = fm.top;
558 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
559 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
560 fmCache[fmCacheCount * 4 + 3] = fm.descent;
561 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562
Anish Athalyec8f9e622014-07-21 15:26:34 -0700563 spanEndCache[spanEndCacheCount] = spanEnd;
564 spanEndCacheCount++;
565 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566
Raph Levien70616ec2015-03-04 10:41:30 -0800567 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800568 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
569 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570
Anish Athalyec8f9e622014-07-21 15:26:34 -0700571 int[] breaks = lineBreaks.breaks;
572 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700573 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700574
Anish Athalyec8f9e622014-07-21 15:26:34 -0700575 // here is the offset of the starting character of the line we are currently measuring
576 int here = paraStart;
577
578 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
579 int fmCacheIndex = 0;
580 int spanEndCacheIndex = 0;
581 int breakIndex = 0;
582 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
583 // retrieve end of span
584 spanEnd = spanEndCache[spanEndCacheIndex++];
585
586 // retrieve cached metrics, order matches above
587 fm.top = fmCache[fmCacheIndex * 4 + 0];
588 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
589 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
590 fm.descent = fmCache[fmCacheIndex * 4 + 3];
591 fmCacheIndex++;
592
593 if (fm.top < fmTop) {
594 fmTop = fm.top;
595 }
596 if (fm.ascent < fmAscent) {
597 fmAscent = fm.ascent;
598 }
599 if (fm.descent > fmDescent) {
600 fmDescent = fm.descent;
601 }
602 if (fm.bottom > fmBottom) {
603 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 }
605
Anish Athalyec8f9e622014-07-21 15:26:34 -0700606 // skip breaks ending before current span range
607 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
608 breakIndex++;
609 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610
Anish Athalyec8f9e622014-07-21 15:26:34 -0700611 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
612 int endPos = paraStart + breaks[breakIndex];
613
Raph Levience4155a2015-03-11 11:02:33 -0700614 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800615
Anish Athalyec8f9e622014-07-21 15:26:34 -0700616 v = out(source, here, endPos,
617 fmAscent, fmDescent, fmTop, fmBottom,
618 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
619 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
620 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800621 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700622
623 if (endPos < spanEnd) {
624 // preserve metrics for current span
625 fmTop = fm.top;
626 fmBottom = fm.bottom;
627 fmAscent = fm.ascent;
628 fmDescent = fm.descent;
629 } else {
630 fmTop = fmBottom = fmAscent = fmDescent = 0;
631 }
632
633 here = endPos;
634 breakIndex++;
635
636 if (mLineCount >= mMaximumVisibleLineCount) {
637 return;
638 }
639 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 }
641
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800642 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 break;
644 }
645
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700646 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700647 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800648 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649
Raph Levien70616ec2015-03-04 10:41:30 -0800650 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700651
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652 paint.getFontMetricsInt(fm);
653
654 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800655 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 fm.top, fm.bottom,
657 v,
658 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700659 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700660 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700661 includepad, trackpad, null,
662 null, bufStart, ellipsize,
663 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800664 }
665 }
666
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667 private int out(CharSequence text, int start, int end,
668 int above, int below, int top, int bottom, int v,
669 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800670 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700671 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700672 boolean needMultiply, byte[] chdirs, int dir,
673 boolean easy, int bufEnd, boolean includePad,
674 boolean trackPad, char[] chs,
675 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
676 float ellipsisWidth, float textWidth,
677 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800678 int j = mLineCount;
679 int off = j * mColumns;
680 int want = off + mColumns + TOP;
681 int[] lines = mLines;
682
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500684 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
685 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 System.arraycopy(mLineDirections, 0, grow2, 0,
687 mLineDirections.length);
688 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500689
690 int[] grow = new int[grow2.length];
691 System.arraycopy(lines, 0, grow, 0, lines.length);
692 mLines = grow;
693 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 }
695
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800696 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 fm.ascent = above;
698 fm.descent = below;
699 fm.top = top;
700 fm.bottom = bottom;
701
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800702 for (int i = 0; i < chooseHt.length; i++) {
703 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
704 ((LineHeightSpan.WithDensity) chooseHt[i]).
705 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700706
707 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800708 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700709 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 }
711
712 above = fm.ascent;
713 below = fm.descent;
714 top = fm.top;
715 bottom = fm.bottom;
716 }
717
Raph Leviend97b0972014-04-24 12:51:35 -0700718 boolean firstLine = (j == 0);
719 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
720 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
721
722 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800723 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724 mTopPadding = top - above;
725 }
726
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800727 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728 above = top;
729 }
730 }
Raph Leviend97b0972014-04-24 12:51:35 -0700731
732 int extra;
733
734 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800735 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736 mBottomPadding = bottom - below;
737 }
738
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800739 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 below = bottom;
741 }
742 }
743
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744
Raph Leviend97b0972014-04-24 12:51:35 -0700745 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800746 double ex = (below - above) * (spacingmult - 1) + spacingadd;
747 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800748 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800749 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800750 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800751 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800752 } else {
753 extra = 0;
754 }
755
756 lines[off + START] = start;
757 lines[off + TOP] = v;
758 lines[off + DESCENT] = below + extra;
759
760 v += (below - above) + extra;
761 lines[off + mColumns + START] = end;
762 lines[off + mColumns + TOP] = v;
763
Raph Levien26d443a2015-03-30 14:18:32 -0700764 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
765 // one bit for start field
766 lines[off + TAB] |= flags & TAB_MASK;
767 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800768
Doug Felt9f7a4442010-03-01 12:45:56 -0800769 lines[off + DIR] |= dir << DIR_SHIFT;
770 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
771 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700772 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800773 // RTL paragraph. Make sure easy is false if this is the case.
774 if (easy) {
775 mLineDirections[j] = linedirs;
776 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800777 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
778 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800779 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700781 if (ellipsize != null) {
782 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
783 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700784 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700785
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800786 boolean doEllipsis =
787 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700788 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
789 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
790 ellipsize == TextUtils.TruncateAt.END);
791 if (doEllipsis) {
792 calculateEllipsis(start, end, widths, widthStart,
793 ellipsisWidth, ellipsize, j,
794 textWidth, paint, forceEllipsis);
795 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 }
797
798 mLineCount++;
799 return v;
800 }
801
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800802 private void calculateEllipsis(int lineStart, int lineEnd,
803 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700805 int line, float textWidth, TextPaint paint,
806 boolean forceEllipsis) {
807 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 // Everything fits!
809 mLines[mColumns * line + ELLIPSIS_START] = 0;
810 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
811 return;
812 }
813
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700814 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700815 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000816 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700817 int ellipsisStart = 0;
818 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800819 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800820
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700821 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700823 if (mMaximumVisibleLineCount == 1) {
824 float sum = 0;
825 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +0900827 for (i = len; i > 0; i--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700828 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700830 if (w + sum + ellipsisWidth > avail) {
831 break;
832 }
833
834 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800835 }
836
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700837 ellipsisStart = 0;
838 ellipsisCount = i;
839 } else {
840 if (Log.isLoggable(TAG, Log.WARN)) {
841 Log.w(TAG, "Start Ellipsis only supported with one line");
842 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800843 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700844 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
845 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 float sum = 0;
847 int i;
848
849 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800850 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800852 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 break;
854 }
855
856 sum += w;
857 }
858
859 ellipsisStart = i;
860 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700861 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
862 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700863 ellipsisCount = 1;
864 }
865 } else {
866 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
867 if (mMaximumVisibleLineCount == 1) {
868 float lsum = 0, rsum = 0;
869 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700871 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800872 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700873 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700875 if (w + rsum > ravail) {
876 break;
877 }
878
879 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 }
881
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700882 float lavail = avail - ellipsisWidth - rsum;
883 for (left = 0; left < right; left++) {
884 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700886 if (w + lsum > lavail) {
887 break;
888 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700890 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 }
892
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700893 ellipsisStart = left;
894 ellipsisCount = right - left;
895 } else {
896 if (Log.isLoggable(TAG, Log.WARN)) {
897 Log.w(TAG, "Middle Ellipsis only supported with one line");
898 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900 }
901
902 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
903 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
904 }
905
Doug Felte8e45f22010-03-29 14:58:40 -0700906 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 // rather than relying on member functions.
908 // The logic mirrors that of Layout.getLineForVertical
909 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800910 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 public int getLineForVertical(int vertical) {
912 int high = mLineCount;
913 int low = -1;
914 int guess;
915 int[] lines = mLines;
916 while (high - low > 1) {
917 guess = (high + low) >> 1;
918 if (lines[mColumns * guess + TOP] > vertical){
919 high = guess;
920 } else {
921 low = guess;
922 }
923 }
924 if (low < 0) {
925 return 0;
926 } else {
927 return low;
928 }
929 }
930
Gilles Debunne66111472010-11-19 11:04:37 -0800931 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 public int getLineCount() {
933 return mLineCount;
934 }
935
Gilles Debunne66111472010-11-19 11:04:37 -0800936 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800938 int top = mLines[mColumns * line + TOP];
939 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
940 line != mLineCount) {
941 top += getBottomPadding();
942 }
943 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 }
945
Gilles Debunne66111472010-11-19 11:04:37 -0800946 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800948 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800949 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800950 line != mLineCount) {
951 descent += getBottomPadding();
952 }
953 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 }
955
Gilles Debunne66111472010-11-19 11:04:37 -0800956 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 public int getLineStart(int line) {
958 return mLines[mColumns * line + START] & START_MASK;
959 }
960
Gilles Debunne66111472010-11-19 11:04:37 -0800961 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800962 public int getParagraphDirection(int line) {
963 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
964 }
965
Gilles Debunne66111472010-11-19 11:04:37 -0800966 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800967 public boolean getLineContainsTab(int line) {
968 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
969 }
970
Gilles Debunne66111472010-11-19 11:04:37 -0800971 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 public final Directions getLineDirections(int line) {
973 return mLineDirections[line];
974 }
975
Gilles Debunne66111472010-11-19 11:04:37 -0800976 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 public int getTopPadding() {
978 return mTopPadding;
979 }
980
Gilles Debunne66111472010-11-19 11:04:37 -0800981 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 public int getBottomPadding() {
983 return mBottomPadding;
984 }
985
Raph Levien26d443a2015-03-30 14:18:32 -0700986 /**
987 * @hide
988 */
989 @Override
990 public int getHyphen(int line) {
991 return mLines[mColumns * line + HYPHEN] & 0xff;
992 }
993
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 @Override
995 public int getEllipsisCount(int line) {
996 if (mColumns < COLUMNS_ELLIPSIZE) {
997 return 0;
998 }
999
1000 return mLines[mColumns * line + ELLIPSIS_COUNT];
1001 }
1002
1003 @Override
1004 public int getEllipsisStart(int line) {
1005 if (mColumns < COLUMNS_ELLIPSIZE) {
1006 return 0;
1007 }
1008
1009 return mLines[mColumns * line + ELLIPSIS_START];
1010 }
1011
1012 @Override
1013 public int getEllipsizedWidth() {
1014 return mEllipsizedWidth;
1015 }
1016
Raph Levien70616ec2015-03-04 10:41:30 -08001017 private static native long nNewBuilder();
1018 private static native void nFreeBuilder(long nativePtr);
1019 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001020
1021 /* package */ static native long nLoadHyphenator(String patternData);
1022
1023 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -08001024
Raph Leviene319d5a2015-04-14 23:51:07 -07001025 private static native void nSetIndents(long nativePtr, int[] indents);
1026
Raph Levienc94f7422015-03-06 19:19:48 -08001027 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1028 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1029 float firstWidth, int firstWidthLineCount, float restWidth,
1030 int[] variableTabStops, int defaultTabStop, int breakStrategy);
Raph Levien70616ec2015-03-04 10:41:30 -08001031
1032 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1033 long nativeTypeface, int start, int end, boolean isRtl);
1034
1035 private static native void nAddMeasuredRun(long nativePtr,
1036 int start, int end, float[] widths);
1037
1038 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1039
1040 private static native void nGetWidths(long nativePtr, float[] widths);
1041
Anish Athalyec8f9e622014-07-21 15:26:34 -07001042 // populates LineBreaks and returns the number of breaks found
1043 //
1044 // the arrays inside the LineBreaks objects are passed in as well
1045 // to reduce the number of JNI calls in the common case where the
1046 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001047 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001048 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001049
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 private int mLineCount;
1051 private int mTopPadding, mBottomPadding;
1052 private int mColumns;
1053 private int mEllipsizedWidth;
1054
Raph Levien26d443a2015-03-30 14:18:32 -07001055 private static final int COLUMNS_NORMAL = 4;
1056 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057 private static final int START = 0;
1058 private static final int DIR = START;
1059 private static final int TAB = START;
1060 private static final int TOP = 1;
1061 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001062 private static final int HYPHEN = 3;
1063 private static final int ELLIPSIS_START = 4;
1064 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065
1066 private int[] mLines;
1067 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001068 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001069
1070 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001071 private static final int DIR_SHIFT = 30;
1072 private static final int TAB_MASK = 0x20000000;
1073
Doug Feltc982f602010-05-25 11:51:40 -07001074 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001076 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001077
1078 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001079
Anish Athalyec8f9e622014-07-21 15:26:34 -07001080 // This is used to return three arrays from a single JNI call when
1081 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001082 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001083 private static final int INITIAL_SIZE = 16;
1084 public int[] breaks = new int[INITIAL_SIZE];
1085 public float[] widths = new float[INITIAL_SIZE];
Raph Levien26d443a2015-03-30 14:18:32 -07001086 public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
Anish Athalyec8f9e622014-07-21 15:26:34 -07001087 // breaks, widths, and flags should all have the same length
1088 }
1089
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090}