| /* |
| * Copyright 2023 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.foundation.text2.input |
| |
| import androidx.compose.foundation.ExperimentalFoundationApi |
| import androidx.compose.foundation.text.appendCodePointX |
| import androidx.compose.runtime.Stable |
| |
| /** |
| * Visual transformation interface for input fields. |
| * |
| * This interface is responsible for 1-to-1 mapping of every codepoint in input state to another |
| * codepoint before text is rendered. Visual transformation is useful when the underlying source |
| * of input needs to remain but rendered content should look different, e.g. password obscuring. |
| */ |
| @ExperimentalFoundationApi |
| fun interface CodepointTransformation { |
| |
| /** |
| * Transforms a single [codepoint] located at [codepointIndex] to another codepoint. |
| * |
| * A codepoint is an integer that always maps to a single character. Every codepoint in Unicode |
| * is comprised of 16 bits, 2 bytes. |
| */ |
| // TODO: add more codepoint explanation or doc referral |
| fun transform(codepointIndex: Int, codepoint: Int): Int |
| |
| companion object { |
| |
| @Stable |
| val None = CodepointTransformation { _, codepoint -> codepoint } |
| } |
| } |
| |
| /** |
| * Creates a masking [CodepointTransformation] that maps all codepoints to a specific [character]. |
| */ |
| @ExperimentalFoundationApi |
| fun CodepointTransformation.Companion.mask(character: Char): CodepointTransformation = |
| MaskCodepointTransformation(character) |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| private class MaskCodepointTransformation(val character: Char) : CodepointTransformation { |
| override fun transform(codepointIndex: Int, codepoint: Int): Int { |
| return character.code |
| } |
| |
| override fun toString(): String { |
| return "MaskCodepointTransformation(character=$character)" |
| } |
| |
| override fun equals(other: Any?): Boolean { |
| if (this === other) return true |
| if (other !is MaskCodepointTransformation) return false |
| |
| if (character != other.character) return false |
| |
| return true |
| } |
| |
| override fun hashCode(): Int { |
| return character.hashCode() |
| } |
| } |
| |
| /** |
| * [CodepointTransformation] that converts all line breaks (\n) into white space(U+0020) and |
| * carriage returns(\r) to zero-width no-break space (U+FEFF). This transformation forces any |
| * content to appear as single line. |
| */ |
| @OptIn(ExperimentalFoundationApi::class) |
| internal object SingleLineCodepointTransformation : CodepointTransformation { |
| |
| private const val LINE_FEED = '\n'.code |
| private const val CARRIAGE_RETURN = '\r'.code |
| |
| private const val WHITESPACE = ' '.code |
| private const val ZERO_WIDTH_SPACE = '\uFEFF'.code |
| |
| override fun transform(codepointIndex: Int, codepoint: Int): Int { |
| if (codepoint == LINE_FEED) return WHITESPACE |
| if (codepoint == CARRIAGE_RETURN) return ZERO_WIDTH_SPACE |
| return codepoint |
| } |
| |
| override fun toString(): String { |
| return "SingleLineCodepointTransformation" |
| } |
| } |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| internal fun CharSequence.toVisualText( |
| codepointTransformation: CodepointTransformation? |
| ): CharSequence { |
| codepointTransformation ?: return this |
| val text = this |
| return buildString { |
| (0 until Character.codePointCount(text, 0, text.length)).forEach { codepointIndex -> |
| val codepoint = codepointTransformation.transform( |
| codepointIndex, Character.codePointAt(text, codepointIndex) |
| ) |
| appendCodePointX(codepoint) |
| } |
| } |
| } |