1beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka/*
2beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * Copyright (C) 2017 The Android Open Source Project
3beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *
4beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * Licensed under the Apache License, Version 2.0 (the "License");
5beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * you may not use this file except in compliance with the License.
6beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * You may obtain a copy of the License at
7beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *
8beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *      http://www.apache.org/licenses/LICENSE-2.0
9beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *
10beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * Unless required by applicable law or agreed to in writing, software
11beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * distributed under the License is distributed on an "AS IS" BASIS,
12beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * See the License for the specific language governing permissions and
14beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * limitations under the License.
15beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka */
16beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
17beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonakapackage android.text;
18beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
19151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonakaimport android.annotation.FloatRange;
20beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonakaimport android.annotation.IntRange;
21beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonakaimport android.annotation.NonNull;
22beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonakaimport android.annotation.Nullable;
23a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonakaimport android.graphics.Rect;
24a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonakaimport android.text.style.MetricAffectingSpan;
25beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
26beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonakaimport com.android.internal.util.Preconditions;
27beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
28beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonakaimport java.util.ArrayList;
29beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonakaimport java.util.Objects;
30beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
31beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka/**
32beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * A text which has the character metrics data.
33beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *
34beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * A text object that contains the character metrics data and can be used to improve the performance
35beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * of text layout operations. When a PrecomputedText is created with a given {@link CharSequence},
36beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * it will measure the text metrics during the creation. This PrecomputedText instance can be set on
37beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * {@link android.widget.TextView} or {@link StaticLayout}. Since the text layout information will
38beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * be included in this instance, {@link android.widget.TextView} or {@link StaticLayout} will not
39beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * have to recalculate this information.
40beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *
41beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * Note that the {@link PrecomputedText} created from different parameters of the target {@link
42beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * android.widget.TextView} will be rejected internally and compute the text layout again with the
43beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * current {@link android.widget.TextView} parameters.
44beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *
45beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * <pre>
46beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * An example usage is:
47beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * <code>
48151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *  static void asyncSetText(TextView textView, final String longString, Executor bgExecutor) {
49beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *      // construct precompute related parameters using the TextView that we will set the text on.
50151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *      final PrecomputedText.Params params = textView.getTextMetricsParams();
51151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *      final Reference textViewRef = new WeakReference<>(textView);
52151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *      bgExecutor.submit(() -> {
53151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *          TextView textView = textViewRef.get();
54151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *          if (textView == null) return;
55151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *          final PrecomputedText precomputedText = PrecomputedText.create(longString, params);
56beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *          textView.post(() -> {
57151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *              TextView textView = textViewRef.get();
58151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka *              if (textView == null) return;
59beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *              textView.setText(precomputedText);
60beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *          });
61beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *      });
62beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *  }
63beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * </code>
64beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * </pre>
65beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka *
66beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka * Note that the {@link PrecomputedText} created from different parameters of the target
67a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka * {@link android.widget.TextView} will be rejected.
68a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka *
69a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka * Note that any {@link android.text.NoCopySpan} attached to the original text won't be passed to
70a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka * PrecomputedText.
71beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka */
72a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonakapublic class PrecomputedText implements Spannable {
73beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    private static final char LINE_FEED = '\n';
74beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
75beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
76beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * The information required for building {@link PrecomputedText}.
77beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     *
78beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Contains information required for precomputing text measurement metadata, so it can be done
79beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * in isolation of a {@link android.widget.TextView} or {@link StaticLayout}, when final layout
80beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * constraints are not known.
81beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
82beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public static final class Params {
83beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        // The TextPaint used for measurement.
84beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        private final @NonNull TextPaint mPaint;
85beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
86beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        // The requested text direction.
87beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        private final @NonNull TextDirectionHeuristic mTextDir;
88beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
89beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        // The break strategy for this measured text.
90beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        private final @Layout.BreakStrategy int mBreakStrategy;
91beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
92beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        // The hyphenation frequency for this measured text.
93beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        private final @Layout.HyphenationFrequency int mHyphenationFrequency;
94beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
95beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        /**
96beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * A builder for creating {@link Params}.
97beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         */
98beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public static class Builder {
99beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            // The TextPaint used for measurement.
100beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            private final @NonNull TextPaint mPaint;
101beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
102beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            // The requested text direction.
103beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
104beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
105beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            // The break strategy for this measured text.
106beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
107beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
108beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            // The hyphenation frequency for this measured text.
109beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            private @Layout.HyphenationFrequency int mHyphenationFrequency =
110beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                    Layout.HYPHENATION_FREQUENCY_NORMAL;
111beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
112beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            /**
113beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * Builder constructor.
114beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
115beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @param paint the paint to be used for drawing
116beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             */
117beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            public Builder(@NonNull TextPaint paint) {
118beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                mPaint = paint;
119beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
120beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
121beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            /**
122beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * Set the line break strategy.
123beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
124beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}.
125beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
126beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @param strategy the break strategy
127beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @return this builder, useful for chaining
128beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @see StaticLayout.Builder#setBreakStrategy
129beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @see android.widget.TextView#setBreakStrategy
130beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             */
131beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            public Builder setBreakStrategy(@Layout.BreakStrategy int strategy) {
132beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                mBreakStrategy = strategy;
133beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                return this;
134beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
135beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
136beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            /**
137beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * Set the hyphenation frequency.
138beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
139beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
140beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
141beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @param frequency the hyphenation frequency
142beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @return this builder, useful for chaining
143beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @see StaticLayout.Builder#setHyphenationFrequency
144beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @see android.widget.TextView#setHyphenationFrequency
145beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             */
146beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            public Builder setHyphenationFrequency(@Layout.HyphenationFrequency int frequency) {
147beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                mHyphenationFrequency = frequency;
148beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                return this;
149beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
150beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
151beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            /**
152beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * Set the text direction heuristic.
153beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
154beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
155beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
156beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @param textDir the text direction heuristic for resolving bidi behavior
157beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @return this builder, useful for chaining
158beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @see StaticLayout.Builder#setTextDirection
159beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             */
160beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
161beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                mTextDir = textDir;
162beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                return this;
163beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
164beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
165beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            /**
166beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * Build the {@link Params}.
167beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             *
168beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             * @return the layout parameter
169beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka             */
170beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            public @NonNull Params build() {
171beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                return new Params(mPaint, mTextDir, mBreakStrategy, mHyphenationFrequency);
172beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
173beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
174beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
175beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        // This is public hidden for internal use.
176beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        // For the external developers, use Builder instead.
177beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        /** @hide */
178beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public Params(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir,
179beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) {
180beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            mPaint = paint;
181beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            mTextDir = textDir;
182beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            mBreakStrategy = strategy;
183beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            mHyphenationFrequency = frequency;
184beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
185beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
186beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        /**
187beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * Returns the {@link TextPaint} for this text.
188beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         *
189beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * @return A {@link TextPaint}
190beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         */
191beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public @NonNull TextPaint getTextPaint() {
192beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            return mPaint;
193beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
194beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
195beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        /**
196beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * Returns the {@link TextDirectionHeuristic} for this text.
197beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         *
198beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * @return A {@link TextDirectionHeuristic}
199beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         */
200beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public @NonNull TextDirectionHeuristic getTextDirection() {
201beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            return mTextDir;
202beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
203beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
204beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        /**
205beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * Returns the break strategy for this text.
206beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         *
207beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * @return A line break strategy
208beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         */
209beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public @Layout.BreakStrategy int getBreakStrategy() {
210beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            return mBreakStrategy;
211beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
212beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
213beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        /**
214beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * Returns the hyphenation frequency for this text.
215beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         *
216beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * @return A hyphenation frequency
217beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         */
218beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public @Layout.HyphenationFrequency int getHyphenationFrequency() {
219beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            return mHyphenationFrequency;
220beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
221beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
222e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka        /** @hide */
223e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka        public boolean isSameTextMetricsInternal(@NonNull TextPaint paint,
224beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy,
225beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                @Layout.HyphenationFrequency int frequency) {
226beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            return mTextDir == textDir
227beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                && mBreakStrategy == strategy
228beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                && mHyphenationFrequency == frequency
229beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                && mPaint.equalsForTextMeasurement(paint);
230beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
231beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
232beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        /**
233beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * Check if the same text layout.
234beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         *
235beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         * @return true if this and the given param result in the same text layout
236beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka         */
237beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        @Override
238beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public boolean equals(@Nullable Object o) {
239beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            if (o == this) {
240beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                return true;
241beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
242beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            if (o == null || !(o instanceof Params)) {
243beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                return false;
244beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
245beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            Params param = (Params) o;
246beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            return isSameTextMetricsInternal(param.mPaint, param.mTextDir, param.mBreakStrategy,
247beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                    param.mHyphenationFrequency);
248beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
249beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
250beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        @Override
251beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        public int hashCode() {
252beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            // TODO: implement MinikinPaint::hashCode and use it to keep consistency with equals.
253beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            return Objects.hash(mPaint.getTextSize(), mPaint.getTextScaleX(), mPaint.getTextSkewX(),
254beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                    mPaint.getLetterSpacing(), mPaint.getWordSpacing(), mPaint.getFlags(),
255beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                    mPaint.getTextLocales(), mPaint.getTypeface(),
256beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                    mPaint.getFontVariationSettings(), mPaint.isElegantTextHeight(), mTextDir,
257beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                    mBreakStrategy, mHyphenationFrequency);
258beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
259e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka
260e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka        @Override
261e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka        public String toString() {
262e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka            return "{"
263e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + "textSize=" + mPaint.getTextSize()
264e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", textScaleX=" + mPaint.getTextScaleX()
265e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", textSkewX=" + mPaint.getTextSkewX()
266e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", letterSpacing=" + mPaint.getLetterSpacing()
267e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", textLocale=" + mPaint.getTextLocales()
268e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", typeface=" + mPaint.getTypeface()
269e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", variationSettings=" + mPaint.getFontVariationSettings()
270e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", elegantTextHeight=" + mPaint.isElegantTextHeight()
271e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", textDir=" + mTextDir
272e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", breakStrategy=" + mBreakStrategy
273e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + ", hyphenationFrequency=" + mHyphenationFrequency
274e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka                + "}";
275e1ffb54167c8ff78855352a324ff8332a33fc805Seigo Nonaka        }
276beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    };
277beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
278c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    /** @hide */
279c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    public static class ParagraphInfo {
280c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        public final @IntRange(from = 0) int paragraphEnd;
281c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        public final @NonNull MeasuredParagraph measured;
282c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka
283c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        /**
284c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka         * @param paraEnd the end offset of this paragraph
285c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka         * @param measured a measured paragraph
286c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka         */
287c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        public ParagraphInfo(@IntRange(from = 0) int paraEnd, @NonNull MeasuredParagraph measured) {
288c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            this.paragraphEnd = paraEnd;
289c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            this.measured = measured;
290c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        }
291c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    };
292c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka
293c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka
294beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // The original text.
295a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    private final @NonNull SpannableString mText;
296beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
297beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // The inclusive start offset of the measuring target.
298beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    private final @IntRange(from = 0) int mStart;
299beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
300beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // The exclusive end offset of the measuring target.
301beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    private final @IntRange(from = 0) int mEnd;
302beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
303beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    private final @NonNull Params mParams;
304beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
305c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    // The list of measured paragraph info.
306c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    private final @NonNull ParagraphInfo[] mParagraphInfo;
307beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
308beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
309beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Create a new {@link PrecomputedText} which will pre-compute text measurement and glyph
310beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * positioning information.
311beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * <p>
312beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * This can be expensive, so computing this on a background thread before your text will be
313beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * presented can save work on the UI thread.
314beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * </p>
315beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     *
316a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka     * Note that any {@link android.text.NoCopySpan} attached to the text won't be passed to the
317a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka     * created PrecomputedText.
318a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka     *
319beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * @param text the text to be measured
320c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka     * @param params parameters that define how text will be precomputed
321beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * @return A {@link PrecomputedText}
322beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
323c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    public static PrecomputedText create(@NonNull CharSequence text, @NonNull Params params) {
324c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        ParagraphInfo[] paraInfo = createMeasuredParagraphs(
325c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka                text, params, 0, text.length(), true /* computeLayout */);
326c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        return new PrecomputedText(text, 0, text.length(), params, paraInfo);
327beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
328beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
329beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /** @hide */
330c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    public static ParagraphInfo[] createMeasuredParagraphs(
331c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            @NonNull CharSequence text, @NonNull Params params,
332beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout) {
333c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        ArrayList<ParagraphInfo> result = new ArrayList<>();
334beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
335c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        Preconditions.checkNotNull(text);
336c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        Preconditions.checkNotNull(params);
337c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
338c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka                && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
339beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
340beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        int paraEnd = 0;
341beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
342beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
343beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            if (paraEnd < 0) {
344beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
345beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                // end.
346beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                paraEnd = end;
347beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            } else {
348beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
349beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
350beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
351c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
352c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka                    params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(),
353c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka                    needHyphenation, computeLayout, null /* no recycle */)));
354beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
355c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        return result.toArray(new ParagraphInfo[result.size()]);
356beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
357beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
358beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // Use PrecomputedText.create instead.
359beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    private PrecomputedText(@NonNull CharSequence text, @IntRange(from = 0) int start,
360c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            @IntRange(from = 0) int end, @NonNull Params params,
361c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            @NonNull ParagraphInfo[] paraInfo) {
362a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        mText = new SpannableString(text, true /* ignoreNoCopySpan */);
363beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        mStart = start;
364beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        mEnd = end;
365c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        mParams = params;
366c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        mParagraphInfo = paraInfo;
367beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
368beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
369beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
370beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Return the underlying text.
371151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @hide
372beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
373beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @NonNull CharSequence getText() {
374beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText;
375beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
376beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
377beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
378beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns the inclusive start offset of measured region.
379beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * @hide
380beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
381beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @IntRange(from = 0) int getStart() {
382beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mStart;
383beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
384beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
385beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
386beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns the exclusive end offset of measured region.
387beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * @hide
388beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
389beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @IntRange(from = 0) int getEnd() {
390beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mEnd;
391beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
392beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
393beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
394beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns the layout parameters used to measure this text.
395beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
396beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @NonNull Params getParams() {
397beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mParams;
398beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
399beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
400beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
401beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns the count of paragraphs.
402beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
403beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @IntRange(from = 0) int getParagraphCount() {
404c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        return mParagraphInfo.length;
405beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
406beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
407beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
408beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns the paragraph start offset of the text.
409beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
410beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
411beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
412c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        return paraIndex == 0 ? mStart : getParagraphEnd(paraIndex - 1);
413beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
414beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
415beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
416beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns the paragraph end offset of the text.
417beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
418beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
419beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
420c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        return mParagraphInfo[paraIndex].paragraphEnd;
421beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
422beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
423beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /** @hide */
424beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) {
425c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        return mParagraphInfo[paraIndex].measured;
426c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    }
427c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka
428c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    /** @hide */
429c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka    public @NonNull ParagraphInfo[] getParagraphInfo() {
430c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        return mParagraphInfo;
431beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
432beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
433beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
434beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns true if the given TextPaint gives the same result of text layout for this text.
435beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * @hide
436beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
437beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public boolean canUseMeasuredResult(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
438beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint,
439beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) {
440beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        final TextPaint mtPaint = mParams.getTextPaint();
441beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mStart == start
442beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            && mEnd == end
443beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            && mParams.isSameTextMetricsInternal(paint, textDir, strategy, frequency);
444beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
445beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
446beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /** @hide */
447beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public int findParaIndex(@IntRange(from = 0) int pos) {
448beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        // TODO: Maybe good to remove paragraph concept from PrecomputedText and add substring
449beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        //       layout support to StaticLayout.
450c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka        for (int i = 0; i < mParagraphInfo.length; ++i) {
451c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            if (pos < mParagraphInfo[i].paragraphEnd) {
452beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                return i;
453beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            }
454beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
455beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        throw new IndexOutOfBoundsException(
456c3328d648e827c8a65f46ed3a8b0ec96076b5ebeSeigo Nonaka            "pos must be less than " + mParagraphInfo[mParagraphInfo.length - 1].paragraphEnd
457beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            + ", gave " + pos);
458beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
459beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
460151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka    /**
461151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * Returns text width for the given range.
462151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * Both {@code start} and {@code end} offset need to be in the same paragraph, otherwise
463151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * IllegalArgumentException will be thrown.
464151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     *
465151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @param start the inclusive start offset in the text
466151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @param end the exclusive end offset in the text
467151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @return the text width
468151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @throws IllegalArgumentException if start and end offset are in the different paragraph.
469151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     */
470151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka    public @FloatRange(from = 0) float getWidth(@IntRange(from = 0) int start,
471151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka            @IntRange(from = 0) int end) {
472151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        Preconditions.checkArgument(0 <= start && start <= mText.length(), "invalid start offset");
473151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        Preconditions.checkArgument(0 <= end && end <= mText.length(), "invalid end offset");
474151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        Preconditions.checkArgument(start <= end, "start offset can not be larger than end offset");
475151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka
476151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        if (start == end) {
477151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka            return 0;
478151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        }
479beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        final int paraIndex = findParaIndex(start);
480beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        final int paraStart = getParagraphStart(paraIndex);
481beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        final int paraEnd = getParagraphEnd(paraIndex);
482beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        if (start < paraStart || paraEnd < end) {
483151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka            throw new IllegalArgumentException("Cannot measured across the paragraph:"
484beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                + "para: (" + paraStart + ", " + paraEnd + "), "
485beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka                + "request: (" + start + ", " + end + ")");
486beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
487beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart);
488beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
489beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
490151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka    /**
491151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * Retrieves the text bounding box for the given range.
492151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * Both {@code start} and {@code end} offset need to be in the same paragraph, otherwise
493151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * IllegalArgumentException will be thrown.
494151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     *
495151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @param start the inclusive start offset in the text
496151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @param end the exclusive end offset in the text
497151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @param bounds the output rectangle
498151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     * @throws IllegalArgumentException if start and end offset are in the different paragraph.
499151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka     */
500a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
501a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka            @NonNull Rect bounds) {
502151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        Preconditions.checkArgument(0 <= start && start <= mText.length(), "invalid start offset");
503151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        Preconditions.checkArgument(0 <= end && end <= mText.length(), "invalid end offset");
504151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        Preconditions.checkArgument(start <= end, "start offset can not be larger than end offset");
505151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        Preconditions.checkNotNull(bounds);
506151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        if (start == end) {
507151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka            bounds.set(0, 0, 0, 0);
508151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka            return;
509151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka        }
510a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        final int paraIndex = findParaIndex(start);
511a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        final int paraStart = getParagraphStart(paraIndex);
512a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        final int paraEnd = getParagraphEnd(paraIndex);
513a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        if (start < paraStart || paraEnd < end) {
514151108a2c66dd55663bea9e3f686793418d5f047Seigo Nonaka            throw new IllegalArgumentException("Cannot measured across the paragraph:"
515a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka                + "para: (" + paraStart + ", " + paraEnd + "), "
516a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka                + "request: (" + start + ", " + end + ")");
517a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        }
518fb0abe1feb2cadcee2a3edb2028e1da04bb8a8d5Seigo Nonaka        getMeasuredParagraph(paraIndex).getBounds(start - paraStart, end - paraStart, bounds);
519a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    }
520a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka
521beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    /**
522beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Returns the size of native PrecomputedText memory usage.
523beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     *
524beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * Note that this is not guaranteed to be accurate. Must be used only for testing purposes.
525beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     * @hide
526beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka     */
527beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public int getMemoryUsage() {
528beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        int r = 0;
529beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        for (int i = 0; i < getParagraphCount(); ++i) {
530beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka            r += getMeasuredParagraph(i).getMemoryUsage();
531beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        }
532beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return r;
533beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
534beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
535beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    ///////////////////////////////////////////////////////////////////////////////////////////////
536a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    // Spannable overrides
537a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    //
538a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    // Do not allow to modify MetricAffectingSpan
539a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka
540a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    /**
541a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka     * @throws IllegalArgumentException if {@link MetricAffectingSpan} is specified.
542a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka     */
543a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    @Override
544a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    public void setSpan(Object what, int start, int end, int flags) {
545a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        if (what instanceof MetricAffectingSpan) {
546a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka            throw new IllegalArgumentException(
547a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka                    "MetricAffectingSpan can not be set to PrecomputedText.");
548a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        }
549a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        mText.setSpan(what, start, end, flags);
550a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    }
551a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka
552a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    /**
553a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka     * @throws IllegalArgumentException if {@link MetricAffectingSpan} is specified.
554a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka     */
555a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    @Override
556a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    public void removeSpan(Object what) {
557a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        if (what instanceof MetricAffectingSpan) {
558a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka            throw new IllegalArgumentException(
559a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka                    "MetricAffectingSpan can not be removed from PrecomputedText.");
560a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        }
561a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka        mText.removeSpan(what);
562a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    }
563a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka
564a553477ddf55d170a66410ed325ae5e5d3005965Seigo Nonaka    ///////////////////////////////////////////////////////////////////////////////////////////////
565beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // Spanned overrides
566beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    //
567beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // Just proxy for underlying mText if appropriate.
568beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
569beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
570beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public <T> T[] getSpans(int start, int end, Class<T> type) {
571beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.getSpans(start, end, type);
572beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
573beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
574beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
575beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public int getSpanStart(Object tag) {
576beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.getSpanStart(tag);
577beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
578beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
579beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
580beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public int getSpanEnd(Object tag) {
581beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.getSpanEnd(tag);
582beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
583beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
584beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
585beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public int getSpanFlags(Object tag) {
586beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.getSpanFlags(tag);
587beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
588beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
589beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
590beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public int nextSpanTransition(int start, int limit, Class type) {
591beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.nextSpanTransition(start, limit, type);
592beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
593beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
594beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    ///////////////////////////////////////////////////////////////////////////////////////////////
595beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // CharSequence overrides.
596beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    //
597beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    // Just proxy for underlying mText.
598beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
599beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
600beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public int length() {
601beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.length();
602beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
603beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
604beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
605beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public char charAt(int index) {
606beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.charAt(index);
607beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
608beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
609beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
610beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public CharSequence subSequence(int start, int end) {
611beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return PrecomputedText.create(mText.subSequence(start, end), mParams);
612beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
613beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka
614beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    @Override
615beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    public String toString() {
616beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka        return mText.toString();
617beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka    }
618beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka}
619