1 /*
2  * Copyright 2022 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.style
18 
19 import androidx.compose.ui.text.PlatformParagraphStyle
20 import androidx.compose.ui.text.internal.checkPrecondition
21 import kotlin.jvm.JvmInline
22 
23 /**
24  * The configuration for line height such as alignment of the line in the provided line height,
25  * whether to apply additional space as a result of line height to top of first line top and bottom
26  * of last line.
27  *
28  * The configuration is applied only when a line height is defined on the text.
29  *
30  * [trim] feature is available only when [PlatformParagraphStyle.includeFontPadding] is false.
31  *
32  * Please check [Trim] and [Alignment] for more description.
33  *
34  * @param alignment defines how to align the line in the space provided by the line height.
35  * @param trim defines whether the space that would be added to the top of first line, and bottom of
36  *   the last line should be trimmed or not. This feature is available only when
37  *   [PlatformParagraphStyle.includeFontPadding] is false.
38  * @param mode defines the behavior when the specified line height is smaller than system preferred
39  *   line height. By specifying [Mode.Fixed], the line height is always set to the specified value.
40  *   This is the default value. By specifying [Mode.Minimum], the specified line height is smaller
41  *   than the system preferred value, the system preferred one is used instead.
42  */
43 class LineHeightStyle(val alignment: Alignment, val trim: Trim, val mode: Mode) {
44 
45     constructor(alignment: Alignment, trim: Trim) : this(alignment, trim, Mode.Fixed)
46 
47     companion object {
48         /**
49          * The default configuration for [LineHeightStyle]:
50          * - alignment = [Alignment.Proportional]
51          * - trim = [Trim.Both]
52          * - mode = [Mode.Fixed]
53          */
54         val Default =
55             LineHeightStyle(alignment = Alignment.Proportional, trim = Trim.Both, mode = Mode.Fixed)
56     }
57 
58     /** Returns a copy of this [LineHeightStyle], optionally overriding some of the values. */
copynull59     fun copy(
60         alignment: Alignment = this.alignment,
61         trim: Trim = this.trim,
62         mode: Mode = this.mode,
63     ) = LineHeightStyle(alignment, trim, mode)
64 
65     override fun equals(other: Any?): Boolean {
66         if (this === other) return true
67         if (other !is LineHeightStyle) return false
68 
69         if (alignment != other.alignment) return false
70         if (trim != other.trim) return false
71         if (mode != other.mode) return false
72 
73         return true
74     }
75 
hashCodenull76     override fun hashCode(): Int {
77         var result = alignment.hashCode()
78         result = 31 * result + trim.hashCode()
79         result = 31 * result + mode.hashCode()
80         return result
81     }
82 
toStringnull83     override fun toString(): String {
84         return "LineHeightStyle(" + "alignment=$alignment, " + "trim=$trim," + "mode=$mode" + ")"
85     }
86 
87     /**
88      * Defines whether the space that would be added to the top of first line, and bottom of the
89      * last line should be trimmed or not. This feature is available only when
90      * [PlatformParagraphStyle.includeFontPadding] is false.
91      */
92     @kotlin.jvm.JvmInline
93     value class Trim private constructor(private val value: Int) {
94 
toStringnull95         override fun toString(): String {
96             return when (value) {
97                 FirstLineTop.value -> "LineHeightStyle.Trim.FirstLineTop"
98                 LastLineBottom.value -> "LineHeightStyle.Trim.LastLineBottom"
99                 Both.value -> "LineHeightStyle.Trim.Both"
100                 None.value -> "LineHeightStyle.Trim.None"
101                 else -> "Invalid"
102             }
103         }
104 
105         companion object {
106             private const val FlagTrimTop = 0x00000001
107             private const val FlagTrimBottom = 0x00000010
108 
109             /**
110              * Trim the space that would be added to the top of the first line as a result of the
111              * line height. Single line text is both the first and last line. This feature is
112              * available only when [PlatformParagraphStyle.includeFontPadding] is false.
113              *
114              * For example, when line height is 3.em, and [Alignment] is [Alignment.Center], the
115              * first line has 2.em height and the height from first line baseline to second line
116              * baseline is still 3.em:
117              * <pre>
118              * +--------+
119              * | Line1  |
120              * |        |
121              * |--------|
122              * |        |
123              * | Line2  |
124              * |        |
125              * +--------+
126              * </pre>
127              */
128             val FirstLineTop = Trim(FlagTrimTop)
129 
130             /**
131              * Trim the space that would be added to the bottom of the last line as a result of the
132              * line height. Single line text is both the first and last line. This feature is
133              * available only when [PlatformParagraphStyle.includeFontPadding] is false.
134              *
135              * For example, when line height is 3.em, and [Alignment] is [Alignment.Center], the
136              * last line has 2.em height and the height from first line baseline to second line
137              * baseline is still 3.em:
138              * <pre>
139              * +--------+
140              * |        |
141              * | Line1  |
142              * |        |
143              * |--------|
144              * |        |
145              * | Line2  |
146              * +--------+
147              * </pre>
148              */
149             val LastLineBottom = Trim(FlagTrimBottom)
150 
151             /**
152              * Trim the space that would be added to the top of the first line and bottom of the
153              * last line as a result of the line height. This feature is available only when
154              * [PlatformParagraphStyle.includeFontPadding] is false.
155              *
156              * For example, when line height is 3.em, and [Alignment] is [Alignment.Center], the
157              * first and last line has 2.em height and the height from first line baseline to second
158              * line baseline is still 3.em:
159              * <pre>
160              * +--------+
161              * | Line1  |
162              * |        |
163              * |--------|
164              * |        |
165              * | Line2  |
166              * +--------+
167              * </pre>
168              */
169             val Both = Trim(FlagTrimTop or FlagTrimBottom)
170 
171             /**
172              * Do not trim first line top or last line bottom.
173              *
174              * For example, when line height is 3.em, and [Alignment] is [Alignment.Center], the
175              * first line height, last line height and the height from first line baseline to second
176              * line baseline are 3.em:
177              * <pre>
178              * +--------+
179              * |        |
180              * | Line1  |
181              * |        |
182              * |--------|
183              * |        |
184              * | Line2  |
185              * |        |
186              * +--------+
187              * </pre>
188              */
189             val None = Trim(0)
190         }
191 
isTrimFirstLineTopnull192         internal fun isTrimFirstLineTop(): Boolean {
193             return value and FlagTrimTop > 0
194         }
195 
isTrimLastLineBottomnull196         internal fun isTrimLastLineBottom(): Boolean {
197             return value and FlagTrimBottom > 0
198         }
199     }
200 
201     /**
202      * Defines how to align the line in the space provided by the line height.
203      *
204      * @param topRatio the ratio of ascent to ascent+descent in percentage. Valid values are between
205      *   0f (inclusive) and 1f (inclusive).
206      */
207     @kotlin.jvm.JvmInline
208     value class Alignment constructor(internal val topRatio: Float) {
209 
<lambda>null210         init {
211             checkPrecondition(topRatio in 0f..1f || topRatio == -1f) {
212                 "topRatio should be in [0..1] range or -1"
213             }
214         }
215 
toStringnull216         override fun toString(): String {
217             return when (topRatio) {
218                 Top.topRatio -> "LineHeightStyle.Alignment.Top"
219                 Center.topRatio -> "LineHeightStyle.Alignment.Center"
220                 Proportional.topRatio -> "LineHeightStyle.Alignment.Proportional"
221                 Bottom.topRatio -> "LineHeightStyle.Alignment.Bottom"
222                 else -> "LineHeightStyle.Alignment(topPercentage = $topRatio)"
223             }
224         }
225 
226         companion object {
227             /**
228              * Align the line to the top of the space reserved for that line. This means that all
229              * extra space as a result of line height is applied to the bottom of the line. When the
230              * provided line height value is smaller than the actual line height, the line will
231              * still be aligned to the top, therefore the required difference will be subtracted
232              * from the bottom of the line.
233              *
234              * For example, when line height is 3.em, the lines are aligned to the top of 3.em
235              * height:
236              * <pre>
237              * +--------+
238              * | Line1  |
239              * |        |
240              * |        |
241              * |--------|
242              * | Line2  |
243              * |        |
244              * |        |
245              * +--------+
246              * </pre>
247              */
248             val Top = Alignment(topRatio = 0f)
249 
250             /**
251              * Align the line to the center of the space reserved for the line. This configuration
252              * distributes additional space evenly between top and bottom of the line.
253              *
254              * For example, when line height is 3.em, the lines are aligned to the center of 3.em
255              * height:
256              * <pre>
257              * +--------+
258              * |        |
259              * | Line1  |
260              * |        |
261              * |--------|
262              * |        |
263              * | Line2  |
264              * |        |
265              * +--------+
266              * </pre>
267              */
268             val Center = Alignment(topRatio = 0.5f)
269 
270             /**
271              * Align the line proportional to the ascent and descent values of the line. For example
272              * if ascent is 8 units of length, and descent is 2 units; an additional space of 10
273              * units will be distributed as 8 units to top, and 2 units to the bottom of the line.
274              * This is the default behavior.
275              */
276             val Proportional = Alignment(topRatio = -1f)
277 
278             /**
279              * Align the line to the bottom of the space reserved for that line. This means that all
280              * extra space as a result of line height is applied to the top of the line. When the
281              * provided line height value is smaller than the actual line height, the line will
282              * still be aligned to the bottom, therefore the required difference will be subtracted
283              * from the top of the line.
284              *
285              * For example, when line height is 3.em, the lines are aligned to the bottom of 3.em
286              * height:
287              * <pre>
288              * +--------+
289              * |        |
290              * |        |
291              * | Line1  |
292              * |--------|
293              * |        |
294              * |        |
295              * | Line2  |
296              * +--------+
297              * </pre>
298              */
299             val Bottom = Alignment(topRatio = 1f)
300         }
301     }
302 
303     /**
304      * Defines if the specified line height value should be enforced.
305      *
306      * The line height is determined by the font file used in the text. So, sometimes the specified
307      * text height can be too tight to show the given text. By using `Adjustment.Minimum` the line
308      * height can be adjusted to the system provided value if the specified line height is too
309      * tight. This is useful for supporting languages that use tall glyphs, e.g. Arabic, Myanmar,
310      * etc.
311      */
312     @JvmInline
313     value class Mode private constructor(private val value: Int) {
314         companion object {
315             /**
316              * Always use the specified line height. Even if the system preferred line height is
317              * larger than specified one, the specified line height is used.
318              */
319             val Fixed = Mode(0)
320 
321             /**
322              * By specifying [Mode.Minimum], when the specified line height is smaller than the
323              * system preferred value, the system preferred one is used instead.
324              */
325             val Minimum = Mode(1)
326         }
327     }
328 }
329