blob: 7828851a28f3553551cc9e352625e9114a801286 [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 Levien70616ec2015-03-04 10:41:30 -0800168 /**
169 * Measurement and break iteration is done in native code. The protocol for using
170 * the native code is as follows.
171 *
Raph Levien26d443a2015-03-30 14:18:32 -0700172 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
173 * stops, break strategy (and possibly other parameters in the future).
Raph Levienc94f7422015-03-06 19:19:48 -0800174 *
175 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800176 * - setLocale (this must be done at least for the first run, optional afterwards)
177 * - one of the following, depending on the type of run:
178 * + addStyleRun (a text run, to be measured in native code)
179 * + addMeasuredRun (a run already measured in Java, passed into native code)
180 * + addReplacementRun (a replacement run, width is given)
181 *
182 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
183 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
184 *
185 * After all paragraphs, call finish() to release expensive buffers.
186 */
187
188 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800189 if (!locale.equals(mLocale)) {
Raph Levien26d443a2015-03-30 14:18:32 -0700190 nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
Raph Levien4c1f12e2015-03-02 16:29:23 -0800191 mLocale = locale;
192 }
193 }
194
Raph Levien70616ec2015-03-04 10:41:30 -0800195 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
196 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
197 start, end, isRtl);
198 }
199
200 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
201 nAddMeasuredRun(mNativePtr, start, end, widths);
202 }
203
204 /* package */ void addReplacementRun(int start, int end, float width) {
205 nAddReplacementRun(mNativePtr, start, end, width);
206 }
207
Raph Leviend3ab6922015-03-02 14:30:53 -0800208 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700209 StaticLayout result = new StaticLayout(this);
210 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800211 return result;
212 }
213
Raph Levien4c1f12e2015-03-02 16:29:23 -0800214 @Override
215 protected void finalize() throws Throwable {
216 try {
217 nFreeBuilder(mNativePtr);
218 } finally {
219 super.finalize();
220 }
221 }
222
223 /* package */ long mNativePtr;
224
Raph Leviend3ab6922015-03-02 14:30:53 -0800225 CharSequence mText;
226 int mStart;
227 int mEnd;
228 TextPaint mPaint;
229 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700230 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800231 TextDirectionHeuristic mTextDir;
232 float mSpacingMult;
233 float mSpacingAdd;
234 boolean mIncludePad;
235 int mEllipsizedWidth;
236 TextUtils.TruncateAt mEllipsize;
237 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700238 int mBreakStrategy;
Raph Leviend3ab6922015-03-02 14:30:53 -0800239
240 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
241
242 // This will go away and be subsumed by native builder code
243 MeasuredText mMeasuredText;
244
Raph Levien4c1f12e2015-03-02 16:29:23 -0800245 Locale mLocale;
246
Raph Levien39b4db72015-03-25 13:18:20 -0700247 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800248 }
249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 public StaticLayout(CharSequence source, TextPaint paint,
251 int width,
252 Alignment align, float spacingmult, float spacingadd,
253 boolean includepad) {
254 this(source, 0, source.length(), paint, width, align,
255 spacingmult, spacingadd, includepad);
256 }
257
Doug Feltcb3791202011-07-07 11:57:48 -0700258 /**
259 * @hide
260 */
261 public StaticLayout(CharSequence source, TextPaint paint,
262 int width, Alignment align, TextDirectionHeuristic textDir,
263 float spacingmult, float spacingadd,
264 boolean includepad) {
265 this(source, 0, source.length(), paint, width, align, textDir,
266 spacingmult, spacingadd, includepad);
267 }
268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 public StaticLayout(CharSequence source, int bufstart, int bufend,
270 TextPaint paint, int outerwidth,
271 Alignment align,
272 float spacingmult, float spacingadd,
273 boolean includepad) {
274 this(source, bufstart, bufend, paint, outerwidth, align,
275 spacingmult, spacingadd, includepad, null, 0);
276 }
277
Doug Feltcb3791202011-07-07 11:57:48 -0700278 /**
279 * @hide
280 */
281 public StaticLayout(CharSequence source, int bufstart, int bufend,
282 TextPaint paint, int outerwidth,
283 Alignment align, TextDirectionHeuristic textDir,
284 float spacingmult, float spacingadd,
285 boolean includepad) {
286 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700287 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700288}
289
290 public StaticLayout(CharSequence source, int bufstart, int bufend,
291 TextPaint paint, int outerwidth,
292 Alignment align,
293 float spacingmult, float spacingadd,
294 boolean includepad,
295 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
296 this(source, bufstart, bufend, paint, outerwidth, align,
297 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700298 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700299 }
300
301 /**
302 * @hide
303 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 public StaticLayout(CharSequence source, int bufstart, int bufend,
305 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700306 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 float spacingmult, float spacingadd,
308 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700309 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700311 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 : (source instanceof Spanned)
313 ? new SpannedEllipsizer(source)
314 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700315 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316
Raph Levien39b4db72015-03-25 13:18:20 -0700317 Builder b = Builder.obtain(source, bufstart, bufend, outerwidth)
Raph Leviend3ab6922015-03-02 14:30:53 -0800318 .setPaint(paint)
Raph Levien39b4db72015-03-25 13:18:20 -0700319 .setAlignment(align)
Raph Leviend3ab6922015-03-02 14:30:53 -0800320 .setTextDir(textDir)
321 .setSpacingMult(spacingmult)
322 .setSpacingAdd(spacingadd)
323 .setIncludePad(includepad)
324 .setEllipsizedWidth(ellipsizedWidth)
325 .setEllipsize(ellipsize)
326 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 /*
328 * This is annoying, but we can't refer to the layout until
329 * superclass construction is finished, and the superclass
330 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700331 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 * This will break if the superclass constructor ever actually
333 * cares about the content instead of just holding the reference.
334 */
335 if (ellipsize != null) {
336 Ellipsizer e = (Ellipsizer) getText();
337
338 e.mLayout = this;
339 e.mWidth = ellipsizedWidth;
340 e.mMethod = ellipsize;
341 mEllipsizedWidth = ellipsizedWidth;
342
343 mColumns = COLUMNS_ELLIPSIZE;
344 } else {
345 mColumns = COLUMNS_NORMAL;
346 mEllipsizedWidth = outerwidth;
347 }
348
Adam Lesinski776abc22014-03-07 11:30:59 -0500349 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
350 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700351 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352
Raph Levien70616ec2015-03-04 10:41:30 -0800353 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700354
Raph Leviend3ab6922015-03-02 14:30:53 -0800355 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 }
357
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700358 /* package */ StaticLayout(CharSequence text) {
359 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360
361 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500362 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
363 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 }
365
Raph Levien39b4db72015-03-25 13:18:20 -0700366 private StaticLayout(Builder b) {
367 super((b.mEllipsize == null)
368 ? b.mText
369 : (b.mText instanceof Spanned)
370 ? new SpannedEllipsizer(b.mText)
371 : new Ellipsizer(b.mText),
372 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
373
374 if (b.mEllipsize != null) {
375 Ellipsizer e = (Ellipsizer) getText();
376
377 e.mLayout = this;
378 e.mWidth = b.mEllipsizedWidth;
379 e.mMethod = b.mEllipsize;
380 mEllipsizedWidth = b.mEllipsizedWidth;
381
382 mColumns = COLUMNS_ELLIPSIZE;
383 } else {
384 mColumns = COLUMNS_NORMAL;
385 mEllipsizedWidth = b.mWidth;
386 }
387
388 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
389 mLines = new int[mLineDirections.length];
390 mMaximumVisibleLineCount = b.mMaxLines;
391
392 generate(b, b.mIncludePad, b.mIncludePad);
393 }
394
Raph Leviend3ab6922015-03-02 14:30:53 -0800395 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
396 CharSequence source = b.mText;
397 int bufStart = b.mStart;
398 int bufEnd = b.mEnd;
399 TextPaint paint = b.mPaint;
400 int outerWidth = b.mWidth;
401 TextDirectionHeuristic textDir = b.mTextDir;
402 float spacingmult = b.mSpacingMult;
403 float spacingadd = b.mSpacingAdd;
404 float ellipsizedWidth = b.mEllipsizedWidth;
405 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800406 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700407 // store span end locations
408 int[] spanEndCache = new int[4];
409 // store fontMetrics per span range
410 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
411 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800412 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 mLineCount = 0;
415
416 int v = 0;
417 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
418
Raph Leviend3ab6922015-03-02 14:30:53 -0800419 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800420 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421
Raph Leviend3ab6922015-03-02 14:30:53 -0800422 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 if (source instanceof Spanned)
426 spanned = (Spanned) source;
427
Doug Felte8e45f22010-03-29 14:58:40 -0700428 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800429 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
430 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700431 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800432 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 else
Doug Felte8e45f22010-03-29 14:58:40 -0700434 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435
Anish Athalyec8f9e622014-07-21 15:26:34 -0700436 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800437 int firstWidth = outerWidth;
438 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800440 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441
442 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700443 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700444 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700446 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800447 firstWidth -= sp[i].getLeadingMargin(true);
448 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700449
Doug Feltc982f602010-05-25 11:51:40 -0700450 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700451 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700452 if (lms instanceof LeadingMarginSpan2) {
453 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700454 firstWidthLineCount = Math.max(firstWidthLineCount,
455 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700456 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 }
458
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800459 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800461 if (chooseHt.length != 0) {
462 if (chooseHtv == null ||
463 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500464 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 }
466
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800467 for (int i = 0; i < chooseHt.length; i++) {
468 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469
Doug Felte8e45f22010-03-29 14:58:40 -0700470 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 // starts in this layout, before the
472 // current paragraph
473
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800474 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 } else {
476 // starts in this paragraph
477
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800478 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 }
480 }
481 }
482 }
483
Raph Levien70616ec2015-03-04 10:41:30 -0800484 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700485 char[] chs = measured.mChars;
486 float[] widths = measured.mWidths;
487 byte[] chdirs = measured.mLevels;
488 int dir = measured.mDir;
489 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800490
491 // tab stop locations
492 int[] variableTabStops = null;
493 if (spanned != null) {
494 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
495 paraEnd, TabStopSpan.class);
496 if (spans.length > 0) {
497 int[] stops = new int[spans.length];
498 for (int i = 0; i < spans.length; i++) {
499 stops[i] = spans[i].getTabStop();
500 }
501 Arrays.sort(stops, 0, stops.length);
502 variableTabStops = stops;
503 }
504 }
505
Raph Levienc94f7422015-03-06 19:19:48 -0800506 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
507 firstWidth, firstWidthLineCount, restWidth,
Raph Levien39b4db72015-03-25 13:18:20 -0700508 variableTabStops, TAB_INCREMENT, b.mBreakStrategy);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509
Anish Athalyec8f9e622014-07-21 15:26:34 -0700510 // measurement has to be done before performing line breaking
511 // but we don't want to recompute fontmetrics or span ranges the
512 // second time, so we cache those and then use those stored values
513 int fmCacheCount = 0;
514 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700515 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700516 if (fmCacheCount * 4 >= fmCache.length) {
517 int[] grow = new int[fmCacheCount * 4 * 2];
518 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
519 fmCache = grow;
520 }
521
522 if (spanEndCacheCount >= spanEndCache.length) {
523 int[] grow = new int[spanEndCacheCount * 2];
524 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
525 spanEndCache = grow;
526 }
Doug Felte8e45f22010-03-29 14:58:40 -0700527
Gilles Debunnecd943a72012-06-07 17:54:47 -0700528 if (spanned == null) {
529 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700530 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700531 measured.addStyleRun(paint, spanLen, fm);
532 } else {
533 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
534 MetricAffectingSpan.class);
535 int spanLen = spanEnd - spanStart;
536 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700537 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700538 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
539 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700540 }
541
Anish Athalyec8f9e622014-07-21 15:26:34 -0700542 // the order of storage here (top, bottom, ascent, descent) has to match the code below
543 // where these values are retrieved
544 fmCache[fmCacheCount * 4 + 0] = fm.top;
545 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
546 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
547 fmCache[fmCacheCount * 4 + 3] = fm.descent;
548 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549
Anish Athalyec8f9e622014-07-21 15:26:34 -0700550 spanEndCache[spanEndCacheCount] = spanEnd;
551 spanEndCacheCount++;
552 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553
Raph Levien70616ec2015-03-04 10:41:30 -0800554 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800555 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
556 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557
Anish Athalyec8f9e622014-07-21 15:26:34 -0700558 int[] breaks = lineBreaks.breaks;
559 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700560 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700561
Anish Athalyec8f9e622014-07-21 15:26:34 -0700562 // here is the offset of the starting character of the line we are currently measuring
563 int here = paraStart;
564
565 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
566 int fmCacheIndex = 0;
567 int spanEndCacheIndex = 0;
568 int breakIndex = 0;
569 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
570 // retrieve end of span
571 spanEnd = spanEndCache[spanEndCacheIndex++];
572
573 // retrieve cached metrics, order matches above
574 fm.top = fmCache[fmCacheIndex * 4 + 0];
575 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
576 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
577 fm.descent = fmCache[fmCacheIndex * 4 + 3];
578 fmCacheIndex++;
579
580 if (fm.top < fmTop) {
581 fmTop = fm.top;
582 }
583 if (fm.ascent < fmAscent) {
584 fmAscent = fm.ascent;
585 }
586 if (fm.descent > fmDescent) {
587 fmDescent = fm.descent;
588 }
589 if (fm.bottom > fmBottom) {
590 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 }
592
Anish Athalyec8f9e622014-07-21 15:26:34 -0700593 // skip breaks ending before current span range
594 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
595 breakIndex++;
596 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800597
Anish Athalyec8f9e622014-07-21 15:26:34 -0700598 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
599 int endPos = paraStart + breaks[breakIndex];
600
Raph Levience4155a2015-03-11 11:02:33 -0700601 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800602
Anish Athalyec8f9e622014-07-21 15:26:34 -0700603 v = out(source, here, endPos,
604 fmAscent, fmDescent, fmTop, fmBottom,
605 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
606 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
607 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800608 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700609
610 if (endPos < spanEnd) {
611 // preserve metrics for current span
612 fmTop = fm.top;
613 fmBottom = fm.bottom;
614 fmAscent = fm.ascent;
615 fmDescent = fm.descent;
616 } else {
617 fmTop = fmBottom = fmAscent = fmDescent = 0;
618 }
619
620 here = endPos;
621 breakIndex++;
622
623 if (mLineCount >= mMaximumVisibleLineCount) {
624 return;
625 }
626 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 }
628
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800629 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 break;
631 }
632
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700633 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700634 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800635 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636
Raph Levien70616ec2015-03-04 10:41:30 -0800637 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700638
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 paint.getFontMetricsInt(fm);
640
641 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800642 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 fm.top, fm.bottom,
644 v,
645 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700646 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700647 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700648 includepad, trackpad, null,
649 null, bufStart, ellipsize,
650 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 }
652 }
653
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 private int out(CharSequence text, int start, int end,
655 int above, int below, int top, int bottom, int v,
656 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800657 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700658 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700659 boolean needMultiply, byte[] chdirs, int dir,
660 boolean easy, int bufEnd, boolean includePad,
661 boolean trackPad, char[] chs,
662 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
663 float ellipsisWidth, float textWidth,
664 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 int j = mLineCount;
666 int off = j * mColumns;
667 int want = off + mColumns + TOP;
668 int[] lines = mLines;
669
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500671 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
672 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 System.arraycopy(mLineDirections, 0, grow2, 0,
674 mLineDirections.length);
675 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500676
677 int[] grow = new int[grow2.length];
678 System.arraycopy(lines, 0, grow, 0, lines.length);
679 mLines = grow;
680 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681 }
682
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800683 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 fm.ascent = above;
685 fm.descent = below;
686 fm.top = top;
687 fm.bottom = bottom;
688
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800689 for (int i = 0; i < chooseHt.length; i++) {
690 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
691 ((LineHeightSpan.WithDensity) chooseHt[i]).
692 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700693
694 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800695 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700696 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 }
698
699 above = fm.ascent;
700 below = fm.descent;
701 top = fm.top;
702 bottom = fm.bottom;
703 }
704
Raph Leviend97b0972014-04-24 12:51:35 -0700705 boolean firstLine = (j == 0);
706 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
707 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
708
709 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800710 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 mTopPadding = top - above;
712 }
713
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800714 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800715 above = top;
716 }
717 }
Raph Leviend97b0972014-04-24 12:51:35 -0700718
719 int extra;
720
721 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800722 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723 mBottomPadding = bottom - below;
724 }
725
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800726 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 below = bottom;
728 }
729 }
730
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731
Raph Leviend97b0972014-04-24 12:51:35 -0700732 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800733 double ex = (below - above) * (spacingmult - 1) + spacingadd;
734 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800735 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800736 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800737 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800738 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739 } else {
740 extra = 0;
741 }
742
743 lines[off + START] = start;
744 lines[off + TOP] = v;
745 lines[off + DESCENT] = below + extra;
746
747 v += (below - above) + extra;
748 lines[off + mColumns + START] = end;
749 lines[off + mColumns + TOP] = v;
750
Raph Levien26d443a2015-03-30 14:18:32 -0700751 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
752 // one bit for start field
753 lines[off + TAB] |= flags & TAB_MASK;
754 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755
Doug Felt9f7a4442010-03-01 12:45:56 -0800756 lines[off + DIR] |= dir << DIR_SHIFT;
757 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
758 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700759 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800760 // RTL paragraph. Make sure easy is false if this is the case.
761 if (easy) {
762 mLineDirections[j] = linedirs;
763 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800764 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
765 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800766 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700768 if (ellipsize != null) {
769 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
770 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700771 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700772
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800773 boolean doEllipsis =
774 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700775 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
776 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
777 ellipsize == TextUtils.TruncateAt.END);
778 if (doEllipsis) {
779 calculateEllipsis(start, end, widths, widthStart,
780 ellipsisWidth, ellipsize, j,
781 textWidth, paint, forceEllipsis);
782 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 }
784
785 mLineCount++;
786 return v;
787 }
788
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800789 private void calculateEllipsis(int lineStart, int lineEnd,
790 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700792 int line, float textWidth, TextPaint paint,
793 boolean forceEllipsis) {
794 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 // Everything fits!
796 mLines[mColumns * line + ELLIPSIS_START] = 0;
797 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
798 return;
799 }
800
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700801 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700802 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000803 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700804 int ellipsisStart = 0;
805 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800806 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700808 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700810 if (mMaximumVisibleLineCount == 1) {
811 float sum = 0;
812 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +0900814 for (i = len; i > 0; i--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700815 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800816
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700817 if (w + sum + ellipsisWidth > avail) {
818 break;
819 }
820
821 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 }
823
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700824 ellipsisStart = 0;
825 ellipsisCount = i;
826 } else {
827 if (Log.isLoggable(TAG, Log.WARN)) {
828 Log.w(TAG, "Start Ellipsis only supported with one line");
829 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700831 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
832 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833 float sum = 0;
834 int i;
835
836 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800837 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800839 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 break;
841 }
842
843 sum += w;
844 }
845
846 ellipsisStart = i;
847 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700848 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
849 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700850 ellipsisCount = 1;
851 }
852 } else {
853 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
854 if (mMaximumVisibleLineCount == 1) {
855 float lsum = 0, rsum = 0;
856 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700858 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800859 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700860 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700862 if (w + rsum > ravail) {
863 break;
864 }
865
866 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 }
868
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700869 float lavail = avail - ellipsisWidth - rsum;
870 for (left = 0; left < right; left++) {
871 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800872
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700873 if (w + lsum > lavail) {
874 break;
875 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700877 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 }
879
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700880 ellipsisStart = left;
881 ellipsisCount = right - left;
882 } else {
883 if (Log.isLoggable(TAG, Log.WARN)) {
884 Log.w(TAG, "Middle Ellipsis only supported with one line");
885 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800886 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 }
888
889 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
890 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
891 }
892
Doug Felte8e45f22010-03-29 14:58:40 -0700893 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 // rather than relying on member functions.
895 // The logic mirrors that of Layout.getLineForVertical
896 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800897 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 public int getLineForVertical(int vertical) {
899 int high = mLineCount;
900 int low = -1;
901 int guess;
902 int[] lines = mLines;
903 while (high - low > 1) {
904 guess = (high + low) >> 1;
905 if (lines[mColumns * guess + TOP] > vertical){
906 high = guess;
907 } else {
908 low = guess;
909 }
910 }
911 if (low < 0) {
912 return 0;
913 } else {
914 return low;
915 }
916 }
917
Gilles Debunne66111472010-11-19 11:04:37 -0800918 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 public int getLineCount() {
920 return mLineCount;
921 }
922
Gilles Debunne66111472010-11-19 11:04:37 -0800923 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800925 int top = mLines[mColumns * line + TOP];
926 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
927 line != mLineCount) {
928 top += getBottomPadding();
929 }
930 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 }
932
Gilles Debunne66111472010-11-19 11:04:37 -0800933 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800935 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800936 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800937 line != mLineCount) {
938 descent += getBottomPadding();
939 }
940 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 }
942
Gilles Debunne66111472010-11-19 11:04:37 -0800943 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 public int getLineStart(int line) {
945 return mLines[mColumns * line + START] & START_MASK;
946 }
947
Gilles Debunne66111472010-11-19 11:04:37 -0800948 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 public int getParagraphDirection(int line) {
950 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
951 }
952
Gilles Debunne66111472010-11-19 11:04:37 -0800953 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 public boolean getLineContainsTab(int line) {
955 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
956 }
957
Gilles Debunne66111472010-11-19 11:04:37 -0800958 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959 public final Directions getLineDirections(int line) {
960 return mLineDirections[line];
961 }
962
Gilles Debunne66111472010-11-19 11:04:37 -0800963 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 public int getTopPadding() {
965 return mTopPadding;
966 }
967
Gilles Debunne66111472010-11-19 11:04:37 -0800968 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 public int getBottomPadding() {
970 return mBottomPadding;
971 }
972
Raph Levien26d443a2015-03-30 14:18:32 -0700973 /**
974 * @hide
975 */
976 @Override
977 public int getHyphen(int line) {
978 return mLines[mColumns * line + HYPHEN] & 0xff;
979 }
980
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800981 @Override
982 public int getEllipsisCount(int line) {
983 if (mColumns < COLUMNS_ELLIPSIZE) {
984 return 0;
985 }
986
987 return mLines[mColumns * line + ELLIPSIS_COUNT];
988 }
989
990 @Override
991 public int getEllipsisStart(int line) {
992 if (mColumns < COLUMNS_ELLIPSIZE) {
993 return 0;
994 }
995
996 return mLines[mColumns * line + ELLIPSIS_START];
997 }
998
999 @Override
1000 public int getEllipsizedWidth() {
1001 return mEllipsizedWidth;
1002 }
1003
Raph Levien70616ec2015-03-04 10:41:30 -08001004 private static native long nNewBuilder();
1005 private static native void nFreeBuilder(long nativePtr);
1006 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001007
1008 /* package */ static native long nLoadHyphenator(String patternData);
1009
1010 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -08001011
Raph Levienc94f7422015-03-06 19:19:48 -08001012 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1013 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1014 float firstWidth, int firstWidthLineCount, float restWidth,
1015 int[] variableTabStops, int defaultTabStop, int breakStrategy);
Raph Levien70616ec2015-03-04 10:41:30 -08001016
1017 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1018 long nativeTypeface, int start, int end, boolean isRtl);
1019
1020 private static native void nAddMeasuredRun(long nativePtr,
1021 int start, int end, float[] widths);
1022
1023 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1024
1025 private static native void nGetWidths(long nativePtr, float[] widths);
1026
Anish Athalyec8f9e622014-07-21 15:26:34 -07001027 // populates LineBreaks and returns the number of breaks found
1028 //
1029 // the arrays inside the LineBreaks objects are passed in as well
1030 // to reduce the number of JNI calls in the common case where the
1031 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001032 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001033 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001034
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035 private int mLineCount;
1036 private int mTopPadding, mBottomPadding;
1037 private int mColumns;
1038 private int mEllipsizedWidth;
1039
Raph Levien26d443a2015-03-30 14:18:32 -07001040 private static final int COLUMNS_NORMAL = 4;
1041 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 private static final int START = 0;
1043 private static final int DIR = START;
1044 private static final int TAB = START;
1045 private static final int TOP = 1;
1046 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001047 private static final int HYPHEN = 3;
1048 private static final int ELLIPSIS_START = 4;
1049 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050
1051 private int[] mLines;
1052 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001053 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001054
1055 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056 private static final int DIR_SHIFT = 30;
1057 private static final int TAB_MASK = 0x20000000;
1058
Doug Feltc982f602010-05-25 11:51:40 -07001059 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001061 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001062
1063 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001064
Anish Athalyec8f9e622014-07-21 15:26:34 -07001065 // This is used to return three arrays from a single JNI call when
1066 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001067 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001068 private static final int INITIAL_SIZE = 16;
1069 public int[] breaks = new int[INITIAL_SIZE];
1070 public float[] widths = new float[INITIAL_SIZE];
Raph Levien26d443a2015-03-30 14:18:32 -07001071 public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
Anish Athalyec8f9e622014-07-21 15:26:34 -07001072 // breaks, widths, and flags should all have the same length
1073 }
1074
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075}