blob: 292e6dec15cf0f649ef6b5b39293d2ab11b46283 [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.ui.text
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextGeometricTransform
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.util.fastMap
/**
* Utility function to be able to save nullable values. It also enables not to use with() scope
* for readability/syntactic purposes.
*/
internal fun <T : Saver<Original, Saveable>, Original, Saveable> save(
value: Original?,
saver: T,
scope: SaverScope
): Any {
return value?.let { with(saver) { scope.save(value) } } ?: false
}
/**
* Utility function to restore nullable values. It also enables not to use with() scope
* for readability/syntactic purposes.
*/
internal inline fun <T : Saver<Original, Saveable>, Original, Saveable, reified Result> restore(
value: Saveable?,
saver: T
): Result? {
if (value == false) return null
return value?.let { with(saver) { restore(value) } as Result }
}
/**
* Utility function to save nullable values that does not require a Saver.
*/
internal fun <T> save(value: T?): T? {
return value
}
/**
* Utility function to restore nullable values that does not require a Saver.
*/
internal inline fun <reified Result> restore(value: Any?): Result? {
return value?.let { it as Result }
}
internal val AnnotatedStringSaver = Saver<AnnotatedString, Any>(
save = {
arrayListOf(
save(it.text),
save(it.spanStyles, AnnotationRangeListSaver, this),
save(it.paragraphStyles, AnnotationRangeListSaver, this),
save(it.annotations, AnnotationRangeListSaver, this),
)
},
restore = {
val list = it as List<Any?>
AnnotatedString(
text = restore(list[0])!!,
spanStyles = restore(list[1], AnnotationRangeListSaver)!!,
paragraphStyles = restore(list[2], AnnotationRangeListSaver)!!,
annotations = restore(list[3], AnnotationRangeListSaver)!!,
)
}
)
private val AnnotationRangeListSaver = Saver<List<AnnotatedString.Range<out Any>>, Any>(
save = {
it.fastMap { range ->
save(range, AnnotationRangeSaver, this)
}
},
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Any>
list.fastMap { item ->
val range: AnnotatedString.Range<out Any> = restore(item, AnnotationRangeSaver)!!
range
}
}
)
private enum class AnnotationType {
Paragraph,
Span,
VerbatimTts,
String
}
private val AnnotationRangeSaver = Saver<AnnotatedString.Range<out Any>, Any>(
save = {
val marker = when (it.item) {
is ParagraphStyle -> AnnotationType.Paragraph
is SpanStyle -> AnnotationType.Span
is VerbatimTtsAnnotation -> AnnotationType.VerbatimTts
else -> AnnotationType.String
}
val item = when (marker) {
AnnotationType.Paragraph -> save(it.item as ParagraphStyle, ParagraphStyleSaver, this)
AnnotationType.Span -> save(it.item as SpanStyle, SpanStyleSaver, this)
AnnotationType.VerbatimTts -> save(
it.item as VerbatimTtsAnnotation,
VerbatimTtsAnnotationSaver,
this
)
AnnotationType.String -> save(it.item)
}
arrayListOf(
save(marker),
item,
save(it.start),
save(it.end),
save(it.tag)
)
},
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Any>
val marker: AnnotationType = restore(list[0])!!
val start: Int = restore(list[2])!!
val end: Int = restore(list[3])!!
val tag: String = restore(list[4])!!
when (marker) {
AnnotationType.Paragraph -> {
val item: ParagraphStyle = restore(list[1], ParagraphStyleSaver)!!
AnnotatedString.Range(item = item, start = start, end = end, tag = tag)
}
AnnotationType.Span -> {
val item: SpanStyle = restore(list[1], SpanStyleSaver)!!
AnnotatedString.Range(item = item, start = start, end = end, tag = tag)
}
AnnotationType.VerbatimTts -> {
val item: VerbatimTtsAnnotation = restore(list[1], VerbatimTtsAnnotationSaver)!!
AnnotatedString.Range(item = item, start = start, end = end, tag = tag)
}
AnnotationType.String -> {
val item: String = restore(list[1])!!
AnnotatedString.Range(item = item, start = start, end = end, tag = tag)
}
}
}
)
private val VerbatimTtsAnnotationSaver = Saver<VerbatimTtsAnnotation, Any>(
save = { save(it.verbatim) },
restore = { VerbatimTtsAnnotation(restore(it)!!) }
)
internal val ParagraphStyleSaver = Saver<ParagraphStyle, Any>(
save = {
arrayListOf(
save(it.textAlign),
save(it.textDirection),
save(it.lineHeight, TextUnit.Saver, this),
save(it.textIndent, TextIndent.Saver, this)
)
},
restore = {
val list = it as List<Any?>
ParagraphStyle(
textAlign = restore(list[0]),
textDirection = restore(list[1]),
lineHeight = restore(list[2], TextUnit.Saver)!!,
textIndent = restore(list[3], TextIndent.Saver)
)
}
)
internal val SpanStyleSaver = Saver<SpanStyle, Any>(
save = {
arrayListOf(
save(it.color, Color.Saver, this),
save(it.fontSize, TextUnit.Saver, this),
save(it.fontWeight, FontWeight.Saver, this),
save(it.fontStyle),
save(it.fontSynthesis),
save(-1), // TODO save fontFamily
save(it.fontFeatureSettings),
save(it.letterSpacing, TextUnit.Saver, this),
save(it.baselineShift, BaselineShift.Saver, this),
save(it.textGeometricTransform, TextGeometricTransform.Saver, this),
save(it.localeList, LocaleList.Saver, this),
save(it.background, Color.Saver, this),
save(it.textDecoration, TextDecoration.Saver, this),
save(it.shadow, Shadow.Saver, this)
)
},
restore = {
val list = it as List<Any?>
SpanStyle(
color = restore(list[0], Color.Saver)!!,
fontSize = restore(list[1], TextUnit.Saver)!!,
fontWeight = restore(list[2], FontWeight.Saver),
fontStyle = restore(list[3]),
fontSynthesis = restore(list[4]),
// val fontFamily = list[5] // TODO restore fontFamily
fontFeatureSettings = restore(list[6]),
letterSpacing = restore(list[7], TextUnit.Saver)!!,
baselineShift = restore(list[8], BaselineShift.Saver),
textGeometricTransform = restore(list[9], TextGeometricTransform.Saver),
localeList = restore(list[10], LocaleList.Saver),
background = restore(list[11], Color.Saver)!!,
textDecoration = restore(list[12], TextDecoration.Saver),
shadow = restore(list[13], Shadow.Saver)
)
}
)
internal val TextDecoration.Companion.Saver: Saver<TextDecoration, Any>
get() = TextDecorationSaver
private val TextDecorationSaver = Saver<TextDecoration, Any>(
save = { it.mask },
restore = { TextDecoration(it as Int) }
)
internal val TextGeometricTransform.Companion.Saver: Saver<TextGeometricTransform, Any>
get() = TextGeometricTransformSaver
private val TextGeometricTransformSaver = Saver<TextGeometricTransform, Any>(
save = { arrayListOf(it.scaleX, it.skewX) },
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Float>
TextGeometricTransform(scaleX = list[0], skewX = list[1])
}
)
internal val TextIndent.Companion.Saver: Saver<TextIndent, Any>
get() = TextIndentSaver
private val TextIndentSaver = Saver<TextIndent, Any>(
save = {
arrayListOf(
save(it.firstLine, TextUnit.Saver, this),
save(it.restLine, TextUnit.Saver, this)
)
},
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Any>
TextIndent(
firstLine = restore(list[0], TextUnit.Saver)!!,
restLine = restore(list[1], TextUnit.Saver)!!
)
}
)
internal val FontWeight.Companion.Saver: Saver<FontWeight, Any>
get() = FontWeightSaver
private val FontWeightSaver = Saver<FontWeight, Any>(
save = { it.weight },
restore = { FontWeight(it as Int) }
)
internal val BaselineShift.Companion.Saver: Saver<BaselineShift, Any>
get() = BaselineShiftSaver
private val BaselineShiftSaver = Saver<BaselineShift, Any>(
save = { it.multiplier },
restore = {
BaselineShift(it as Float)
}
)
internal val TextRange.Companion.Saver: Saver<TextRange, Any>
get() = TextRangeSaver
private val TextRangeSaver = Saver<TextRange, Any>(
save = {
arrayListOf(save(it.start), save(it.end))
},
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Any>
TextRange(restore(list[0])!!, restore(list[1])!!)
}
)
internal val Shadow.Companion.Saver: Saver<Shadow, Any>
get() = ShadowSaver
private val ShadowSaver = Saver<Shadow, Any>(
save = {
arrayListOf(
save(it.color, Color.Saver, this),
save(it.offset, Offset.Saver, this),
save(it.blurRadius)
)
},
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Any>
Shadow(
color = restore(list[0], Color.Saver)!!,
offset = restore(list[1], Offset.Saver)!!,
blurRadius = restore(list[2])!!
)
}
)
internal val Color.Companion.Saver: Saver<Color, Any>
get() = ColorSaver
private val ColorSaver = Saver<Color, Any>(
save = { it.value },
restore = { Color(it as ULong) }
)
internal val TextUnit.Companion.Saver: Saver<TextUnit, Any>
get() = TextUnitSaver
@OptIn(ExperimentalUnitApi::class)
private val TextUnitSaver = Saver<TextUnit, Any>(
save = {
arrayListOf(save(it.value), save(it.type))
},
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Any>
TextUnit(restore(list[0])!!, restore(list[1])!!)
}
)
internal val Offset.Companion.Saver: Saver<Offset, Any>
get() = OffsetSaver
private val OffsetSaver = Saver<Offset, Any>(
save = {
if (it == Offset.Unspecified) {
false
} else {
arrayListOf(save(it.x), save(it.y))
}
},
restore = {
if (it == false) {
Offset.Unspecified
} else {
val list = it as List<Any?>
Offset(restore(list[0])!!, restore(list[1])!!)
}
}
)
internal val LocaleList.Companion.Saver: Saver<LocaleList, Any>
get() = LocaleListSaver
private val LocaleListSaver = Saver<LocaleList, Any>(
save = {
it.localeList.fastMap { locale ->
save(locale, Locale.Saver, this)
}
},
restore = {
@Suppress("UNCHECKED_CAST")
val list = it as List<Any>
LocaleList(list.fastMap { item -> restore(item, Locale.Saver)!! })
}
)
internal val Locale.Companion.Saver: Saver<Locale, Any>
get() = LocaleSaver
private val LocaleSaver = Saver<Locale, Any>(
save = { it.toLanguageTag() },
restore = { Locale(languageTag = it as String) }
)