blob: aafcf44a73fcf00f9f9195f67830467670bb41f7 [file] [log] [blame]
Seigo Nonaka9d3bd082018-01-11 10:02:12 -08001/*
2 * Copyright (C) 2010 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.annotation.FloatRange;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.graphics.Paint;
24import android.text.AutoGrowArray.ByteArray;
25import android.text.AutoGrowArray.FloatArray;
26import android.text.AutoGrowArray.IntArray;
27import android.text.Layout.Directions;
28import android.text.style.MetricAffectingSpan;
29import android.text.style.ReplacementSpan;
30import android.util.Pools.SynchronizedPool;
31
32import dalvik.annotation.optimization.CriticalNative;
33
34import libcore.util.NativeAllocationRegistry;
35
36import java.util.Arrays;
37
38/**
39 * MeasuredParagraph provides text information for rendering purpose.
40 *
41 * The first motivation of this class is identify the text directions and retrieving individual
42 * character widths. However retrieving character widths is slower than identifying text directions.
43 * Thus, this class provides several builder methods for specific purposes.
44 *
45 * - buildForBidi:
46 * Compute only text directions.
47 * - buildForMeasurement:
48 * Compute text direction and all character widths.
49 * - buildForStaticLayout:
50 * This is bit special. StaticLayout also needs to know text direction and character widths for
51 * line breaking, but all things are done in native code. Similarly, text measurement is done
52 * in native code. So instead of storing result to Java array, this keeps the result in native
53 * code since there is no good reason to move the results to Java layer.
54 *
55 * In addition to the character widths, some additional information is computed for each purposes,
56 * e.g. whole text length for measurement or font metrics for static layout.
57 *
58 * MeasuredParagraph is NOT a thread safe object.
59 * @hide
60 */
61public class MeasuredParagraph {
62 private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
63
64 private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
65 MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
66
67 private MeasuredParagraph() {} // Use build static functions instead.
68
69 private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1);
70
71 private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead.
72 final MeasuredParagraph mt = sPool.acquire();
73 return mt != null ? mt : new MeasuredParagraph();
74 }
75
76 /**
77 * Recycle the MeasuredParagraph.
78 *
79 * Do not call any methods after you call this method.
80 */
81 public void recycle() {
82 release();
83 sPool.release(this);
84 }
85
86 // The casted original text.
87 //
88 // This may be null if the passed text is not a Spanned.
89 private @Nullable Spanned mSpanned;
90
91 // The start offset of the target range in the original text (mSpanned);
92 private @IntRange(from = 0) int mTextStart;
93
94 // The length of the target range in the original text.
95 private @IntRange(from = 0) int mTextLength;
96
97 // The copied character buffer for measuring text.
98 //
99 // The length of this array is mTextLength.
100 private @Nullable char[] mCopiedBuffer;
101
102 // The whole paragraph direction.
103 private @Layout.Direction int mParaDir;
104
105 // True if the text is LTR direction and doesn't contain any bidi characters.
106 private boolean mLtrWithoutBidi;
107
108 // The bidi level for individual characters.
109 //
110 // This is empty if mLtrWithoutBidi is true.
111 private @NonNull ByteArray mLevels = new ByteArray();
112
113 // The whole width of the text.
114 // See getWholeWidth comments.
115 private @FloatRange(from = 0.0f) float mWholeWidth;
116
117 // Individual characters' widths.
118 // See getWidths comments.
119 private @Nullable FloatArray mWidths = new FloatArray();
120
121 // The span end positions.
122 // See getSpanEndCache comments.
123 private @Nullable IntArray mSpanEndCache = new IntArray(4);
124
125 // The font metrics.
126 // See getFontMetrics comments.
127 private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
128
129 // The native MeasuredParagraph.
130 // See getNativePtr comments.
131 // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
132 private /* Maybe Zero */ long mNativePtr = 0;
133 private @Nullable Runnable mNativeObjectCleaner;
134
135 // Associate the native object to this Java object.
136 private void bindNativeObject(/* Non Zero*/ long nativePtr) {
137 mNativePtr = nativePtr;
138 mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
139 }
140
141 // Decouple the native object from this Java object and release the native object.
142 private void unbindNativeObject() {
143 if (mNativePtr != 0) {
144 mNativeObjectCleaner.run();
145 mNativePtr = 0;
146 }
147 }
148
149 // Following two objects are for avoiding object allocation.
150 private @NonNull TextPaint mCachedPaint = new TextPaint();
151 private @Nullable Paint.FontMetricsInt mCachedFm;
152
153 /**
154 * Releases internal buffers.
155 */
156 public void release() {
157 reset();
158 mLevels.clearWithReleasingLargeArray();
159 mWidths.clearWithReleasingLargeArray();
160 mFontMetrics.clearWithReleasingLargeArray();
161 mSpanEndCache.clearWithReleasingLargeArray();
162 }
163
164 /**
165 * Resets the internal state for starting new text.
166 */
167 private void reset() {
168 mSpanned = null;
169 mCopiedBuffer = null;
170 mWholeWidth = 0;
171 mLevels.clear();
172 mWidths.clear();
173 mFontMetrics.clear();
174 mSpanEndCache.clear();
175 unbindNativeObject();
176 }
177
178 /**
Seigo Nonaka783f9612018-01-20 12:11:13 -0800179 * Returns the length of the paragraph.
180 *
181 * This is always available.
182 */
183 public int getTextLength() {
184 return mTextLength;
185 }
186
187 /**
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800188 * Returns the characters to be measured.
189 *
190 * This is always available.
191 */
192 public @NonNull char[] getChars() {
193 return mCopiedBuffer;
194 }
195
196 /**
197 * Returns the paragraph direction.
198 *
199 * This is always available.
200 */
201 public @Layout.Direction int getParagraphDir() {
202 return mParaDir;
203 }
204
205 /**
206 * Returns the directions.
207 *
208 * This is always available.
209 */
210 public Directions getDirections(@IntRange(from = 0) int start, // inclusive
211 @IntRange(from = 0) int end) { // exclusive
212 if (mLtrWithoutBidi) {
213 return Layout.DIRS_ALL_LEFT_TO_RIGHT;
214 }
215
216 final int length = end - start;
217 return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
218 length);
219 }
220
221 /**
222 * Returns the whole text width.
223 *
Seigo Nonaka783f9612018-01-20 12:11:13 -0800224 * This is available only if the MeasuredParagraph is computed with buildForMeasurement.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800225 * Returns 0 in other cases.
226 */
227 public @FloatRange(from = 0.0f) float getWholeWidth() {
228 return mWholeWidth;
229 }
230
231 /**
232 * Returns the individual character's width.
233 *
Seigo Nonaka783f9612018-01-20 12:11:13 -0800234 * This is available only if the MeasuredParagraph is computed with buildForMeasurement.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800235 * Returns empty array in other cases.
236 */
237 public @NonNull FloatArray getWidths() {
238 return mWidths;
239 }
240
241 /**
242 * Returns the MetricsAffectingSpan end indices.
243 *
244 * If the input text is not a spanned string, this has one value that is the length of the text.
245 *
Seigo Nonaka783f9612018-01-20 12:11:13 -0800246 * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800247 * Returns empty array in other cases.
248 */
249 public @NonNull IntArray getSpanEndCache() {
250 return mSpanEndCache;
251 }
252
253 /**
254 * Returns the int array which holds FontMetrics.
255 *
256 * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
257 *
Seigo Nonaka783f9612018-01-20 12:11:13 -0800258 * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800259 * Returns empty array in other cases.
260 */
261 public @NonNull IntArray getFontMetrics() {
262 return mFontMetrics;
263 }
264
265 /**
266 * Returns the native ptr of the MeasuredParagraph.
267 *
Seigo Nonaka783f9612018-01-20 12:11:13 -0800268 * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800269 * Returns 0 in other cases.
270 */
271 public /* Maybe Zero */ long getNativePtr() {
272 return mNativePtr;
273 }
274
275 /**
Seigo Nonaka783f9612018-01-20 12:11:13 -0800276 * Returns the width of the given range.
277 *
278 * This is not available if the MeasuredParagraph is computed with buildForBidi.
279 * Returns 0 if the MeasuredParagraph is computed with buildForBidi.
280 *
281 * @param start the inclusive start offset of the target region in the text
282 * @param end the exclusive end offset of the target region in the text
283 */
284 public float getWidth(int start, int end) {
285 if (mNativePtr == 0) {
286 // We have result in Java.
287 final float[] widths = mWidths.getRawArray();
288 float r = 0.0f;
289 for (int i = start; i < end; ++i) {
290 r += widths[i];
291 }
292 return r;
293 } else {
294 // We have result in native.
295 return nGetWidth(mNativePtr, start, end);
296 }
297 }
298
299 /**
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800300 * Generates new MeasuredParagraph for Bidi computation.
301 *
302 * If recycle is null, this returns new instance. If recycle is not null, this fills computed
303 * result to recycle and returns recycle.
304 *
305 * @param text the character sequence to be measured
306 * @param start the inclusive start offset of the target region in the text
307 * @param end the exclusive end offset of the target region in the text
308 * @param textDir the text direction
309 * @param recycle pass existing MeasuredParagraph if you want to recycle it.
310 *
311 * @return measured text
312 */
313 public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text,
314 @IntRange(from = 0) int start,
315 @IntRange(from = 0) int end,
316 @NonNull TextDirectionHeuristic textDir,
317 @Nullable MeasuredParagraph recycle) {
318 final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
319 mt.resetAndAnalyzeBidi(text, start, end, textDir);
320 return mt;
321 }
322
323 /**
324 * Generates new MeasuredParagraph for measuring texts.
325 *
326 * If recycle is null, this returns new instance. If recycle is not null, this fills computed
327 * result to recycle and returns recycle.
328 *
329 * @param paint the paint to be used for rendering the text.
330 * @param text the character sequence to be measured
331 * @param start the inclusive start offset of the target region in the text
332 * @param end the exclusive end offset of the target region in the text
333 * @param textDir the text direction
334 * @param recycle pass existing MeasuredParagraph if you want to recycle it.
335 *
336 * @return measured text
337 */
338 public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint,
339 @NonNull CharSequence text,
340 @IntRange(from = 0) int start,
341 @IntRange(from = 0) int end,
342 @NonNull TextDirectionHeuristic textDir,
343 @Nullable MeasuredParagraph recycle) {
344 final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
345 mt.resetAndAnalyzeBidi(text, start, end, textDir);
346
347 mt.mWidths.resize(mt.mTextLength);
348 if (mt.mTextLength == 0) {
349 return mt;
350 }
351
352 if (mt.mSpanned == null) {
353 // No style change by MetricsAffectingSpan. Just measure all text.
354 mt.applyMetricsAffectingSpan(
355 paint, null /* spans */, start, end, 0 /* native static layout ptr */);
356 } else {
357 // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
358 int spanEnd;
359 for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
360 spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
361 MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
362 MetricAffectingSpan.class);
363 spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
364 mt.applyMetricsAffectingSpan(
365 paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
366 }
367 }
368 return mt;
369 }
370
371 /**
372 * Generates new MeasuredParagraph for StaticLayout.
373 *
374 * If recycle is null, this returns new instance. If recycle is not null, this fills computed
375 * result to recycle and returns recycle.
376 *
377 * @param paint the paint to be used for rendering the text.
378 * @param text the character sequence to be measured
379 * @param start the inclusive start offset of the target region in the text
380 * @param end the exclusive end offset of the target region in the text
381 * @param textDir the text direction
382 * @param recycle pass existing MeasuredParagraph if you want to recycle it.
383 *
384 * @return measured text
385 */
386 public static @NonNull MeasuredParagraph buildForStaticLayout(
387 @NonNull TextPaint paint,
388 @NonNull CharSequence text,
389 @IntRange(from = 0) int start,
390 @IntRange(from = 0) int end,
391 @NonNull TextDirectionHeuristic textDir,
Seigo Nonaka87b15472018-01-12 14:06:29 -0800392 boolean computeHyphenation,
Seigo Nonaka783f9612018-01-20 12:11:13 -0800393 boolean computeLayout,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800394 @Nullable MeasuredParagraph recycle) {
395 final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
396 mt.resetAndAnalyzeBidi(text, start, end, textDir);
397 if (mt.mTextLength == 0) {
398 // Need to build empty native measured text for StaticLayout.
399 // TODO: Stop creating empty measured text for empty lines.
400 long nativeBuilderPtr = nInitBuilder();
401 try {
402 mt.bindNativeObject(
Seigo Nonaka87b15472018-01-12 14:06:29 -0800403 nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
Seigo Nonaka783f9612018-01-20 12:11:13 -0800404 computeHyphenation, computeLayout));
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800405 } finally {
406 nFreeBuilder(nativeBuilderPtr);
407 }
408 return mt;
409 }
410
411 long nativeBuilderPtr = nInitBuilder();
412 try {
413 if (mt.mSpanned == null) {
414 // No style change by MetricsAffectingSpan. Just measure all text.
415 mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
416 mt.mSpanEndCache.append(end);
417 } else {
418 // There may be a MetricsAffectingSpan. Split into span transitions and apply
419 // styles.
420 int spanEnd;
421 for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
422 spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
423 MetricAffectingSpan.class);
424 MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
425 MetricAffectingSpan.class);
426 spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
427 MetricAffectingSpan.class);
428 mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
429 nativeBuilderPtr);
430 mt.mSpanEndCache.append(spanEnd);
431 }
432 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800433 mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
Seigo Nonaka783f9612018-01-20 12:11:13 -0800434 computeHyphenation, computeLayout));
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800435 } finally {
436 nFreeBuilder(nativeBuilderPtr);
437 }
438
439 return mt;
440 }
441
442 /**
443 * Reset internal state and analyzes text for bidirectional runs.
444 *
445 * @param text the character sequence to be measured
446 * @param start the inclusive start offset of the target region in the text
447 * @param end the exclusive end offset of the target region in the text
448 * @param textDir the text direction
449 */
450 private void resetAndAnalyzeBidi(@NonNull CharSequence text,
451 @IntRange(from = 0) int start, // inclusive
452 @IntRange(from = 0) int end, // exclusive
453 @NonNull TextDirectionHeuristic textDir) {
454 reset();
455 mSpanned = text instanceof Spanned ? (Spanned) text : null;
456 mTextStart = start;
457 mTextLength = end - start;
458
459 if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
460 mCopiedBuffer = new char[mTextLength];
461 }
462 TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
463
464 // Replace characters associated with ReplacementSpan to U+FFFC.
465 if (mSpanned != null) {
466 ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
467
468 for (int i = 0; i < spans.length; i++) {
469 int startInPara = mSpanned.getSpanStart(spans[i]) - start;
470 int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
471 // The span interval may be larger and must be restricted to [start, end)
472 if (startInPara < 0) startInPara = 0;
473 if (endInPara > mTextLength) endInPara = mTextLength;
474 Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
475 }
476 }
477
478 if ((textDir == TextDirectionHeuristics.LTR
479 || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
480 || textDir == TextDirectionHeuristics.ANYRTL_LTR)
481 && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
482 mLevels.clear();
483 mParaDir = Layout.DIR_LEFT_TO_RIGHT;
484 mLtrWithoutBidi = true;
485 } else {
486 final int bidiRequest;
487 if (textDir == TextDirectionHeuristics.LTR) {
488 bidiRequest = Layout.DIR_REQUEST_LTR;
489 } else if (textDir == TextDirectionHeuristics.RTL) {
490 bidiRequest = Layout.DIR_REQUEST_RTL;
491 } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
492 bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
493 } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
494 bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
495 } else {
496 final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
497 bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
498 }
499 mLevels.resize(mTextLength);
500 mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
501 mLtrWithoutBidi = false;
502 }
503 }
504
505 private void applyReplacementRun(@NonNull ReplacementSpan replacement,
506 @IntRange(from = 0) int start, // inclusive, in copied buffer
507 @IntRange(from = 0) int end, // exclusive, in copied buffer
508 /* Maybe Zero */ long nativeBuilderPtr) {
509 // Use original text. Shouldn't matter.
510 // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
511 // backward compatibility? or Should we initialize them for getFontMetricsInt?
512 final float width = replacement.getSize(
513 mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
514 if (nativeBuilderPtr == 0) {
515 // Assigns all width to the first character. This is the same behavior as minikin.
516 mWidths.set(start, width);
517 if (end > start + 1) {
518 Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
519 }
520 mWholeWidth += width;
521 } else {
522 nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
523 width);
524 }
525 }
526
527 private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer
528 @IntRange(from = 0) int end, // exclusive, in copied buffer
529 /* Maybe Zero */ long nativeBuilderPtr) {
530 if (nativeBuilderPtr != 0) {
531 mCachedPaint.getFontMetricsInt(mCachedFm);
532 }
533
534 if (mLtrWithoutBidi) {
535 // If the whole text is LTR direction, just apply whole region.
536 if (nativeBuilderPtr == 0) {
537 mWholeWidth += mCachedPaint.getTextRunAdvances(
538 mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
539 mWidths.getRawArray(), start);
540 } else {
541 nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
542 false /* isRtl */);
543 }
544 } else {
545 // If there is multiple bidi levels, split into individual bidi level and apply style.
546 byte level = mLevels.get(start);
547 // Note that the empty text or empty range won't reach this method.
548 // Safe to search from start + 1.
549 for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
550 if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point
551 final boolean isRtl = (level & 0x1) != 0;
552 if (nativeBuilderPtr == 0) {
553 final int levelLength = levelEnd - levelStart;
554 mWholeWidth += mCachedPaint.getTextRunAdvances(
555 mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
556 isRtl, mWidths.getRawArray(), levelStart);
557 } else {
558 nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
559 levelEnd, isRtl);
560 }
561 if (levelEnd == end) {
562 break;
563 }
564 levelStart = levelEnd;
565 level = mLevels.get(levelEnd);
566 }
567 }
568 }
569 }
570
571 private void applyMetricsAffectingSpan(
572 @NonNull TextPaint paint,
573 @Nullable MetricAffectingSpan[] spans,
574 @IntRange(from = 0) int start, // inclusive, in original text buffer
575 @IntRange(from = 0) int end, // exclusive, in original text buffer
576 /* Maybe Zero */ long nativeBuilderPtr) {
577 mCachedPaint.set(paint);
578 // XXX paint should not have a baseline shift, but...
579 mCachedPaint.baselineShift = 0;
580
581 final boolean needFontMetrics = nativeBuilderPtr != 0;
582
583 if (needFontMetrics && mCachedFm == null) {
584 mCachedFm = new Paint.FontMetricsInt();
585 }
586
587 ReplacementSpan replacement = null;
588 if (spans != null) {
589 for (int i = 0; i < spans.length; i++) {
590 MetricAffectingSpan span = spans[i];
591 if (span instanceof ReplacementSpan) {
592 // The last ReplacementSpan is effective for backward compatibility reasons.
593 replacement = (ReplacementSpan) span;
594 } else {
595 // TODO: No need to call updateMeasureState for ReplacementSpan as well?
596 span.updateMeasureState(mCachedPaint);
597 }
598 }
599 }
600
601 final int startInCopiedBuffer = start - mTextStart;
602 final int endInCopiedBuffer = end - mTextStart;
603
604 if (replacement != null) {
605 applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
606 nativeBuilderPtr);
607 } else {
608 applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
609 }
610
611 if (needFontMetrics) {
612 if (mCachedPaint.baselineShift < 0) {
613 mCachedFm.ascent += mCachedPaint.baselineShift;
614 mCachedFm.top += mCachedPaint.baselineShift;
615 } else {
616 mCachedFm.descent += mCachedPaint.baselineShift;
617 mCachedFm.bottom += mCachedPaint.baselineShift;
618 }
619
620 mFontMetrics.append(mCachedFm.top);
621 mFontMetrics.append(mCachedFm.bottom);
622 mFontMetrics.append(mCachedFm.ascent);
623 mFontMetrics.append(mCachedFm.descent);
624 }
625 }
626
627 /**
628 * Returns the maximum index that the accumulated width not exceeds the width.
629 *
630 * If forward=false is passed, returns the minimum index from the end instead.
631 *
Seigo Nonaka783f9612018-01-20 12:11:13 -0800632 * This only works if the MeasuredParagraph is computed with buildForMeasurement.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800633 * Undefined behavior in other case.
634 */
635 @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
636 float[] w = mWidths.getRawArray();
637 if (forwards) {
638 int i = 0;
639 while (i < limit) {
640 width -= w[i];
641 if (width < 0.0f) break;
642 i++;
643 }
644 while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
645 return i;
646 } else {
647 int i = limit - 1;
648 while (i >= 0) {
649 width -= w[i];
650 if (width < 0.0f) break;
651 i--;
652 }
653 while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
654 i++;
655 }
656 return limit - i - 1;
657 }
658 }
659
660 /**
661 * Returns the length of the substring.
662 *
Seigo Nonaka783f9612018-01-20 12:11:13 -0800663 * This only works if the MeasuredParagraph is computed with buildForMeasurement.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800664 * Undefined behavior in other case.
665 */
666 @FloatRange(from = 0.0f) float measure(int start, int limit) {
667 float width = 0;
668 float[] w = mWidths.getRawArray();
669 for (int i = start; i < limit; ++i) {
670 width += w[i];
671 }
672 return width;
673 }
674
Seigo Nonaka49ca0242018-01-24 16:46:14 -0800675 /**
676 * This only works if the MeasuredParagraph is computed with buildForStaticLayout.
677 */
678 @IntRange(from = 0) int getMemoryUsage() {
679 return nGetMemoryUsage(mNativePtr);
680 }
681
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800682 private static native /* Non Zero */ long nInitBuilder();
683
684 /**
685 * Apply style to make native measured text.
686 *
687 * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
688 * @param paintPtr The native paint pointer to be applied.
689 * @param start The start offset in the copied buffer.
690 * @param end The end offset in the copied buffer.
691 * @param isRtl True if the text is RTL.
692 */
693 private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
694 /* Non Zero */ long paintPtr,
695 @IntRange(from = 0) int start,
696 @IntRange(from = 0) int end,
697 boolean isRtl);
698
699 /**
700 * Apply ReplacementRun to make native measured text.
701 *
702 * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
703 * @param paintPtr The native paint pointer to be applied.
704 * @param start The start offset in the copied buffer.
705 * @param end The end offset in the copied buffer.
706 * @param width The width of the replacement.
707 */
708 private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
709 /* Non Zero */ long paintPtr,
710 @IntRange(from = 0) int start,
711 @IntRange(from = 0) int end,
712 @FloatRange(from = 0) float width);
713
714 private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
Seigo Nonaka87b15472018-01-12 14:06:29 -0800715 @NonNull char[] text,
Seigo Nonaka783f9612018-01-20 12:11:13 -0800716 boolean computeHyphenation,
717 boolean computeLayout);
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800718
719 private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
720
721 @CriticalNative
Seigo Nonaka783f9612018-01-20 12:11:13 -0800722 private static native float nGetWidth(/* Non Zero */ long nativePtr,
723 @IntRange(from = 0) int start,
724 @IntRange(from = 0) int end);
725
726 @CriticalNative
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800727 private static native /* Non Zero */ long nGetReleaseFunc();
Seigo Nonaka49ca0242018-01-24 16:46:14 -0800728
729 @CriticalNative
730 private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800731}