1 /*
2 * Copyright 2019 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
17 package androidx.compose.ui.text.android
18
19 import android.graphics.text.LineBreakConfig
20 import android.graphics.text.LineBreaker
21 import android.text.Layout
22 import android.text.Layout.Alignment
23 import android.text.TextDirectionHeuristic
24 import android.text.TextDirectionHeuristics
25 import androidx.annotation.IntDef
26 import androidx.annotation.IntRange
27
28 /**
29 * LayoutCompat class which provides all supported attributes by framework, and also defines default
30 * value of those attributes for Compose.
31 */
32 internal object LayoutCompat {
33 const val ALIGN_NORMAL = 0
34 const val ALIGN_OPPOSITE = 1
35 const val ALIGN_CENTER = 2
36 const val ALIGN_LEFT = 3
37 const val ALIGN_RIGHT = 4
38
39 @Retention(AnnotationRetention.SOURCE)
40 @IntDef(ALIGN_NORMAL, ALIGN_CENTER, ALIGN_OPPOSITE, ALIGN_LEFT, ALIGN_RIGHT)
41 internal annotation class TextLayoutAlignment
42
43 const val JUSTIFICATION_MODE_NONE = LineBreaker.JUSTIFICATION_MODE_NONE
44 const val JUSTIFICATION_MODE_INTER_WORD = LineBreaker.JUSTIFICATION_MODE_INTER_WORD
45
46 @Retention(AnnotationRetention.SOURCE)
47 @IntDef(JUSTIFICATION_MODE_NONE, JUSTIFICATION_MODE_INTER_WORD)
48 internal annotation class JustificationMode
49
50 const val HYPHENATION_FREQUENCY_NONE = Layout.HYPHENATION_FREQUENCY_NONE
51 const val HYPHENATION_FREQUENCY_NORMAL = Layout.HYPHENATION_FREQUENCY_NORMAL
52 const val HYPHENATION_FREQUENCY_NORMAL_FAST = Layout.HYPHENATION_FREQUENCY_NORMAL_FAST
53 const val HYPHENATION_FREQUENCY_FULL = Layout.HYPHENATION_FREQUENCY_FULL
54 const val HYPHENATION_FREQUENCY_FULL_FAST = Layout.HYPHENATION_FREQUENCY_FULL_FAST
55
56 @Retention(AnnotationRetention.SOURCE)
57 @IntDef(
58 HYPHENATION_FREQUENCY_NONE,
59 HYPHENATION_FREQUENCY_NORMAL,
60 HYPHENATION_FREQUENCY_NORMAL_FAST,
61 HYPHENATION_FREQUENCY_FULL,
62 HYPHENATION_FREQUENCY_FULL_FAST
63 )
64 internal annotation class HyphenationFrequency
65
66 const val BREAK_STRATEGY_SIMPLE = LineBreaker.BREAK_STRATEGY_SIMPLE
67 const val BREAK_STRATEGY_HIGH_QUALITY = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY
68 const val BREAK_STRATEGY_BALANCED = LineBreaker.BREAK_STRATEGY_BALANCED
69
70 @Retention(AnnotationRetention.SOURCE)
71 @IntDef(BREAK_STRATEGY_SIMPLE, BREAK_STRATEGY_HIGH_QUALITY, BREAK_STRATEGY_BALANCED)
72 internal annotation class BreakStrategy
73
74 const val LINE_BREAK_STYLE_NONE = LineBreakConfig.LINE_BREAK_STYLE_NONE
75 const val LINE_BREAK_STYLE_LOOSE = LineBreakConfig.LINE_BREAK_STYLE_LOOSE
76 const val LINE_BREAK_STYLE_NORMAL = LineBreakConfig.LINE_BREAK_STYLE_NORMAL
77 const val LINE_BREAK_STYLE_STRICT = LineBreakConfig.LINE_BREAK_STYLE_STRICT
78
79 @Retention(AnnotationRetention.SOURCE)
80 @IntDef(
81 LINE_BREAK_STYLE_NONE,
82 LINE_BREAK_STYLE_LOOSE,
83 LINE_BREAK_STYLE_NORMAL,
84 LINE_BREAK_STYLE_STRICT
85 )
86 internal annotation class LineBreakStyle
87
88 const val LINE_BREAK_WORD_STYLE_NONE = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
89 const val LINE_BREAK_WORD_STYLE_PHRASE = LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE
90
91 @Retention(AnnotationRetention.SOURCE)
92 @IntDef(LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE)
93 internal annotation class LineBreakWordStyle
94
95 const val TEXT_DIRECTION_LTR = 0
96 const val TEXT_DIRECTION_RTL = 1
97 const val TEXT_DIRECTION_FIRST_STRONG_LTR = 2
98 const val TEXT_DIRECTION_FIRST_STRONG_RTL = 3
99 const val TEXT_DIRECTION_ANY_RTL_LTR = 4
100 const val TEXT_DIRECTION_LOCALE = 5
101
102 @Retention(AnnotationRetention.SOURCE)
103 @IntDef(
104 TEXT_DIRECTION_LTR,
105 TEXT_DIRECTION_RTL,
106 TEXT_DIRECTION_FIRST_STRONG_LTR,
107 TEXT_DIRECTION_FIRST_STRONG_RTL,
108 TEXT_DIRECTION_ANY_RTL_LTR,
109 TEXT_DIRECTION_LOCALE
110 )
111 internal annotation class TextDirection
112
113 const val TEXT_GRANULARITY_CHARACTER = 0
114 const val TEXT_GRANULARITY_WORD = 1
115
116 @Retention(AnnotationRetention.SOURCE)
117 @IntDef(
118 TEXT_GRANULARITY_CHARACTER,
119 TEXT_GRANULARITY_WORD,
120 )
121 internal annotation class TextGranularity
122
123 const val DEFAULT_ALIGNMENT = ALIGN_NORMAL
124
125 internal const val DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_FIRST_STRONG_LTR
126
127 const val DEFAULT_LINESPACING_MULTIPLIER = 1.0f
128
129 internal const val DEFAULT_LINESPACING_EXTRA = 0.0f
130
131 internal const val DEFAULT_INCLUDE_PADDING = false
132
133 internal const val DEFAULT_MAX_LINES = Integer.MAX_VALUE
134
135 internal const val DEFAULT_BREAK_STRATEGY = BREAK_STRATEGY_SIMPLE
136
137 internal const val DEFAULT_LINE_BREAK_STYLE = LINE_BREAK_STYLE_NONE
138
139 internal const val DEFAULT_LINE_BREAK_WORD_STYLE = LINE_BREAK_WORD_STYLE_NONE
140
141 internal const val DEFAULT_HYPHENATION_FREQUENCY = HYPHENATION_FREQUENCY_NONE
142
143 const val DEFAULT_JUSTIFICATION_MODE = JUSTIFICATION_MODE_NONE
144
145 internal const val DEFAULT_FALLBACK_LINE_SPACING = true
146
147 internal val DEFAULT_LAYOUT_ALIGNMENT = Alignment.ALIGN_NORMAL
148
149 internal val DEFAULT_TEXT_DIRECTION_HEURISTIC: TextDirectionHeuristic =
150 TextDirectionHeuristics.FIRSTSTRONG_LTR
151 }
152
153 /**
154 * Returns the line number at the offset
155 *
156 * If the automatic line break didn't happen at the given offset, this returns the 0 origin line
157 * number that contains the given offset character. If the automatic line break happened at the
158 * given offset, this returns the preceding 0 origin line number that contains the given offset
159 * character if upstream is true. Otherwise, returns the line number that contains the given offset
160 * character.
161 *
162 * @param offset a character offset in the text
163 * @param upstream true if you want to get preceding line number for the line broken offset. false
164 * if you want to get the following line number for the line broken offset. This is ignored if the
165 * offset it not a line broken offset.
166 * @return the line number
167 */
getLineForOffsetnull168 internal fun Layout.getLineForOffset(@IntRange(from = 0) offset: Int, upstream: Boolean): Int {
169 if (offset <= 0) return 0
170 if (offset >= text.length) return lineCount - 1
171 val downstreamLineNo = getLineForOffset(offset)
172 val lineStart = getLineStart(downstreamLineNo)
173 val lineEnd = getLineEnd(downstreamLineNo)
174
175 if (lineStart != offset && lineEnd != offset) {
176 return downstreamLineNo
177 }
178
179 return if (lineStart == offset) {
180 if (upstream) downstreamLineNo - 1 else downstreamLineNo
181 } else { // lineEnd == offset
182 if (upstream) downstreamLineNo else downstreamLineNo + 1
183 }
184 }
185