LineBreak.android.kt

/*
 * Copyright 2022 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.style

import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.style.LineBreak.Strategy
import androidx.compose.ui.text.style.LineBreak.Strictness
import androidx.compose.ui.text.style.LineBreak.WordBreak

// TODO(b/246340708): Remove @sample LineBreakSample from the actual class
/**
 * When soft wrap is enabled and the width of the text exceeds the width of its container,
 * line breaks are inserted in the text to split it over multiple lines.
 *
 * There are a number of parameters that affect how the line breaks are inserted.
 * For example, the breaking algorithm can be changed to one with improved readability
 * at the cost of speed.
 * Another example is the strictness, which in some languages determines which symbols can appear
 * at the start of a line.
 *
 * This represents a configuration for line breaking on Android, describing [Strategy], [Strictness],
 * and [WordBreak].
 *
 * @sample androidx.compose.ui.text.samples.LineBreakSample
 * @sample androidx.compose.ui.text.samples.AndroidLineBreakSample
 *
 * @param strategy defines the algorithm that inserts line breaks
 * @param strictness defines the line breaking rules
 * @param wordBreak defines how words are broken
 */
@Immutable
actual class LineBreak(
    val strategy: Strategy,
    val strictness: Strictness,
    val wordBreak: WordBreak
) {
    actual companion object {
        /**
         * The greedy, fast line breaking algorithm. Ideal for text that updates often,
         * such as a text editor, as the text will reflow minimally.
         *
         * <pre>
         * +---------+
         * | This is |
         * | an      |
         * | example |
         * | text.   |
         * | 今日は自  |
         * | 由が丘で  |
         * | 焼き鳥を  |
         * | 食べま   |
         * | す。     |
         * +---------+
         * </pre>
         */
        actual val Simple: LineBreak = LineBreak(
            strategy = Strategy.Simple,
            strictness = Strictness.Normal,
            wordBreak = WordBreak.Default
        )

        /**
         * Balanced line lengths, hyphenation, and phrase-based breaking.
         * Suitable for short text such as titles or narrow newspaper columns.
         *
         * <pre>
         * +---------+
         * | This    |
         * | is an   |
         * | example |
         * | text.   |
         * | 今日は   |
         * | 自由が丘  |
         * | で焼き鳥  |
         * | を食べ   |
         * | ます。   |
         * +---------+
         * </pre>
         */
        actual val Heading: LineBreak = LineBreak(
            strategy = Strategy.Balanced,
            strictness = Strictness.Loose,
            wordBreak = WordBreak.Phrase
        )

        /**
         * Slower, higher quality line breaking for improved readability.
         * Suitable for larger amounts of text.
         *
         * <pre>
         * +---------+
         * | This    |
         * | is an   |
         * | example |
         * | text.   |
         * | 今日は自  |
         * | 由が丘で  |
         * | 焼き鳥を  |
         * | 食べま   |
         * | す。     |
         * +---------+
         * </pre>
         */
        actual val Paragraph: LineBreak = LineBreak(
            strategy = Strategy.HighQuality,
            strictness = Strictness.Strict,
            wordBreak = WordBreak.Default
        )
    }

    fun copy(
        strategy: Strategy = this.strategy,
        strictness: Strictness = this.strictness,
        wordBreak: WordBreak = this.wordBreak
    ): LineBreak = LineBreak(
        strategy = strategy,
        strictness = strictness,
        wordBreak = wordBreak
    )

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is LineBreak) return false

        if (strategy != other.strategy) return false
        if (strictness != other.strictness) return false
        if (wordBreak != other.wordBreak) return false

        return true
    }

    override fun hashCode(): Int {
        var result = strategy.hashCode()
        result = 31 * result + strictness.hashCode()
        result = 31 * result + wordBreak.hashCode()
        return result
    }

    override fun toString(): String =
        "LineBreak(strategy=$strategy, strictness=$strictness, wordBreak=$wordBreak)"

    /**
     * The strategy used for line breaking.
     */
    @JvmInline
    value class Strategy private constructor(private val value: Int) {
        companion object {
            /**
             * Basic, fast break strategy. Hyphenation, if enabled, is done only for words
             * that don't fit on an entire line by themselves.
             *
             * <pre>
             * +---------+
             * | This is |
             * | an      |
             * | example |
             * | text.   |
             * +---------+
             * </pre>
             */
            val Simple: Strategy = Strategy(1)

            /**
             * Does whole paragraph optimization for more readable text,
             * including hyphenation if enabled.
             *
             * <pre>
             * +---------+
             * | This    |
             * | is an   |
             * | example |
             * | text.   |
             * +---------+
             * </pre>
             */
            val HighQuality: Strategy = Strategy(2)

            /**
             * Attempts to balance the line lengths of the text, also applying automatic
             * hyphenation if enabled. Suitable for small screens.
             *
             * <pre>
             * +-----------------------+
             * | This is an            |
             * | example text.         |
             * +-----------------------+
             * </pre>
             */
            val Balanced: Strategy = Strategy(3)
        }

        override fun toString(): String = when (this) {
            Simple -> "Strategy.Simple"
            HighQuality -> "Strategy.HighQuality"
            Balanced -> "Strategy.Balanced"
            else -> "Invalid"
        }
    }

    /**
     * Describes the strictness of line breaking, determining before which characters
     * line breaks can be inserted. It is useful when working with CJK scripts.
     */
    @JvmInline
    value class Strictness private constructor(private val value: Int) {
        companion object {
            /**
             * Default breaking rules for the locale, which may correspond to [Normal] or [Strict].
             */
            val Default: Strictness = Strictness(1)

            /**
             * The least restrictive rules, suitable for short lines.
             *
             * For example, in Japanese it allows breaking before iteration marks, such as 々, 〻.
             */
            val Loose: Strictness = Strictness(2)

            /**
             * The most common rules for line breaking.
             *
             * For example, in Japanese it allows breaking before characters like
             * small hiragana (ぁ), small katakana (ァ), halfwidth variants (ァ).
             */
            val Normal: Strictness = Strictness(3)

            /**
             * The most stringent rules for line breaking.
             *
             * For example, in Japanese it does not allow breaking before characters like
             * small hiragana (ぁ), small katakana (ァ), halfwidth variants (ァ).
             */
            val Strict: Strictness = Strictness(4)
        }

        override fun toString(): String = when (this) {
            Default -> "Strictness.None"
            Loose -> "Strictness.Loose"
            Normal -> "Strictness.Normal"
            Strict -> "Strictness.Strict"
            else -> "Invalid"
        }
    }

    /**
     * Describes how line breaks should be inserted within words.
     */
    @JvmInline
    value class WordBreak private constructor(private val value: Int) {
        companion object {
            /**
             * Default word breaking rules for the locale.
             * In latin scripts this means inserting line breaks between words,
             * while in languages that don't use whitespace (e.g. Japanese) the line can break
             * between characters.
             *
             * <pre>
             * +---------+
             * | This is |
             * | an      |
             * | example |
             * | text.   |
             * | 今日は自  |
             * | 由が丘で  |
             * | 焼き鳥を  |
             * | 食べま   |
             * | す。     |
             * +---------+
             * </pre>
             */
            val Default: WordBreak = WordBreak(1)

            /**
             * Line breaking is based on phrases.
             * In languages that don't use whitespace (e.g. Japanese), line breaks are not inserted
             * between characters that are part of the same phrase unit.
             * This is ideal for short text such as titles and UI labels.
             *
             * <pre>
             * +---------+
             * | This    |
             * | is an   |
             * | example |
             * | text.   |
             * | 今日は   |
             * | 自由が丘  |
             * | で焼き鳥  |
             * | を食べ   |
             * | ます。   |
             * +---------+
             * </pre>
             */
            val Phrase: WordBreak = WordBreak(2)
        }

        override fun toString(): String = when (this) {
            Default -> "WordBreak.None"
            Phrase -> "WordBreak.Phrase"
            else -> "Invalid"
        }
    }
}