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